--- slideOptions: transition: slide --- ### React思維進化 <!-- .slide: class="small" --> - 3-1 如何在子 component 裡觸發更新父 component 資料 - 3-2 深入理解 batch update 與 updater function --- 小9 * 非本科系 * 1-2年 * 前端工程師 --- ### Review <!-- .slide: class="small" --> - component: 組裝畫面的藍圖 - 呼叫 component: 產生實例 - 從外部傳入參數 props: 客製化畫面產生流程 </br> <div style="display: flex;flex-direction:column;margin-left:80px;margin-top:30px"> <span style="color: gray;text-align: left">● 製作珍珠奶茶的流程與配方 → component function<!-- .element: class="fragment small" data-fragment-index="2" --></span> <span style="color: gray;text-align: left">● 調製產出珍珠奶茶 → 產生實例<!-- .element: class="fragment small" data-fragment-index="2" --></span> <span style="color: gray;text-align: left">● 客製化調整甜度冰塊 → 傳入 props 客製化<!-- .element: class="fragment small" data-fragment-index="3" --></span> </div> ---- ### props <!-- .slide: class="small" --> - 用來控制組件顯示的內容或行為<!-- .element: class="small" --> - 為維持單向資料流的可靠性,唯讀且不可修改<!-- .element: class="small" --> - 沒有任何資料型別的限制<!-- .element: class="small" -->  ---- ### state <!-- .slide: class="small" --> - 儲存臨時且可更新的資料 - 唯一合法的更新方式:透過呼叫 <span style="color: orange;">setState</span> 方法,並觸發 re-render ```javascript [4] import {useState} from "react" export default function Counter() { const [state, setState] = useState(initialState) ... } ``` component 觸發 re-render 的2種情形<!-- .element: class="fragment small" data-fragment-index="1" --> ● 本身的 setState 被呼叫時<!-- .element: class="fragment small" data-fragment-index="1" --> ● 當父代或祖父代 component 發生 re-render<!-- .element: class="fragment small" data-fragment-index="1" --> --- ### 在子 component 觸發更新父 component <!-- .slide: class="small" --> - setState 方法可透過 props 傳遞到子 component - 每次呼叫 setState 時,所更新的 state 及觸發 re-render 的 component 是固定的,不受呼叫位置影響 <div style="display: flex; justify-content: center; gap: 10px;">   </div> <span style="color: orange;">→ React 沒有專門的「子 component 向上溝通父 component」機制,因為 setState 即可達成<!-- .element: class="fragment small" data-fragment-index="1" --></span> <span style="color: orange;">→ 無論 setState 被哪個 component 呼叫,它都只會影響擁有該 state 的 Component,並觸發該 Component 及其子 Component 重新渲染<!-- .element: class="fragment small" data-fragment-index="2" --></span> --- <div style="display: flex; justify-content: center; gap: 10px;">   </div> --- ### Quiz #### Q1: 為什麼 React 並沒有且也不需要子 component 向上溝通父 component 的專門機制? <!-- .slide: class="small" --> <ul> <li>props 可傳遞任何類型的資料,包括 setState 方法或封裝後的函式</li> <li>呼叫 setState 方法所更新的 state 及觸發 re-render 的 component 是固定的</li> </ul><!-- .element: class="fragment small" data-fragment-index="1" --> --- #### Q2: 如何在子 component 中觸發更新父 component 的state資料? <!-- .slide: class="small" --> <ul> <li>父 component 透過 props 傳遞 setState 或封裝 setState 的函式給子 component</li> <li>子 component 從 props 取得該函式並調用,以觸發父component 的 state 更新</li> </ul><!-- .element: class="fragment small" data-fragment-index="1" --> --- ### 3-2 深入理解Batch update與updater function --- <!-- .slide: class="small" --> - 在同一事件中連續多次調用 setState,實際上只會re-render一次 ```javascript [] export default function Counter() { const [count, setCount] = useState(0) const handleButtonClick = () => { setCount(1) //並不會馬上更新 state setCount(2) //並不會馬上更新 state setCount(3) //沒有其他程式碼需要執行,開始進行一次 re-render } ... } ``` <br> ● 每次呼叫 setState 方法時,React 會依序記錄到待執行的佇列(queue)<!-- .element: class="fragment small" data-fragment-index="1" --> ● 將多個 setState 進行批次處理,只進行一次re-render來完成畫面更新<!-- .element: class="fragment small" data-fragment-index="2" --> ---- <!-- .slide: class="small" --> ### 佇列 (queue) - 一種資料結構,資料先進先出(First In First Out,FIFO <div style="display: flex; justify-content: center; gap: 10px;">   </div> --- - 加入 queue 的概念 <div style="display: flex; justify-content: center; gap: 10px;">   </div> - 使用直接賦值,只會保留最後一次賦值的結果 ---- <span style="color: orange;">batch update (automatic batching)</span> 藉由合併多次的 re-render 為單次來節省效能的機制<!-- .element: class="small" --> ---- ### batch update <!-- .slide: class="small" --> ● 防止以半成品的資料狀態進行render,以免畫面更新錯誤 ● 不同的state的setState交叉連續呼叫也會支援batch update<!-- .element: class="fragment" data-fragment-index="1" --> ```javascript [] export default function Character() { const [count, setCount] = useState(0) const [name, setName] = useState("Zet") const handleClick = () => { setCount(1) setName("Foo") setName("Bar") setCount(2) setCount(3) } ... } //count=3, name="Bar" ``` <!-- .element: class="fragment small" data-fragment-index="1" --> <br> ● React 18+,讓 batch update 支援更多非同步情境(如 Promise, setTimeout, fetch),進一步減少不必要的 re-render<!-- .element: class="small" --><!-- .element: class="fragment small" data-fragment-index="2" --> --- #### 不想使用 batch update: flushSync() <!-- .slide: class="small" --> - 強制 React **立即執行 re-render**,而不是等待事件完成後才執行 - 將 setState 方法的調用放入 <span style="color: orange;">flushSync() 的 callback 函式中</span> ```javascript [] import { flushSync } from "react-dom" function handleClick() { flushSync(() => { setCount((c) => c + 1); }); console.log("此時畫面已更新"); } ``` 👉 React 18 的全自動 Batch Update 在大多數情況下都能符合預期行為,提升效能並減少不必要的 re-render<!-- .element: class="small" --><!-- .element: class="fragment small" data-fragment-index="1" --> 👉 flushSync() 會強制 re-render,可能破壞 React 的批次更新機制,導致效能下降或影響 UI 互動<!-- .element: class="small" --><!-- .element: class="fragment small" data-fragment-index="1" --> ---- #### [flushSync() 範例](https://react.dev/reference/react-dom/flushSync) <iframe src="https://react.dev/reference/react-dom/flushSync" width="800" height="600"></iframe> --- ### 如何基於state原有值去計算新的值並連續調用setState方法 <!-- .slide: class="small" --> ```javascript [] export default function Counter() { const [count, setCount] = useState(0) const handleButtonClick = () => { setCount(count+1) //0+1 setCount(count+1) //0+1 setCount(count+1) //0+1 } ... } //每次點擊count只會+1 ``` <!-- <iframe src="https://react.dev/learn/queueing-a-series-of-state-updates#react-batches-state-updates" width="800" height="600"></iframe> --> <br> ● 同一次render中的state值為固定且不變<!-- .element: class="fragment small" data-fragment-index="1" --> ● closure 會導致函式捕捉舊的 state 值<!-- .element: class="fragment small" data-fragment-index="1" --> ---- <!-- .slide: class="small" -->  👉每次 render 都是<span style="color: orange;">獨立的快照</span>,同一次 render 內的 state 值是固定,不會在當前 render 內改變,直到下一次 re-render。 👉setState 只是「觸發」新的 render,不會影響當前 render 內的變數值 [Ref:State as a Snapshot](https://react.dev/learn/state-as-a-snapshot) <div>💡使用 <span style="color: orange;">updater function</span> (使用函式更新 state)</div><!-- .element: class="fragment" data-fragment-index="2" --> ---- ### updater function <!-- .slide: class="small" --> ``` javascript setState(preValue => preValue + 1) ``` - 新的 state 是依賴上次 state 運算後的結果,而非用某個新的值取代舊的 state </br> 👉 確保 setState 總是基於最新的 state 值進行計算<!-- .element: class="fragment" data-fragment-index="1" --> </br> 👉 同樣不會立即被執行,但queue紀錄的是updater function本身updater function<!-- .element: class="fragment" data-fragment-index="1" --> --- #### updater function <div style="display: flex; justify-content: center; gap: 10px;">   </div> - 為保證每次執行結果一致,updater function 必須為純函式,不應包含任何副作用<!-- .element: class="fragment small" data-fragment-index="1" --> - React 在嚴格模式會執行2次<!-- .element: class="fragment small" data-fragment-index="1" --> --- #### 以取代值與 updater function 調用 setState 方法 <div style="display: flex; justify-content: center; gap: 10px;">  <!-- .element: class="fragment small" data-fragment-index="1" --> </div> --- #### updater function 後有取代的動作 <div style="display: flex; justify-content: center; gap: 10px;">  <!-- .element: class="fragment small" data-fragment-index="1" --> </div> --- <!-- .slide: class="small" --> #### 適合使用 updater function 情境 若子 component 也是需要依賴原資料更新,只傳 setState 可以省略額外傳 state ```javascript [5,10] import {useState} from "react" import TextControls from './TextControls' export default function TextWrapper() { const [count, setCount] = useState(0) return ( <div> <p>Count Value {count}</p> <CounterControls setCount={setCount} /> </div> ) } ``` ```javascript [2-4] export default function CounterControls({ setCount }) { const decrement = () => setCount(preCount => preCount - 1) const increment = () => setCount(preCount => preCount + 1) const resetCounter = () => setCount(0) return ( <div> <button onClick={decrement}>-</button> <button onClick={increment}>+</button> <button onClick={resetCounter}>reset</button> </div> ) } ``` --- #### 適合使用updater function情境 <!-- .slide: class="small" --> 1. 當 state 的更新依賴於舊的 state 值時(例如加減計數、點擊次數) 2. 當有多個 setState 可能在同一個 render 內執行時(避免批次更新影響結果) 3. 因 setState 本身不是同步的,在某些非同步情況下,使用 updater function 可能會讓 state 更新的順序變得難以預測 --- #### setState 直接賦值 vs. updater function  --- ### Quiz #### Q3.什麼是batch update? <br> 當一事件中多次呼叫 setState 方法後,React 會自動的統一只呼叫一次 re-render 來完成畫面更新,藉由合併多次的 re-render 為單次來節省效能的機制<!-- .element: class="fragment small" data-fragment-index="1" --> --- #### Q4.如果想要手動指定某些 setState 方法的呼叫不要 batch update 時,可以怎麼做? <br> React 18+提供 flushSync 方法,將 setState 方法放入此callback函式中,React 就會以同步的方式立即性的觸發 component 的 re-render,並完成 actual DOM 的更新<!-- .element: class="fragment small" data-fragment-index="1" --> --- #### Q5.什麼是 updater function? 比起直接指定新的 state 值來說,有什麼優點? <br> ● setState 方法可透過呼叫時傳入一組 updater function 作為參數(如 n => n + 1),會根據舊有的 state 值延伸計算並更新,回傳一個新的值<!-- .element: class="fragment small" data-fragment-index="1" --> <br> ● 確保使用的 state 為當下最新的<!-- .element: class="fragment small" data-fragment-index="2" --> ● 同一函式若有多次連續更新特定state時,可確保每次都是最新的值<!-- .element: class="fragment small" data-fragment-index="2" --> --- #### Q6.點擊按鈕後,count數值會更新為?  *setTimeout裡的count會是原先useState的0<!-- .element: class="fragment small" data-fragment-index="1" --> *0 -> 100 -> 1,最後結果為1<!-- .element: class="fragment small" data-fragment-index="1" --> --- #### Q7.點擊按鈕,count數值會更新為?  *updater function取出的值會式內部state最新的值<!-- .element: class="fragment small" data-fragment-index="1" --> *100 -> 101<!-- .element: class="fragment small" data-fragment-index="1" --> → 在某些非同步情況下,使用 updater function 可能會讓 state 更新的順序變得難以預測<!-- .element: class="fragment small" data-fragment-index="2" --> --- 3-1~3-2回顧 <!-- .slide: class="small" --> - setState 方法或封裝後的函式可透過 props 傳遞到子component,子 component 直接呼叫即可更新父 component 的 state。 - 子組件無法直接修改父組件的 state,setState 是唯一觸發更新的方式 - 無論 setState 在何處被調用,React 只關心 state 變更,並依傳入的值更新並觸發該 state 所屬的 component 進行 re-render --- 3-1~3-2回顧 <!-- .slide: class="small" --> - Batch update: React 會將同一事件內的多個 setState 調用批次處理(queue),合併成單次 re-render,以提升效能並減少不必要的渲染 - updater function: 當更新依賴於舊狀態時,使用 updater function 確保結果基於最新狀態(非用新的值取代舊的 state) ``` javascript setState(preValue => newValue) ``` --- Questions?  ---
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up