亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么從Context源碼實現React性能優化

發布時間:2022-04-19 17:40:24 來源:億速云 閱讀:170 作者:zzz 欄目:大數據

這篇文章主要介紹“怎么從Context源碼實現React性能優化”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“怎么從Context源碼實現React性能優化”文章能幫助大家解決問題。

組件render的時機

Context的實現與組件的render息息相關。在講解其實現前,我們先來了解render的時機。

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

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

在React中,每當觸發更新(比如調用this.setState、useState),會為組件創建對應的fiber節點。

fiber節點互相鏈接形成一棵Fiber樹。

有2種方式創建fiber節點:

bailout,即復用前一次更新該組件對應的fiber節點作為本次更新的fiber節點。

render,經過diff算法后生成一個新fiber節點。組件的render(比如ClassComponent的render方法調用、FunctionComponent的執行)就發生在這一步。

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

React性能確實不算很棒。但如你所見,Fiber樹生成過程中并不是所有組件都會render,有些滿足優化條件的組件會走bailout邏輯。

比如,對于如下Demo:

function Son() {   console.log('child render!');   return <div>Son</div>; }   function Parent(props) {   const [count, setCount] = React.useState(0);    return (     <div onClick={() => {setCount(count + 1)}}>       count:{count}       {props.children}     </div>   ); }   function App() {   return (     <Parent>       <Son/>     </Parent>   ); }  const rootEl = document.querySelector("#root"); ReactDOM.render(<App/>, rootEl);

在線Demo地址[2]

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

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

bailout的條件

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

1.oldProps === newProps

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

注意這里是全等比較。

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

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

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

2.context value沒有變化

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

3.workInProgress.type === current.type

更新前后fiber.type不變,比如div沒變為p。

4.!includesSomeLane(renderLanes, updateLanes) ?

當前fiber上是否存在更新,如果存在那么更新的優先級是否和本次整棵Fiber樹調度的優先級一致?

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

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

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

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

不會bailout也不會render,就像不存在一樣。對應的DOM不會產生任何變化。

老Context API的實現現

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

我們先看被廢棄的老ContextAPI的實現。

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

Context對應數據會保存在棧中。

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

在歸階段,Context不斷出棧。

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

shouldComponentUpdate的實現

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

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

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

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

當使用shouldComponentUpdate,這個組件bailout的條件會產生變化:

-- oldProps === newProps

++ SCU === false

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

-- oldProps === newProps

++ 淺比較oldProps與newsProps相等

回到老ContextAPI。

當這些性能優化手段:

使組件命中bailout邏輯

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

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

換言之,不會再經歷Context的入棧、出棧。

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

新Context API的實現

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

當通過:

ctx = React.createContext();

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

如:

ctx = React.createContext();  const NumProvider = ({children}) => {   const [num, add] = useState(0);    return (     <Ctx.Provider value={num}>       <button onClick={() => add(num + 1)}>add</button>       {children}     </Ctx.Provider>   ) }

使用:

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

當遍歷組件生成對應fiber時,遍歷到Ctx.Provider組件,Ctx.Provider內部會判斷context value是否變化。

如果context  value變化,Ctx.Provider內部會執行一次向下深度優先遍歷子樹的操作,尋找與該Provider配套的Consumer。

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

注意這里的實現非常巧妙:

一般更新是由組件調用觸發更新的方法產生。比如上文的NumProvider組件,點擊button調用add會觸發一次更新。

觸發更新的本質是為了讓組件創建對應fiber時不滿足bailout條件4:

!includesSomeLane(renderLanes, updateLanes) ?

從而進入render邏輯。

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

則Child對應fiber就不滿足條件4。

這就解決了老ContextAPI的問題:

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

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

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

最終遍歷進行到Child,由于其不滿足條件4,會進入render邏輯,調用組件對應函數。

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

在函數調用中會調用useContext從Context棧中找到對應更新后的context value并返回。

關于“怎么從Context源碼實現React性能優化”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

山东| 雅江县| 固始县| 罗平县| 舒城县| 宾阳县| 隆安县| 社会| 永新县| 吴桥县| 灵宝市| 汾西县| 卫辉市| 比如县| 金乡县| 黄大仙区| 义乌市| 鄂州市| 娄底市| 德格县| 沐川县| 雷波县| 丹巴县| 焦作市| 奉贤区| 陵川县| 达日县| 郁南县| 富阳市| 清水河县| 浮山县| 宣汉县| 德昌县| 凌源市| 安阳市| 廊坊市| 若尔盖县| 贵港市| 安泽县| 汶上县| 宁南县|