日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

React重新渲染超詳細(xì)講解_React

作者:何遇er ? 更新時(shí)間: 2022-12-24 編程語言

Web 前端開發(fā)者對渲染和重新渲染應(yīng)該不陌生,在 React 中,它們究竟是什么意思?

  • 渲染:React 讓組件根據(jù)當(dāng)前的 props 和 state 描述它要展示的內(nèi)容。
  • 重新渲染:React 讓組件重新描述它要展示的內(nèi)容。

要將組件顯示到屏幕上,React 的工作主要分為兩個(gè)階段,本文介紹與 React 渲染相關(guān)的知識。

  • render 階段(渲染階段):計(jì)算組件的輸出并收集所有需要應(yīng)用到 DOM 上的變更。
  • commit 階段(提交階段):將 render 階段計(jì)算出的變更應(yīng)用到 DOM 上。

在 commit 階段 React 會更新 DOM 節(jié)點(diǎn)和組件實(shí)例的 ref,如果是類組件,React會同步運(yùn)行 componentDidMount 或 componentDidUpdate 生命周期方法,如果是函數(shù)組件,React會同步運(yùn)行 useLayoutEffect 勾子,當(dāng)瀏覽器繪制 DOM 之后,再運(yùn)行所有的 useEffect 勾子。

React 重新渲染

初始化渲染之后,下面的這些原因會讓React重新渲染組件:

類組件

  • 調(diào)用 this.setState 方法。
  • 調(diào)用this.forceUpdate方法。

函數(shù)組件

  • 調(diào)用 useState 返回的 setState。
  • 調(diào)用 useReducer 返回的 dispatch。

其他

  • 組件訂閱的 context value 發(fā)生變更
  • 重新調(diào)用 ReactDOM.render(<AppRoot>)

假設(shè)組件樹如下

默認(rèn)情況,如果父組件重新渲染,那么 React 會重新渲染它所有的子組件。當(dāng)用戶點(diǎn)擊組件 A 中的按鈕,使 A 組件 count 狀態(tài)值加1,將發(fā)生如下的渲染流程:

  • React將組件A添加到重新渲染隊(duì)列中。
  • 從組件樹的頂部開始遍歷,快速跳過不需要更新的組件。
  • React發(fā)生A組件需要更新,它會渲染A。A返回B和C
  • B沒有被標(biāo)記為需要更新,但由于它的父組件A被渲染了,所以React會渲染B
  • C沒有被標(biāo)記為需要更新,但由于它的父組件A被渲染了,所以React會渲染C,C返回D
  • D沒有標(biāo)記為需要更新,但由于它的父組件C被渲染了,所以D會被渲染。

在默認(rèn)渲染流程中,React 不關(guān)心子組件的 props 是否改變了,它會無條件地渲染子組件。很可能上圖中大多數(shù)組件會返回與上次完全相同的結(jié)果,因此 React 不需要對DOM 做任何更改,但是,React 仍然會要求組件渲染自己并對比前后兩次渲染輸出的結(jié)果,這兩者都需要時(shí)間。

Reconciliation

Reconciliation 被稱為 diff 算法,它用來比較兩顆 React 元素樹之間的差異,為了讓組件重新渲染變得高效,React 盡可能地復(fù)用現(xiàn)有的組件和 DOM。為了降低時(shí)間復(fù)雜度,Diff 算法基于如下兩個(gè)假設(shè):

  • 兩個(gè)不同類型的元素對應(yīng)的元素樹完全不同。
  • 在同一個(gè)列表中,如果兩個(gè)元素key屬性的值相同,那么它們被識別為同一個(gè)元素。

元素類型對 Diff 的影響

React 使用元素的 type 字段比較元素類型是否相同,如果兩顆樹在相同位置要渲染的元素類型相同,那么 React 就重用這些元素,并在適當(dāng)?shù)臅r(shí)候更新,不需要重新創(chuàng)建元素,這意味著,只要一直要求 React 將某組件渲染在相同的位置,那么 React 始終不會卸載該組件。如果相同位置的元素類型不同,例如從 div 到 span 或者從ComponentA 到 ComponentB,React會認(rèn)為整個(gè)樹發(fā)生了變化,為了加快比較過程,React 會銷毀整個(gè)現(xiàn)有的組件樹,包括所有的 DOM 節(jié)點(diǎn),然后重新創(chuàng)建元素。

瀏覽器內(nèi)置元素的 type 字段是一個(gè)字符串,自定義組件元素的 type 字段是一個(gè)類或者函數(shù),由于元素類型對 Diff的影響,所以在渲染期間不要?jiǎng)?chuàng)建組件,只要?jiǎng)?chuàng)建一個(gè)新的組件,那么它的 type 字段就是不同的引用,這將導(dǎo)致 React 不斷地銷毀并重新創(chuàng)建子組件樹。不要有如下的代碼:

function ParentCom() {
  // 每一次渲染 ParentCom 時(shí),都會創(chuàng)建新的ChildCom組件
  function ChildCom() {/**do something*/}
  return <ChildCom />
}

上述代碼不推薦,正確的做法是將 ChildCom 放在ParentCom 的外面。

key 對 Diff 的影響

React 識別元素的另一種方式是通過 key 屬性,key 作為組件的唯一標(biāo)識符不會當(dāng)作prop傳遞到組件中,可以給任何組件添加一個(gè) key 屬性來標(biāo)注它,更改 key 的值會導(dǎo)致舊的組件實(shí)例和 DOM 被銷毀。

列表是使用 key 屬性的主要場景,在 React 官方文檔中提到,不要將數(shù)組的下標(biāo)作為 key 值,而是用數(shù)據(jù)唯一 ID 作為 key 值。在這里分別介紹這兩種方式的區(qū)別。

