---
# System prepended metadata

title: 《React 思維進化》閱讀筆記

---

《React 思維進化》閱讀筆記

# 2-1 DOM 與 Virtual DOM

1. DOM 的操作會綁定渲染引擎重繪畫面。React 透過比較新舊 Virtual DOM 差異、執行最小範圍的 DOM 操作，達到效能優化。
2. [Virtual DOM 效益釐清](https://www.zhihu.com/question/31809713)：框架只是**足夠高效**，並更好維護
   1. 框架的目的是提供抽象層，讓開發者不需直接操作 DOM，以[聲明式 declarative 的方式建立 UI（非宣告式 imperative）](https://react.dev/learn/reacting-to-input-with-state#how-declarative-ui-compares-to-imperative)，從而讓程式碼更容易維護。
   2. 因為多了一層框架 API，如 Virtual DOM Diff 的 JS 計算，不會比手動操作 DOM 高效（但大部分手動操作的程式碼也不是以最高效的方式操作 DOM，以渲染新的 list 為例）。
   3. 框架提供足夠高效且可以處理所有可能更新場景的解法：
      - React - Virtual DOM
      - Vue - 依賴搜集
      - Angular - 髒檢查 (dirty check)
      - 性能比較需區分不同場景：
        - 初始渲染：Virtual DOM > 髒檢查 >= 依賴收集
        - 小量數據更新：依賴收集 >> Virtual DOM + 優化 > 髒檢查 > Virtual DOM（無優化）
        - 大量數據更新：髒檢查 + 優化 >= 依賴收集 + 優化 > Virtual DOM

# 2-2 React Element：描述並組成畫面的最小單位

1. Virtual DOM 上的一個節點就是一個 React Element

    ```jsx
    // 官網建議用 JSX 取代 createElement 
    React.createElement(
      'h1', // 元素類型
      { className: 'greeting', key: 'unique' }, // 屬性
      'Hello ', // 第三個參數後都是子元素
      createElement('i', null, 'child'),
    );

    // React Element 是 JS 物件
    {
      type: 'h1',
      props: { className: 'greeting', children: ['Hello', { type: 'i', ...} ] },
      key: 'unique',
      ref: null,
      $$typeof: Symbol('react.element'),
    }
    ```

2. **Treat React elements and their props as [immutable](https://en.wikipedia.org/wiki/Immutable_object)** and never change their contents after creation.
   - React will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) the returned element and its `props` property **shallowly** to enforce this.

3. [和 DOM element 的對應與差異](https://react.dev/reference/react-dom/components)
   - inline style 改成 obj / `for` 改成 `htmlFor` 等。
   - 補充 - Custom HTML elements (tag with a dash):
     - Web Component 簡介：在任何現代瀏覽器中直接使用，具有跨框架互操作性。

       | 特性             | Web Components           | 框架元件                 |
       | ---------------- | ------------------------ | ----------------------- |
       | **跨框架互操作性** | 高，適合跨框架使用        | 低，耦合於特定框架       |
       | **樣式隔離**     | 原生 Shadow DOM         | 通過工具模擬實現         |
       | **數據綁定**     | 手動實現                | 框架內建支持            |
       | **學習曲線**     | 較高，需要熟悉原生 API   | 較低，框架提供高級抽象   |
       | **性能**         | 高，但需要手動優化       | 高效，依賴框架內建的優化機制 |

     ```jsx
     const App = () => {
       return <my-custom-element class="my-class" for="input-id"></my-custom-element>;
     };

     // button 被當作自定義元素處理
     const App = () => {
       return <button is="my-custom-button" custom-attr="123"></button>;
     };
     ```

4. 面試問題：component function 回傳的值是什麼？
   - 答案：React Element，它是描述 UI 的 JavaScript 物件。

     ```jsx
     // 編譯階段
     const MyComponent = () => <div>Hello</div>;
     ↓
     const MyComponent = () => React.createElement('div', null, 'Hello');
     ↓
     {
       type: 'div',
       props: { children: 'Hello' },
       // 其他內部屬性
     }

     // 運行階段：將 React Elements 組織成 Virtual DOM，經過 Diff 更新 DOM
     ```

# 2-3 Render React Element

章節目標：了解 React 如何將 Virtual DOM 的結構元素： React element， 轉換成實際的DOM element 並繪製到瀏覽器畫面

- 瀏覽器環境繪製
    - 使用React 提供的`react-dom`
    - 流程概念：指定瀏覽器畫面中的特定區塊，讓React對其擁有管轄權來持續進行 Virtual DOM 轉換為 DOM 的單向同步化
    - 具體步驟：
        1. 準備容器，用來輸出 Virtual DOM 轉換為後的 DOM 結果
        
        ```jsx
        // index.html
        <body>
        <div id="root"></div>
        </body>
        ```
        
        2. 選取目標容器，用此元素透過呼叫 `ReactDOM.createRoot()`方法， 建立畫面渲染管轄的入口點（root）
        
        ```jsx
        // index.js
        import ReactDOM from "react-dom/client";
        
        // 選取容器元素
        const rootElement = document.getElementById("root");
        // 建立 React App 用來管理DOM element 輸出結果的入口點
        const root = ReactDOM.createRoot(rootElement);
        ```
        
        3. 準備 React element
        
        ```jsx
        // index.js
        import React from "react";
        
        const buttonReactElement = React.createElement(
        	'button', // HTML elements
        	{ id: 'button' }, // attribute
        	'按鈕' // child element
        )
        ```
        
        4. 透過 root.render() 將 React element 繪製成實際 DOM element
        
        ```jsx
        // index.js
        root.render(buttonReactElement);
        ```
        
        5. 結果
        
        ```jsx
        	<body>
        	<div id="root">
        		<button id="button">按鈕</button>
        	</div>
        </body>
        ```
        
    
    - 補充
        
1. 1.         <aside>
        💡
        
        `createRoot()` API[官方文件](https://react.dev/reference/react-dom/client/createRoot)
        
        ```jsx
        const root = createRoot(domNode, options?)
        // options => onCaughtError, onUncaughtError, onRecoverableError, identifierPrefix
        
        // 回傳的物件包含兩個方法render, unmount
        const { render, unmount } = root
        ```
        
        </aside>
        
      
        
        Q：為何要使用其他DOM，而不用 document.body 當 root 的容器元素？
        A：React不建議主要是第三方套件時常針對 document.body 的子元素修改或操作，會造成React管理元素不穩定的情況，因此建議為React另開專門容器 （如果用 body 當容器，React 會在console 中報錯提醒）
        
        
        <aside>
        💡
        
        Q：如果 root 裡有非 React element 的 DOM element 會如何？
        A：會在root.render()後，被轉換後輸出的實際DOM覆蓋掉
        
        </aside>
        
        <aside>
        💡
        
        Q：root 只能有一個嗎？
        A：React 也支援多個 root，但假如是 APP 是 SPA（Single-Page-Application）會建議只使用一個 root (補充其他多個root的情境)
        
        </aside>
        
        <aside>
        💡
        
        React DOM版本差異：
        
        ```jsx
        // < React 18
        import ReactDOM from "react-dom";
        
        ReactDOM.render(buttonReactElement, rootElement)
        ```
        
        </aside>
        
    
    <aside>
    🔥
    
    **React 只會更新真正需要被更新的DOM element**
    
    Virtual DOM 之所以能最小化DOM操作，主要是透過 React 對新舊 React element 的比對，避免產生不必要的DOM操作，讓開發者專注於管理React element
    
    </aside>
    
- 瀏覽器以外的環境繪製
    
    React 的畫面繪製是基於 兩階段（`Reconciler`, `Renderer`） 流程進行：
    
    - Reconciler
    定義及管理畫面結構描述
    瀏覽器環境：建立新 React Element ，在有更新需求時比對新舊 React Element ，接著交給 Renderer
    - Renderer
    將畫面結構描述轉為實際畫面
    瀏覽器環境：react-dom 產生的 root 繪製為實際的DOM，如後續 Reconciler 比較新舊差異時進行同步化工作
    
    <aside>
    💡
    
    個人對2-3-3的理解是，因繪製拆分為兩步驟，reconciler 可通用（只要環境能跑JS），當 renderer 這階段 替換為支援目標環境的 renderer，就可以管理瀏覽器DOM以外的介面繪製，ex: APP 的 React Native, React-pdf 等等。
    
    React: “**Learn once, write anywhere”**
    
    </aside>


# 2-4 JSX 語法概念釐清

1. 使用 `babel` 或 `esBuild`（如 Vite 預設）等轉譯器，透過各自的 JSX transformer（例如 `@babel/plugin-transform-react-jsx`），將 JSX 轉為 JS。
   - 補充：
     - compiler：高階語言轉低階機器語言。
     - transpiler：高階語言轉另一高階語言。

2. [React 17+ 提供 jsx-runtime](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)，為 `createElement()` 的優化版。
   - 新的 Babel JSX transformer 會在轉譯時自動引入。
   - [@vitejs/plugin-react npm](https://www.npmjs.com/package/@vitejs/plugin-react)：使用 automatic JSX runtime。
       參考：[GitHub - esbuild PR #2349](https://github.com/evanw/esbuild/pull/2349)

# 2-5 JSX 語法規則與畫面渲染

條件判斷渲染：注意 0 雖然是 falsy，但數字會被轉成字串印在畫面上
```jsx
const MyFunc = ({num}) => (
    // ...
    { 0 && <div>不會出現</div> }
    // ...
)
```

章節目標：理解JSX語法的設計脈絡

- JSX 與 HTML 標籤閉合的差異
    - Q：HTML 不一定需加 “閉標籤” 的原因
        1. 標籤本身為「空元素」，只需要寫“開標籤”即可 ( ex:  <img>, <input> )
        2. 瀏覽器對 HTML 解析的容錯性，對於需要閉合的標籤，瀏覽器會自動推斷對應的閉標籤 ( ex:  <div></div> )
        
        <aside>
        💡
        
        自我閉合： `<div></div>`  ⇒ `<div/>`  當標籤內沒有其他子元素，可以用此自我閉合方式表達
        
        </aside>
        
    - Q：JSX 一定需要進行閉合的原因
        
        不管是自我閉合、還是加 閉標籤，JSX 一定要做閉合原因在於：
        
        在 JSX transformer 進行轉譯成 `createElement()` 寫法時，
        
        第三個參數 (指定子元素) 欄位，在缺乏閉合情況下，會無法正確解析 React element 層級關係
        
    
- 在 JSX 內表達 字串字面值 及  表達式 及  JSX 的差異
    - 字串字面值：以雙引號包住 (ex: id="foo")
        
        ```jsx
        const element = <div id="foo">字串字面值</div> 
        ```
        
    - 表達式：以大括號包住 (ex: onClick={handleClick})
        
        ```jsx
        const handleClick = () => {
        	console.log('click')
        }
        const element = <div id="foo" onClick={handleClick}>表達式</div> 
        ```
        
    - JSX：是一種表達式，但可以不需要用大括號包住 (ex: <input />)
        
        ```jsx
        const element = (
        	<div id="foo" onClick={handleClick}> 
        		<input />  
        	</div>  // <input /> 是 React.createElement('input') 的表達式，但為更簡單表達，可省略大括號
        )
        ```
        
- React Element 不同型別的子元素的處理方式
    - React element：直接轉換為對應的DOM element
    - 字串值：印出
    - Boolean / `null` / `undefined` ：忽略不印，作為判斷渲染使用
    - Array：攤開子元素（如果子元素是可印型別）後印出
    - Object / function ：無法印出，報錯
- 畫面渲染邏輯
    - 列表動態渲染
        
        ```jsx
        const items = ['1', '2']
        
        const element = (
        	<div>
        	{items.map((item)=> <li>{item}</li>)}
        	</div>
        )
        ```
        
        經過 transpiler 後 ⇒
        
        ```jsx
        const items = ['1', '2']
        
        const element = React.createElement(
        	'div',
        	null,
        	items.map((item) => React.createElement('li', null, item))
        )
        ```
        
        <aside>
        💡
        
        Key
        
        列表渲染 需要為React element 填上不重複key，作為Virtual DOM 效能優化處理
        
        </aside>
        
    
    - 條件判斷渲染
        1. if / else
        2. &&
        3. 三元運算

<aside>
💡

Q：JSX 語法第一層為何只能有一個節點？

A：因為 一個JSX 表達的是呼叫 一個 `createElement()` 的結果，也就是 一個React element 。故無法同時表達兩個React element

以下面錯誤程式為例：

```jsx
const elements = ( // error
	<input />
	<button />
) 
```

多個根節點在樹狀資料結構來看是不合法的

為此，`Fragment`  即是 React 提供用來避免多餘的容器元素、不會渲染到實際DOM的解決方案。

</aside>


# 2-6 單向資料流
1. 單向資料流是什麼？畫面是原始資料透過模板與渲染邏輯產生的延伸結果。
2. 目的：資料與畫面可預測性良好且易於理解
3. 如何實現：「資料與畫面分離，資料驅動畫面更新」、「維持單一資料來源」
4. 方法一：資料與模板的綁定關係，因應資料更新的範圍，更新對應的 DOM，[範例](https://codesandbox.io/p/sandbox/96fzf5)。（Vue）
    - 優：精準操作 DOM 減少效能浪費
    - 缺：人為做複雜的綁定易有缺失
5. 方法二：資料更新後一律重繪，[範例](https://codesandbox.io/p/sandbox/ke7msp)。（React）
    - 優：簡單直覺
    - 缺：浪費效能
6. React 透過 virtual DOM 比較新舊結構差異，減少效能浪費


# 2-7 component 初探


1. 什麼是 component？根據需求將關心的特徵和行為歸納，抽象化成一套適用的流程或邏輯（具通用性和重用性）
3. 定義 component / 創建藍圖：透過函示表達「一段產生特定結構的 React Element 的流程」
4. 呼叫 component / 建立實例：實例之間彼此獨立、互不影響
5. props
    - React 沒有限制 props 的資料型別
    - [props 是唯獨且不可修改的](https://hackmd.io/sGFgW-5nSrmz9tgCjba97Q?both=&stext=1472%3A197%3A1%3A1735734479%3AdpdChb)，有時 React 無法阻擋或警告，需要開發者自行注意
    - 從函式參數解構出來的變數是以 const 定義的
        ```jsx
        const MyFunc = ({num}) => {
            num = num + 1 //會報錯，runtime error
            // ...
        }
        ```
    - children: 不可以是物件或函式
    
### 2-7-6 ～ 2-7-8 父子 component 及  render

- component 內除了可包含實際DOM 的 React element 還可包含 其他component作為子元件
    
    ```jsx
    // 包含實際 DOM
    const Component1 = <div/>
    
    // 包含 子元件
    const Component2 = <Component3/>
    const Component3 = <div>元件3</div>
    ```
    

- 為何 component 命名首字母需大寫
    1. JSX transformer 的辨識需求，轉譯結果也會不同
    2. 社群的命名慣例，方便其他開發者辨識是否是自定義元件
    
    ```jsx
    // 舉例同上，該如何辨識標籤是表達：字面值 or 表達式？以大寫開頭區分
    const Component1 = <div>元件1</div> // => React.CreateElement('div')
    const Component2 = <Component3/> //  => React.CreateElement(Component3)
    ```
    

- component render
    
    以 component 呼叫，到執行 component function ，最終回傳描述轉譯後的 React element，稱之為“ 一次render ”
    
    父層的 一次render 會執行到不再遇到子元件為止
    
    - 流程範例
    
    ```jsx
    const Component1 = () => (
    	<>
    		<Component2/>
    		<Component2/>
    	</>
    )
    
    const Component2 = () => (
    	<>
    		<Component3/>
    		<Component3/>
    	</>
    )
    
    const Component3 = () => (
    	<h3>h3</h3>
    )
    ```
    ![image](https://hackmd.io/_uploads/rypP1YYUyx.png)

    
    
    <aside>
    💡
    
    component re-render
    
    內部狀態更新時發生，面對結構新舊差異，React 是一律重繪、而非更改原本 React element 
    
    </aside>
    
# 2-8 state 初探
1. 什麼是 state？
    - 用於記憶狀態的可更新的資料，更新時觸發重繪
    - 必須依附在 component 內，生命週期同 component
    - component 的不同實例 state 互不影響
2. 如何使用 useState？
    `const [state, setState] = useState(init)`
    - 透過呼叫 setter function，觸發 re-render，即重新呼叫 component function 產生新的 react element（3-2 batch update, updater function）
    - 依據固定的呼叫順序區別同個 component 內的 useState 呼叫，因此僅 top level 使用（不可放在迴圈、判斷式中）
    - 回傳陣列而非物件，方便解構賦值
    - React 官方文件：[State: A Component's Memory](https://react.dev/learn/state-a-components-memory)
3. `state` vs. `props` ？
    - state：儲存 component 狀態
    - props：傳遞資料
4. 補充：[How does React know which state to return? ](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e)
    ![image](https://hackmd.io/_uploads/BkIHIznUkx.png)
    
# 2-9 React 畫面更新流程機制（reconciliation）

- render phase 與 commit phase
    
    component 畫面管理機制的兩個階段
    
    - render phase
    - commit phase

- 畫面更新流程機制 reconciliation
    
  ![image](https://hackmd.io/_uploads/rkNTyKtIyg.png)

    
    <aside>
    💡
    
    [Object.is()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 
    
    靜態確認兩個值是否是相同值
    
    </aside>
    
    <aside>
    💡
    
    component re-reder 
    
    父層如果有 re-reder ，在沒有額外處理的情況下( 子元件用React.memo() 包.. 等 )
    子元件也會跟著進行 re-reder 
    但 元件有 re-reder 不等於 實際 DOM 有被更新
    
    </aside>

# 3-1 子元件觸發父元件資料更新
1. react 沒有特別設計向上溝通的機制，原本的規則就足以達成
2. 將 setter function 傳給子元件，或甚至傳給 react 以外的環境呼叫（如：redux、web api），都可以成功更新該 setter function 對應的 state，而 state 所屬的 component 也會 re-render
3. 子元件會因為父元件 re-render 取得更新後的 state

# 3-2 
1. batch update：一個事件中呼叫多次不同或相同的 setter function 時，會自動依序合併 state 的更新結果，最後只 re-render 一次，節省效能。[範例](https://codesandbox.io/p/sandbox/determined-golick-vgkvk2)


- 什麼是 Batch Update
    
    一次事件中呼叫多個setState，會將多個re-render合併為一次的節省效能機制
    
    ```jsx
    const [state, setState] = useState(0)
    const [state2, setState2] = useState(0)
    const handler = () => {
    	 setState(state+1);
    	 setState2(state+2)
     }
     
     // 兩項都執行完才進行re-render
    ```
    
 
    💡
    
    如果不想自動Batch Update，可使用 `flushSync` 強制執行re-render 但要留意使用情境，避免不符合React渲染機制情形
    
    ```jsx
    const [state, setState] = useState(0)
    const [state2, setState2] = useState(0)
    const handler = () => {
    		flushSync(
    			setState(state+1);
    		)
    		
    		console.log(state)
    		// 要留意state此時還是0
    }
    ```

    
- React 18 的改進
    - React 18 前：只在事件處理函數中支援Batch update
    - React 18 後：所有情況都支援（含 promise、setTimeout）
    
- 什麼是side effect
    
    在函式執行過程中，對外部造成互動或修改 ex: 修改函式外的變數、操作DOM
    
    <aside>
    💡
    
    Pure Function  ⇒ 執行後沒有任何副作用的函式，
    
    </aside>
    

<aside>
💡

Closure：js 基於同一 event handler 下會狀態將是固定不變

```jsx
 const [state, setState] = useState(0)
 const handler = () => {
	 setState(state+1);
	 setState(state+1);
	 setState(state+1);
 }
 
 // 最終state只會是: 1
```

</aside>

- Updater Function
    - 使用時機：需要基於更新後的 state 計算新值時
    - 正確用法：setState(prev =>prev + 1)
    
    ```jsx
    const [state, setState] = useState(0)
     const handler = () => {
    	 setState(prev =>prev + 1); // state:1
    	 setState(prev =>prev + 1); // state:2
    	 setState(prev =>prev + 1); // state:3
    	 
    	 setState(100); // state:100
     }
     
     // 最終得到 state:100
     
    ```
    
    <aside>
    💡
    
    所以假設元件只需要變更值，而不需把值渲染到畫面時，props就只需要傳入setState即可
    
    </aside>

# 3-3 immutable State：維持資料可靠性
1. 複習基本觀念
    - 原始型別：immutable，變數只能透過重新賦值更新
    ``` js
    const str = 'React';
    str[0] = 'V'
    console.log(str) // 'React'
    ```
    - 物件型別：mutable
2. React 開發須遵守資料 immutable，應產生全新物件傳入 setter function。[範例](https://codesandbox.io/p/sandbox/qr-code-3-3-3-forked-y7hh9z?file=%2Fsrc%2FApp.jsx%3A3%2C32)
    -  為正確觸發 re-render
    -  為正確讀取舊資料。情境：文章編輯器 redo、undo 功能
    -  讓效能優化機制正確作用，如：useCallback、useMemo (參考沒變就不會重新觸發處理) 

# 3-4 immutable update

更新 state 應該要以 immutable 方式更新

以產生新的陣列或物件，而不是用 mutate 方式更改原有物件

<aside>

💡 state代表的是對應某個特定歷史時刻的值，因此state不應該以 **修改** 處理

</aside>

- 物件的 immutable update 方法
    - 用 spread 語法複製後，增加新的屬性
        
        ```jsx
        const oldObj = {a: '1', b: '2'}
        const newObj = {...oldObj, a: '3', c:'4'}
        
        // newObj = { a: '3', b: '2', c:'4'}
        ```
        
    - 巢狀物件的 spread 寫法
        
        ```jsx
        const oldObj = {a: '1', inner: { c: '2', d: '4' }}
        const newObj = {...oldObj, inner: {...oldObj.inner, d: '5'} }
        
        // newObj = { a: '1', inner: { c: '2', d: '5' }}
        ```
        
    - 踢除物件中特定屬性（ 賦值解構 ＋ rest語法）
        
        ```jsx
        const oldObj = {a: '1', b: '2'}
        const {a, ...newObj} = oldObj
        
        // newObj = { b: '2' }
        ```
        
        <aside>
        💡
        
        [spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 與 [rest](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/rest_parameters) 語法的寫法看起來完全相同，但實際作法則是相反
        spread ⇒ 將物件或陣列的元素攤平
        
        rest ⇒ 收集多個元素並「壓縮」為單一元素
        
        </aside>
        

- 陣列的 immutable update 方法
    - spread 語法
        
        ```jsx
        // 在開頭加值
        const oldArr = [1, 2]
        const newArr = [3, oldArr]
        
        // 在中間加值 => slice
        const oldArr = [1, 2, 4, 5]
        const newArr = [...oldArr.slice(0, 2), 3, ...oldArr.slice(2)] // [1, 2, 3, 4, 5]
        
        ```
        
    - filter, map, slice 都會返回新陣列

- 陣列的 mutate 方法
    
    以下兩種方法皆會mutate 既有陣列，必須先複製後再使用方法處理
    
    - sort
    - reverse

- 需要留意的巢狀複製
    
    ```jsx
    const [ state, setState ] = useState([
    	{product: 'foo', price: 100},
    	{product: 'bar', price: 100},
    	{product: 'fizz', price: 100},
    ])
    
    // 單純spread陣列來修改會mutate到既有state，因為物件是參考型別，複製出來的仍是舊有陣列同一批的物件參考
    const newState = [...state ]
    newState[0].price = 200 //錯誤
    setState(newState) 
    
    // 正確方式
    const newState = state.map((obj, index)=> index===1 ? {...obj, price: 200 } : obj )
    setState(newState) 
    ```
    
    <aside>
    💡 deep clone v.s. shallow clone
    
    immutable update並不需要deep clone ，如果沒有修改則沿用舊參考
    
    主要因效能考量、及會有失去參考相等性，造成 React 認為值有更新的情況
    
    </aside>
    
# 4-1 Component 生命週期
1. Mount：Component 首次出現時
a. render phase：`JSX -> React Element -> Fiber Node`
b. commit phase：建立 DOM element 執行 appendChild()
c. 執行本次 render 對應的 effect
2. Update：component re-render/reconciliation
a. 以新版本 props, state 重新跑過 `JSX -> React Element -> Fiber Node`
b. 比較新舊 Fiber Tree，標記變更提交給 commit phase
c. commit phase：只更新差異處
d. 執行 effect 的 cleanup function
e. 執行本次 render 對應的 effect
3. Umount
a. 執行 effect 的 cleanup function
b. 移除 DOM element
c. 清除該 component 的 fiber node

- 補充 fiber，[參考](https://medium.com/starbugs/react-%E9%96%8B%E7%99%BC%E8%80%85%E4%B8%80%E5%AE%9A%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E5%BA%95%E5%B1%A4%E6%9E%B6%E6%A7%8B-react-fiber-c3ccd3b047a1)
    1. react 16+ 推出，是將 source code 重構後的新架構（本質上是 JavaScript 物件）
    2. 優點：將頁面渲染的任務切分成 chunks、任務區分 priority、任務可以暫停再繼續、效能提升
     - 如何達成：
         - React15 及以前，Reconciler 階段採用遞迴的方式創建 virtual DOM。缺點是遞迴不能中斷且是同步的，如果元件樹的層級很深，會佔用 main thread 很多時時間，造成頁面卡頓等
         - 在 fiber 架構下使用 linked list 資料結構遍歷 component tree
    4. 什麼時候產生？ `JSX -> React Element -> Fiber Node`
        透過 `createFiberFromTypeAndProps` 方法
        ```tsx
        const fiberNode = {
          type: MyComponent, // Component 的類型（Function / Class / HTML tag）
          key: null, // React 的 key 屬性
          stateNode: null, // 對於 Class Component 來說，這裡是實例
          child: null, // 指向第一個子 Fiber
          sibling: null, // 指向下一個兄弟 Fiber
          return: parentFiber, // 指向父 Fiber
          alternate: null, // 指向舊的 Fiber（用於 Reconciliation）
          effectTag: "Update", // 代表這個 Fiber 需要做的變更（新增、刪除、更新）
          pendingProps: {}, // 這次 render 會使用的 props
          memoizedProps: {}, // 上一次 render 使用的 props
          memoizedState: null, // 上一次 render 的 state
        }; ```

# 4-2 fuction component vs. class component
1. 在 class component 中雖然 props 是 mutable，但 this 不是，因此使用 this.props 會取得最新的資料，導致非同步事件中讀取到錯誤版本的 props。 [差異範例](https://codesandbox.io/p/sandbox/3jy2t5)
2. function component 則會取得該次 render 的 props 跟 state



# 4-3 每次 render 都有自己的props、state、event handler 函式

function component 每次render都有自己版本的 props、state、event handler

**資料來源**：這是與class component最大的差異

- class component：
    
    資料從 this.props 來，但 this 本身是 mutable 物件，因此有時可能新舊值時間差、造成顯示與預期不符的狀況。
    
- function component：
    
    React 實際並沒有監聽資料的變化，而是在每一次觸發 setState 時，使用Object.is()進行比較。
    
    如有差異，則以當下最新的 props、state 再一次執行 component function
    

- 每次 render 也有 自己版本的 event handler function
    
    核心機制主要是來自於 js 的  `closure`
    
    ⇒ 基於 closure 去記住當下使用到的props、state
    因此每一次render 所產出的，都是只有命名相同 但 獨立、互不相關的 function
    
    ```jsx
    function Component(){
    	const [ state, setState ] = useState(0)
    	
    	const handleClickAlert = () => {
            settimeout(()=>{
                alert(`${state}`)
            }, 3000)
    	}
    	
    	const increment = () => { setState(state+1) }
    	
    	// 先點擊alert按鈕，再馬上按+1按鈕 => 3秒後的alert仍會顯示0
    	return(
    		<button onClick={increment}>+1</button>
    		<button onClick={handleClickAlert}>alert</button> 
    	)
    }
    ```
    ---
    <aside>
    💡
    
    immutable資料及函式closure的特性是維持React核心觀念：一律重繪的重要基礎。
    
    每次render ，props 及 state 都有當下時期獨立且不變的快照版本，因此連帶函式也變得穩定且可預期。
    
    </aside>
    
    因此對開發者非常重要，需要有意識的去維持資料是immutable，維持整體的單向資料流
    
# 5-1 effect 初探
1. effect（副作用）：和外部系統互動，如：存取函式外的變數、發起網路請求、修改 DOM element 等
2. 副作用帶來的負面影響：可預測性降低、測試困難/高耦合度（需要模擬或隔離外部資源）、增加維護和理解的成本、優化限制
3. React Component Function 中副作用的負面影響：
   - re-render 時函式多次執行，造成副作用影響難以預測
   - 副作用可能拖慢或阻塞本身產生 react element 的流程，如：修改 DOM element 還牽涉到瀏覽器渲染引擎
4. 解法：使用 useEffect 處理副作用
   - 可以透過 cleanup 函式清除逆轉或副作用造成的影響
   - 管理副作用執行的時間點（render 流程完後）
5. [撰寫步驟](https://react.dev/reference/react/useEffect)
    a. 定義 setup function
    b. 加上 cleanup function：避免 memory leak。
    c. 加上 dependencies
     - 跳過不必要的副作用處理
     - 值為 setup 中有使用的 reactive values（props, state, variables and functions declared inside your component. can’t choose!）
     - 建議使用 react eslint 檢查漏掉的 deps
     - [比較 array, empty array,  no dependency array](https://react.dev/reference/react/useEffect#examples-dependencies)
6. 每次 render 都有該次 render 的 setup/cleanup function，和 event handler 同樣都是 render 輸出結果的一部分，會因為閉包特性捕捉該次 render 的 props 和 state 快照。
7. 執行流程（re-render 一次為例）
    a. 元件初次掛載到 DOM 上，跑 `setup1`
    b. re-render 時，
        - 先跑 `cleanup1`（code runs with the old props and state.）
        - 再跑 `setup2`（code runs with the new props and state.）
    c. 元件 unmounts 時，跑`cleanup2`
    

# 5-2 useEffect 不是 function component 的生命週期API

useEffect真正的用途：將原始資料同步到畫面以外的副作用處理

- 為何部分開發者會有useEffect是 生命週期API 的思維？
    1. 早期class component 設計，副作用會在 `componentDidUpdate` 、 `componentDidMount` 等生命週期API處理，因此有經驗的開發者會認為useEffect執行時機類似，而將它理解為生命週期API。
    2. 早期官方文件有提及：useEffect可以模擬class component 的生命週期API
    可能是為了讓使用者更快的過渡class component 到 function component的說法，但實際上是一種誤導，後來這個內容也移除了。

- 宣告式設計 v.s. 指令式設計
    - 宣告式設計：只關注於預期的“結果”，不在乎過程。
    - 指令式設計：著重於達到目標的“過程”。
    
    以維護及資料流方面，指令式設計因為是多個步驟疊加，較難一眼看出預期結果; 宣告式設計則較容易。
    
    ---
    <aside>
    💡
    
    React的宣告式設計與 useEffect 的關聯？
    
    useEffect並不在乎副作用的處理流程(mount \ update…)、或是渲染次數多寡，都應該在資料同步後到副作用產生的結果正確。
    
    </aside>
    
    ----

- 為何生命週期API被取代了？
    
    以往生命週期API的開發方式，養成了開發者必須依照個生命週期階段拆解動作、來達成資料同步的效果，但這種開發方式容易因為遺漏某一階段的處理而產生錯誤。
    
    所以useEffect 才會在每次render 後都執行，以確保副作用是隨資料流變化而同步的
    
- `副作用應該是就算render後每次都執行，依然都要能保持正確性`
    
    所以，想用依賴項來控制副作用執行時機是錯誤的思維
    
    Dependencies應該用優化效能角度去思考，判斷何時跳過執行副作用是安全的，再加入對應的依賴項。
    
    - 錯誤思維範例：我只想執行一次effect函式，所以依賴項放空陣列。
    
    - 正確思維範例：effect函式真的沒有任何依賴，所以依賴項放空陣列。
    
    ---
    
    <aside>
    💡
    
    如果副作用真的有特定商業邏輯，應該是在effect函式中用條件判斷處理
    
    </aside>
    
    ---
---

💡補充文件

[https://overreacted.io/a-complete-guide-to-useeffect/](https://overreacted.io/a-complete-guide-to-useeffect/)

---

# 5-3 不要欺騙 hooks 的 deps
1. 欺騙 hooks 的問題：無法取得該次 reder 的 props 或 state
2. 使用 updater function，移除 deps（讓 effect 函式自給自足）
[1, 2 範例](https://codesandbox.io/p/sandbox/qr-code-5-3-1-forked-jd4l6j)
3. 如何處理函式型別的依賴
   - 將函式定義在 effect 裡

<!-- <br/> -->
Q: 如果不想把函式放在effect?
1. 方法一：將函式內容抽到component外部 =>可避免重覆寫依賴項，達到效能優化
2. 方法二：用useCallback包，並填寫正確的依賴項=>將資料流的變動正確更新到函式上，如沒有變動函式則不會發生改變

:warning: 並不是所有component內的函式都要用useCallback包．以下情境需要：
1. 函式被使用在effect中
2. 作為props被傳遞到React.memo()包裹的component

> 建議安裝linter，判斷修正hook dependencies
> eslint-plugin-react-hooks、VS Code plugin-ESLint

:fire: <b>應始終對依賴項保持誠實，不應依想要執行的情境而選擇放入的依賴項</b>

# 5-4 effect 在 mount 時會執行 2 次
1. react 18 的 breaking change，為了檢查副作用的安全可靠性
2. react 未來的版本規劃： component 要設計足夠彈性，多次 mount, unmount 都不會壞，達到 resuable state。[參考](https://github.com/reactwg/react-18/discussions/19)
3. 補充：現有的 HMR （Hot Module Replacement）機制就符合這種特性：更新存檔時不需要重整就套用 component 的變動
    - vite / react CRA / webpack 等內建 HMR 機制
    - 實現原理
         1. vite 使用 chokidar 等工具監控元件是否修改，在 dev server 和 browser 間建立 websocket 連線（可切到 network 的 ws 看）
         2. vite dev server 發現元件變化後，重新編譯該元件，並透過 websocket 通知瀏覽器進行更新
         3. vite 內建的 HMR js 接收到 websocket 訊息，會執行元件替換
    - Fast Refresh 是 React 團隊針對 HMR 的強化版本，可以保留 component state（ vite 2+ 開始支援，透過 @vitejs/plugin-react 這個官方插件來實現）
        - 實作：把 `plugins: [react()]` 移除， state 不會保留
    - HMR 歷史
        - 2015	Webpack 1 引入 HMR 概念，開發者能修改 JS 模組並自動更新，不需整頁重新整理。
        - 2016+	React Hot Loader 被廣泛使用來支援 React 的 HMR，保持元件 state。
        - 2020	Vite 登場，利用原生 ES 模組（ESM）加上更快的模組熱更新，支援 out-of-the-box HMR，省去了複雜配置。
4. 為什麼要達到 resuable state？
    - 支持 Fast Refresh：Fast Refresh 每次儲存檔案都會觸發 effect 重跑
    - 支持未來的特性，如：Offscreen API
        - 讓 component「離開畫面但保留狀態」的機制 (還處於 lab 階段)，[使用場景](https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#offscreen-rendering)
            - A router can prerender screens in the background so that when a user navigates to them, they’re instantly available.
            - A tab switching component can preserve the state of hidden tabs, so the user can switch between them without losing their progress.
A virtualized list component can prerender additional rows above and below the visible window.
            - When opening a modal or popup, the rest of the app can be put into “background” mode so that events and updates are disabled for everything except the modal.
        - 不是靠 CSS display: none，而是 React 本身不渲染到畫面上，但保留整個 virtual DOM 與 state，在需要時快速還原並再次 mount
        - **元件在生命週期裡 mount 和 unmount 不只一次，即使 deps 資料沒有更新，effect 仍可能執行**

#  5-5 副作用處理的常見情境
- 常見副作用的設計問題
    1. 疊加而非覆蓋的操作，造成多次執行後結果不如預期
    2. race condition，如：請求回傳速度不一
    3. memory leak，如：監聽事件未被取消
處理方式
1. fetch api：透過宣告 flag 解決 race condition。建議使用第三方套件，如： react query、swr、react apollo
2. 避免外部套件重複初始化
    ``` tsx
    useEffect(() => {
        if(!ref.current) ref.current = thirdPartyLib.init()
    }, []);
    ```
3. 需頻繁將資料同步到第三方套件時，可以加上 throttle 等效能調校處理
    ``` tsx
      // 使用 throttle 控制每 500 毫秒只調用一次 fetchData
      import { throttle } from 'lodash';
      const throttledFetchData =  throttle(fetchData, 500)
        
    ```
4. 監聽或訂閱事件，需加上 cleanup function，如： click event、setInterval


# 5-6 useCallback & useMemo
- 函式在function component遇到的問題
當在function component所建立的函式，在 `useEffect` 被呼叫時，依賴項即使正確的寫了此函式，也並不能達到效能優化
    
    ```jsx
    function SearchResults(props) {
      async function fetchData(query) {
        const result = await axios(
          `https://foo.com/api/search?query=${query}&rowsPerPage=${props.rows}`,
        );
      }
     
      useEffect(
        () => {
          fetchData('react').then(result => { ... });
        },
        [fetchData] 
        // 雖然依賴有寫，但因為每次rerender都會重新產生新的fetchData，無法達到優化
      );
      // ...
    }
    ```
    

- useCallback
    - 運作原理
        
        會在每一次render建立一個以當次state資料的函式，再依照依賴項去比對，如果依賴項沒有更新，則回傳快取的函式，有更新則使用新建立的函式。
        
        ```jsx
        function SearchResults(props) {
          const fetchData = useCallback(
            async (query) => {
              const result = await axios(
                `https://foo.com/api/search?query=${query}&rowsPerPage=${props.rows}`,
              );
              return result;
            },
            [props.rows]
          );
         
          useEffect(
            () => {
              fetchData('react').then(result => { ... });
            },
            [fetchData]
          );
         
          // ...
        }
        ```
        
     
        ---
            🔥 useCallback本身的運作原理並不算節省效能
            因爲不論新舊的依賴項是否相同，仍會先依照新版本及舊版本的狀態去產生對應的函式，
            後續才會進行比對判斷要回傳哪項函式

        ---
        
    - 使用情境
        
        當在function component所建立的函式，需要在useEffect 中使用，或需要作為props被傳入子元件時，則建議使用useCallback包．
        
        ---
            🔥React.memo

            如傳入props沒變化則略過render流程，回傳快取的render結果。
            此時如props有包含在父層元件裡建立函式，且沒有使用useCallback包住，也會導致效能優化失敗．

        ---
        

- useMemo
    
    作爲效能優化的工具，會比對依賴項的差異，如相同則回傳快取，不同則使用新值．


    假設在function component 裡建立一個陣列的 state，當其被列在useEffect依賴項時，因每次rerender時陣列都會重新被建立，而失去效能優化作用，因此這種情境就該使用useMemo包．

    ```jsx
    import React from 'react';

    function Child(props) {
      return (
        <>
          <div>Hello, {props.name}</div>
          {props.numbers.map(num => (
            <p>{num}</p>
          ))}
        </>
      );
    }

    const MemoizedChild = React.memo(Child);

    function Parent() {
      const numbers = [1, 2, 3]; // 每次render都會建立新的陣列

      useEffect(
        () => console.log(numbers),
        [numbers] 
      );

      return (
        <MemoizedChild
          name="zet"
          numbers={numbers}
        />
      );
    }
    ```

    ```jsx
    // 應該改為如下：
    const numbers = useMemo(()=>[1, 2, 3], []); 
    ```


#  5-7 hooks 設計原理
1. fiber node：負責儲存目前React 應用程式的最新狀態資料，只會存在一份
React element：用於描述某個歷史時刻的畫，會隨著rerender 產生好幾份（Q：怎麼看前幾分的react elements?)
2. fiber node 中以 linked list 存放狀態資料，故會需要固定 每次 render 時 hook 的呼叫順序
3. 不讓 hook 執行：unmount 該 component
4. hook 的設計是為了管理狀態並方便共用邏輯，故選擇以函式為載體。不需要基於 key，避免覆蓋或重複呼叫的衝突


- 單一state 在fiber node 儲存
![截圖 2025-04-25 凌晨12.25.45](https://hackmd.io/_uploads/SkvGIJOJeg.png)
- 多個state 在fiber node 儲存
![截圖 2025-04-25 凌晨12.27.06](https://hackmd.io/_uploads/HkDPUyuylx.png)
在使用useState時並沒有告知 React 每個state 的自定義名稱或key
因此需依照相同順序，才能維持機制正常

- 如使用key來定義state，可能產生以下情況
![截圖 2025-04-25 凌晨12.39.30](https://hackmd.io/_uploads/H1xUt1Oyee.png)
- 依照順序則能產生樹狀結構避免key衝突問題
![截圖 2025-04-25 凌晨12.40.38](https://hackmd.io/_uploads/BkQct1_keg.png)
