1. 程式人生 > >React踩坑筆記 —— 差分演算法(一)

React踩坑筆記 —— 差分演算法(一)

目錄


概要

  • 《React踩坑筆記 —— 差分演算法(二)》

React提供了宣告式的API,以至於我們不需要擔心每次更新具體發生了什麼更改。這使得我們開發應用變得很容易,但始終無法清楚React內部是如何實現的。本文解釋了在

“差分演算法” 中如何做出選擇,使得元件更新,在足夠快的高效能應用中仍然可以預測。

在理解 “差分演算法” 之前,首先我們需要去理解:

  • document與Dom nodes的關係;
  • document與react元件的關係;
  • react元件與react元素樹的關係;
  • render()函式的作用與生命週期;
  • ReactDOM.render()函式的作用。

Document與React元素樹

JSX程式碼

import React from "react";
import ReactDOM from 'react-dom';

const
First = () => (<div> <h1>第一棵樹</h1> <div>附加資訊</div> </div>); const Second = () => (<div> <h1>第二棵樹</h1> <Third /> </div>); const Third = () => (<div> <h1>第三棵樹</h1> <div>附加資訊</div> </div>
); const App = () => (<div> <First /> <Second /> </div>); ReactDOM.render(<App />, document.getElementById('root'));

圖示

在這裡插入圖片描述

說明

萬變不離其宗,對於一個HTML頁面,documentNode永遠會是它的基礎組成部分(實際上document也只是一個特別的Node),如果把document看做一張紙,那麼Node可以看成點綴在紙上的文字、圖片或符號。

在React中,React元件是由真實的Dom Node組成,最終會被轉化為Dom Node並在document中渲染出來(事件\樣式\選擇器,只屬於真實的DOM Node)。

當你使用React的時候,你可以認為:

  • 每一個React元件都對應一棵React元素樹
  • 或者說,在某一時間點上每一個render()函式都建立了一棵React元素樹
  • 這裡的render()函式,見下文。
  • 每一個React元件可以由一個或多個React元件以及HTMLElement組成,即是 —— 每一棵樹都可以作為另一棵樹的子樹,正如上圖所示:<Third /><Second />的子樹,<Second /><First />又都是<App />的子樹。
  • 在上圖中,<App />元件所對應的React元素樹,是最大的一棵樹。通過API ——ReactDOM.render(element, container[, callback])將這棵樹掛載到容器(container)中。
  • 所謂容器(container),不過是document中某個指定的Div或其它塊級元素
  • 所以,無論通過JavaScript選擇器還是dom操作外掛還是React提供的Refs,對Dom node的操作都會被保留在這個document中,並在元件stateprops發生改變的時候參與 “差分演算法”
  • 所以,如果當前頁面被重新整理,那麼原來的document也不復存在:
    • React元件會被重新掛載
    • Dom node會被重新插入
    • 如果你想保留某些狀態,你需要高於頁面的儲存方式,例如sessionStorage( 標籤頁 localStorage( 瀏覽器 cookie( 瀏覽器 Web SQL( 瀏覽器 伺服器資料( 任何地方
  • 路由(Router),只會引起某些元件的解除安裝或掛載,而不會顛覆整個document。

render()

對於Function元件(無狀態元件)就是自身,對於Class元件(有狀態元件)就是從React.Component繼承的render()方法。

返回值型別

render()被呼叫時,可以依賴propsstate來構造返回值,其返回值型別包括如下:

  • React elements. 典型地通過JSX語法建立,例如<div /><MyComponent />
  • Arrays and fragments. 由於 “差分演算法” ,React要求render()必須返回單元素(single),React提供了<></><React.Fragment>允許開發者從render()中返回多元素
  • Portals. 有時我們需要將子元件渲染到父元件外的某個Dom node上,例如對話方塊、懸浮卡、提示資訊。Portals 為我們都提供了首選方式。
  • String and numbers. 字串和數字被以文字節點(Text nodes)的形式渲染到Dom中
  • Booleans or null. 返回Null表示什麼都不做。 (大多數情況是返回 test && <Child /> ,這裡test 是布林值(boolean)

純函式

render()應該是一個純函式,返回值只依賴於stateprops,並且不會產生副作用,副作用包括:

  • 修改stateprops
  • 呼叫非純函式。如 Date.now()Math.random()
  • 網路請求,路由跳轉
  • 瀏覽器互動。setTimeout()console.log()

如果你需要瀏覽器互動網路請求,可以選擇在componentDidMount()其它合適的宣告周期函式中執行。有些條件沒有做硬性要求,但是保證render()作為純函式,能夠提高元件更新的效能。

生命週期

元件在掛載(Mounting)和更新(Updating)時,render()都會被執行,併產生一棵新的React元素樹,然後執行 “ 差分演算法” 更新UI(使用者介面)。宣告週期執行順序如下。詳情可見官方文件《component-lifecycle》

Mounting

  • constructor()
  • static getDerivedStateFromProps()
  • UNSAFE_componentWillMount()
  • render()
  • componentDidMount()

Updating

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • UNSAFE_componentWillUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

ReactDOM.render()

ReactDOM.render(element, container[, callback])

該方法將整個React元素樹渲染到document中指定的<Div />容器中,並返回根元件(對應最大元素樹)的引用(Function 元件 —— 無狀態元件,沒有例項(Instance)所以返回null)。

如果這個React元素(根元件)先前已經被渲染到容器中,那麼將對它執行更新,按照 “差分演算法” 去呈現它。

如果這個可選的引數—— 回撥函式,被提供。那麼會在元件被渲染更新後呼叫。

Note

  • ReactDOM.render()控制著容器(container)節點的內容,第一次呼叫時,任何存在的Dom元素都會被替換,之後再呼叫會使用React Dom diffing algorithm(差分演算法)進行高效更新。
  • ReactDOM.render()不修改容器節點(只修改容器的子節點)。
  • ReactDOM.render() 當前返回根元件(root ReactComponent )例項的引用。然而,返回值的使用已經被遺留了,應該避免去使用它,因為在未來的React版本中,在某些情況下React會採用非同步的方式去渲染元件。如果你真的需要引用根元件例項,首選解決方案是為你的根元件新增《callback ref》
  • 另外,使用ReactDOM.render()去融合服務端渲染容器(server-rendered container)已經被遺棄了,並將在React 17被移除,代替使用hydrate()