UseEffect深入理解,当React 中当前正在发出请求的组件从页面上卸载了,理想情况下这个请求也应该取消掉,那么如何把请求的取消和页面的卸载关联在一起呢?

本文介绍了如何在React中使用AbortController取消组件卸载时的fetch请求,包括useFetch hook的实现,并讨论了如何避免浅比较依赖导致的问题。关键点在于 useEffect 的依赖管理和AbortController的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

取消请求

React 中当前正在发出请求的组件从页面上卸载了,理想情况下这个请求也应该取消掉,那么如何把请求的取消和页面的卸载关联在一起呢?

这里要考虑利用 useEffect 传入函数的返回值:

useEffect(() => {
  return () => {
    // 页面卸载时执行
  };
}, []);

假设我们的请求是利用 fetch,那么还有一个需要运用的知识点:AbortController,简单看一下它的用法:
https://developer.mozilla.org/zh-CN/docs/web/api/abortcontroller
AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。

const abortController = new AbortController();

fetch(url, {
  // 这里传入 signal 进行关联
  signal: abortController.signal,
});

// 这里调用 abort 即可取消请求
abortController.abort();

那么结合 React 封装一个 useFetch 的 hook:

export function useFetch = (config, deps) => {
  const abortController = new AbortController()
  const [loading, setLoading] = useState(false)
  const [result, setResult] = useState()

  useEffect(() => {
    setLoading(true)
    fetch({
      ...config,
      signal: abortController.signal
    })
      .then((res) => setResult(res))
      .finally(() => setLoading(false))
  }, deps)

  useEffect(() => {
    return () => abortController.abort()
  }, [])

  return { result, loading }
}

那么比如在路由发生切换,Tab 发生切换等场景下,被卸载掉的组件发出的请求也会被中断。

深比较依赖

在使用 useEffect 等需要传入依赖的 hook 时,最理想的状况是所有依赖都在真正发生变化的时候才去改变自身的引用地址,但是有些依赖不太听话,每次渲染都会重新生成一个引用,但是内部的值却没变,这可能会让 useEffect 对于依赖的「浅比较」没法正常工作。
比如说:

const getDep = () => {
  return {
    foo: 'bar',
  };
};

useEffect(() => {
  // 无限循环了
}, [getDep()]);

这是一个人为的例子,由于 getDeps 函数返回的对象每次执行都是一个全新的引用,所以会导致触发渲染->effect->渲染->effect 的无限更新。

有一个比较取巧的解决办法,把依赖转为字符串:

const getDep = () => {
  return {
    foo: 'bar',
  };
};

const dep = JSON.stringify(getDeps());

useEffect(() => {
  // ok!
}, [dep]);

这样对比的就是字符串 “{ foo: ‘bar’ }” 的值,而不是对象的引用,那么只有在值真正发生变化时才会触发更新。

当然最好还是用社区提供的方案:useDeepCompareEffect,它选用深比较策略,对于对象依赖来说,它逐个对比 key 和 value,在性能上会有所牺牲。

如果你的某个依赖触发了多次无意义的接口请求,那么宁愿选用 useDeepCompareEffect ,在对象比较上多花费些时间可比重复请求接口要好得多。

useDeepCompareEffect 大致原理:

import { isEqual } from 'lodash';
export function useDeepCompareEffect(fn, deps) {
  const trigger = useRef(0);
  const prevDeps = useRef(deps);
  if (!isEqual(prevDeps.current, deps)) {
    trigger.current++;
  }
  prevDeps.current = deps;
  return useEffect(fn, [trigger.current]);
}

真正传入 useEffect 用以更新的是 trigger 这个数字值。用useRef 保留上一次传入的依赖,每次都利用 lodash 的 isEqual 对本次依赖和旧依赖进行深比较,如果发生变化,则让 trigger 的值增加。

当然我们也可以用 fast-deep-equal 这个库,根据官方的 benchmark 对比,它比 lodash 的效率高 7 倍左右。

原文章:https://github.com/sl1673495/blogs/issues/62

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值