React 核心团队成员解释“代数效应与 React”

新闻 前端
React核心团队成员Sebastian Markbåge[1](React Hooks的发明者)曾说:我们在React中做的就是践行代数效应(Algebraic Effects)。

 React核心团队成员Sebastian Markbåge[1](React Hooks的发明者)曾说:我们在React中做的就是践行代数效应(Algebraic Effects)。

那么,代数效应是什么呢?它和React有什么关系呢。

[[345619]]

什么是代数效应

代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。

接下来我们用虚构的语法来解释。

假设我们有一个函数getTotalPicNum,传入2个用户名称后,分别查找该用户在平台保存的图片数量,最后将图片数量相加后返回。

  1. function getTotalPicNum(user1, user2) { 
  2.  
  3.   const num1 = getPicNum(user1); 
  4.  
  5.   const num2 = getPicNum(user2); 
  6.  
  7.   return picNum1 + picNum2; 
  8.  

在getTotalPicNum中,我们不关注getPicNum的实现,只在乎“获取到两个数字后将他们相加的结果返回”这一过程。

接下来我们来实现getPicNum。

"用户在平台保存的图片数量"是保存在服务器中的。所以,为了获取该值,我们需要发起异步请求。

为了尽量保持getTotalPicNum的调用方式不变,我们首先想到了使用async await:

  1. async function getTotalPicNum(user1, user2) { 
  2.  
  3.   const num1 = await getPicNum(user1); 
  4.  
  5.   const num2 = await getPicNum(user2); 
  6.  
  7.   return picNum1 + picNum2; 
  8.  

但是,async await是有传染性的 —— 当一个函数变为async后,这意味着调用他的函数也需要是async,这破坏了getTotalPicNum的同步特性。

有没有什么办法能保持getTotalPicNum保持现有调用方式不变的情况下实现异步请求呢?

没有。不过我们可以虚构一个。

我们虚构一个类似try...catch的语法 —— try...handle与两个操作符perform、resume。

  1. function getPicNum(name) { 
  2.  
  3.   const picNum = perform name; 
  4.  
  5.   return picNum; 
  6.  
  7.  
  8. try { 
  9.  
  10.   getTotalPicNum('kaSong''xiaoMing'); 
  11.  
  12. } handle (who) { 
  13.  
  14.   switch (who) { 
  15.  
  16.     case 'kaSong'
  17.  
  18.       resume with 230
  19.  
  20.     case 'xiaoMing'
  21.  
  22.       resume with 122
  23.  
  24.     default
  25.  
  26.       resume with 0
  27.  
  28.   } 
  29.  

当执行到getTotalPicNum内部的getPicNum方法时,会执行perform name。

此时函数调用栈会从getPicNum方法内跳出,被最近一个try...handle捕获。类似throw Error后被最近一个try...catch捕获。

类似throw Error后Error会作为catch的参数,perform name后name会作为handle的参数。

与try...catch最大的不同在于:当Error被catch捕获后,之前的调用栈就销毁了。而handle执行resume后会回到之前perform的调用栈。

对于case 'kaSong',执行完resume with 230;后调用栈会回到getPicNum,此时picNum === 230

再次申明,try...handle的语法是虚构的,只是为了演示代数效应的思想。

总结一下:代数效应能够将副作用(例子中为请求图片数量)从函数逻辑中分离,使函数关注点保持纯粹。

并且,从例子中可以看出,perform resume不需要区分同步异步。

代数效应在React中的应用

那么代数效应与React有什么关系呢?最明显的例子就是Hooks。

对于类似useState、useReducer、useRef这样的Hook,我们不需要关注FunctionComponent的state在Hook中是如何保存的,React会为我们处理。

我们只需要假设useState返回的是我们想要的state,并编写业务逻辑就行。

  1. function App() { 
  2.  
  3.   const [num, updateNum] = useState(0); 
  4.  
  5.    
  6.  
  7.   return ( 
  8.  
  9.     <button onClick={() => updateNum(num => num + 1)}>{num}</button>   
  10.  
  11.   ) 
  12.  

如果这个例子还不够明显,可以看看官方的Suspense Demo[2]

在Demo中ProfileDetails用于展示用户名称。而用户名称是异步请求的。

但是Demo中完全是同步的写法。

  1. function ProfileDetails() { 
  2.  
  3.   const user = resource.user.read(); 
  4.  
  5.   return <h1>{user.name}</h1>; 
  6.  

代数效应与Generator

从React15到React16,协调器(Reconciler)重构的一大目的是:将老的同步更新的架构变为异步可中断更新。

异步可中断更新可以理解为:更新在执行过程中可能会被打断(浏览器时间分片用尽或有更高优任务插队),当可以继续执行时恢复之前执行的中间状态。

这就是代数效应中try...handle的作用。

其实,浏览器原生就支持类似的实现,这就是Generator。

但是Generator的一些缺陷使React团队放弃了他:

类似async,Generator也是传染性的,使用了Generator则上下文的其他函数也需要作出改变。这样心智负担比较重。

Generator执行的中间状态是上下文关联的。

考虑如下例子:

  1. function* doWork(A, B, C) { 
  2.  
  3.   var x = doExpensiveWorkA(A); 
  4.  
  5.   yield; 
  6.  
  7.   var y = x + doExpensiveWorkB(B); 
  8.  
  9.   yield; 
  10.  
  11.   var z = y + doExpensiveWorkC(C); 
  12.  
  13.   return z; 
  14.  

每当浏览器有空闲时间都会依次执行其中一个doExpensiveWork,当时间用尽则会中断,当再次恢复时会从中断位置继续执行。

只考虑“单一优先级任务的中断与继续”情况下Generator可以很好的实现异步可中断更新。

但是当我们考虑“高优先级任务插队”的情况,如果此时已经完成doExpensiveWorkA与doExpensiveWorkB计算出x与y。

此时B组件接收到一个高优更新,由于Generator执行的中间状态是上下文关联的的,所以重新计算y时无法复用之前已经计算出的x,需要重新计算。

如果通过全局变量保存之前执行的中间状态,又会引入新的复杂度。

更详细的解释可以参考这个issue[3]

基于这些原因,React没有采用Generator实现协调器。

代数效应与Fiber

Fiber并不是计算机术语中的新名词,他的中文翻译叫做纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。

在很多文章中将纤程理解为协程的一种实现。在JS中,协程的实现便是Generator。

所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。

React Fiber可以理解为:

React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。

其中每个任务更新单元为React Element对应的Fiber节点。

 

责任编辑:张燕妮 来源: 程序员的那些事
相关推荐

2023-07-22 00:33:07

React团队数据

2021-05-24 06:00:20

ReactJSXJS

2020-01-07 15:40:43

React前端技术准则

2023-02-02 08:41:14

React团队Vite

2023-03-24 12:34:56

2022-06-27 07:23:20

React​并发

2022-07-06 15:07:47

React开发

2020-11-24 07:48:32

React

2015-10-10 16:02:36

React NativAndroid

2022-05-06 07:31:01

useEventReactHook

2022-08-22 16:23:11

React特性

2022-10-29 08:55:19

页面react

2023-05-31 07:29:46

2021-12-16 06:21:16

React组件前端

2022-04-15 08:07:21

ReactDiff算法

2021-10-15 14:28:30

React 组件渲染

2023-03-19 11:42:19

React新官方文档

2020-10-23 09:26:57

React-Redux

2021-02-02 08:11:50

火焰图组件技术

2023-07-03 07:51:47

点赞
收藏

51CTO技术栈公众号