全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-11-09_React 之 Race Condition

您的位置:首页 >> 新闻 >> 行业资讯

React 之 Race Condition 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 竞态条件Race Condition,中文译为竞态条件,旨在描述一个系统或者进程的输出,依赖于不受控制事件的出现顺序或者出现时机。 举个简单的例子: if(x==5)//The"Check" { y=x*2;//The"Act" //如果其他的线程在"if(x==5)"and"y=x*2"执行之间更改了x的值 //y就可能不等于10. } 你可能想,JavaScript 是单线程,怎么可能出现这个问题? React 与竞态条件确实如此,但前端有异步渲染,所以竞态条件依然有可能出现,我们举个 React 中常见的例子。 这是一个非常典型的数据获取代码: classArticleextendsComponent{ state={ article:null componentDidMount(){ this.fetchData(this.props.id); } asyncfetchData(id){ constarticle=awaitAPI.fetchArticle(id); this.setState({article } //... } 看起来没什么问题,但这段代码还没有实现数据更新,我们再改一下: classArticleextendsComponent{ state={ article:null componentDidMount(){ this.fetchData(this.props.id); } componentDidUpdate(prevProps){ if(prevProps.id!==this.props.id){ this.fetchData(this.props.id); } } asyncfetchData(id){ constarticle=awaitAPI.fetchArticle(id); this.setState({article } //... } 当组件传入新的id时,我们根据新的id请求数据,然后setState最新获取的数据。 这时就可能出现竞态条件,比如用户选完立刻点击下一页,我们请求id为 1 的数据,紧接着请求id为 2 的数据,但因为网络或者接口处理等原因,id为 2 的接口提前返回,便会先展示id为 2 的数据,再展示id为 1 的数据,这就导致了错误。 我们可以想想遇到这种问题的场景,比如类似于百度的搜索功能,切换 tab 等场景,虽然我们也可以使用诸如 debounce 的方式来缓解,但效果还是会差点,比如使用 debounce,用户在输入搜索词的时候,展示内容会长期处于空白状态,对于用户体验而言,我们可以做的更好。 那么我们该如何解决呢?一种是在切换的时候取消请求,还有一种是借助一个布尔值来判断是否需要更新,比如这样: functionArticle({id}){ const[article,setArticle]=useState(null); useEffect(()={ letdidCancel=false; asyncfunctionfetchData(){ constarticle=awaitAPI.fetchArticle(id); //如果didCancel为true说明用户已经取消了 if(!didCancel){ setArticle(article); } } fetchData(); //执行下一个effect之前会执行 return()={ didCancel=true; },[id]); //... } 当然你也可以用 ahooks 中的useRequest,它的内部有一个 ref 变量记录最新的 promise,也可以解决 Race Condition 的问题: functionArticle({id}){ const{data,loading,error}=useRequest(()=fetchArticle(id),{ refreshDeps:[id] //... } 效果演示问题复现为了方便大家自己测试这个问题,我们提供相对完整的代码。以《Avoiding Race Conditions when Fetching Data with React Hooks》中的例子为例,出现 Race Condition 问题的代码如下: constfakeFetch=person={ returnnewPromise(res={ setTimeout(()=res(`${person}'sdata`),Math.random()*5000); }; constApp=()={ const[data,setData]=useState(''); const[loading,setLoading]=useState(false); const[person,setPerson]=useState(null); useEffect(()={ setLoading(true); fakeFetch(person).then(data={ setData(data); setLoading(false); },[person]); consthandleClick=(name)=()={ setPerson(name) } return( Fragment buttononClick={handleClick('Nick')}Nick'sProfile/button buttononClick={handleClick('Deb')}Deb'sProfile/button buttononClick={handleClick('Joe')}Joe'sProfile/button {person( Fragment h1{person}/h1 p{loading?'Loading...':data}/p /Fragment )} /Fragment }; 我们实现了一个fakeFetch函数,用于模拟接口的返回,具体返回的时间为Math.random() * 5000),用于模拟数据的随机返回。 实现效果如下:从效果图中可以看到,我们按顺序点击了Nick、Deb、Joe,理想情况下,结果应该显示Joe's Data,但最终显示的数据为最后返回的Nick's Data。 布尔值解决现在,我们尝试用一个canceled布尔值解决: constApp=()={ const[data,setData]=useState(''); const[loading,setLoading]=useState(false); const[person,setPerson]=useState(null); useEffect(()={ letcanceled=false; setLoading(true); fakeFetch(person).then(data={ if(!canceled){ setData(data); setLoading(false); } return()=(canceled=true); },[person]); return( Fragment buttononClick={()=setPerson('Nick')}Nick'sProfile/button buttononClick={()=setPerson('Deb')}Deb'sProfile/button buttononClick={()=setPerson('Joe')}Joe'sProfile/button {person( Fragment h1{person}/h1 p{loading?'Loading...':data}/p /Fragment )} /Fragment }; 实现效果如下:即便接口没有按照顺序返回,依然不影响最终显示的数据。 useRequest 解决我们也可以借助ahooks的useRequest方法,修改后的代码如下: constApp2=()={ const[person,setPerson]=useState('Nick'); const{data,loading}=useRequest(()=fakeFetch(person),{ refreshDeps:[person], consthandleClick=(name)=()={ setPerson(name) } return( Fragment buttononClick={handleClick('Nick')}Nick'sProfile/button buttononClick={handleClick('Deb')}Deb'sProfile/button buttononClick={()=setPerson('Joe')}Joe'sProfile/button {person( Fragment h1{person}/h1 p{loading?'Loading...':data}/p /Fragment )} /Fragment }; 代码效果如上,就不重复录制了。 考虑到部分同学可能会对useRequest的使用感到困惑,我们简单介绍一下useRequest的使用: useRequest的第一个参数是一个异步函数,在组件初次加载时,会自动触发该函数执行。同时自动管理该异步函数的loading、data、error等状态。 useRequest同样提供了一个options.refreshDeps参数,当它的值变化后,会重新触发请求。 const[userId,setUserId]=useState('1'); const{data,run}=useRequest(()=getUserSchool(userId),{ refreshDeps:[userId], }); 上面的示例代码,useRequest 会在初始化和 userId 变化时,触发函数执行。与下面代码实现功能完全一致: const[userId,setUserId]=useState('1'); const{data,refresh}=useRequest(()=getUserSchool(userId)); useEffect(()={ refresh(); },[userId]); Suspense这篇之所以讲 Race Condition,主要还是为了引入讲解 Suspense,借助 Suspense,我们同样可以解决 Race Condition: //实现参考的React官方示例:https://codesandbox.io/s/infallible-feather-xjtbu functionwrapPromise(promise){ letstatus="pending"; letresult; letsuspender=promise.then( r={ status="success"; result= }, e={ status="error"; result= } return{ read(){ if(status==="pending"){ throwsuspender; }elseif(status==="error"){ throwresult; }elseif(status==="success"){ returnresult; } } } constfakeFetch=person={ returnnewPromise(res={ setTimeout(()=res(`${person}'sdata`),Math.random()*5000); }; functionfetchData(userId){ returnwrapPromise(fakeFetch(userId)) } constinitialResource=fetchData('Nick'); functionUser({resource}){ constdata=resource.read(); returnp{data}/p } constApp=()={ const[person,setPerson]=useState('Nick'); const[resource,setResource]=useState(initialResource); consthandleClick=(name)=()={ setPerson(name) setResource(fetchData(name)); } return( Fragment buttononClick={handleClick('Nick')}Nick'sProfile/button buttononClick={handleClick('Deb')}Deb'sProfile/button buttononClick={handleClick('Joe')}Joe'sProfile/button Fragment h1{person}/h1 Suspensefallback={'loading'} Userresource={resource}/ /Suspense /Fragment /Fragment }; 而关于 Suspense 的具体讲解,详见下篇。 React 系列React 之 createElement 源码解读React 之元素与组件的区别React 之 Refs 的使用和 forwardRef 的源码解读React 之 Context 的变迁与背后实现React 系列的预热系列,带大家从源码的角度深入理解 React 的各个 API 和执行过程,全目录不知道多少篇,预计写个 50 篇吧。 阅读原文

上一篇:2019-02-14_图解神经机器翻译中的注意力机制 下一篇:2022-03-22_加拿大阿尔伯塔大学韩杰教授招收电子与计算机专业博士博后

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价