我正在创建一个自定义钩子,以在表单提交时获取api,我在useEffect钩子中进行api调用,并且我有一个reducer来处理钩子的状态。
状态之一是trigger
首先设置为false,它控制useEffect是否执行任何操作,关键是挂钩返回一个翻转trigger
值的函数,该函数仅在调用此函数时才触发useEffect。
问题是即使在组件仍然明显挂载的情况下,在api调用期间也会调用useEffect的cleanup函数。
清除功能似乎被触发,因为我根据先前的值设置了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()}}>
...
答案 0 :(得分:2)
可以在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]);
答案 1 :(得分:0)
汤姆·芬尼(Tom Finney)从评论中得出的解决方案:
italic
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, [])
答案 2 :(得分:0)
当前,您正在触发状态更改,这将触发重新渲染,从而触发效果,然后将调用您的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;
答案 3 :(得分:0)
要记住的两件事:
如果您问我,清理这个名字是令人误解的。回调实际上在componentDidUpdate场景中的其余代码之前运行,因此在这方面,清理工作不如准备工作那么多。标题清除仅应在卸载后进行。