React核心团队成员SebastianMarkb?ge[1](ReactHooks的发明者)曾说过:我们在React中所做的就是练习代数效应(AlgebraicEffects)。那么,什么是代数效应呢?它与React有什么关系?什么是代数效应代数效应是函数式编程中的一个概念,用于将副作用与函数调用分开。接下来我们用一个虚构的语法来解释。假设我们有一个函数getTotalPicNum,传入2个用户名后,分别求用户在平台保存的图片数量,最后将图片数量相加返回。functiongetTotalPicNum(user1,user2){constnum1=getPicNum(user1);constnum2=getPicNum(user2);returnpicNum1+picNum2;}在getTotalPicNum中,我们不关注getPicNum的实现,我们只关心“获取后相加”两个数字返回”过程的结果。接下来我们实现getPicNum。“用户在平台保存的图片数量”存储在服务器中。所以,为了得到这个值,我们需要进行异步请求。为了保持getTotalPicNum的调用方式不变,我们首先想到了使用asyncawait:asyncfunctiongetTotalPicNum(user1,user2){constnum1=awaitgetPicNum(user1);constnum2=awaitgetPicNum(user2);returnpicNum1+picNum2;}但是asyncawait有传染性——当一个函数变为异步时,意味着调用函数也需要异步,这打破了getTotalPicNum的同步性质。有没有办法在现有调用方法中保留getTotalPicNum的同时实现异步请求?不,但我们可以编一个。我们发明了一种类似于try...catch的语法-try...handle和两个运算符perform和resume。functiongetPicNum(name){constpicNum=performname;returnpicNum;}try{getTotalPicNum('kaSong','xiaoMing');}handle(who){switch(who){case'kaSong':resumewith230;case'xiaoMing':resumewith122;default:resumewith0;}}执行getTotalPicNum里面的getPicNum方法时,会执行performname。这时函数调用栈会跳出getPicNum方法,被最新的try...handle捕获。类似于throwError,被最新的try...catch捕获。和throwError类似,Error会作为catch的参数,perform名字后面的名字会作为handle的参数。与try...catch最大的不同在于,当??Error被catch捕获时,之前的调用栈被销毁。handle执行resume后,会回到上一个perform的调用栈。对于'kaSong',执行resumewith230;后,调用堆栈将返回到getPicNum。此时picNum===230再次声明try...handle的语法是虚构的,只是为了演示代数效应的思想。总结一下:Algebraiceffects可以将副作用(在本例中,请求图像的数量)从功能逻辑中分离出来,保持功能焦点的纯粹。并且从例子中可以看出,performresume不需要区分同步和异步。代数效应在React中的应用那么代数效应和React有什么关系呢?最明显的例子是Hooks。对于useState、useReducer、useRef这样的Hook,我们不需要关心FunctionComponent的状态在Hook中是如何保存的,React会帮我们处理。我们只需要假设useState返回我们想要的状态并编写业务逻辑。functionApp(){const[num,updateNum]=useState(0);return({user.name}
;}AlgebraiceffectsandGenerator从React15到React16,Reconciler重构的主要目的之一是:改变旧的同步更新架构到异步可中断更新。异步可中断更新可以理解为:更新可能在执行过程中被中断(浏览器时间片耗尽或有更高优先级的任务队列),当可以继续执行时,恢复上一次执行的中间状态。这就是try...handle在代数效应中的作用。事实上,浏览器本身就支持类似的实现,这就是Generator。但是Generator的一些缺点让React团队放弃了他:与async类似,Generator也具有传染性,使用Generator时需要改变context的其他功能。这是沉重的精神负担。生成器执行的中间状态是上下文相关的。考虑以下示例:function*doWork(A,B,C){varx=doExpensiveWorkA(A);yield;vary=x+doExpensiveWorkB(B);yield;varz=y+doExpensiveWorkC(C);returnz;}无论何时浏览当处理器空闲时,它会依次执行其中一个doExpensiveWork。当时间用完时,它将被中断。当它再次恢复时,它会从中断的位置继续执行。Generator在只考虑“单优先级任务的中断和继续”时,可以很好地实现异步可中断更新。但是当我们考虑“高优先级任务跳队”的情况时,如果此时doExpensiveWorkA和doExpensiveWorkB已经完成,则计算x和y。此时,组件B收到高质量的更新。由于Generator执行的中间状态是context-dependent,所以之前计算的x在重新计算y的时候不能复用,需要重新计算。如果通过全局变量保存上一次执行的中间状态,会引入新的复杂度。更详细的解释可以参考这个issue[3]由于这些原因,React没有使用Generator来实现coordinator。代数效应和FiberFiber并不是计算机术语中的新术语。它的中文译名为Fiber,与Process、Thread、Coroutine是同一个程序执行过程。在很多文章中,纤程被理解为协程的一种实现。在JS中,协程的实现是Generator。因此,我们可以将Fiber和Generator理解为代数效应思想在JS中的体现。ReactFiber可以理解为:React内部实现的一套状态更新机制。支持不同优先级的任务,可以中断和恢复,恢复后可以复用之前的中间状态。每个任务更新单元是对应ReactElement的一个Fiber节点。
