React Fiber学习小结

730 阅读4分钟

前天在油管上看了大神的一个关于js优化的分享,佩服的五体投地。但是视频又太短了,就20来分钟,有点意犹未尽的意思,还好视频结尾放了几篇大神写的关于react fiber的文章,所以赶紧找来学习了一下。

Fiber

fiber node是react内部用来代表一些需要完成的工作的一种数据结构,每一个react element都会对应一个fiber node。之前react15里的vdom tree现在就变成了一个fiber tree,每一个fiber node都保存着指向其sibling, child 和 parent的指针。所以fiber tree大概就是一个双向的linked list吧。

为啥搞fiber

既然已经有了vdom tree,为啥又搞了这么个fiber tree?

我没细读过源码,不过从官方文档,大概因为vdom tree的遍历采用了递归的写法。 递归一旦开始,在结束之前,都不受控了。也就是说,如果react触发一次update,那么在update完成之前,浏览器主线程啥都干不了。。 如果这个update耗时比较久,那就会造成卡顿。

所以最大的问题就是遍历起来不受控了。。想要控制咋办?拿while来改写递归呗。所以react组里的大佬们就搞出了这么个"fiber linked list"。

fiber工作流程

fiber的工作大概可以分为两个阶段: 1. render 2. commit

render phase

在第一次mount之后,react会为每一个渲染的react element都建立一个fiber node,并组成一颗树(current)。当更新触发的时候,react会建立另外一颗树(workInProgress),所有的updates都会反映在这颗树上。并且两颗树之间的节点都有一个alternate的属性,指向自己在另一颗树上的映射。在这个阶段,react会在workInProgress的节点上更新state和props,但并不会做任何带有side effect的操作(留给commit阶段),这里react只会给每个节点记录在commit阶段到底应该做些啥操作,这个信息保存在effectTag字段上。

除了构建workInProgress树之外,为了提高性能,react 还会维护一个effect list。就是用所有有sideeffect操作的节点构建一个linkedlist。 等真的到了要执行这些操作的时候,直接扫这个list就好了,而不用去遍历整个树。

除了上面的工作,render phase还会调用以下的函数:

  1. [UNSAFE_]componentWillMount (deprecated)
  2. [UNSAFE_]componentWillReceiveProps (deprecated)
  3. getDerivedStateFromProps
  4. shouldComponentUpdate
  5. [UNSAFE_]componentWillUpdate (deprecated)
  6. render

commit phase

render阶段生成了current树,workInProgress树和effect list,接下来就进入到commit 阶段。

在commit阶段,react会直接遍历effect list,完成每个节点的操作。在所有节点完成之后,用workInProgress树来替换current树,旧的current树就可以被垃圾回收了。

在commit阶段,除了完成每个节点的工作外,还会跑这几个函数:

  1. 对于有snapShop标签的node,调用getSnapshotBeforeUpdate
  2. 对于有Deletion标签的node,调用componentWillUnmount
  3. 对于有Placement标签的node,调用componentDidMount
  4. 对于有Update标签的node,调用componentDidUpdate

那么问题来了, 为啥react要大费周章的搞这么两个阶段呢?

原因是在render phase阶段,所有操作可以是异步的。 react会把遍历整颗树的任务切成小任务,放在requestIdleCallback里面来执行。 但是每次在暂停之后,下一次再继续的时候,有可能会放弃掉之前做的所有任务重头来过(这个的原因不是特别清楚,可能是在两个任务之前,某个状态改变了,之前的任务可能已经失效了?),所以所有会产生副作用的操作都不能放在这个阶段执行。 react 16.3把很多原来常用的生命周期标记为unsafe也是这个道理。就是因为react的大佬们发现使用者老在这些生命周期函数写一些带副作用的逻辑。这种写法在fiber的体系下问题就很大(可能会被执行多次), 官方的这篇文章把这个事情也说的特别清楚了。

而commit phase则相反,所有的操作都会一口气做完(同步)。所以啊,在commit phase调用的生命周期里面,最好还是不要做太重的活为好。。

参考文献

React Fiber Architecture The how and why on React’s usage of linked list in Fiber to walk the component’s tree

Inside Fiber: in-depth overview of the new reconciliation algorithm in React

Update on Async Rendering