本文是对React Fiber知识点的学习记录,网上有不少大佬对React Fiber的详细讲解,想要深刻了解React Fiber的能够查看文章后面的引用。html
React Fiber在React v16引入,至关因而对核心渲染机制的一次重构。在没有引入这种算法以前,React在全部更新都没有完成时会一直占用主线程,直接致使的现象是渲染期间页面的其余js动效会卡住直到主线程继续,使页面出现卡顿的现象。
看一个官方例子:react
<!DOCTYPE html> <html style="width: 100%; height: 100%; overflow: hidden"> <head> <meta charset="utf-8"> <title>Fiber Example</title> </head> <body> <h1>Fiber Example</h1> <div id="container"> <p> To install React, follow the instructions on <a href="https://github.com/facebook/react/">GitHub</a>. </p> <p> If you can see this, React is <strong>not</strong> working right. If you checked out the source from GitHub make sure to run <code>npm run build</code>. </p> </div> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6/babel.js"></script> <script type="text/babel"> var dotStyle = { position: 'absolute', background: '#61dafb', font: 'normal 15px sans-serif', textAlign: 'center', cursor: 'pointer', }; var containerStyle = { position: 'absolute', transformOrigin: '0 0', left: '50%', top: '50%', width: '10px', height: '10px', background: '#eee', }; var targetSize = 25; class Dot extends React.Component { constructor() { super(); this.state = { hover: false }; } enter() { this.setState({ hover: true }); } leave() { this.setState({ hover: false }); } render() { var props = this.props; var s = props.size * 1.3; var style = { ...dotStyle, width: s + 'px', height: s + 'px', left: (props.x) + 'px', top: (props.y) + 'px', borderRadius: (s / 2) + 'px', lineHeight: (s) + 'px', background: this.state.hover ? '#ff0' : dotStyle.background }; return ( <div style={style} onMouseEnter={() => this.enter()} onMouseLeave={() => this.leave()}> {this.state.hover ? '*' + props.text + '*' : props.text} </div> ); } } class SierpinskiTriangle extends React.Component { shouldComponentUpdate(nextProps) { var o = this.props; var n = nextProps; return !( o.x === n.x && o.y === n.y && o.s === n.s && o.children === n.children ); } render() { let {x, y, s, children} = this.props; if (s <= targetSize) { return ( <Dot x={x - (targetSize / 2)} y={y - (targetSize / 2)} size={targetSize} text={children} /> ); return r; } var newSize = s / 2; var slowDown = true; if (slowDown) { var e = performance.now() + 0.8; while (performance.now() < e) { // Artificially long execution time. } } s /= 2; return [ <SierpinskiTriangle x={x} y={y - (s / 2)} s={s}> {children} </SierpinskiTriangle>, <SierpinskiTriangle x={x - s} y={y + (s / 2)} s={s}> {children} </SierpinskiTriangle>, <SierpinskiTriangle x={x + s} y={y + (s / 2)} s={s}> {children} </SierpinskiTriangle>, ]; } } class ExampleApplication extends React.Component { constructor() { super(); this.state = { seconds: 0, useTimeSlicing: true, }; this.tick = this.tick.bind(this); this.onTimeSlicingChange = this.onTimeSlicingChange.bind(this); } componentDidMount() { this.intervalID = setInterval(this.tick, 1000); } tick() { if (this.state.useTimeSlicing) { // Update is time-sliced. // 使用时间分片的方式更新 // https://github.com/facebook/react/pull/13488 将此api移除 // deferredUpdates是将更新推迟 https://juejin.im/entry/59c4885f6fb9a00a4456015d ReactDOM.unstable_deferredUpdates(() => { this.setState(state => ({ seconds: (state.seconds % 10) + 1 })); }); } else { // Update is not time-sliced. Causes demo to stutter. // 更新没有作时间分片,致使卡顿 stutter(结巴) this.setState(state => ({ seconds: (state.seconds % 10) + 1 })); } } onTimeSlicingChange(value) { this.setState(() => ({ useTimeSlicing: value })); } componentWillUnmount() { clearInterval(this.intervalID); } render() { const seconds = this.state.seconds; const elapsed = this.props.elapsed; const t = (elapsed / 1000) % 10; const scale = 1 + (t > 5 ? 10 - t : t) / 10; const transform = 'scaleX(' + (scale / 2.1) + ') scaleY(0.7) translateZ(0.1px)'; return ( <div> <div> <h3>Time-slicing</h3> <p>Toggle this and observe the effect</p> <Toggle onLabel="On" offLabel="Off" onChange={this.onTimeSlicingChange} value={this.state.useTimeSlicing} /> </div> <div style={{ ...containerStyle, transform }}> <div> <SierpinskiTriangle x={0} y={0} s={1000}> {this.state.seconds} </SierpinskiTriangle> </div> </div> </div> ); } } class Toggle extends React.Component { constructor(props) { super(); this.onChange = this.onChange.bind(this); } onChange(event) { this.props.onChange(event.target.value === 'on'); } render() { const value = this.props.value; return ( <label onChange={this.onChange}> <label> {this.props.onLabel} <input type="radio" name="value" value="on" checked={value} /> </label> <label> {this.props.offLabel} <input type="radio" name="value" value="off" checked={!value} /> </label> </label> ); } } var start = new Date().getTime(); function update() { ReactDOM.render( <ExampleApplication elapsed={new Date().getTime() - start} />, document.getElementById('container') ); requestAnimationFrame(update); } requestAnimationFrame(update); </script> </body> </html>
react fiber针对这种状况作了什么优化呢?主要是两点:一、将任务拆分红一小块一小块,二、获取到时间片才执行任务
下面主要来说讲这两点git
学过React的都知道,React有虚拟dom树,vDom-tree,要把计算任务拆分,那就要有任务恢复和任务完成后提交等功能,普通的vDom并不具有记录这些信息的能力,所以React Fiber从虚拟节点衍生出了一套Fiber节点,来记录任务状态。每个Fiber节点都会对应一个虚拟节点,这样计算任务就拆分红了一个个小块,相似于下图,会有一个对应关系
简单描述下它的流程:
1.数据状态发生改变,即发生setState
2.开始diff算法
3.到达一个节点,生成对应的fiber节点,记录状态
4.查看当前是否有执行时间
5.有执行时间,计算完当前节点(节点的增删改),到下一个节点,继续这个步骤
6.到某个节点没有执行时间,则保存fiber状态,每一个fiber都记录了下一个任务指向
7.从新获取到了执行时间,从当前记录的fiber节点开始往下继续
8.执行到最后的节点,将结果进行一级一级提交,这样直到根节点,组件就知道它已经完成了数据计算
9.最后一步,将最终确认的dom结果渲染到页面中github
这个就比较简单了,刚开始我还觉得React使用了什么骚操做来弄得,看了大佬们的文章后才知道,原来是用了浏览器的api: requestIdleCallback和requestAnimationFrame,requestAnimationFrame会在每一帧结束后肯定调用它的回调函数,用于处理高优先级任务,为了达到不影响页面效果requestIdleCallback用于处理低优先任务,它的执行时机不肯定。主要说下requestIdleCallback,每次调用requestIdleCallback,会告诉你如今是不是空闲时间,空闲时间有多久,它的用法:算法
// 一窥它的行为 requestIdleCallback(function(){ console.log('1'); let a = 1000000000; while(a > 0){ a--; } }) requestIdleCallback(function(){console.log('2')}) requestIdleCallback(function(){console.log('3')}, {timeout:10}) // 使用方式 const tasks = [...] // 任务队列 function taskHandler(deadline) { // deadline.timeRemaining() 能够获取到当前帧剩余时间 while (deadline.timeRemaining() > 0 && tasks.length > 0) { // do something } // 没时间了,但还有任务,继续注册一个,等到下次有时间了会执行 if (tasks.length > 0){ requestIdleCallback(taskHandler); } } requestIdelCallback(taskHandler);
上面说到任务的优先级,在fiber任务执行完进行dom更新的时候,这块是无法作任务拆分的,若是遇到dom变化很大,更新耗时的状况也会形成卡顿,这个无法避免,若是此时有用户交互发生,形成卡顿会下降用户体验,Fiber针对这种状况也作了优化,将任务分红优先级,像用户输入,交互等行为属于高优先级,会优先处理,而后页面渲染,diff计算等属于次优先级。npm
几篇对React Fiber不错的介绍:
React-从源码分析React Fiber工做原理
理解react16.3的fiber架构
React Fiber
React Fiber是什么
React Fiber初探
react-fiber-resourcesapi