--- type: slide --- # React 讀書會筆記 - 第四週 --- ## 2-8 React 畫面更新的發動機:state初探 --- ### 觀念回顧 - 單向資料流 - 畫面結果是原始資料透過模板與渲染邏輯所產生的延伸結果,而這個過程是單向且不可逆的。當資料發生更新時,畫面才會產生對應的更新,以資料去驅動畫面。 - React 的一律重繪 - React 在每次資料更新時採一律重繪方式更新畫面,而重繪不是直接針對DOM進行操作,而是根據新的資料重新生成React element,並透過新舊 React element 的比較結果,僅更新真正有異動需求的實際 DOM element,已完成單向資料流的維護。 --- ### 什麼是 state - 在前端應用程式中,需要一種臨時的"可更新資料"來記憶應用程式當下的狀態,並且在資料發生更新的時候也要連動去更新對應的畫面,這種資料稱之為應用程式的「State(狀態資料)」 - 在React中,state 必須依附在component身上才能記憶並維持資料,而發起state資料的更新並啟動重繪時,也只會重繪該component以內(包含子孫代的component)的畫面區塊 - 在function component中,可以透過呼叫useState的hook來定義存取state。 - 補充(class component的state管理) --- ### useState初探 - useState為React內建的hook,是一種僅允許在function component內呼叫的特殊函式。可想像為一種「在component內註冊並存取狀態資料」的工具。 ``` import { useState } from 'react'; export default function App(props) { const [state, setState] = useState(initialState); // .. } ``` --- - useState接收initialState為此 state 的初始預設值並回傳一個陣列 - 第一個為「該次render的當前state值」 - 第二個為「用來更新state值的setState方法」是一個普通的JS函式 - 命名的convention,非React運作機制的要求,因此非強制必須遵守的,是有益於增加程式碼的可讀性及團隊協作。 - state 命名為符合商業邏輯上的意義 - setState 則命名為 set + 上述的名稱 ``` // 以計數器的資料為例: const [count, setCount] = useState(0); ``` --- ### 使用 setState 的方法 - 透過呼叫setState方法來更新state的值,並觸發該component的re-render。 - 重新產生一份新的 React element而非重繪實際的DOM --- ### useState 的範例演示-1 ``` // Counter component export default function Counter() { const [count, setCount] = useState(0) return ( <div> <button>-</button> <span>{count}</span> <button>+</button> </div> ) } ``` - 中間span包裹的為目前Counter的狀態值,初始為0 - 當按下 - 的按鈕時,count會減少1,\<span>內的值會連動更新 - 當按下 + 的按鈕時,count會增加1,\<span>內的值會連動更新 --- ### useState 的範例演示-2 ``` // Counter component export default function Counter() { const [count, setCount] = useState(0) const handleDecrementButtonClick = () => { console.log("counter decrement!") } const handleIncrementButtonClick = () => { console.log("counter inrement!") } return ( <div> <button onClick={handleDecrementButtonClick}>-</button> <span>{count}</span> <button onClick={handleIncrementButtonClick}>+</button> </div> ) } ``` --- ### useState 的範例演示-3 ``` // Counter component export default function Counter() { const [count, setCount] = useState(0) const handleDecrementButtonClick = () => { setState(count - 1); } const handleIncrementButtonClick = () => { setState(count + 1); } return ( <div> <button onClick={handleDecrementButtonClick}>-</button> <span>{count}</span> <button onClick={handleIncrementButtonClick}>+</button> </div> ) } ``` --- ### useState 的範例結果 --- ### 補充觀念 - onClick - onClick in React element - 實際的DOM element e.g. \<button> - 自定義的 function component e.g. \<CustomButton> - Fragment 類型 i.e. \<>, \<Fragment> --- ### 補充觀念 - setState - 呼叫 setState 後,React 並不會立即性觸發 re-render,而是等待事件內的所有城市結束後才會進行 re-render,也就是可能很常聽到的 「setState 方法是非同步」的這個說法 --- ### 補充觀念 - hooks 位置 - Hooks 僅可在 component function 內的頂層作用域被呼叫 ``` function AppComponent() { useState(); // OK if(...) { useState() // Not OK } for(...) { useState() // Not OK } array.forEach(() => { useState() // Not OK }) } useState(); // Not even in component function ``` --- ### 補充觀念 - useState 回傳值為 Array - 呼叫useState時回傳Arra的設計是為了讓開發者可以更方便地將這兩者分別賦值到自訂命名的變數上。 ``` // 如果為回傳物件 const { state: count, setState: setCount } = useState(0); const { state: name, setState: setName } = useState('Foo') ``` (Sandbox) --- ### 補充觀念 - 觸發re-render的唯一合法手段 (SandBox) --- ### 補充觀念 - 辨認在同一個component中的多個state - component 中所有的hooks在每次render都會依賴固定呼叫順序 - 內部機制存在一個有序的列表,依照固定呼叫的順序去儲存useState或任何其他hook的狀態資料 --- ### 補充觀念 - component在不同實例之間的狀態資料是獨立的 ``` export default function App() { return <Counter /> <Counter /> <Counter /> ) } // 此三個都是Counter component產生的實例 // 但各自的資料皆是獨立、互不影響的 ``` --- ### 補充觀念 props 與 state 之間的關鍵區別 - 1 - State存在的機制 - 作為「更新資料後就進行一率重繪來連動更新畫面」 - 若前端App為完全靜態的,就不需使用State - Props存在的機制 - 為了服務「由父component往下傳遞資料給子component」 - 因此並非為用來儲存資料而是用來傳遞資料,我們不該直接修改其內容,以維護單向資料流的可靠性 --- ### 補充觀念 props 與 state 之間的關鍵區別 - 2 - state是儲存應用程式狀態資料的載體,props只是資料由父component傳遞給子component的管道 - 故二者並非同一類型的概念,不存在二選一的問題 --- ### 2-8 Questions 1. React的 state 本質是什麼?State在React的畫面管理機制中扮演了什麼角色? 2. State與component的關係是什麼? 3. 為什麼useState的回傳值會是一個陣列? 4. React是如何辨認並區分同一個component中的多個state的? 5. 同一個component在多個地方被呼叫,他們之間的state資料會互通嗎?為什麼? --- ## 2-9 React 畫面更新的流程機制:Reconciliation --- ### Render phase 與 commit phase - React 畫面處理機制大致上分為兩階段 1. 產生一份描述最新畫面結構的React element 2. 將React element 轉換成實際的DOM element - 在component的畫面管理機制,這兩階段分別為 1. Render phase: component正在渲染並產生React element的階段 2. Commit phase: component正在將React elemen的畫面結構「提交」並處理到實際的DOM --- ### 產生初始畫面 - Reader phase - 執行component function,以 props 與 state 產生初始畫面的 React element - 將產生好的 React element 交給 commit phase 繼續處理 - Commit phase - 將 Render phase 產生的react element 全部進行轉換成實際的DOM element - 透過瀏覽器的DOM API appendChild()全部放置到實際畫面中 - 「首次render並commit到實際DOM」這個流程也常被稱為「mount」 --- ### 更新畫面時 - Render phase - 再次執行 component function 以新版本的props與state完整的重新產生對應的新版React element - 比較新舊的 react element 的樹狀結構,找出差異之處(diffing) - 將React elemen差異之處交給commit phase - Commit phase - 只操作並更新新舊React elemen的差異之處所對應的實際DOM,其餘DOM不進行任何操作 --- ### Reconciliation - 1. 呼叫 setState - 當我們呼叫setState,React會先透過執行 Object.is() 來檢查新傳入的state是否改變,如果資料判定沒有更新則畫面也不需更新,直接中斷接下來的流程。 --- ### Reconciliation - 2. 更新 state 並 re-render - 若資料判定有更新,則執行component function,以新版本的props與state等資料來render一份新的React element --- ### Reconciliation - 3. 更新對應處的DOM element - 將新版的 React element 與前一次生成的舊版 React element,使用diffing演算法來進行樹狀結構的詳細比較 - 在 Commit phase 中 React 自動去操作並更新這些有需要更新的DOM element --- ### 總結 reconciliation 流程 1. 呼叫setState 2. Object.is() 比較舊有state及新指定的state 一樣 => 中斷,不一樣 => 繼續 3. Render phase: 更新state資料並re-render產生新版的React element 4. Render phase: 以diffing演算法比較新舊React element並找出差異之處 5. Commit phase: 操作並更新新舊React element有差異之處所對應的DOM element --- ### setState 觸發子component的re-render - 呼叫 setState 觸發re-render時,如果componen中有呼叫其他的component的話會連帶使這些子component也進行re-render,並層層往下觸發 --- ### --- ### 2-9 Question 1. 什麼是 Render phase 以及 commit phase? 2. 解釋 React 更新畫面的 Reconciliation 流程。 3. 一個 component 有哪些可能會被觸發 re-render 的情形? ---