接下來這篇要來看 React 產生並管理 UI 畫面的核心運作流程與原理。
如果只是單純的照著官方文件學下來不是說不會,畢竟官方文件也是花了4章節在講有關render相關的事情,但還是有些矇矇懂懂的,其實這感覺跟學JavaScript很像,功能正常畫面會出來,但問到為什麼會這樣都是答的支支吾吾,因此這次的目標就是搞懂這個Render!
條件渲染
元件通常都是需要根據不同的條件顯示不同的內容,例如我想要顯示我喜歡的影片等等。在React裡,我們可以透過JavaScript所提供的if
或是運算子(operators)。
例如我想要讓畫面出現完成的打勾沒完成的保持原樣就可以這樣用:
1 | if (isPacked) { |
在正常情況下都是會 return <li className="item">{name}</li>
,但是如果isPacked
是true
的時候,則會 return 另一組JSX tree。
我們也可以用條件(三元)運算子
(Conditional (ternary) operator)來完成這段code:
1 | return ( |
是不是非常簡潔,但要記得不要因為嵌套條件標記而變得混亂。
我們也可以用邏輯與運算符 ( &&
):
1 | return ( |
我們可以將其理解為“如果isPacked
,則 ( &&
) 呈現 ✔
,否則,不呈現任何內容”。
這邊要特別提醒,不要在 && 左側的條件不要放進數字,在 JavaScript 條件判斷時會把 0
轉成 boolean,但在 React 不會做這件事,React 會直接渲染 0
,所以需要在 &&
左邊的條件要直接放進 boolean importance > 0 &&
而非 importance &&
。
1 | function Item({ name, importance }) { |
另外補充一種很常運用 &&
跟 ||
的方法 。
1 | //(條件/運算式A) && (條件/運算式B) |
一開始看到這個我常常被搞混,但看了 Boolean 的真假判斷 之後,稍微有些理解,由於本章是複習React 所以就不深入解說,有興趣的人可以點擊連結去閱讀。
回到題目,由於我們知道只有下方這幾個值會變成false
也就是「falsy」值(true
的話叫做「truthy」值):
Undefined
Null
+0
,0
, orNaN
- 空字串
""
或''
因此這邊我們只需要判斷 truthy
跟 falsy
來決定要回傳哪一個。
在 && 的時候,只要第一個值為 falsy
回傳第一個值,否則回傳後者。
在 | | 的時候,只要第一個值為 truthy
回傳第一個值,否則回傳後者。
渲染列表
在官方文件裡這張主要內容是在講如何使用 map()
跟 filter()
。
假設我現在有一個陣列 people
,我想要把是 chemist 的資料呈現在畫面上,我們可以這樣做:
1 | const people = [{ |
這樣就可以呈現出來在畫面上,但這時候我們看到錯誤:
1 | Warning: Each child in a list should have a unique “key” prop. |
字面上的意思是,你需要給陣列中的每一個 React 元素一個唯一的編號 (unique key),因為 React 需要知道實際上哪個元素是哪一個,React 的 Virtual DOM 才能確定哪些元素是需要在 DOM 中被新增的、哪些是要更新的、哪些是要被刪除的。
另外不建議使用 index 作為key值。
如果陣列的項目沒有被分配 key,React 預設 index 作為 key。但在項目順序可能改變的情況下,官方並不建議使用 index 作為 key。原因是:
- 會影響效能。(這跟React預測元件更新的diffing演算法有關)
- 因為key是基於索引值,因此如果更動順序,也會修改當前的key,可能會導致不合預期的結果。
官方建議的 key來源:
- 來自數據庫的數據
- 本地生成的數據
關於Key兩個規定:
- key 必須在兄弟元素中是唯一的,但是不同的陣列中可以使用相同的 key
- Key 在渲染過程中不應該被生成或更改,因為這樣會破壞Key的唯一性和穩定性。
關於第二點Zet助教有特別說明:
這將導致渲染之間的 Key 永遠不會匹配,從而導致每次都重新創建所有元件和 DOM。這不僅速度慢,而且還會丟失列表項中的任何用戶輸入。相反,請使用基於數據的穩定 ID。
React 畫面更新的核心機制
前面已經說完渲染的使用方式,接下來談談底層的部分,接下來的內容都來自Zet助教鐵人賽文章,有興趣的可以前往觀看原版的。
Virtual DOM
首先我們要知道什麼是 Virtual DOM:
從意義上來說,Virtual DOM 是真實 DOM 的虛構描述體,它實際上也是一種樹狀結構的資料。
當 DOM 的節點需要更動時,不會直接修改 DOM,而是透過 DOM diff 演算法比較 Virtual DOM 修改前與修改後的樹狀結構,再批次更新真實的 DOM 節點。
每一個 Virtual DOM element 的資料都是普通的 JavaScript 物件變數,內容則嘗試在描述一個真實的 DOM element 預計要長的樣子,接著透過負責渲染畫面的程式處理後,就能將 Virtual DOM element 轉換並產生成實際的 DOM element,以更新瀏覽器的實際畫面。
所以實際流程是這樣的:
Virtual DOM (React element) => 經過 Diff Algorithm & root.render() => 產生畫面上的真實 DOM。這個過程永遠單向
在 Diff Algorithm 時,新的 Virtual DOM Tree 與此前最後一次舊畫面用的 Virtual DOM Tree 進行兩棵樹的結構細節比較,其中差異之處才是本次畫面更新中真正有需要變更的部分
至於這樣的好處就是我們能夠透過這個流程,我們就可以將真實的 DOM 操作範圍最小化,並限縮在這些真正需要變更的地方,來盡可能的減少因 DOM 操作而造成的效能花費。
更進階的內容可以參考:關於 Virtual DOM 效益的進階討論
一律重繪的渲染策略
我們了解了Virtual DOM 的概念後,接下來了解React 的一律重繪的渲染策略:
『Virtual DOM 在JavaScript 中只是一些自定義的普通變數資料而已,並不像真實 DOM 那樣有直接與瀏覽器的渲染引擎綁定,因此React才可以做出重繪整個 Virtual DOM。』
在這個策略下,React 只需要知道資料有發生變化就行,而不關心資料具體變化的差異在哪,然後進行一律重繪的流程。說的更具體一點的說,React 中一律重繪的是 React elements。
因此我們在 React 中在講「渲染」或「render」時,通常都是在說 Virtual DOM elements(也就是 React elements)的產生,而不是在指真實 DOM elements 的操作。而「重繪 Virtual DOM」在 React 中通常也被稱為「re-render」,具體在 React 中的行為就是「以新的資料(props 或 state)重新再執行一次 component function,並產生新版的 React elements」。
但就算採取這個方式,當App檔案越來越龐大且複雜後,如果無止境的重繪還是會造成效能問題,因此React 會以 component 作為一律重繪的切分單位,而這也是會什麼 state 必須依附於 component function 的其中一個原因。當我們呼叫一個 state 的 setState
方法時,React 就只會重繪該 state 所屬的 component 以及它底下的所有子孫 components,而不會整個 App 都重繪。
Reconciliation
延續上一個段落,當我們在 component 裡呼叫 setState
方法來觸發資料更新時,此時 React 會先以 Object.is()
方法來檢查新傳入的 state 是否與舊的不同,如果相同的話則判定資料沒有變化所以畫面不用更新,就會直接中斷接下來的流程,反之可能有畫面更新的需求。
React component 會自動觸發 re-render 的流程並且透過 Virtual DOM 還有 Diff 演算法算出畫面中實際需要更新的部分,比對更新前後 virtual DOM 的差異之後,再交由react-dom
負責自動去操作更新這些 DOM elements去更動真實的 DOM,有效減少渲染的次數 ,而這個 Diff 的過程也被稱作 reconciliation。
另外setState
觸發的 re-render
會連帶觸發子 components 的 re-render
。