# React 思維進化
* React 中的副作用處理-初探 Effect
* useEffect 其實不是生命週期 API
---
## React 中的副作用處理
**-初探 Effect**
什麼是 useEffect?
<span> useEffect 是一個 React Hook<!-- .element: class="fragment" data-fragment-index="1" --></span>
<span> 可以讓 component 與外部系統同步化<!-- .element: class="fragment" data-fragment-index="2" --></span>
----
如何同步化?
--
<div>
將 Component 以外的資料<!-- .element: class="fragment" data-fragment-index="1" --></div>
<div>
同步化到 Component 內部<!-- .element: class="fragment" data-fragment-index="2" --></div>
--
<div> 將 Component 內部的資料流與外部系統同步<!-- .element: class="fragment" data-fragment-index="3" --></div>
----
對伺服器、後端API發出請求
<!-- 除了 get 以外,拿來同步 component 內部更新內部資料,也可能是 post 更新、修改等操作 -->
監聽某些事件
<!-- 監聽使用者觸發了 onScroll 滑動事件 -->
存取 React 外部的狀態管理套件
<span>"外部系統" => 是指某些 React 無法控制的程式碼 <!-- .element: class="fragment fragment-highlight-red" data-fragment-index="1" --></span>
----

<!--
* setInterval() and clearInterval()
* 事件處理器訂閱: window.addEventListener 和 window.removeEventListener
* 第三方動畫 Library API 像 animation:start 和 animation:reset()
-->
---
## 什麼是副作用? side effect?
----
函式
除了回傳一個結果值以外
還依賴或影響函式外部以外的其他東西
影響變數等等操作
```===javascript
let globalVariable = 0
function calculateDouble(number){
globalVariable +=1
fetch(/*....*/).then((res) => {
/*....*/
})
document.getElementById('app').style.color = "red"
return number*2
}
```
----
副作用聽起來不好?
但是有時候
我們就需要它的副作用的影響或效果,
同時的,
副作用可能會造成一些負面的影響。
* 可預期性降低
<!-- 帶有副作用的函式也會更難以預測行為 -->
* 測試困難
<!-- 難以進行單元測試,因為涉及外部資源或狀態,難以模擬或隔離外部因素來做測試 -->
* 高耦合度
<!-- 副作用往往增加系統各部分之間的緊密程度,會增加修改或重構的困難度。 -->
* 難以維護和理解
<!-- 通常在查看包含有副作用的程式,它跟上下文和其他程式碼都有相連關係,會比較難理解或修改。 -->
* 優化限制
----
若在 Component Fn 中 直接處理副作用程式碼
一些累加可能拖慢 React Element 的產生
可能影響到使用者體驗差
<!-- 就無法留住人停留在該網頁 -->

