做者:Shadeed
譯者:前端小智
來源:dmitripavlutin
點贊再看,微信搜索【大遷世界】,B站關注【前端小智】這個沒有大廠背景,但有着一股向上積極心態人。本文
GitHub
https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。javascript最近開源了一個 Vue 組件,還不夠完善,歡迎你們來一塊兒完善它,也但願你們能給個 star 支持一下,謝謝各位了。html
github 地址:https://github.com/qq44924588...前端
useEffect()
主要用來管理反作用,好比經過網絡抓取、直接操做 DOM、啓動和結束計時器。vue
雖然useEffect()
和 useState
(管理狀態的方法)是最經常使用的鉤子之一,但須要一些時間來熟悉和正確使用。java
使用useEffect()
時,你可能會遇到一個陷阱,那就是組件渲染的無限循環。在這篇文章中,會講一下產生無限循環的常見場景以及如何避免它們。react
假設咱們有一個功能組件,該組件裏面有一個 input
元素,組件是功能是計算 input
更改的次數。git
咱們給這個組件取名爲 CountInputChanges
,大概的內容以下:github
function CountInputChanges() { const [value, setValue] = useState(''); const [count, setCount] = useState(-1); useEffect(() => setCount(count + 1)); const onChange = ({ target }) => setValue(target.value); return ( <div> <input type="text" value={value} onChange={onChange} /> <div>Number of changes: {count}</div> </div> ) }
<input type =「 text」 value = {value} onChange = {onChange} />
是受控組件。value
變量保存着 input
輸入的值,當用戶輸入輸入時,onChange
事件處理程序更新 value
狀態。面試
這裏使用useEffect()
更新count
變量。每次因爲用戶輸入而致使組件從新渲染時,useEffect(() => setCount(count + 1))
就會更新計數器。微信
由於useEffect(() => setCount(count + 1))
是在沒有依賴參數的狀況下使用的,因此()=> setCount(count + 1)
會在每次渲染組件後執行回調。
你以爲這樣寫會有問題嗎?打開演示本身試試看:https://codesandbox.io/s/infi...
運行了會發現count
狀態變量不受控制地增長,即便沒有在input
中輸入任何東西,這是一個無限循環。
問題在於useEffect()
的使用方式:
useEffect(() => setCount(count + 1));
它生成一個無限循環的組件從新渲染。
在初始渲染以後,useEffect()
執行更新狀態的反作用回調函數。狀態更新觸發從新渲染。從新渲染以後,useEffect()
執行反作用回調並再次更新狀態,這將再次觸發從新渲染。
無限循環能夠經過正確管理useEffect(callback, dependencies)
依賴項參數來修復。
由於咱們但願count
在值更改時增長,因此能夠簡單地將value
做爲反作用的依賴項。
import { useEffect, useState } from 'react'; function CountInputChanges() { const [value, setValue] = useState(''); const [count, setCount] = useState(-1); useEffect(() => setCount(count + 1), [value]); const onChange = ({ target }) => setValue(target.value); return ( <div> <input type="text" value={value} onChange={onChange} /> <div>Number of changes: {count}</div> </div> ); }
添加[value]
做爲useEffect
的依賴,這樣只有當[value]
發生變化時,計數狀態變量纔會更新。這樣作能夠解決無限循環。
除了依賴,咱們還能夠經過 useRef() 來解決這個問題。
其思想是更新 Ref 不會觸發組件的從新渲染。
import { useEffect, useState, useRef } from "react"; function CountInputChanges() { const [value, setValue] = useState(""); const countRef = useRef(0); useEffect(() => countRef.current++); const onChange = ({ target }) => setValue(target.value); return ( <div> <input type="text" value={value} onChange={onChange} /> <div>Number of changes: {countRef.current}</div> </div> ); }
useEffect(() => countRef.current++)
每次因爲value
的變化而從新渲染後,countRef.current++
就會返回。引用更改自己不會觸發組件從新渲染。
即便正確設置了useEffect()
依賴關係,使用對象做爲依賴關係時也要當心。
例如,下面的組件CountSecrets
監聽用戶在input
中輸入的單詞,一旦用戶輸入特殊單詞'secret'
,統計 'secret' 的次數就會加 1。
import { useEffect, useState } from "react"; function CountSecrets() { const [secret, setSecret] = useState({ value: "", countSecrets: 0 }); useEffect(() => { if (secret.value === 'secret') { setSecret(s => ({...s, countSecrets: s.countSecrets + 1})); } }, [secret]); const onChange = ({ target }) => { setSecret(s => ({ ...s, value: target.value })); }; return ( <div> <input type="text" value={secret.value} onChange={onChange} /> <div>Number of secrets: {secret.countSecrets}</div> </div> ); }
打開演示(https://codesandbox.io/s/infi...)本身試試,當前輸入 secret
,secret.countSecrets
的值就開始不受控制地增加。
這是一個無限循環問題。
爲何會這樣?
secret
對象被用做useEffect(..., [secret])
。在反作用回調函數中,只要輸入值等於secret
,就會調用更新函數
setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));
這會增長countSecrets
的值,但也會建立一個新對象。
secret
如今是一個新對象,依賴關係也發生了變化。因此useEffect(..., [secret])
再次調用更新狀態和再次建立新的secret
對象的反作用,以此類推。
JavaScript 中的兩個對象只有在引用徹底相同的對象時才相等。
解決由循環建立新對象而產生的無限循環問題的最好方法是避免在useEffect()
的dependencies
參數中使用對象引用。
let count = 0; useEffect(() => { // some logic }, [count]); // Good!
let myObject = { prop: 'Value' }; useEffect(() => { // some logic }, [myObject]); // Not good! useEffect(() => { // some logic }, [myObject.prop]); // Good!
修復<CountSecrets>
組件的無限循環問題,能夠將useEffect(..., [secret]))
變爲 useEffect(..., [secret.value])
。
僅在secret.value
更改時調用反作用回調就足夠了,下面是修復後的代碼:
import { useEffect, useState } from "react"; function CountSecrets() { const [secret, setSecret] = useState({ value: "", countSecrets: 0 }); useEffect(() => { if (secret.value === 'secret') { setSecret(s => ({...s, countSecrets: s.countSecrets + 1})); } }, [secret.value]); const onChange = ({ target }) => { setSecret(s => ({ ...s, value: target.value })); }; return ( <div> <input type="text" value={secret.value} onChange={onChange} /> <div>Number of secrets: {secret.countSecrets}</div> </div> ); }
useEffect(callback, deps)
是在組件渲染後執行callback(反作用)
的 Hook。若是不注意反作用的做用,可能會觸發組件渲染的無限循環。
生成無限循環的常見狀況是在反作用中更新狀態,沒有指定任何依賴參數
useEffect(() => { // Infinite loop! setState(count + 1); });
避免無限循環的一種有效方法是正確設置依賴項:
useEffect(() => { // No infinite loop setState(count + 1); }, [whenToUpdateValue]);
另外,也可使用 Ref,更新 Ref 不會觸發從新渲染:
useEffect(() => { // No infinite loop countRef.current++; });
無限循環的另外一種常見方法是使用對象做爲useEffect()
的依賴項,並在反作用中更新該對象(有效地建立一個新對象)
useEffect(() => { // Infinite loop! setObject({ ...object, prop: 'newValue' }) }, [object]);
避免使用對象做爲依賴項,只使用特定的屬性(最終結果應該是一個原始值):
useEffect(() => { // No infinite loop setObject({ ...object, prop: 'newValue' }) }, [object.whenToUpdateProp]);
當使用useEffect()
時,你還知道有其它方式會引發無限循環陷阱嗎?
~完,我是小智,咱們下期見~
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:https://dmitripavlutin.com/re...
文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。