本文是對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