---
# System prepended metadata

title: '[Day 19] 每一次 render 都有自己的 props、state 以及 event handlers + [Day 20] 每一次 render 都有自己的 effects'
tags: [iT 邦, 閱讀筆記, 一次打破 React 常見的學習門檻與觀念誤解]

---

# [Day 19] 每一次 render 都有自己的 props、state 以及 event handlers + [Day 20] 每一次 render 都有自己的 effects

###### tags: `閱讀筆記` `iT 邦` `一次打破 React 常見的學習門檻與觀念誤解`

## `state` 放在 template 產生的結果只是該次 render 時的結果

`<p>Counter: {counter}</p>` 並不是將 `counter` 丟在 template 後，並且偵測值的變化才會更新 UI，而是透過 `state` 及 `setStateFunc` 告知 React 當這筆資料有變動時就要與歷史 `state` 比對，真的不同才要 re-render 元件。所以 template 上的 `counter` 只是當前元件 re-render 後得出的值（常數）。

```javascript!
const App = () => {
  const [counter, setCounter] = useState(1)
  
  return (
    <p>Counter: {counter}</p>
  )
}
```

```javascript!
const App = () => {
  const counter = 1 // setStateFunc 回傳的值
  
  return (
    <p>Counter: {counter}</p>
  )
}
```

```javascript!
const App = () => {
  const counter = 2 // setStateFunc 回傳的值
  
  return (
    <p>Counter: {counter}</p>
  )
}
```

```javascript!
const App = () => {
  const counter = 3 // setStateFunc 回傳的值
  
  return (
    <p>Counter: {counter}</p>
  )
}
```


## re-render 是什麼？

re-render 就是該元件重新叫用一次，就真的只是當前元件從頭到尾重新叫用一次：

```javascript!
const App = () => {
  const [counter, setCounter] = useState(1)
  
  return (
    <>
      <p>Counter: {counter}</p>
      // re-render 時這些 components 也會重新叫用一次，因為 re-render 就是這個函式的所有東西都重新執行一次
      <SomeComponent1 />
      <SomeComponent2 />
      <SomeComponent3 />
      <SomeComponent4 />
    </>
  )
}
```

### 所以每次 re-render 時都有自己的 `state` 及所有函式

因次，當我們先點擊 `handleToggleAlert` 後，在 `alert` 出現前快速點擊 `handleIncreseCounter` 更動 `counter`，待 `alert` 出現時，會得到叫用 `handleToggleAlert` 時當前的 `counter`：

```javascript!
const App = () => {
  const [counter, setCounter] = useState(1);

  // 每次 re-render 時都會重新建立一個 handleIncreseCounter，所以每次 re-render handleIncreseCounter 都有自己的 scope
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  // 每次 re-render 時都會重新建立一個 handleToggleAlert，所以每次 re-render handleToggleAlert 都有自己的 scope
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`Counter: ${counter}`);
    }, 3000);
  };
  
  return (
    <div className="App">
      <p>Counter: {counter}</p>
      <button onClick={handleIncreseCounter}>+ 1</button>
      <button onClick={handleToggleAlert}>alert!</button>
    </div>
  );
}
```