假如 Todo List 中有 10 項(xiàng),先用數(shù)組下標(biāo)作為 key 的值,這 10 項(xiàng) Todo 的 key 值為 0...9,現(xiàn)在刪除數(shù)組的第 6 項(xiàng)和第 7 項(xiàng),并在數(shù)組末尾添加 3 個(gè)新的數(shù)據(jù)項(xiàng),我們最終將得到 key 值為0..10的 Todo,看起來只是在末尾新增 1 項(xiàng),將原來的列表從10項(xiàng)變成了11項(xiàng),React 很樂意復(fù)用已有的 DOM 節(jié)點(diǎn)和組件實(shí)例,這意味著原來 #6 對應(yīng)的組件實(shí)例沒有被銷毀,現(xiàn)在它接收新的 props 用于呈現(xiàn)原來的 #8。在這個(gè)例子中 React 會創(chuàng)建 1 個(gè)Todo,更新 4 個(gè)Todo。

如果使用數(shù)據(jù)的 ID 作為 key 值,React 能發(fā)現(xiàn)第 6 項(xiàng)和第 7 項(xiàng)被刪除了,它也能發(fā)現(xiàn)數(shù)組新增了 3 項(xiàng),所以 React 會銷毀 #6 和 #7 項(xiàng)對應(yīng)的組件實(shí)例及其關(guān)聯(lián)的 DOM,還會創(chuàng)建 3 個(gè)組件實(shí)例及其關(guān)聯(lián)的 DOM。

提高渲染性能

要將組件顯示在界面上,組件必須經(jīng)歷渲染流程,但渲染工作有時(shí)候會被認(rèn)為是浪費(fèi)時(shí)間,如果渲染的輸出結(jié)果沒有改變,它對應(yīng)的DOM節(jié)點(diǎn)也不需要更新,此時(shí)與該組件相關(guān)的渲染工作真的是在浪費(fèi)時(shí)間。React組件的輸出結(jié)果始終基于當(dāng)前 props 和 state 的值,因此,如果我們知道組件的 props 和 state 沒有改變,那么我們可以無后顧之憂地讓組件跳過重新渲染。

跳過重新渲染

React 提供了 3 個(gè)主要的API讓我們跳過重新渲染:

  • React.Component 的 shouldComponentUpdate:這是類組件可選的生命周期函數(shù),它在組件 render 階段早期被調(diào)用,如果返回false,React 將跳過重新渲染該組件,使用它最常見的場景是檢查組件的 props 和 state 是否自上次以來發(fā)生了變更,如果沒有改變則返回false。
  • React.PureComponent:它在 React.Component 的基礎(chǔ)上添加默認(rèn)的 shouldComponentUpdate 去比較組件的 props 和 state 自上次渲染以來是否有變更。
  • React.memo():它是一個(gè)高階組件,接收自定義組件作為參數(shù),返回一個(gè)被包裹的組件,被包裹的組件的默認(rèn)行為是檢查 props 是否有更改,如果沒有,則跳過重新渲染。

上述方法都通過‘淺比較’來確定值是否有變更,如果通過 mutable 的方式修改狀態(tài),這些 API 會認(rèn)為狀態(tài)沒有變。

  • 如果組件在其渲染過程中返回的元素的引用與上一次渲染時(shí)的引用完全相同,那么 React 不會重新渲染引用相同的組件。示例如下:
function ShowChildren(props: {children: React.ReactNode}) {
    const [count, setCount] = useState<number>(0)
    return (
        <div>
            {count} <button onClick={() => setCount(c => c + 1)}>click</button>
            {/* 寫法一 */}
            {props.children}
            {/* 寫法二 */}
            {/* <Children/> */}
        </div>
    )
}

上述 ShowChildren 的 props.children 對應(yīng) Children 組件,因此寫法一和寫法二在瀏覽器中呈現(xiàn)一樣。點(diǎn)擊按鈕不會讓寫法一的 Children 組件重新渲染,但是會使寫法二的 Children 組件重新渲染。

上述4種方式跳過重新渲染意味著 React 會跳過整個(gè)子樹的重新渲染。

Props 對渲染優(yōu)化的影響

默認(rèn)情況,只要組件重新渲染,React 會重新渲染所有被它嵌套的后代組件,即便組件的 props 沒有變更。如果試圖通過 React.memo 和 React.PureComponent 優(yōu)化組件的渲染性能,那么要注意每個(gè) prop 的引用是否有變更。下面的示例試圖使用 React.memo 讓組件不重新渲染,但事與愿違,組件會重新渲染,代碼如下:

const MemoizedChildren = React.memo(Children)
function Parent() {
    const onClick = () => { /** todo*/}
    return <MemoizedChildren onClick={onClick}/>
}

上述代碼中,Parent 組件重新渲染會創(chuàng)建新的 onClick 函數(shù),所以對 MemoizedChildren 而言,props.onClic k的引用有變化,最終被 React.memo 包裹的Children 會重新渲染,如果讓組件跳過重新渲染對你真的很重要,那么在上述代碼中將 React.memo 與 useCallback 配合使用才能達(dá)到目的。

總結(jié)

渲染與更新 DOM 是不同的事情,組件經(jīng)歷了渲染,DOM 不一定會更新,如果渲染組件返回的結(jié)果與上次的相同,那么它的 DOM 節(jié)點(diǎn)不需要有任何更新。與 React 渲染密切相關(guān)的還有另一個(gè)概念,即Immutability,在React 狀態(tài)的不變性一文已介紹過它。

原文鏈接:https://juejin.cn/post/7168257213738254344

欄目分類
最近更新