React useEffect清理功能意外调用

时间:2019-03-27 11:48:47

标签: javascript reactjs react-hooks

我正在创建一个自定义钩子,以在表单提交时获取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()}}>
...

4 个答案:

答案 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)

要记住的两件事:

  1. 清理回调运行每次useEffect运行排除组件的安装,并包含组件的卸载。如果要模拟componentWillUnMount,请使用useEffect和空的依赖项数组。
  2. 清除回调在useEffect中的其余代码之前之前运行。对于具有依赖项的useEffect来说,记住这一点很重要,因为当任何依赖项发生变化并且清理回调和效果中的其余代码都运行时,它将被调用。这是componentDidUpdate行为。

如果您问我,清理这个名字是令人误解的。回调实际上在componentDidUpdate场景中的其余代码之前运行,因此在这方面,清理工作不如准备工作那么多。标题清除仅应在卸载后进行。