----
我們可使用 useEffect
隔離副作用
使之在每次 render 更新完成後才執行
就不會有機會造成 Component fn
產生 React Element 時被阻塞
避免副作用的處理直接阻塞畫面的產生與更新
----
所以我們需要 useEffect
<span> 來處理副作用<!-- .element: class="fragment" data-fragment-index="1" --></span>
<span> 清除或逆轉產生的副作用影響<!-- .element: class="fragment" data-fragment-index="2" style="color:yellow"--></span>
<span> 避免副作用疊加所造成的不可預期性的錯誤<!-- .element: class="fragment" data-fragment-index="3" style="color:yellow"--></span>
----
如何清除副作用?
* 當 component 隨著每次 re-render, 需重複執行副作用後,(若有產生)都會需要清掉前次產生的副作用。
* 解決方式:
=> 回傳 cleanup fn,透過建立 cleanup fn 來清除副作用所造成的影響。
* 時機點:
會在每次副作用執行前,或 unMount 的時候被執行。
----
若有副作用的產生
但沒使用 cleanup fn 清除副作用產生的影響
可能 component 被 unmount 還在持續觸發事件
導致 memory leak 以及效能浪費等問題
----
什麼是 memory leak (記憶體洩漏)?
<span>
當一段程式碼不在使用某些記憶體,但該段記憶體卻未被適時釋放,
導致記憶體無法被其他程式使用或重新分配
導致系統可使用記憶體越來越少。 <!-- .element: class="fragment" data-fragment-index="1" style="size=10" --></span>
<!-- Javascript 語言本身雖有自動的垃圾回收機制,但若未有 cleanup 函式將對未使用 connection 解除綁定的話,它會依然保持 connection 並在有 connection變動時不斷被觸發相對應的 callback 函式,就算 component 被 unMount 還是會持續占用記憶體空間來存放,並且浪費效能去執行它。
除非應用程式被關閉並重新執行,不然對此占用會是持續性,且越累積越多-->
----
與外部伺服器連線,建立 connection
```===javascript
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
```
---
useEffect(effectFunction, dependencies?)
effect fn 若無回傳 cleanup fn,
effect fn 回傳 undefined
cleanup fn <span>清除副作用所造成的影響<!-- .element: class="fragment" data-fragment-index="1" style="color:yellow"--></span>
dependencies 可選填的陣列參數 <span>優化效能<!-- .element: class="fragment" data-fragment-index="2" style="color:yellow"--></span>
----
以 useEffect 來定義並管理一段副作用
可分成此三大步驟:
1. 定義一個 effect 函式來處理副作用
2. 加上 cleanup 函式來清除副作用所帶來的影響(若有需要的話)
3. 指定 effect 函式裡的依賴陣列,以跳過某些不必要的副作用處理,避免效能浪費
---
Dependencies 參數
* 用來告知 React effect 函式是否可以在某些次 re-render 時跳過執行。
* 陣列內放的每一個數值會經過<span> `Object.is()`<!-- .element: class="fragment" data-fragment-index="1" style="color:yellow"--></span> 判斷 數值是否有被更新,若無更新,此次可安全跳過執行此副作用<span>可避免浪費效能<!-- .element: class="fragment" data-fragment-index="2" style="color:red"--></span>
* dependencies 陣列應包含 effect fn 在此 component 所依賴影響的資料項目。
<span>如: props, state, 或任何受到資料流所影響的延伸資料。<!-- .element: class="fragment" data-fragment-index="3" style="color:red"--></span>
----
<span>是用來控制執行時機?<!-- .element: class="fragment" data-fragment-index="1" style="color:red"--></span>
* 直接不提供 dependecies 參數
=> 每一次 re-render 都要執行副作用
<span>沒有 dependenecies 幫助優化的陣列參數,每次都要執行副作用<!-- .element: class="fragment" data-fragment-index="2" style="color:yellow"--></span>
* 在 dependencies 提供一個空陣列
=> 僅有初次 render 時執行此副作用,re-render 都可以跳過執行
<span>有 dependenecies 幫助優化的陣列參數,但依賴需執行的變數不存在,故只有初次渲染要執行副作用<!-- .element: class="fragment" data-fragment-index="3" style="color:yellow"--></span>
---
Component 每次 render
都有其自己版本的 props, state,
event handlers, effect 函式, cleanup 函式,
會透過 closure 的特性,
捕捉並記住該次 render 中 props 和 state 的快照。
[repo 範例](https://codesandbox.io/p/sandbox/useeffect-render-example2-vywz9w?file=%2Fsrc%2FSubscriptionComponent.js%3A6%2C53)
----
<!-- .slide:data-background-color="#fff" -->
```flow!
st=>start: 首次 Render
e=>end: 結束
op=>operation: 從 useState 取得目前的 state值為1 作為快照值
op2=>operation: 以目前的資料流建立 event handler, effect fn, cleanup fn
op3=>operation: 以state 快照值產生新的畫面 React Element 綁定資料(event handler)
op4=>operation: 轉換 RE, 並繪製到實際的DOM上
op5=>operation: 執行 effect 函式
op6=>operation: (首次render跳過)執行前次 cleanup 函式
st->op->op2->op3->op4->op6->op5
```
----
<!-- .slide:data-background-color="#fff" -->
```flow!
st=>start: 經過一次按鈕點擊,執行setCurrentOrder(currentOrder + 1),觸發 re-render
e=>end: 結束
op=>operation: 執行到 useState時,React 在內部執行之前呼叫 setState的動作。
將待計算動作完成,並將 state的值取代更新為 1+1= 2
op1=>operation: 從 useState 取得目前的 state 作為快照值
op2=>operation: 以目前的資料流建立 event handler, effect fn, cleanup fn
op3=>operation: 以state 快照值產生新的畫面 React Element 綁定資料(event handler)
op4=>operation: 轉換 RE, 並繪製到實際的DOM上
op5=>operation: 執行 effect 函式
op6=>operation: 執行前次 cleanup 函式
st->op->op1->op2->op3->op4->op6->op5
```
---
React 中的副作用處理: effect 初探
章節重要觀念自我檢測
`---`
* 什麼是副作用?為什麼我們需要透過 `useEffect` 在 React component function 中處理副作用?
* 以 `useEffect` 來處理一段副作用的三大步驟是什麼?
----
React 中的副作用處理: effect 初探
章節重要觀念自我檢測
`---`
* 解釋「每次 render 都有其自己版本的 effect 與 cleanup 函式」是什麼意思?
* 一次 render 中的 effect 函式與 cleanup 函式會在什麼時間點被執行?
---
## useEffect 是宣告式的同步化
而非生命週期API
`----`
useState 方法更新資料
<!-- react 會以最新的資料重新 render component,將原始資料同步化到畫面結果-->
useEffect 的用途: 是將 "原始資料"
同步化到畫面以外的副作用處理上
----
宣告式與指令式程式設計
React vs. JavaScript
宣告式: 只關注結果,不管如何達成目的
指令式: 一一步驟告知執行的順序,來達到結果
----
useEffect 也是以宣告式的概念來設計
<!-- useEffect 讓你根據目前的 props 與 state 資料來同步化那些與畫面無關的其他東西,也就是副作用的處理 -->
<!-- useEffect 不是 function component 生命週期的 API-->
<!-- 執行時機看起來與 class component 中的
componentDidMount 以及 componentDidUpdate 類似 -->
但其實 useEffect 副作用處理
將原始資料同步化到 React Element 以外的東西上
無論重複 render 了幾次
資料流與程式邏輯都應該保持同步化與正常操作
----
為什麼要以 useEffect 的資料流
同步化取代生命週期API?
<span> 什麼是生命週期API? <!-- .element: class="fragment" data-fragment-index="1" --></span>
<span>[React Component Lifecycle - Hooks / Methods Explained](https://www.youtube.com/watch?v=m_mtV4YaI8c)<!-- .element: class="fragment" data-fragment-index="2" --></span>
----
class component 生命週期API:
1. constructor: 建立 component
2. componentDidMount: component 首次 render 執行
3. componentDidUpdate(prevProps, prevState, snapshot?): component re-render 後執行
4. componentWillUnMount: component 被 unMount,移除前執行
5. shouldComponentUpdate(nextProps, nextState): 通常用來優化性能,預設返回 true,若返回 false 可以防止 component 被 re-render
----
class component 生命週期API:
6. getDerivedStateFromProps(props, state): React 會在render(初次render,或更新re-render)前執行。它會回傳 object 來更新 state,或是 null 不執行更新。
7. getSnapShotBeforeUpdate(prevProps, prevState): React 在 React 更新 DOM之前呼叫它. 讓 component 抓取 DOM 在更新以前的資訊 (e.g. scroll 位置) 回傳值會傳入componentDidUpdate 的參數
9. componentDidCatch(error,info): 當發生錯誤時,React 會按照定義錯誤訊息顯示在畫面上。
----
Component 初次 render 訂閱狀況
```javaccript=
componentDidMount(){
OrderAPI.subscribeStatus(
this.props.id
this.handleStatusChange
)
}
componentWillUnMount(){
OrderAPI.unsubscribeStatus(
this.props.id
this.handleStatusChange
)
}
```
----
componentDidUpdate
不會自動抓取前次的 id 取消訂閱
也不會自動以新的 id 來重新訂閱
=> 進而造成 memory leak 等問題
<!-- 忘記正確處理 componentDidUpdate
正是 class component 常見的 bug 來源 -->
```javaccript=
componentDidMount(){
OrderAPI.subscribeStatus(
this.props.id
this.handleStatusChange
)
}
componentDidUpdate(prevProps){
OrderAPI.unsubscribeStatus(
prevProps.id
this.handleStatusChange
)
OrderAPI.subscribeStatus(
this.props.id
this.handleStatusChange
)
}
componentWillUnMount(){
OrderAPI.unsubscribeStatus(
this.props.id
this.handleStatusChange
)
}
```
<!-- 這是處理一個副作用,想像若是同個 component 要同時處理多個副作用,需要再生命週期API裡面分別添加邏輯,程式碼就很容易互相打架壞掉 -->
----
<!-- class component 的 didMount, didUpdate, willUnMount 會需要開發者將這些處理情境拆開思考,去決定在這些情境要分別執行那些動作,副作用處理是會被散落在不同的地方,將他們正確的一一寫入,並正確的組合再一起才能做到正確地將資料同步化到副作用處理上。 -->
useEffect 從 didMount + didUpdate + willUnMount 中
提取其中副作用來統一整合
<!-- 類似的情境 -->
<span> 只需要一個 useEffect 搞定 <!-- .element: class="fragment" data-fragment-index="3" style="color:yellow"--></span>
只要定義一段描述同步化的邏輯
然後再按需求加上 cleanup function
去清除可能造成的副作用影響。
---
## Dependencies 是一種效能優化
--
而非執行時機的控制
```javascript=
import { useState, useEffect } from "react"
export default function App(props){
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `Hello ${props.name}`
},[props.name])
return (
<button onClick={()=> setCount(count + 1)}>
Click me
</button>
)
}
```
----
加入 dependencies 流程模擬:
* 首次 render 時,確認資料流 props.name = 'foo' 與 state count 的值為 0
1. 渲染出 React Element 產生相對應的 DOM 結構
2. 執行本次 render 對應的 effect 函式,以及對應本次 props.name 的值為'foo'
3. 記憶此 useEffect 的 dependencies 裡的 props.name 值為'foo',作為下次 effect 函式是否執行的比較依據。
----
* 經過使用者點擊觸發 setCount,觸發re-render 後,確認資料流 props.name = 'foo' 與 state count 的值為 0 + 1 = 1
1. 比較 React Element 差異,找出要更新的RE,畫面產生相對應的 DOM 處理
2. 檢查 useEffect 的 dependencies 陣列的每一個項目值,找到為一個 props.name,比較與前一次 render 時是否相同 (使用 Object.is() 判斷)
3. 確認相同,可跳過執行此 useEffect 函式
----
* 若父 Component render 改變了 props.name 為 "bar",觸發子 component 第三次 re-render,state count 的值仍為 1
1. 比較 React Element 差異,找出要更新的RE,畫面產生相對應的 DOM 處理
2. 檢查 useEffect 的 dependencies 陣列的每一個項目值,找到為一個 props.name,比較與前一次 render 時是否相同 (使用 Object.is() 判斷)
----
3. 前一次版本的 props.name "foo",已經更新為 "bar",須照常執行本次 render 的 effect 函式。
4. 執行本次 props.name 值為 "bar" 的 effect 函式
5. 記憶此 useEffect 中的 dependencies 陣列中所有變數的值,也就是 props.name 值為 "bar" ,做為下一次依賴相比較的依據。
----
dependencies 能不能放 Object 和 Fn?
放原始型別與 Object 的差異?

<!-- 盡可能地減少不必要的 Object dependencies,若真的需要可以將它移到 effect 函式的內部,改監控 roomId number 型別就好 -->
---
若沒有任何依賴項,則可以填寫空陣列
<span>但不可以欺騙 React 來試圖控制 effect 函式執行時機<!-- .element: class="fragment" data-fragment-index="1" style="color:yellow"--></span>
<span>應要改用條件判斷<!-- .element: class="fragment" data-fragment-index="2" style="color:red"--></span>
<span>這個 effect 函式執行的時機<!-- .element: class="fragment" data-fragment-index="3" style="color:red"--></span>
<!-- 讓你的副作用處理即使根本沒提供 dependencies 參數,也能在每次 render 時都能保持程式碼正確的執行 -->
---
useEffect 其實不是生命週期的API
章節重要觀念自我檢測
* `useEffect` 是 function component 的生命週期 API 嗎?為什麼?
* 為什麼 React 要以 `useEffect` 的資料流同步化這種設計來取代生命週期 API?
* `useEffect` 的 `dependencies` 機制的設計目的與用途是什麼?
* 我們可以用 `dependencies` 參數來模擬 function component 生命週期 API 的效果嗎?
---
# The End
{"slideOptions":"{\"transition\":\"slide\"}","title":"React 思維進化","contributors":"[{\"id\":\"aef1372a-7cb3-4de4-bc8d-affbed099920\",\"add\":22981,\"del\":11268}]","description":"React 中的副作用處理-初探 Effect"}