引用原文: React Fiber ArchitectureReact Fiber is an ongoing reimplementation of React's core algorithm. It is the culmination of over two years of research by the React team.javascript
The goal of React Fiber is to increase its suitability for areas like animation, layout,and gestures. Its headline feature is incremental rendering: the ability to split rendering work into chunks and spread it out over multiple frames.html
Other key features include the ability to pause, abort, or reuse work as new updates come in; the ability to assign priority to different types of updates; and new concurrency primitives.java
React Fibre 是 React 核心算法正在进行的从新实现。它是 React 团队两年多的研究成果。react
React Fiber 的目标是提升其对动画,布局和手势等领域的适用性。它的主体特征是增量渲染:可以将渲染工做分割成块,并将其分散到多个帧中。git
其余主要功能包括在进行更新时暂停,停止或从新使用工做的能力,为不一样类型的更新分配优先权的能力和新的并发原语。github
先来看一下react组件渲染时经历的生命周期:web
挂载阶段:算法
constructor()
componentWillMount()
render()
componentDidMount()
更新阶段:npm
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate
卸载阶段:redux
componentWillUnmount()
在以前的版本中,若是你实现一个很复杂的深度嵌套的复合组件,会出现下面的状况:
现有层级关系以下的四个组件:
组件渲染时调用的生命周期顺序:
上图展现的是A,B,C,D的挂载阶段调用的生命周期渲染顺序,能够看到从顶层组件开始调用各生命周期,一直向下,直至调用完最底层子组件的生命周期。而后再向上调用。
组件更新阶段同理。
组件挂载以后,假如修改最上层组件的数据(state),组件更新时的调用栈:
若是这是一个很大,层级很深的组件,能够想像到,组件在渲染时,调用栈过长,再加上若是在期间进行了各类复杂的操做,就可能致使长时间阻塞主线程,react渲染它须要几十甚至几百毫秒,这样的话react就会一直占用浏览器主线程,任何其余的操做(包括用户的点击,鼠标移动等操做)都没法执行,带来很是很差的用户体验。
React Fiber 就是为了解决上面的问题而生。
好似一个潜水员,当它一头扎进水里,就要往最底层一直游,直到找到最底层的组件,而后他再上岸。 在这期间,岸上发生的任何事,都不能对他进行干扰,若是有更重要的事情须要他去作(如用户操做),也必须得等他上岸。Fiber 本质上是一个虚拟的堆栈帧,新的调度器会按照优先级自由调度这些帧,从而将以前的同步渲染改为了异步渲染,在不影响体验的状况下去分段计算更新。它让潜水员会每隔一段时间就上岸,看是否有更重要的事情要作。
对于如何区别优先级,React 有本身的一套逻辑。对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的状况下,React 会每 16 ms(之内) 暂停一下更新,返回来继续渲染动画。
浏览器自己也不断进化中,随着页面由简单的展现转向WebAPP,它须要一些新能力来承载更多节点的展现与更新。
下面是一些自救措施:
react官方采用的是 requestIdleCallback,为了兼容全部平台,facebook 单独实现了其功能,做为一个独立的 npm 包使用 react-schedule
其做用是会在浏览器空闲时期依次调用函数, 这就能够在主事件循环中执行后台或低优先级的任务,并且不会对像动画和用户交互这样延迟触发并且关键的事件产生影响。函数通常会按先进先调用的顺序执行,除非函数在浏览器调用它以前就到了它的超时时间。
简化后的大体流程图以下:
reconciliation
和 commit
对于异步渲染,如今渲染有两个阶段:reconciliation
和 commit
。前者过程是能够打断的,后者不能暂停,会一直更新界面直到完成。
reconciliation 处理过程
1 若是当前节点不须要更新,直接把子节点clone过来,要更新的话标记更新类型
2 更新当前节点状态(props, state, context等)
3 调用shouldComponentUpdate()
4 调用组件实例方法 render() 得到新的子节点,并为子节点建立 Fiber Node(建立过程会尽可能复用现有 Fiber Node,子节点增删也发生在这里)
5 若是没有产生 child fiber,进入下一阶段 completeUnitOfWork
Reconciliation 阶段 (React算法,用来比较2颗树,以肯定哪些部分须要从新渲染)
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
Commit 阶段 (用于呈现React应用的数据更改。一般是setState
的结果。最终致使从新渲染。)
componentDidMount
componentDidUpdate
componentWillUnmount
由于 reconciliation
阶段是能够被打断的,因此 reconciliation
阶段会执行的生命周期函数就可能会出现调用屡次的状况,从而引发 Bug。因此对于 reconciliation
阶段调用的几个函数,除了 shouldComponentUpdate
之外,其余都应该避免去使用,而且 React16 中也引入了新的 API 来解决这个问题。
因而官方推出了getDerivedStateFromProps
,让你在render设置新state,你主要返回一个新对象,它就主动帮你setState。因为这是一个静态方法,你不能取到 this
,固然你也不能操做instance
,这就阻止了你屡次操做setState。这样一来,getDerivedStateFromProps
的逻辑应该会很简单,这样就不会出错,不会出错,就不会打断DFS过程。
getDerivedStateFromProps
取代了原来的componentWillMount
与componentWillReceiveProps
方法,该函数会在组件 初始化 和 更新 时被调用
class ExampleComponent extends React.Component { // Initialize state in constructor, // Or with a property initializer. state = {}; static getDerivedStateFromProps(nextProps, prevState) { if (prevState.someMirroredValue !== nextProps.someValue) { return { derivedData: computeDerivedState(nextProps), someMirroredValue: nextProps.someValue }; } // Return null to indicate no change to state. return null; } }
在进入commi阶段时,组件多了一个新钩子叫getSnapshotBeforeUpdate
,它与commit阶段的钩子同样只执行一次。getSnapshotBeforeUpdate
用于替换 componentWillUpdate
,该函数会在 update
后 DOM 更新前被调用,用于读取最新的 DOM 数据。
因而整个流程变成这样:(引用大神@司徒正美的图)
结合 React Fiber 架构 建议以下使用react生命周期
class ExampleComponent extends React.Component { // 用于初始化 state constructor() {} // 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用 // 由于该函数是静态函数,因此取不到 `this` // 若是须要对比 `prevProps` 须要单独在 `state` 中维护 static getDerivedStateFromProps(nextProps, prevState) {} // 判断是否须要更新组件,多用于组件性能优化 shouldComponentUpdate(nextProps, nextState) {} // 组件挂载后调用 // 能够在该函数中进行请求或者订阅 componentDidMount() {} // 用于得到最新的 DOM 数据 getSnapshotBeforeUpdate() {} // 组件即将销毁 // 能够在此处移除订阅,定时器等等 componentWillUnmount() {} // 组件销毁后调用 componentDidUnMount() {} // 组件更新后调用 componentDidUpdate() {} // 渲染组件函数 render() {} // 如下函数不建议使用 UNSAFE_componentWillMount() {} UNSAFE_componentWillUpdate(nextProps, nextState) {} UNSAFE_componentWillReceiveProps(nextProps) {} }