一文弄懂React的useEffect經(jīng)常執(zhí)行兩次
引言
在使用React開發(fā)應(yīng)用時(shí),許多開發(fā)者都會(huì)遇到一個(gè)看似奇怪的現(xiàn)象:useEffect鉤子在某些情況下會(huì)執(zhí)行兩次。這種現(xiàn)象在開發(fā)模式下尤其常見,而在生產(chǎn)模式下則通常不會(huì)出現(xiàn)。對(duì)于初次接觸React或?qū)eact內(nèi)部機(jī)制理解不深的開發(fā)者來說,這可能會(huì)引發(fā)困惑甚至導(dǎo)致錯(cuò)誤的調(diào)試方向。本文將深入探討這一現(xiàn)象的根本原因,分析React的設(shè)計(jì)理念,并解釋為什么這種行為實(shí)際上是React團(tuán)隊(duì)有意為之的優(yōu)化策略。
主體
1. useEffect的基本行為
首先,我們需要明確useEffect的基本工作原理。useEffect是React Hooks中用于處理副作用的核心API,它的執(zhí)行時(shí)機(jī)是在組件渲染完成之后。根據(jù)依賴項(xiàng)的不同,useEffect的執(zhí)行可以分為以下幾種情況:
- 無依賴數(shù)組:每次渲染后都會(huì)執(zhí)行
- 空依賴數(shù)組:僅組件掛載時(shí)執(zhí)行一次
- 有依賴項(xiàng):依賴項(xiàng)變化時(shí)執(zhí)行
然而,即便是在空依賴數(shù)組的情況下,開發(fā)者有時(shí)也會(huì)觀察到useEffect被執(zhí)行兩次,這與預(yù)期不符。這種現(xiàn)象主要發(fā)生在React 18及更高版本的開發(fā)模式下。
2. React 18的嚴(yán)格模式(Strict Mode)
React 18引入了一個(gè)重要的變化:在開發(fā)模式下默認(rèn)啟用了嚴(yán)格模式(Strict Mode)。嚴(yán)格模式會(huì)故意讓你的組件掛載兩次,以此來幫助發(fā)現(xiàn)潛在的問題。這種行為的目的是:
- 檢測(cè)不純的渲染(如直接修改props或state)
- 驗(yàn)證effect的清理函數(shù)是否正確實(shí)現(xiàn)
- 發(fā)現(xiàn)意外的副作用
當(dāng)組件掛載兩次時(shí),相關(guān)的useEffect也會(huì)隨之執(zhí)行兩次。這是React團(tuán)隊(duì)故意設(shè)計(jì)的,目的是幫助開發(fā)者在早期發(fā)現(xiàn)并修復(fù)問題,而不是等到生產(chǎn)環(huán)境中才暴露出來。
3. 雙重渲染的深層機(jī)制
React 18的雙重渲染機(jī)制實(shí)際上是一種"mount → unmount → remount"的過程。具體來說:
- 首次渲染和掛載組件
- 執(zhí)行所有effect(第一次執(zhí)行)
- React立即卸載組件
- 執(zhí)行所有effect的cleanup函數(shù)
- 重新掛載組件(第二次執(zhí)行effect)
這種機(jī)制確保了你的組件能夠正確處理"掛載→卸載→重新掛載"的生命周期,這在現(xiàn)實(shí)應(yīng)用中是很常見的場(chǎng)景(如用戶在頁(yè)面間快速導(dǎo)航)。
4. 為什么只在開發(fā)模式下?
這種雙重渲染行為僅限于開發(fā)模式,原因如下:
- 性能考量:雙重渲染會(huì)增加額外的計(jì)算開銷
- 調(diào)試目的:生產(chǎn)環(huán)境不需要這種嚴(yán)格的檢查
- 用戶體驗(yàn):不應(yīng)該影響最終用戶的體驗(yàn)
React團(tuán)隊(duì)通過這種方式在開發(fā)階段模擬了更嚴(yán)格的環(huán)境,幫助開發(fā)者提前發(fā)現(xiàn)潛在問題。
5. 實(shí)際案例分析
讓我們通過幾個(gè)具體例子來說明這種現(xiàn)象:
案例1:空依賴數(shù)組
useEffect(() => {
console.log("Effect ran");
return () => console.log("Cleanup ran");
}, []);
在開發(fā)模式下,你會(huì)看到以下輸出:
Effect ran Cleanup ran Effect ran
案例2:API調(diào)用
useEffect(() => {
fetchData();
}, []);
這種代碼在開發(fā)模式下會(huì)觸發(fā)兩次API調(diào)用,可能導(dǎo)致意外的副作用。正確的做法應(yīng)該是添加取消邏輯:
useEffect(() => {
const controller = new AbortController();
fetchData({ signal: controller.signal });
return () => controller.abort();
}, []);
6. 如何正確處理雙重執(zhí)行
為了避免雙重執(zhí)行帶來的問題,開發(fā)者應(yīng)該:
- 確保effect有正確的清理函數(shù)
- 對(duì)于冪等操作(如日志記錄)可以忽略重復(fù)執(zhí)行
- 對(duì)于非冪等操作(如POST請(qǐng)求)應(yīng)該添加防護(hù)
- 使用refs來跟蹤effect是否已經(jīng)執(zhí)行過
7. 設(shè)計(jì)哲學(xué)探討
React團(tuán)隊(duì)選擇這種設(shè)計(jì)有幾個(gè)深層次的考慮:
- 可恢復(fù)性:確保組件能在任何時(shí)刻被正確地重新掛載
- 一致性:開發(fā)環(huán)境盡可能模擬生產(chǎn)環(huán)境的邊緣情況
- 可預(yù)測(cè)性:通過嚴(yán)格模式暴露潛在問題,而不是隱藏它們
這種設(shè)計(jì)鼓勵(lì)開發(fā)者編寫更健壯的代碼,能夠處理各種邊界情況。
8. 性能優(yōu)化技巧
雖然雙重渲染在開發(fā)模式下是有意的行為,但我們?nèi)钥梢酝ㄟ^一些技巧來優(yōu)化性能:
- 使用
useMemo和useCallback減少不必要的重新計(jì)算 - 將大型effect拆分為多個(gè)小型effect
- 合理設(shè)置依賴數(shù)組,避免過度觸發(fā)
- 考慮使用
useLayoutEffect處理必須同步執(zhí)行的副作用
9. 常見誤解澄清
關(guān)于useEffect的雙重執(zhí)行,有幾個(gè)常見的誤解需要澄清:
- 這不是bug:這是React團(tuán)隊(duì)有意為之的設(shè)計(jì)
- 不影響生產(chǎn)環(huán)境:生產(chǎn)構(gòu)建會(huì)自動(dòng)禁用這種行為
- 不是所有effect都會(huì)執(zhí)行兩次:只有某些特定情況下才會(huì)出現(xiàn)
- 不是React 18特有的:早期版本在某些配置下也有類似行為
10. 與其他框架的對(duì)比
對(duì)比其他主流框架的處理方式:
- Vue:在開發(fā)模式下也有類似的警告機(jī)制,但不會(huì)強(qiáng)制雙重執(zhí)行
- Angular:依賴變更檢測(cè)策略,開發(fā)模式下的行為差異較小
- Svelte:編譯時(shí)優(yōu)化,較少出現(xiàn)運(yùn)行時(shí)意外行為
React的選擇體現(xiàn)了其"顯式優(yōu)于隱式"的設(shè)計(jì)哲學(xué)。
總結(jié)
useEffect在開發(fā)模式下的雙重執(zhí)行行為是React團(tuán)隊(duì)精心設(shè)計(jì)的功能,旨在幫助開發(fā)者提前發(fā)現(xiàn)并解決潛在的問題。理解這一行為背后的設(shè)計(jì)理念和實(shí)現(xiàn)機(jī)制,有助于我們編寫更健壯、更可靠的React組件。作為開發(fā)者,我們應(yīng)該:
- 接受并理解這種設(shè)計(jì)選擇
- 確保effect邏輯具備冪等性或正確的清理機(jī)制
- 利用嚴(yán)格模式提高代碼質(zhì)量
- 區(qū)分開發(fā)和生產(chǎn)環(huán)境的行為差異
通過這種方式,我們可以充分利用React提供的開發(fā)工具,構(gòu)建出更高質(zhì)量的應(yīng)用程序。記住,React的這些"看似奇怪"的行為背后,往往有著深思熟慮的設(shè)計(jì)考量。
到此這篇關(guān)于一文弄懂React的useEffect經(jīng)常執(zhí)行兩次的文章就介紹到這了,更多相關(guān)React useEffect執(zhí)行兩次內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Cloud?Studio構(gòu)建React完成點(diǎn)餐H5頁(yè)面(騰訊云?Cloud?Studio?實(shí)戰(zhàn)訓(xùn)練營(yíng))
最近也是有機(jī)會(huì)參與到了騰訊云舉辦的騰訊云Cloud Studio實(shí)戰(zhàn)訓(xùn)練營(yíng),借此了解了騰訊云Cloud?Studio產(chǎn)品,下面就來使用騰訊云Cloud?Studio做一個(gè)實(shí)戰(zhàn)案例來深入了解該產(chǎn)品的優(yōu)越性吧,感興趣的朋友跟隨小編一起看看吧2023-08-08
詳解react-native WebView 返回處理(非回調(diào)方法可解決)
這篇文章主要介紹了詳解react-native WebView 返回處理(非回調(diào)方法可解決),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02
使用react-color實(shí)現(xiàn)前端取色器的方法
本文通過代碼給大家介紹了使用react-color實(shí)現(xiàn)前端取色器的方法,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
React虛擬渲染實(shí)現(xiàn)50個(gè)或者一百個(gè)圖表渲染
這篇文章主要為大家介紹了React虛擬渲染實(shí)現(xiàn)50個(gè)或者100個(gè)圖表渲染的實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
React實(shí)現(xiàn)菜單欄滾動(dòng)功能
本文將會(huì)基于react實(shí)現(xiàn)滾動(dòng)菜單欄功能,點(diǎn)擊菜單,內(nèi)容區(qū)域會(huì)自動(dòng)滾動(dòng)到對(duì)應(yīng)卡片,內(nèi)容區(qū)域滑動(dòng),指定菜單欄會(huì)被選中,代碼簡(jiǎn)單易懂,感興趣的朋友一起看看吧2024-03-03
React通過hook實(shí)現(xiàn)封裝表格常用功能
這篇文章主要為大家詳細(xì)介紹了React通過hook封裝表格常用功能的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12
React?Hooks之useDeferredValue鉤子用法示例詳解
useDeferredValue鉤子的主要目的是在React的并發(fā)模式中提供更流暢的用戶體驗(yàn),特別是在有高優(yōu)先級(jí)和低優(yōu)先級(jí)更新的情況下,本文主要講解一些常見的使用場(chǎng)景及其示例2023-09-09

