---
# System prepended metadata

title: React 思維 3-1~3-2

---

---
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" -->

 ![Screenshot 2025-03-26 215833](https://hackmd.io/_uploads/HJs0hkza1l.png)

----

### 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;">
    
![Screenshot 2025-03-31 111756](https://hackmd.io/_uploads/SkyzCRvakg.png)
    
![Screenshot 2025-03-31 111813](https://hackmd.io/_uploads/rkzzCRwTJx.png)
    
</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;">
    
![Screenshot 2025-03-31 143044](https://hackmd.io/_uploads/HJVXoWdp1g.png)

![re-render](https://hackmd.io/_uploads/H1NA7BzTke.jpg)

</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;">
    
![queue](https://hackmd.io/_uploads/SyrdosMayl.jpg)
    
![描述文字](https://i.imgur.com/AjMPykQ.jpg)
    
</div>

---

- 加入 queue 的概念

<div style="display: flex; justify-content: center; gap: 10px;">

![Screenshot 2025-03-31 114233](https://hackmd.io/_uploads/ryQamJ_ayg.png)

![3-1-Page-2](https://hackmd.io/_uploads/BkRgN3z61g.jpg)

</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" -->

![Screenshot 2025-03-31 150946](https://hackmd.io/_uploads/rkRXEGOpke.png)

👉每次 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;">
    
![Screenshot 2025-03-28 162910](https://hackmd.io/_uploads/H1_ZLSETyl.png)

![updater fn](https://hackmd.io/_uploads/rJn-8r4akg.jpg)

</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;">
    
![Screenshot 2025-03-28 172015](https://hackmd.io/_uploads/rku71LN61g.png)

![3-1-Page-4](https://hackmd.io/_uploads/H12PkUVake.jpg)<!-- .element: class="fragment small" data-fragment-index="1" -->

</div>

---

#### updater function 後有取代的動作

<div style="display: flex; justify-content: center; gap: 10px;">
    
![Screenshot 2025-03-28 173425](https://hackmd.io/_uploads/HyB-b846ke.png)
    
![3-1-Page-5 (2)](https://hackmd.io/_uploads/r1tab8VTyg.jpg)<!-- .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


![Screenshot 2025-03-29 191039](https://hackmd.io/_uploads/HkIhPnrakl.png)


---

### 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數值會更新為?

![Screenshot 2025-03-31 102810](https://hackmd.io/_uploads/HJ4jfRva1x.png)

*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數值會更新為?

![Screenshot 2025-03-31 103425](https://hackmd.io/_uploads/HkwCQCPT1e.png)

*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?
![](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExaHZhMjlhNXZ6YTU5eTlnYXJlZWJqc2xvZGZnZTVhdHpudXcxMGt1bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/26gsjCZpPolPr3sBy/giphy.gif)

---

