React項目中如果使用函數式組件進行開發時,如果想在不使用class組件的情況下使用state和其他React特性,Hook將是你的不二選擇。
而Effect Hook又是一種比較常見的Hook,可以在函數式組件中執行副作用操作。剛開始可以理解為created、update生命周期。
一、為effect添加依賴
const [count, setCount] = useState(0)
useEffect(() => {
const fn = setTimeout(() => {
console.log(count)
setCount(count+1)。
}, 1000)
})
// 0
// 1
// 2
useEffect(() => {
const fn = setTimeout(() => {
console.log(count)
setCount(count+1)
}, 1000)
},[])
// 0
effect的執行機制,是比較兩次依賴項是否相同,不同則執行相關effect。
如果不替effect添加依賴,則是比較兩次effect。而我們知道在函數式組件中,每次渲染的state、effect都是不同的。只要組件內部狀態發生變化就會執行effect。
而將依賴項設置為一個固定值,則effect只會初始化時執行一次。
二、effect內部狀態自調用
上面兩種情況是比較簡單的,在復雜的業務邏輯中,我們往往需要依據某個狀態的變化然后再來執行副作用操作,並且還需要為effect設置清除函數。我們看看下面這段代碼:
useEffect(() => {
const fn = setTimeout(() => {
console.log(count)
setCount(count+1)
}, 1000)
return () => clearTimeout(fn)
},[count])
// 0
// 1
// 2
我們在effect中添加了清除函數,並且需要將count作為依賴在count變化時執行內部代碼。這應該是大家使用Hook Effect比較常見的用法。
但是就這個例子而言,這種設置依賴的寫法是有邏輯漏洞的。在effect內部只需要將count做遞增,而此時React是知道count的值,所以effect並不要使用count,可以使用setState的函數形式代替傳參。
useEffect(() => {
const fn = setTimeout(() => {
console.log(count)
setCount(c => c+1)
}, 1000)
return () => clearTimeout(fn)
})
// 0
// 1
// 2
setCount(c => c+1)就是在方法內部發送更新指令,告訴React去更新state的值,這樣就將count從effect依賴中去掉,保持內部邏輯干凈無污染。
三、將函數放在effect內部
有時一個effect內部函數代碼量過多,我們可以將代碼進行分割單獨封裝成函數,使整個邏輯看起來清晰明了。可能我們為了進一步追求代碼規范,需要將函數抽離放在effect外部。
const countPlayer = () => {
return setTimeout(() => {
setCount(count+10)
},1000)
}
useEffect(() => {
const fn = countPlayer()
return () => clearTimeout(fn)
},[])
console.log(count)
// 0
// 10
比如我們要在初始化時通過接口獲取數據進行setState賦值然后渲染UI,並且獲取數據操作只需要執行一次。上面的代碼顯示count為0時執行了effect副作用,通過countPlayer函數進行組件state初始化操作。第二次由於依賴沒有變更就跳過了直接往后執行。
邏輯上看起來沒問題,但是如果當代碼量過大並且存在多個函數嵌套依賴的情況,就很難保證准確更新相關effect依賴。並且如果項目安裝了eslint-plugin-react-hooks,在項目打包運行時就會出現警告。

這種情況,我們將函數移動到effect內部就能很好地規避這個問題。
四、將函數放在effect外部
如果一個函數在多個effect中被復用,這時我們就必須將函數放在effect外部了。至於邏輯漏洞和代碼eslint告警的問題,一般有兩種方法解決:
- 將函數轉化成純函數,移動到組件外部;
- 使用useCallback Hook進行包裝;
如果一個函數沒有使用組件內部任何state、props就可以轉化成純函數,在不同的effect中任意調用。
如果函數內部需要對state等進行setState操作,可以使用useCallback將整個函數包裝成一個依賴state的可變值,然后作為effect的依賴使用。
可能有的小伙伴會問,為啥不直接將countPlayer函數作為依賴呢?下面我們看看這樣設置后的運行情況。
const countPlayer = () => {
return setTimeout(() => {
setCount(count+10)
},1000)
}
useEffect(() => {
const fn = countPlayer()
return () => clearTimeout(fn)
},[countPlayer])
console.log(count)
// 0
// 10
// 20
// ...
會出現無限循環自調用的bug。之前我們提過,在函數式組件內部,每次獲取的函數體、state、effect都不一樣,所以會導致effect陷入死循環。下面我們將函數用useCallback進行包裝看看效果。
const countPlayer = useCallback(() => {
return setTimeout(() => {
setCount(c => c+10)
},1000)
},[])
useEffect(() => {
const fn = countPlayer()
return () => clearTimeout(fn)
},[countPlayer])
console.log(count)
// 0
// 10
這樣既滿足了將函數體抽離出effect的目的;並且通過useCallback包裝使函數體本身成為了一個擁有獨立依賴的可變值,可以在不同的effect之間復用。
至此,effect Hook一些常用的思路和使用技巧都講解完畢!
