在React使用中避免競爭條件和內(nèi)存泄漏

關(guān)注公眾號?前端人,回復“加群”
添加無廣告優(yōu)質(zhì)學習群
文章來源:Dev
文章地址:dev.to/saranshk/avoiding-race-conditions-and-memory-leaks-in-react-useeffect-3mme
讓我們學習如何處理“Can’t perform a React state update on an unmounted component”警告
讓我們看一下從API請求獲取數(shù)據(jù)的實現(xiàn),并查看此組件中是否有發(fā)生競爭情況的可能性:
import?React,?{?useEffect}?from?'react';
export?default?function?UseEffectWithRaceCondition()?{
??const?[todo,?setTodo]?=?useState(null);
??useEffect(()?=>?{
????const?fetchData?=?async?()?=>?{
??????const?response?=?await?fetch('https://jsonplaceholder.typicode.com/todos/1');
??????const?newData?=?await?response.json();
??????setTodo(newData);
????};
????fetchData();
??},?[]);
??if?(data)?{
????return?<div>{data.title}div>;
??}?else?{
????return?null;
??}
}
我們已經(jīng)指定了一個空數(shù)組作為對useEffect React hook的依賴。因此,我們確保獲取請求僅發(fā)生一次。但是此組件仍然容易出現(xiàn)爭用情況和內(nèi)存泄漏。如何?
如果API服務(wù)器花了一些時間來響應并且在接收到響應之前已卸載組件,則會發(fā)生內(nèi)存泄漏。盡管已卸載該組件,但仍會在完成時收到對請求的響應。然后將解析響應并調(diào)用setTodo。React會發(fā)出警告:
無法在已卸載的組件上執(zhí)行React狀態(tài)更新。這是空操作,但它表明應用程序中發(fā)生內(nèi)存泄漏。要修復,請取消使用useEffect清理功能中的所有訂閱和異步任務(wù)。
消息非常簡單。
相同問題的另一個潛在情況可能是待辦事項列表ID作為道具被傳遞。
import?React,?{?useEffect}?from?'react';
export?default?function?UseEffectWithRaceCondition(?{id}?)?{
??const?[todo,?setTodo]?=?useState(null);
??useEffect(()?=>?{
????const?fetchData?=?async?()?=>?{
??????const?response?=?await?fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
??????const?newData?=?await?response.json();
??????setTodo(newData);
????};
????fetchData();
??},?[id]);
??if?(data)?{
????return?<div>{data.title}div>;
??}?else?{
????return?null;
??}
}
如果鉤子在請求完成之前收到了另一個ID,而第二個請求在第一個請求之前完成了,那么我們將在組件中看到第一個請求的數(shù)據(jù)。
競態(tài)條件問題的潛在解決方案 有兩種方法可以解決此問題。兩種方法都利用useEffect提供的清除功能。
我們可以使用布爾標志來確保組件已安裝。這樣,我們僅在標志為true時更新狀態(tài)。而且,如果我們在一個組件內(nèi)發(fā)出多個請求,我們將始終顯示最后一個的數(shù)據(jù)。
每當卸載組件時,我們都可以使用AbortController取消先前的請求。IE中不支持AbortController。因此,如果要使用這種方法,我們需要考慮一下。
useEffect清理與布爾標志
useEffect(()?=>?{
??let?isComponentMounted?=?true;
????const?fetchData?=?async?()?=>?{
??????const?response?=?await?fetch('https://jsonplaceholder.typicode.com/todos/1');
??????const?newData?=?await?response.json();
??????if(isComponentMounted)?{
????????setTodo(newData);
??????}
????};
????fetchData();
????return?()?=>?{
??????isComponentMounted?=?false;
????}
??},?[]);
此修補程序依賴useEffect的清除功能的工作方式。如果一個組件渲染多次,則在執(zhí)行下一個效果之前,將清除上一個效果。
由于這種工作方式,由于ID發(fā)生了更改,因此對于我們的其他多個請求示例也可以正常工作。從某種意義上說,我們?nèi)匀惶幱诟偁帬顟B(tài),即在后臺會有多個請求在運行。但是,只有上一個請求的結(jié)果才會顯示在UI上。
使用AbortController進行useEffect清理
盡管以前的方法可行,但這并不是處理比賽條件的最佳方法。這些請求在后臺進行。在后臺使用過時的請求是不必要地消耗了用戶的帶寬。瀏覽器也限制了并發(fā)請求的最大數(shù)量(最大6-8)。
從上一篇有關(guān)如何取消HTTP提取請求的文章中,我們了解到已添加到DOM標準的AbortController API。我們可以利用它完全中止我們的請求。
useEffect(()?=>?{
??let?abortController?=?new?AbortController();
????const?fetchData?=?async?()?=>?{
??????try?{
????????const?response?=?await?fetch('https://jsonplaceholder.typicode.com/todos/1',?{
????????????signal:?abortController.signal,
??????????});
??????const?newData?=?await?response.json();
????????setTodo(newData);
??????}
??????catch(error)?{
?????????if?(error.name?===?'AbortError')?{
??????????//?Handling?error?thrown?by?aborting?request
????????}
??????}
????};
????fetchData();
????return?()?=>?{
??????abortController.abort();
????}
??},?[]);
由于中止請求會引發(fā)錯誤,因此我們需要顯式處理它。
而且該解決方案的工作方式與上一個類似。在重新渲染的情況下,執(zhí)行下一個效果之前將執(zhí)行清除功能。不同之處在于,由于我們使用的是AbortController,瀏覽器也會取消請求。
這是我們在使用React的useEffect鉤子發(fā)出API請求時避免競爭條件的兩種方法。如果要使用允許取消請求的某些第三方庫作為一項功能,則可以使用Axios或React查詢來提供許多其他功能。
回復 資料包領(lǐng)取我整理的進階資料包回復 加群,加入前端進階群console.log("文章點贊===文章點在看===你我都快樂"Bug離我更遠了,快樂離我更近了
