使用useInterval钩子取消异步请求

时间:2019-10-06 15:27:27

标签: javascript reactjs react-hooks

使用useEffect的清理操作超级轻松地取消请求

useEffect(() => {
  let ignore = false;
  fetchData(id).then(data => {
    if (!ignore) {
      setData(data);
    }
  });
  return () => (ignore = true);
}, [id]);

我想做类似的事情,但我需要使用useInterval

来轮询数据

我想用fetchData(id)轮询数据,如果请求已触发但id在响应解析之前发生了变化,则忽略返回的响应。

2 个答案:

答案 0 :(得分:0)

这是我想出的useInterval

function (callback, options, cleanup) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (options.delay !== null) {
      let id = setInterval(tick, options.delay);
      return () => {        
        clearInterval(id);
        cleanup && cleanup();
      }
    }
    return () => cleanup && cleanup();
  }, [options]);
}

我这样使用

const [fetchOptions, setFetchOptions] = useState({delay: 5000, id: 'someid'});

let ignore = false;
useInterval(
  () => {
    fetchData(fetchOptions.id).then(data => {
      if (!ignore) {
        setData(data);
      }
    });
  },
  fetchOptions,
  () => (ignore = true),
);

我不确定是否有更好的方法来编写此代码。我关心的是ignore变量的作用范围是功能/组件的上下文。在问题的代码示例中,ignore变量位于useEffect内,感觉更干净。

这种方法的缺点是fetchOptions必须是一个useState变量,否则,如果它只是函数中的一个常量,它将在每个渲染器上重置useInterval钩子。

答案 1 :(得分:0)

针对您的特殊情况

假设您收到id作为道具或类似物品,这行得通吗?

const Foo = ({ id }) => {
  const [data, setData] = useState()

  const passedId = useRef()
  passedId.current = id

  useInterval(() => {
    fetchData(id)
      .then(response => id === passedId.current && setData(response))
  }, 5000)

  // Render some JSX with the data

我在本地测试了一些非常类似的东西,本质上是这样的:

  1. Foo收到id = 6
  2. 6存储在passedId
  3. useInterval滴答,我们要求id = 6
  4. 该组件收到id = 7
  5. 7存储在passedId
  6. id = 6的请求已完成,但是passedId.current === 7使得setData未被调用
  7. id = 7的请求已完成,id === passedId.currentsetData被调用

将类似的内容放入useInterval

注意-未经测试

取消效果如此简单的原因可能是因为该函数返回了自己的清理功能,因此ignore不必在外部范围内。但是setInterval不允许返回值。我们可能可以使用setTimeout来解决此问题:

function useInterval(callback, delay) {
  useEffect(() => {
    let cleanup
    function tick() {
      cleanup = callback()
      return setTimeout(tick, delay)
    }
    if (delay !== null ) {
      const id = tick()
      return () => {
        clearTimeout(id)
        cleanup && cleanup()
      }
    }
    return () => cleanup && cleanup()
  }, [callback, delay])

与此相关的一个问题是,现在我们在依赖项数组中有callback,因此应使用useInterval创建给useCallback的回调:

const Foo = ({ id }) => {
  const [data, setData] = useState()

  const pollData = useCallback(() => {
    let shouldUpdate = true

    fetchData(id).then(resp => shouldUpdate && setData(resp))

    return () => shouldUpdate = false
  }, [id])

  useInterval(pollData, 5000)

  // Render some JSX with the data
  1. 调用useInterval时,我们设置了一个执行useEffect的{​​{1}}
  2. 执行callback时,返回值(对其进行的清理)存储在callback中,其作用范围是效果,并且将cleanup设置为在{{ 1}}毫秒
  3. 如果setTimeout发生了变化(即,我们得到了一个新的delay),那么我们将清除超时并为先前的回调运行清除操作
  4. callback毫秒后,id被再次执行