React useEffect 清理函数意外调用

Gat*_*oyu 5 javascript reactjs react-hooks

我正在创建一个自定义钩子以在表单提交时获取 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};
}
Run Code Online (Sandbox Code Playgroud)

用法:

function MyComponent () {
    const { datas, isLoading, isError, triggerFetch } = useFetchApi(query);
    return (
        <form onSubmit={event => {event.preventDefault(); triggerFetch()}}>
...
Run Code Online (Sandbox Code Playgroud)

Gat*_*oyu 1

汤姆·芬尼(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};
}
Run Code Online (Sandbox Code Playgroud)