Function Component 不存在生命週期,這也是為什麼 React 開始推薦使用函數組件的原因,可以減少學習成本。
useEffect()
作用: 為函數組件處理副作用。
副作用定義: 副作用是相對主作用來說的,一個函數除了主作用,其他的都是副作用。對 React 而言,主作用就是根據數據(state/props)渲染頁面,除此都是副作用。
常見副作用:
- 手動修改 DOM
- localStorage 操作
- 數據請求 AJAX 發送
React 官方文檔:
每次 render 後都會執行 useEffect 嗎? 是的!預設情況下,它在第一個 render 和隨後每一個更新之後執行。你可能會發現把 effect 想成發生在「render 之後」更為容易,而不是考慮「mount」和「更新」。 React 保證 DOM 在執行 effect 時已被更新。
每次重新 render 時,我們都會安排一個 different effect 來替代上一個。在某種程度上,這使 effect 的行為更像是 render 結果的一部分 — 每個 effect 都「屬於」特定的 render (useEffect 會在應用下一個 effect 之前,清除之前的 effect)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { useEffect, useState } from "react"; export default function App() { const [count, setCount] = useSate(0); const [name, setName] = useSate("Celeste")
useEffect(()=>{ setTimeout(()=>{ console.log(count) }, 10000) })
return ( <div> {count} {name} <button onClick={() => {setName("Smith")}}>改name</button> <button onClick={() => {setCount(++count)}}>改 count</button> </div> ) }
|
效能優化 ⬇️
在目前的 useEffect 中,在 name 有變化時,useEffect 並不需要去被執行,因為它所依賴的是 count,為了讓 useEffect 可以僅在 count 有變化才執行,就需要使用 useEffect 第 2 個參數(為一個陣列),該陣列需列出所有依賴項。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useEffect, useState } from "react"; export default function App() { const [count, setCount] = useSate(0); const [name, setName] = useSate("Celeste")
useEffect(()=>{ setInterval(()=>{ console.log(count) }, 10000) }, [count])
return ( <div> {count} {name} <button onClick={() => {setName("Smith")}}>改name</button> <button onClick={() => {setCount(++count)}}>改 count</button> </div> ) }
|
避免內存洩漏 ⬇️
有些程式碼如果不執行清除的話,在組件被卸載時,仍會占用內存(如: setInterval),所以需要再 useEffect 中返回一個 function ,來讓 React 去清除不需要的程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { useEffect, useState } from "react"; export default function App() { const [count, setCount] = useSate(0); const [name, setName] = useSate("Celeste")
useEffect(()=>{ const timer = setInterval(()=>{ console.log(count) }, 10000) return () => { clearInterval(timer); } }, [count])
return ( <div> {count} {name} <button onClick={() => {setName("Smith")}}>改name</button> <button onClick={() => {setCount(++count)}}>改 count</button> </div> ) }
|
React 官方文檔提出: useEffect 會在應用下一個 effect 之前,清除之前的 effect。這也代表 React 會在執行新的 useEffect 之前,先執行上一個的 useEffect 的清除(也就是 return 的函數)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const timer = setInterval(() => { console.log(count); }, 10000);
clearInterval(timer); const timer = setInterval(() => { console.log(count); }, 10000);
clearInterval(timer); const timer = setInterval(() => { console.log(count); }, 10000);
|
useRef()
作用: 獲取真實 DOM 或類組件的方法。
原則: 不能對 function component 使用 ref。
什麼時候該使用 Ref:
- 管理 focus、選擇文字、或影音播放。
- 觸發即時的動畫。
- 與第三方 DOM 函式庫整合。
React 提示不要過度使用 Ref,在可以使用 state 管理數據的位置,就應該使用 state。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { useEffect, useState, useRef } from "react"; export default function App() { const [name, setName] = useSate("Celeste")
const nameEl = useRef(null); const ageEl = useRef(null);
useEffect(()=>{ console.log(ageEl); ageEl.current.focus(); })
return ( <div> {/* 透過 ref 來指定與哪個 useRef 連結*/} 姓名: <input ref= {nameEl} /> 年齡: <input ref= {ageEl} /> <button onClick={() => {setName("Smith")}}>改name</button> </div> ) }
|
useCallback()
作用: 防止因為組件重新渲染,導致方法被重新創建,起到緩存作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| impport { useState, useCallback } from "react"; export default function App(){ const [count, setCount] = useState(0); const [name, setName] = usesState("Celeste");
const changeCount = useCallback(()=>{ console.log(name); setCount(++count) }, [count]);
return ( <div> count: {count} <button onClick={changeCount}>改變 count</button> <hr/> {name} {/* 當 name 被改變,App 組件被更新載入時,並不會再次去創建 changeCount */} <button onClick={()=> {setName("Smith")}}>改變 name</button> </div> ) }
|
useMemo()
作用: 防止因為組件重新渲染,導致方法所返回的值被重新計算創建,起到緩存作用,與useCallback
不同的是, useMemo
會執行第一個參數(函數)並返回一個值,並且儲存該返回值,之後只要依賴沒改變,就不會重新執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| impport { useState, useMemo } from "react"; export default function App(){ const [count, setCount] = useState([1, 2, 3]); const [name, setName] = usesState("Celeste");
const addCount = useMemo(() => () => { console.log(name); let sum = 0; count.forEach(num => { sum+= num }) return sum }, [count]);
return ( <div> count: {count.map((num)=><span>{num}</span>)}, 總計: {addCount()} <button onClick={changeCount}>改變 count</button> <hr/> {name} {/* 當 name 被改變,App 組件被更新載入時,並不會重新去計算 addCount */} <button onClick={()=> {setName("Smith")}}>改變 name</button> </div> ) }
|
對於 useMemo 使用時機的更多了解: Memorized Hook 在幹嘛?