偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

從Context源碼實現(xiàn)談React性能優(yōu)化

開發(fā) 前端
這篇文章主要介紹Context的實現(xiàn)原理,源碼層面掌握React組件的render時機,從而寫出高性能的React組件,源碼層面了解shouldComponentUpdate、React.memo、PureComponent等性能優(yōu)化手段的實現(xiàn)。

[[358779]]

 學(xué)完這篇文章,你會收獲:

  1. 了解Context的實現(xiàn)原理
  2. 源碼層面掌握React組件的render時機,從而寫出高性能的React組件
  3. 源碼層面了解shouldComponentUpdate、React.memo、PureComponent等性能優(yōu)化手段的實現(xiàn)

我會盡量將文章寫的通俗易懂。但是,要完全理解文章內(nèi)容,需要你掌握這些前置知識:

  1. Fiber架構(gòu)的大體工作流程
  2. 優(yōu)先級與更新在React源碼中的意義

組件render的時機

Context的實現(xiàn)與組件的render息息相關(guān)。在講解其實現(xiàn)前,我們先來了解render的時機。

換句話說,組件在什么時候render?

這個問題的答案,已經(jīng)在React組件到底什么時候render啊聊過。在這里再概括下:

在React中,每當觸發(fā)更新(比如調(diào)用this.setState、useState),會為組件創(chuàng)建對應(yīng)的fiber節(jié)點。

fiber節(jié)點互相鏈接形成一棵Fiber樹。

有2種方式創(chuàng)建fiber節(jié)點:

bailout,即復(fù)用前一次更新該組件對應(yīng)的fiber節(jié)點作為本次更新的fiber節(jié)點。

render,經(jīng)過diff算法后生成一個新fiber節(jié)點。組件的render(比如ClassComponent的render方法調(diào)用、FunctionComponent的執(zhí)行)就發(fā)生在這一步。

經(jīng)常有同學(xué)問:React每次更新都會重新生成一棵Fiber樹,性能不會差么?

React性能確實不算很棒。但如你所見,F(xiàn)iber樹生成過程中并不是所有組件都會render,有些滿足優(yōu)化條件的組件會走bailout邏輯。

