[英]React useEffect cleanup function unexpectedly called
我正在創建一個自定義鈎子來在表單提交時獲取 api,我在 useEffect 鈎子內進行 api 調用,並且我有一個減速器來處理鈎子的狀態。 其中一個狀態是首先將trigger
設置為 false 以控制 useEffect 是否執行任何操作,關鍵是鈎子返回一個函數,該函數會翻轉trigger
值,該函數僅在您調用此函數時觸發 useEffect。 問題是在 api 調用期間調用了 useEffect 的清理函數,即使該組件顯然仍處於掛載狀態。
清理功能似乎因為我設置的值被解雇trigger
基於其之前的值,當我設置trigger
為固定值的清理功能不叫,但我失去了我的功能
const fetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_TRIGGER':
return {
...state,
trigger: !state.trigger
}
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false
};
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
isError: false,
datas: action.payload,
};
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
}
const useFetchApi = (query, initialData = []) => {
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer, {
isLoading: false,
isError: false,
datas: initialData,
trigger: false
});
const triggerFetch = _ => dispatch({ type: 'FETCH_TRIGGER' });
const cancel = _ => { console.log("canceling");isCancelled = true };
useEffect(_ => {
if (!state.trigger)
return;
triggerFetch();
(async _ => {
dispatch({ type: 'FETCH_INIT' });
try {
const datas = await query();
if (!isCancelled) { //isCancelled is true at this point
dispatch({ type: 'FETCH_SUCCESS', payload: datas })
}
} catch (err) {
if (!isCancelled) {
dispatch({ type: 'FETCH_FAILURE', payload: err })
}
}
})();
return cancel;
}, [state.trigger]);
return { ...state, triggerFetch};
}
用法:
function MyComponent () {
const { datas, isLoading, isError, triggerFetch } = useFetchApi(query);
return (
<form onSubmit={event => {event.preventDefault(); triggerFetch()}}>
...
可以在 useEffect 回調中使用局部變量。 歸功於@gaearon
https://codesandbox.io/s/k0lm13kwxo
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; }
}, [query]);
Tom Finney 從評論中給出的解決方案:
You could add another use effect that didn't do anything except for return that cancel function and have it with an empty array dependency that would mimic componentWillUnmount like useEffect(() => cancel, [])
const useFetchApi = (query, initialData = []) => {
let isCancelled = false;
const [state, dispatch] = useReducer(fetchReducer, {
isLoading: false,
isError: false,
datas: initialData,
trigger: false
});
const triggerFetch = _ => dispatch({ type: 'FETCH_TRIGGER' });
const cancel = _ => { console.log("canceling");isCancelled = true };
useEffect(_ => {
if (!state.trigger)
return;
triggerFetch();
(async _ => {
dispatch({ type: 'FETCH_INIT' });
try {
const datas = await query();
if (!isCancelled) {
dispatch({ type: 'FETCH_SUCCESS', payload: datas })
}
} catch (err) {
if (!isCancelled) {
dispatch({ type: 'FETCH_FAILURE', payload: err })
}
}
})();
}, [state.trigger]);
useEffect(_=> cancel, []) //remove return cancel from useEffect and replace by this
return { ...state, triggerFetch};
}
當前,您正在觸發狀態更改,這將觸發重新渲染,這將觸發一個效果,然后調用您的 API。 您真正想做的就是調用您的 API。
在下面的示例代碼中,我將triggerFetch
更改為實際執行查詢並刪除了trigger
狀態。 我添加了一個沒有依賴項的效果,以允許在卸載時取消。 我還更改了取消方法以使用 ref 而不是局部變量,以便它在重新渲染時保持不變。
import { useReducer, useEffect, useRef } from "react";
const fetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return {
...state,
isLoading: true,
isError: false
};
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
isError: false,
datas: action.payload
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
isError: true
};
default:
throw new Error();
}
};
const useFetchApi = (query, initialData = []) => {
const cancelledRef = useRef(false);
const [state, dispatch] = useReducer(fetchReducer, {
isLoading: false,
isError: false,
datas: initialData,
trigger: false
});
const triggerFetch = async _ => {
dispatch({ type: "FETCH_INIT" });
try {
const datas = await query();
if (!cancelledRef.current) {
dispatch({ type: "FETCH_SUCCESS", payload: datas });
}
} catch (err) {
if (!cancelledRef.current) {
dispatch({ type: "FETCH_FAILURE", payload: err });
}
}
};
useEffect(_ => {
return _ => {
console.log("canceling");
cancelledRef.current = true;
};
}, []);
return { ...state, triggerFetch };
};
export default useFetchApi;
要記住的 2 件事:
如果你問我,清理這個名字是有誤導性的。 回調實際上在 componentDidUpdate 場景中的其余代碼之前運行,因此在這方面它不像准備那樣清理。 標題清理僅在卸載時才值得。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.