--- type: slide --- ## React 思維進化 ## 2-8 ~ 2-9 --- ## Recap --- ## 2-8 ### React 畫面更新的發動機: ### state 初探 --- * #### React 的 state 概念,以及 state 與一律重繪機制的關聯 * #### 了解 <font color="orange"> useState </font> 的基本用法與觀念 --- ### 什麼是 state (狀態資料) * 「可更新的資料」來記憶應用程式當下的狀態 * 資料發生更新時也連動去更新對應的畫面 --- ### React 核心設計模式:單向資料流 * 將資料管理與畫面渲染分開進行處理 * 原始資料是畫面的結果起源,更新原始資料的行為則是連動畫面更新 * state 機制則為可更新的原始資料角色 --- ### 一律重繪制策略 * 每當原始資料更新時,重繪畫面的 React element * 比較新舊 React element 差異之處,進行實際 DOM 的更新 --- ### state 機制 * 作為 component 內狀態資料的儲存載體,記憶並維持狀態資料 * state 資料更新,觸發該 component 以內畫面重繪(包含子元件) * 生命週期隨著 component 存在與死亡 --- ### useState 初探 * React 內建的 hooks 實踐 state 與畫面重繪的核心機制 * 用來定義、更新程式的狀態資料,並觸發元件區塊畫面的重繪達到瀏覽器更新 --- ### 什麼是 Hooks * 由 React 提供的 API,<font color="orange">use</font> 開頭的函式 --- ### Hooks 解決了什麼問題 ``` 原因:16.8 版以前想要使用到生命週期的方法,只能使用 class component ``` ``` 1. 狀態相關的邏輯在 class component 之間難以複用 2. 複雜元件的邏輯會越寫越讓人難理解 3. class 對於開發者來說不好理解 ``` --- ![Screenshot 2024-03-17 at 9.27.48 PM](https://hackmd.io/_uploads/r1qv8dECa.png) --- #### 使用 Hooks 規則 * 僅可以在 funciton component 頂層作用域內呼叫的特殊函式 ``` export default function Counter() { useState(0); // O if(...) { useState(0); // X } for(...) { useState(0); // X } } useState(0); // X ``` --- ### useState ``` import { useState } from 'react'; const initState = 0 export default App(props) { const [state, setState] = useState(initState) } ``` ``` 1. state: 該次 render 的當前 state 值 2. setState: 用來更新 state 值的方法 3. initState: 用來指定 state 初始值 ``` --- #### state & setState 關係 ``` * 透過 setState 方法用來更新 state 觸發元件的重繪機制(re-render) * React 開發慣例,陣列解構將 useState 回傳值可重新命名 const [count, setCount] = useState(0) ``` --- #### 範例演示 ![Screenshot 2024-03-16 at 11.14.16 PM](https://hackmd.io/_uploads/Bk-DDVmCa.png) --- ![Screenshot 2024-03-16 at 11.09.15 PM](https://hackmd.io/_uploads/BkE4INmCa.png) --- #### state 存放「會更新的動態原始資料」 ![Screenshot 2024-03-16 at 11.18.34 PM](https://hackmd.io/_uploads/ByJPd4QAp.png) --- ### DOM Events * <font color="orange">DOM事件處理</font>是指處理在網頁上發生的事件 * 點擊按鈕(click) * 鍵盤按鍵(keyup) * 鼠標移動(mousemove) --- #### onClick 事件綁定 ![Screenshot 2024-03-16 at 11.40.14 PM](https://hackmd.io/_uploads/SJXd6VX0p.png) --- ### 觀念釐清 ``` React element: 1. 對應實際 DOM element 如 <div>、<button> 2. 自定義 component function 如 <Foo> 3. Fragment 如 <> 或 <Fragment> ``` * 上述種類中僅<font color="orange">對應實際 DOM element</font>,才會內建 **onClick 事件綁定的 props** * React 會將事件處理綁定到瀏覽器對應的事件觸發上 --- ``` <button onClick={handelClick}> <Foo onClick={handelClick}> ``` --- #### 使用 setState 方法 * 呼叫 setState 會接受新的 state 當作參數,並觸發**元件重繪(re-render)** * re-render 並不是渲染實際 DOM element,而是元件重新執行一次產生新的 React element --- ![Screenshot 2024-03-16 at 11.57.09 PM](https://hackmd.io/_uploads/HJqiVHmRT.png) ``` 執行到 setCount 方法後: 1.以參數指定新的值為當前的 prveCount +/- 1 2.觸發元件 re-render ``` --- ![Screenshot 2024-03-17 at 12.16.54 AM](https://hackmd.io/_uploads/By17IHXA6.png) ``` Counter component re-render: 1. 產生新的 React element 2. 比較新舊版本的 React element 差異僅 <span> 不同 3. 瀏覽器操作更新該處的實際 DOM element ``` --- * React 不會立即觸發 re-render,而是等待正在執行的事件內所有程式都結束,才進行 re-render。 --- #### 為什麼 useState 的回傳值是一個陣列 ``` const { state: count, setState: setCount } = useState(0) const { state: email, setState: setEmail } = useState('') ``` ``` const counter = useState(0); console.log("counter", counter); // [0, f] ``` --- #### 呼叫 setState 正確觸發 re-render 唯一合法手段 * state 作為單向資料流的起點,透過 setState 方法更新資料才能正確觸發 re-render 進行畫面更新 --- #### React 如何辨認 component 多個 state ``` const [name, setName] = useState('name') const [email, setEmail] = useState('email') ``` * 透過定義不同命名避免衝突 * hooks 在每次 render 都會依賴於固定的呼叫順序區分彼此 --- #### componet 不同實例之間的狀態獨立 * component 是一種藍圖,透過藍圖創建實例 * 每個實例為獨立、互不影響;實例擁有的 state 也是各自獨立 --- #### Props 與 state 區別 ``` 並非只是「靜態唯獨資料」、「可更新資料」,兩者定位完全不同: * state -> 狀態資料的維護,儲存應用程式狀態資料載體 * props -> 服務父元件往下傳遞給子元件的管道 總結:兩者為不同類型的概念,不存在二選一的使用狀態 ``` --- ### QA * React 中 State 的本質是什麼? State 在 React 畫面管理機制中扮演什麼角色? * State 與 component 的關係是什麼? --- ## 2-9 ### React 畫面更新的流程機制: ### reconciliation --- * #### 什麼是 render phase 以及 commit phase * #### 了解畫面管理的 reconciliation 機制流程 --- ### Render phase ### Commit phase --- #### 畫面處理機制分為兩個階段 1. 產生描述畫面結構的 React element 2. 將 React element 結構轉換成實際 DOM element --- * **<font color="orange">Render phase</font>**: component 正在渲染並產生 React element * **<font color="orange">Commit phase</font>**: component 正在將 React element 提交並處理實際的瀏覽器 DOM 中 --- ### 產生初始畫面 render component --- * Render phase: * 執行 component function 以 props & state 等資料來產生初始畫面 * 將 React element 交給 commit phase 處理 * Commit phase: * 第一次的 render 實際的 DOM 還沒有任何對應的 DOM element,因此將所有 React element 進行轉換並透過 DOM API <font color="orange">appendChild()</font> 放置到實際畫面中 * 此流程也被稱為 <font color="orange">mount</font>,當流程完成時稱為 <font color="orange">mounted</font> --- ### 更新畫面 re-render component --- * Render phase: * 再次執行 component function 以新的 props & state 等資料,完整的重新產生對應的 React element * 將新的 React element 與前一次的 React element 進行樹狀結構比較,找出其中差異之處,將其交給 commit phase 處理 * Commit phase: * 只去操作並更新差異之處所對應的實際 DOM element --- ### Reconciliation --- #### 1. call setState 更新 state 並發起 re-render * call setState 時,React 會先透過 `Object.is()` 檢查新傳入的 state 值是否與舊有值不同: * 否:中斷接下來的流程 * 是:發起 component function re-render --- #### 2. 更新 state 並 re-render component function * 以新的 props 與 state 等資料來 render 出一份新版的 React element --- #### 3. 新舊版本的 React element 比較,更新差異之處所對應的 DOM element * React 會以 diffing 演算法來進行樹狀結構比對新版的 React element 與舊版的 React element * 比較出差異的部分,接著 commit phase 中,React 會自動操作更新 DOM element --- #### Reconciliation 完整流程 ![Screenshot 2024-03-24 at 12.20.49 PM](https://hackmd.io/_uploads/SJjUcQpRT.png) --- ### diffing 演算法 reconciliation 過程中使用的比對策略,複雜度 O(n),必須實現以下兩點假設 --- #### 兩個不同的 element 會產生出不同的 Tree ![Screenshot 2024-03-31 at 1.43.48 PM](https://hackmd.io/_uploads/r1rmudL1R.png) --- #### 使用 key 屬性提示子元素在不同的渲染中是穩定的 ![Screenshot 2024-03-24 at 12.13.28 PM](https://hackmd.io/_uploads/r12KOQpC6.png) --- #### component re-render 觸發情況 * component 本身有定義 state,該 setState 被呼叫時 * component 本身沒有定義 state,而是父層級發生 re-render,深為子元件也連帶 re-render --- ### QA * React 更新畫面 Reconciliation 流程 * component 哪些可能會被觸發 re-render 情況