比如,對于如下Demo:

  1. function Son() { 
  2.   console.log('child render!'); 
  3.   return <div>Son</div>; 
  4.  
  5.  
  6. function Parent(props) { 
  7.   const [count, setCount] = React.useState(0); 
  8.  
  9.   return ( 
  10.     <div onClick={() => {setCount(count + 1)}}> 
  11.       count:{count
  12.       {props.children} 
  13.     </div> 
  14.   ); 
  15.  
  16.  
  17. function App() { 
  18.   return ( 
  19.     <Parent> 
  20.       <Son/> 
  21.     </Parent> 
  22.   ); 
  23.  
  24. const rootEl = document.querySelector("#root"); 
  25. ReactDOM.render(<App/>, rootEl); 

 在線Demo地址[2]

點擊Parent組件的div子組件,觸發(fā)更新,但是child render!并不會打印。

這是因為Son組件會進入bailout邏輯。

bailout的條件

要進入bailout邏輯,需同時滿足4個條件:

1.oldProps === newProps

即本次更新的props全等于上次更新的props。

注意這里是全等比較。

我們知道組件render會返回JSX,JSX是React.createElement的語法糖。

所以render的返回結(jié)果實際上是React.createElement的執(zhí)行結(jié)果,即一個包含props屬性的對象。

即使本次更新與上次更新props中每一項參數(shù)都沒有變化,但是本次更新是React.createElement的執(zhí)行結(jié)果,是一個全新的props引用,所以oldProps !== newProps。

2.context value沒有變化

我們知道在當前React版本中,同時存在新老兩種context,這里指老版本context。

3.workInProgress.type === current.type

更新前后fiber.type不變,比如div沒變?yōu)閜。

4.!includesSomeLane(renderLanes, updateLanes) ?

當前fiber上是否存在更新,如果存在那么更新的優(yōu)先級是否和本次整棵Fiber樹調(diào)度的優(yōu)先級一致?

如果一致代表該組件上存在更新,需要走render邏輯。

bailout的優(yōu)化還不止如此。如果一棵fiber子樹所有節(jié)點都沒有更新,即使所有子孫fiber都走bailout邏輯,還是有遍歷的成本。

所以,在bailout中,會檢查該fiber的所有子孫fiber是否滿足條件4(該檢查時間復(fù)雜度O(1))。

如果所有子孫fiber本次都沒有更新需要執(zhí)行,則bailout會直接返回null。整棵子樹都被跳過。

不會bailout也不會render,就像不存在一樣。對應(yīng)的DOM不會產(chǎn)生任何變化。

老Context API的實現(xiàn)現(xiàn)

在我們大體了解了render的時機。有了這個概念,就能理解ContextAPI是如何實現(xiàn)的,以及為什么被重構(gòu)。

我們先看被廢棄的老ContextAPI的實現(xiàn)。

Fiber樹的生成過程是通過遍歷實現(xiàn)的可中斷遞歸,所以分為遞和歸2個階段。

Context對應(yīng)數(shù)據(jù)會保存在棧中。

在遞階段,Context不斷入棧。所以Concumer可以通過Context棧向上找到對應(yīng)的context value。

在歸階段,Context不斷出棧。

那么老ContextAPI為什么被廢棄呢?因為他沒法和shouldComponentUpdate或Memo等性能優(yōu)化手段配合。

shouldComponentUpdate的實現(xiàn)

要探究更深層的原因,我們需要了解shouldComponentUpdate的原理,后文簡稱其為SCU。

使用SCU是為了減少不必要的render,換句話說:讓本該render的組件走bailout邏輯。

剛才我們介紹了bailout需要滿足的條件。那么SCU是作用于這4個條件的哪個呢?

顯然是第一條:oldProps === newProps

當使用shouldComponentUpdate,這個組件bailout的條件會產(chǎn)生變化:

-- oldProps === newProps

++ SCU === false

同理,使用PureComponenet和React.memo時,bailout的條件也會產(chǎn)生變化:

-- oldProps === newProps

++ 淺比較oldProps與newsProps相等

回到老ContextAPI。

當這些性能優(yōu)化手段:

使組件命中bailout邏輯

同時如果組件的子樹都滿足bailout的條件4

那么該fiber子樹不會再繼續(xù)遍歷生成。

換言之,不會再經(jīng)歷Context的入棧、出棧。

這種情況下,即使context value變化,子孫組件也沒法檢測到。

新Context API的實現(xiàn)

知道老ContextAPI的缺陷,我們再來看新ContextAPI是如何實現(xiàn)的。

當通過:

  1. ctx = React.createContext(); 

創(chuàng)建context實例后,需要使用Provider提供value,使用Consumer或useContext訂閱value。

如:

  1. ctx = React.createContext(); 
  2.  
  3. const NumProvider = ({children}) => { 
  4.   const [num, add] = useState(0); 
  5.  
  6.   return ( 
  7.     <Ctx.Provider value={num}> 
  8.       <button onClick={() => add(num + 1)}>add</button> 
  9.       {children} 
  10.     </Ctx.Provider> 
  11.   ) 

使用:

  1. const Child = () => { 
  2.   const {num} = useContext(Ctx); 
  3.   return <p>{num}</p> 

  當遍歷組件生成對應(yīng)fiber時,遍歷到Ctx.Provider組件,Ctx.Provider內(nèi)部會判斷context value是否變化。

如果context value變化,Ctx.Provider內(nèi)部會執(zhí)行一次向下深度優(yōu)先遍歷子樹的操作,尋找與該Provider配套的Consumer。

在上文的例子中會最終找到useContext(Ctx)的Child組件對應(yīng)的fiber,并為該fiber觸發(fā)一次更新

注意這里的實現(xiàn)非常巧妙:

一般更新是由組件調(diào)用觸發(fā)更新的方法產(chǎn)生。比如上文的NumProvider組件,點擊button調(diào)用add會觸發(fā)一次更新。

觸發(fā)更新的本質(zhì)是為了讓組件創(chuàng)建對應(yīng)fiber時不滿足bailout條件4:

!includesSomeLane(renderLanes, updateLanes) ?

從而進入render邏輯。

在這里,Ctx.Provider中context value變化,Ctx.Provider向下找到消費context value的組件Child,為其fiber觸發(fā)一次更新。

則Child對應(yīng)fiber就不滿足條件4。

這就解決了老ContextAPI的問題:

由于Child對應(yīng)fiber不滿足條件4,所以從Ctx.Provider到Child,這棵子樹沒法滿足:

  • !! 子樹中所有子孫節(jié)點都滿足條件4

所以即使遍歷中途有組件進入bailout邏輯,也不會返回null,即不會無視這棵子樹的遍歷。

最終遍歷進行到Child,由于其不滿足條件4,會進入render邏輯,調(diào)用組件對應(yīng)函數(shù)。

  1. const Child = () => { 
  2.   const {num} = useContext(Ctx); 
  3.   return <p>{num}</p> 

 在函數(shù)調(diào)用中會調(diào)用useContext從Context棧中找到對應(yīng)更新后的context value并返回。

總結(jié)

React性能一大關(guān)鍵在于:減少不必要的render。

從上文我們看到,本質(zhì)就是讓組件滿足4個條件,從而進入bailout邏輯。

而ContextAPI本質(zhì)是讓Consumer組件不滿足條件4。

我們也知道了,React雖然每次都會遍歷整棵樹,但會有bailout的優(yōu)化邏輯,不是所有組件都會render。

極端情況下,甚至某些子樹會被跳過遍歷(bailout返回null)。

參考資料

[1]React技術(shù)揭秘: http://react.iamkasong.com/

[2]在線Demo地址: https://codesandbox.io/s/quirky-chaplygin-5bx67?file=/src/App.js

 

責(zé)任編輯:姜華 來源: 魔術(shù)師卡頌
相關(guān)推薦

2021-05-27 13:37:24

開發(fā)技能React

2021-08-27 14:26:06

開發(fā)技能React

2019-02-25 07:07:38

技巧React 優(yōu)化

2023-11-01 17:57:56

React應(yīng)用程序性能

2016-12-19 10:00:00

React性能優(yōu)化

2022-08-03 09:11:31

React性能優(yōu)化

2017-09-27 18:21:36

報表性能集算器

2023-11-01 11:59:13

2009-06-29 15:39:53

Servlet和JSPServlet引擎

2015-09-16 10:13:16

游戲性能

2020-06-22 07:30:00

React開發(fā)工具

2019-03-14 15:38:19

ReactJavascript前端

2023-07-03 07:51:47

2021-11-23 09:45:26

架構(gòu)系統(tǒng)技術(shù)

2020-01-13 10:45:35

JavaScript解析前端

2019-09-18 08:53:55

2020-01-06 11:22:06

TCPLinux內(nèi)核

2023-06-16 09:08:39

ReactContextRFC

2025-04-02 07:29:14

2023-11-01 11:51:08

Linux性能優(yōu)化
點贊
收藏

51CTO技術(shù)棧公眾號