React useEffect使用教程
這篇文章會(huì)假設(shè)你對(duì)useEffectAPI有一定程度的了解。
一、每一次渲染都有它自己的 Props and State
在我們討論 effects 之前,我們需要先討論一下渲染,當(dāng)我們更新 state 的時(shí)候,React會(huì)重新渲染組件。每一次渲染都能拿到獨(dú)立的 state,這個(gè)狀態(tài)值是函數(shù)中的一個(gè)常量。
這里關(guān)鍵的點(diǎn)在于任意一次渲染中的常量都不會(huì)隨著時(shí)間改變。渲染輸出會(huì)變是因?yàn)槲覀兊慕M件被一次次調(diào)用,而每一次調(diào)用引起的渲染中,它包含的值獨(dú)立于其他渲染。
如果 props 和 state 在不同的渲染中是相互獨(dú)立的,那么使用到它們的任何值也是獨(dú)立的(包括事件處理函數(shù))。它們都“屬于”一次特定的渲染。即便是事件處理中的異步函數(shù)調(diào)用“看到”的也是這次渲染中的值。
二、每次渲染都有它自己的Effects
讓我們先看向官網(wǎng)的 useEffect 的例子:
function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
effect是如何讀取到最新的count
狀態(tài)值的呢?
也許,是某種 watching 機(jī)制類似 vue 中的數(shù)據(jù)響應(yīng)式使得能夠在 effect 函數(shù)內(nèi)更新?也或許是一個(gè)可變的值,React 會(huì)在我們組件內(nèi)部修改它以使我們的 effect 函數(shù)總能拿到最新的值?
都不是。
我們已經(jīng)知道是某個(gè)特定渲染中的常量。事件處理函數(shù)“看到”的是屬于它那次特定渲染中的狀態(tài)值。對(duì)于 effects 也同樣如此:
并不是count
的值在“不變”的 effect 中發(fā)生了改變,而是 effect 函數(shù)本身在每一次渲染中都不相同。
React 會(huì)記住你提供的 effect 函數(shù),并且會(huì)在每次更改作用于DOM并讓瀏覽器繪制屏幕后去調(diào)用它。
所以雖然我們說(shuō)的是一個(gè) effect(這里指更新document的title),但其實(shí)每次渲染都是一個(gè)不同的函數(shù)— 并且每個(gè) effect 函數(shù)看到的 props 和 state 都來(lái)自于它屬于的那次特定渲染。
三、關(guān)于依賴項(xiàng)不要對(duì)React撒謊
現(xiàn)在只需要記?。喝绻阍O(shè)置了依賴項(xiàng),effect 中用到的所有組件內(nèi)的值都要包含在依賴中。這包括props,state,函數(shù)組件內(nèi)的任何東西。
在下面這個(gè)組件中,我們的直覺是:“開啟一次定時(shí)器,清除也是一次”。直覺上我們會(huì)設(shè)置依賴為 '[]'。“我只想運(yùn)行一次 effect ”,但是這樣對(duì)嗎?
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>; }
我們以為他會(huì)一直遞增下去,但實(shí)際上他只會(huì)遞增一次,你想要觸發(fā)一次因?yàn)樗嵌〞r(shí)器 ,但為什么會(huì)有問(wèn)題?
在第一次渲染中我們執(zhí)行了setCount(0 + 1)
。但是我們?cè)O(shè)置了[]
依賴,effect不會(huì)再重新運(yùn)行,它后面每一秒都會(huì)調(diào)用setCount(0 + 1)。
我們對(duì) React 撒謊說(shuō)我們的 effect 不依賴組件內(nèi)的任何值,可實(shí)際上我們的 effect 有依賴。
四、兩種誠(chéng)實(shí)告知依賴的方法
第一種策略是在依賴中包含所有 effect 中用到的組件內(nèi)的值。讓我們?cè)谝蕾囍邪?code>count
useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]);
這在我們大部分初級(jí)開發(fā)者的眼中都沒有什么問(wèn)題,并且程序確實(shí)不會(huì)出任何 bug,現(xiàn)在,每次修改都會(huì)重新運(yùn)行 effect,這能解決問(wèn)題但是我們的定時(shí)器會(huì)在每一次改變后清除和重新設(shè)定。這肯定不是我們想要的結(jié)果。
第二種策略是修改 effect 內(nèi)部的代碼以確保它包含的值只會(huì)在需要的時(shí)候發(fā)生變更。
在這個(gè)場(chǎng)景中,我們其實(shí)并不需要在effect中使用 count。當(dāng)我們想要根據(jù)前一個(gè)狀態(tài)更新狀態(tài)的時(shí)候,我們可以使用的函數(shù)形式:
useEffect(() => { const id = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(id); }, []);
我們需要告知React的僅僅是去遞增狀態(tài) - 不管它現(xiàn)在具體是什么值。注意我們做到了移除依賴,并且沒有撒謊。我們的 effect 不再讀取渲染中的count
值。
五、來(lái)自u(píng)seReducer的助攻
如果我們有兩個(gè)互相依賴的狀態(tài),或者我們想基于一個(gè) prop 來(lái)計(jì)算下一次的 state,setCount(c => c + 1)
它并不能做到。幸運(yùn)的是,有一個(gè)更強(qiáng)大的姐妹模式,它的名字叫useReducer。
我們先來(lái)修改上面的例子讓它包含兩個(gè)狀態(tài):count 和 step 。我們的定時(shí)器會(huì)每次在 count 上增加一個(gè) step 值:
function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => { const id = setInterval(() => { setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [step]); return ( <> <h1>{count}</h1> <input value={step} onChange={e => setStep(Number(e.target.value))} /> </> ); }
注意我們沒有撒謊。既然我們?cè)?effect 里使用了step
,我們就把它加到依賴?yán)?。所以這也是為什么代碼能運(yùn)行正確。
這個(gè)例子目前的行為是修改會(huì)重啟定時(shí)器 - 因?yàn)樗且蕾図?xiàng)之一。在大多數(shù)場(chǎng)景下,這正是你所需要的。清除上一次的effect然后重新運(yùn)行新的effect并沒有任何錯(cuò)。不過(guò),假如我們不想在改變后重啟定時(shí)器,我們?cè)撊绾螐膃ffect中移除對(duì)的依賴呢?
下面這句話我希望你作為一名 react 開發(fā)人員要記下來(lái):
當(dāng)你想更新一個(gè)狀態(tài),并且這個(gè)狀態(tài)更新依賴于另一個(gè)狀態(tài)的值時(shí),你可能需要用useReducer
去替換它們。
reducer 可以讓你把組件內(nèi)發(fā)生了什么(actions)和狀態(tài)如何響應(yīng)并更新分開表述。
我們用一個(gè)dispatch
依賴去替換 effect 的依賴 step
:
function Counter() { const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]); return ( <> <h1>{count}</h1> <input value={step} onChange={e => { dispatch({ type: 'step', step: Number(e.target.value) }); }} /> </> ); } const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') { return { count: count + step, step }; } else if (action.type === 'step') { return { count, step: action.step }; } else { throw new Error(); } }
你可能會(huì)問(wèn):“這怎么就更好了?”答案是React會(huì)保證dispatch
在組件的聲明周期內(nèi)保持不變。所以上面例子中不再需要重新訂閱定時(shí)器。
相比于直接在 effect 里面讀取狀態(tài),它 dispatch 了一個(gè)action來(lái)描述發(fā)生了什么。這使得我們的 effect 和狀態(tài)解耦。我們的 effect 不再關(guān)心怎么更新狀態(tài),它只負(fù)責(zé)告訴我們發(fā)生了什么。更新的邏輯全都交由 reducer 去統(tǒng)一處理。
六、把函數(shù)移到Effects里
一個(gè)典型的誤解是認(rèn)為函數(shù)不應(yīng)該成為依賴。舉個(gè)例子,下面的代碼看上去可以運(yùn)行正常:
function SearchResults() { const [data, setData] = useState({ hits: [] }); async function fetchData() { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react', ); setData(result.data); } useEffect(() => { fetchData(); }, []); // ...
需要明確的是,上面的代碼可以正常工作。但這樣做在組件日漸復(fù)雜的迭代過(guò)程中我們很難確保它在各種情況下還能正常運(yùn)行。
如果我們?cè)谀承┖瘮?shù)內(nèi)使用了某些 state 或者 prop:
function SearchResults() { const [query, setQuery] = useState('react'); // Imagine this function is also long function getFetchUrl() { return 'https://hn.algolia.com/api/v1/search?query=' + query; } // Imagine this function is also long async function fetchData() { const result = await axios(getFetchUrl()); setData(result.data); } useEffect(() => { fetchData(); }, []); // ... }
如果我們忘記去更新使用這些函數(shù)(很可能通過(guò)其他函數(shù)調(diào)用)的effects的依賴,我們的effects就不會(huì)同步props和state帶來(lái)的變更。這當(dāng)然不是我們想要的。
如果某些函數(shù)僅在effect中調(diào)用,你可以把它們的定義移到effect中:
function SearchResults() { // ... useEffect(() => { // We moved these functions inside! function getFetchUrl() { return 'https://hn.algolia.com/api/v1/search?query=react'; } async function fetchData() { const result = await axios(getFetchUrl()); setData(result.data); } fetchData(); }, []); }
這么做有什么好處呢?我們不再需要去考慮這些“間接依賴”。我們的依賴數(shù)組也不再撒謊:在我們的 effect 中確實(shí)沒有再使用組件范圍內(nèi)的任何東西。
如果我們后面修改getFetchUrl
去使用狀態(tài) query
,我們更可能會(huì)意識(shí)到我們正在effect里面編輯它因此,我們需要把 query
添加到effect的依賴?yán)铮?/p>
function SearchResults() { const [query, setQuery] = useState('react'); useEffect(() => { function getFetchUrl() { return 'https://hn.algolia.com/api/v1/search?query=' + query; } async function fetchData() { const result = await axios(getFetchUrl()); setData(result.data); } fetchData(); }, [query]); }
七、我不想把可復(fù)用的函數(shù)放到Effect里
有時(shí)候你可能不想把函數(shù)移入 effect 里。比如,組件內(nèi)有幾個(gè) effect 使用了相同的函數(shù),你不想在每個(gè) effect 里復(fù)制黏貼一遍這個(gè)邏輯。也或許這個(gè)函數(shù)是一個(gè) prop。
在這種情況下你應(yīng)該忽略對(duì)函數(shù)的依賴嗎?這么做是不對(duì)的。再次強(qiáng)調(diào),effects不應(yīng)該對(duì)它的依賴撒謊。通常我們還有更好的解決辦法。一個(gè)常見的誤解是,“函數(shù)從來(lái)不會(huì)改變”。但是這篇文章你讀到現(xiàn)在,你知道這顯然不是事實(shí)。實(shí)際上,在組件內(nèi)定義的函數(shù)每一次渲染都在變。
function SearchResults() { function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, []); useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, []); }
在這個(gè)例子中,你可能不想把getFetchUrl
移到 effects 中,因?yàn)槟阆霃?fù)用邏輯。
另一方面,如果你對(duì)依賴很“誠(chéng)實(shí)”,你可能會(huì)掉到陷阱里。我們的兩個(gè) effects 都依賴 getFetchUrl
,而它每次渲染都不同,所以我們的依賴數(shù)組會(huì)變得無(wú)用:
function SearchResults() { function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, [getFetchUrl]); useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, [getFetchUrl]); // ... }
我們有兩個(gè)更簡(jiǎn)單的解決辦法。
第一個(gè), 如果一個(gè)函數(shù)沒有使用組件內(nèi)的任何值,你應(yīng)該把它提到組件外面去定義,然后就可以自由地在 effects 中使用:
function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } function SearchResults() { useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, []); useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, []); // ... }
你不再需要把它設(shè)為依賴,因?yàn)樗鼈儾辉阡秩痉秶鷥?nèi),因此不會(huì)被數(shù)據(jù)流影響。
或者, 你也可以把它包裝成useCallback Hook:
function SearchResults() { const getFetchUrl = useCallback((query) => { return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []); useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, [getFetchUrl]); useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, [getFetchUrl]); // ... }
我們用 useCallback 對(duì) getFetchUrl 做了一層緩存,現(xiàn)在只有當(dāng)依賴項(xiàng)變化的時(shí)候,才會(huì)重新執(zhí)行 useCallback 來(lái)返回新的函數(shù),依賴項(xiàng)沒有變化的時(shí)候就算組件 rerender 了,這個(gè)函數(shù)也不會(huì)重新執(zhí)行,這樣我們把 getFetchUrl 作為 useEffect 的依賴就沒問(wèn)題了。
不同于傳遞參數(shù)的方式,現(xiàn)在我們從狀態(tài)中讀取 query:
function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(() => { return 'https://hn.algolia.com/api/v1/search?query=' + query; }, [query]); useEffect(() => { const url = getFetchUrl(); // ... Fetch data and do something ... }, [getFetchUrl]); // ... }
如果query
保持不變,useCallback
也會(huì)保持不變,我們的 effect 也不會(huì)重新運(yùn)行。但是如果修改了 query,useCallback 也會(huì)隨之改變,因此會(huì)重新請(qǐng)求數(shù)據(jù)。這就像你在Excel里修改了一個(gè)單元格的值,另一個(gè)使用它的單元格會(huì)自動(dòng)重新計(jì)算一樣。
到此這篇關(guān)于React useEffect使用教程的文章就介紹到這了,更多相關(guān)React useEffect內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Hooks中模擬Vue生命周期函數(shù)的指南
React Hooks 提供了一種在函數(shù)組件中使用狀態(tài)和其他 React 特性的方式,而不需要編寫類組件,Vue 的生命周期函數(shù)和 React Hooks 之間有一定的對(duì)應(yīng)關(guān)系,本文給大家介紹了React Hooks中模擬Vue生命周期函數(shù)的指南,需要的朋友可以參考下2024-10-10react中實(shí)現(xiàn)拖拽排序react-dnd功能
這篇文章主要介紹了react中實(shí)現(xiàn)拖拽排序react-dnd功能,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02React如何使用refresh_token實(shí)現(xiàn)無(wú)感刷新頁(yè)面
本文主要介紹了React如何使用refresh_token實(shí)現(xiàn)無(wú)感刷新頁(yè)面,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04關(guān)于React Native使用axios進(jìn)行網(wǎng)絡(luò)請(qǐng)求的方法
axios是一個(gè)基于Promise的Http網(wǎng)絡(luò)庫(kù),可運(yùn)行在瀏覽器端和Node.js中,Vue應(yīng)用的網(wǎng)絡(luò)請(qǐng)求基本都是使用它完成的。這篇文章主要介紹了React Native使用axios進(jìn)行網(wǎng)絡(luò)請(qǐng)求,需要的朋友可以參考下2021-08-08React?高階組件與Render?Props優(yōu)缺點(diǎn)詳解
這篇文章主要weidajai?介紹了React?高階組件與Render?Props優(yōu)缺點(diǎn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11