![](https://hackmd.io/_uploads/SySFszAE3.gif)
（會發現 `alert` 會出現 3，而不是 18）

實際上會是這樣子：

```javascript!
const App = () => {
  const counter = 1 // setStateFunc 回傳的值

  // 每次 re-render 時都會重新建立一個 handleIncreseCounter，所以每次 re-render handleIncreseCounter 都有自己的 scope
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  // 每次 re-render 時都會重新建立一個 handleToggleAlert，所以每次 re-render handleToggleAlert 都有自己的 scope
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`Counter: ${counter}`);
    }, 3000);
  };
  
  return (
    // ...
  );
}
```

```javascript!
const App = () => {
  const counter = 2 // setStateFunc 回傳的值

  // 每次 re-render 時都會重新建立一個 handleIncreseCounter，所以每次 re-render handleIncreseCounter 都有自己的 scope
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  // 每次 re-render 時都會重新建立一個 handleToggleAlert，所以每次 re-render handleToggleAlert 都有自己的 scope
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`Counter: ${counter}`);
    }, 3000);
  };
  
  return (
    // ...
  );
}
```

```javascript!
const App = () => {
  const counter = 3 // setStateFunc 回傳的值

  // 每次 re-render 時都會重新建立一個 handleIncreseCounter，所以每次 re-render handleIncreseCounter 都有自己的 scope
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  // 每次 re-render 時都會重新建立一個 handleToggleAlert，所以每次 re-render handleToggleAlert 都有自己的 scope
  /* 
   * Step 1: counter = 3 時呼叫了這個函式，因此 setTimeout 內的 callback 會往外找 counter 變數
   * Step 2: 瀏覽器開始倒數
   * Step 3: 於此同時又一直瘋狂地點擊 handleToggleAlert 更新 state
   * Step 4: 倒數完 setTimeout 的 callback 被丟入 event queue 排隊
   * Step 5: 直到 stack 空了，callback 執行，因為是在 counter = 3 的那次循環叫用，所以 callback 已經取得 counter = 3 的變數，因此就會顯示 3
   */
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`Counter: ${counter}`);
    }, 3000);
  };
  
  return (
    // ...
  );
}
```

其他 `handleIncreseCounter` 叫用後的樣子：

```javascript!
const App = () => {
  const counter = 4 // setStateFunc 回傳的值

  // 每次 re-render 時都會重新建立一個 handleIncreseCounter，所以每次 re-render handleIncreseCounter 都有自己的 scope
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  // 每次 re-render 時都會重新建立一個 handleToggleAlert，所以每次 re-render handleToggleAlert 都有自己的 scope
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`Counter: ${counter}`);
    }, 3000);
  };
  
  return (
    // ...
  );
}

// ... 接下來就是這樣子，知道 stack 空了，event queue 的 callback 被推到 stack 後執行該 callback
```

## 每次 re-render 時也會有自己的 effect function

> effect function 就是 `useEffect` 中第一位收的 `callback`。

每次 re-render 就是重新叫用該元件，所以當元件內有 effect function 時，每次 re-render 時也會有自己的 effect function：

> 快速複習 useEffect：
> - useEffect 收兩個 parameters，第一個為 `callback`，第二個為 `dependency array`
> - 不管給不給 dependency array，第一次元件渲染完成後就會叫用一次 `callback`
> - useEffect 還可以回傳 callback，稱為 clean up function，當元件 `unmounted` 或者下一次 `callback` 要被叫用前，就會叫用 clean up function
> - `dependency array`：[] -> 只會在元件渲染後叫用、不給 -> 每次元件 re-render 都會叫用、[someValues...] -> 讓 React 監聽裡面的值，當裡面的值有更動（與上次 render 時的值不同）就會叫用 effect function

```javascript!
export default function App() {
  const [counter, setCounter] = useState(1);

  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  useEffect(() => {
    // effect function
    console.log("after template render", counter);

    // clean up function
    return () => {
      console.log("before next effect function got called", counter);
    };
    // dependency array -> 監聽 counter
  }, [counter]);

  return (
    // template...
  );
}
```

因為有給 `dependency array`，因此在元件第一次渲染後呼叫 effect function 後，React 會監聽 `counter`，當 `counter` 與上次 render 時不同的話就會重新叫用 effect function：

![](https://hackmd.io/_uploads/H1V1ZLCNh.gif)

### 為什麼 clean up function 會拿到上回 render 時的 `state`？

實際來看元件內的流程就會像是這樣：

```javascript!
export default function App() {
  const [counter, setCounter] = useState(1);

  // Step 2: 透過事件變更 counter
  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  useEffect(() => {
    // Step 1: 待元件第一次渲染完畢後，叫用內部的 effect function
    // 會得到 1，因為是在第一次 render 後叫用 effect function
    // effect function
    console.log("after temp

    return () => {
      console.log("before next effect function got called", counter);
    };
  }, [counter]);

  return (
    // template...
  );
}
```

```javascript!
export default function App() {
  // Step 1: setStateFunc 叫用後 React 發現 counter 確實不同，因此觸發 re-render
  const [counter, setCounter] = useState(1); // -> 此時 counter = 2

  const handleIncreseCounter = () => {
    setCounter((prev) => prev + 1);
  };

  useEffect(() => {
    // Step 3: 監聽的 counter 確實不同，待上次 render 時的 clean up function 執行完後就執行 effect function
    // 所以這邊 counter = 2，因為是在 counter = 2 渲染後才叫用 effect function
    console.log("after template render", counter);

    // Step 2: 待元件 render 完畢，template 渲染後，先執行上一次 render 時的 clean up function
    // 所以這邊 counter = 1，因為這個 clean up function 是上次 render 時產生的
    // Step 4: 重新建立 counter = 2 時的 clean up function，待下次滿足條件時再次叫用
    return () => {
      console.log("before next effect function got called", counter);
    };
  }, [counter]);
  
  return (
    // template...
  );
}
```

因為每次 render 時元件內的 state、props 及 functions（包含 events、effect functions 及 clean up functions 等...）等都是不變的（都是屬於自己當前的 scope）所以遵從範疇的觀念，clean up function 就是會拿到上回 render 時的資料。
==因此在處理物件型別的 `state` 時需要特別注意不要使用 mutable way 更新，因為這樣子會破壞每次 render 都是當前 scope 值的概念。==

## 回到文章範例

當我們點選按鈕時會延遲至少 3 秒才會出現 `alert`：

```javascript!
const ShowAlert = ({ currentTabName }) => {
  const handleToggleAlert = () => {
    setTimeout(() => {
      alert(`當前選擇的 tab ${currentTabName}`);
    }, 3000);
  };
  return <button onClick={handleToggleAlert}>Show alert</button>;
};

export default ShowAlert;
```

因為在點擊按鈕觸發 `handleToggleAlert` 時，`handleToggleAlert` 內 callback 的 scope 是屬於當前 render 的，所以自然會保存當前 render 時的 `props`。假設當前是手機遊戲，我們在點擊後切換至電腦遊戲，那麼在 3 秒後還是會出現手機遊戲 -> 是看叫用當下 `props` 是什麼決定 `alert` 出現什麼 -> scope 的原理。

## 程式範例

[It - React - [Day 19] 每一次 render 都有自己的 props、state 以及 event handlers](https://codesandbox.io/s/it-react-day-19-mei-yi-ci-render-du-you-zi-ji-de-props-state-yi-ji-event-handlers-qdtt8u?file=/src/App.js)
[It - React - [Day 20] 每一次 render 都有自己的 effects](https://codesandbox.io/s/it-day-20-mei-yi-ci-render-du-you-zi-ji-de-effects-e667lg?file=/src/App.js)

## Recap

- `state` 放在 template 只是單純地渲染當前 render 時 `state` 常數
  - 並不是 template 中的 `state` 監聽改動後更新，是 render -> 更新 template
- render 就是函式重新叫用一次
- 每次 render 後函式內的東西都是自己的 scope（`state`、`functions`、`props` 等皆是）
  - 所以每次 render 時的值是不會互相影響的
  - 物件型別就要特別注意：使用 immutable update
  - 維持 React 資料單向流的核心觀念 


每次 render 時都是自己的 scope 可以想像成：

```javascript!
const render = () => {
  return { ... }
}

// 第一次渲染
template = render()
  
// state 更新後重新叫用元件
template = render()
  
// 每次 render 元件都是建立新的 scope...
```

## 參考資料

1. [[Day 19] 每一次 render 都有自己的 props、state 以及 event handlers](https://ithelp.ithome.com.tw/articles/10304009)
2. [[Day 20] 每一次 render 都有自己的 effects](https://ithelp.ithome.com.tw/articles/10304650)