前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个巨大争议,关于 useEffect 与竞态问题

一个巨大争议,关于 useEffect 与竞态问题

作者头像
用户6901603
发布2024-01-05 11:35:43
2160
发布2024-01-05 11:35:43
举报
文章被收录于专栏:不知非攻不知非攻

React 知命境第 37 篇,原创第 144 篇

最近一个月因为一个技术方案问题,好几个人断断续续跟我产生了不小的争议。

事情的起因是这样的,我之前在介绍 useEffect 用法的时候,将一个状态作为 useEffect 的依赖项。

代码语言:javascript
复制
useEffect(() => {  
  loading && getList()  
}, [loading])

事实上,我很明确这个用法存在争议,React 官方文档也在新文档里用了大量的篇幅来解释为什么不建议这样使用。当然,我并不同意官方文档的观点,因为我的这种用法在开发效率上有非常大的提升,因此在付费群里,我直播也反复解释了这样做带来的巨大收益是什么。包括在公众号里我也花了几篇文章来解释这样做的目的是什么。

如果 React 官方团队继续搞一些骚操作,例如在 React 18 中,强制让 Effect 传空数组时连续执行两次,让他的实际表现与文档说明中不一致,搞不好我会放弃跟进 React 新版本。这种强制行为,让我以后都不好意思吹 React 的自由性了。

但是呢,我没想到的是,在评论区,我第一次开始听说 useEffect 存在竞态问题,并以此作为理由说不建议使用 useEffect 来请求接口。

然后我仔细考虑了一下,又回忆了自己这么多年用 react 也没发现什么竞态问题呀,所以 useEffect 应该是与竞态问题无关的。于是呢,我就这样回复了他。

收益非常高,也不会有竞态问题,这是一种数据驱动的高效运用。只要对你来说代码逻辑是可控的,你也不用移出 effect

可我没想到的是,这个 useEffect 竞态问题,居然得到了很多人的认同。

然后就有一个群里的哥们跟我说这个问题,表示 useEffect 会存在竞问题,不应该使用它来请求接口,而应该按照官方文档的建议,直接在事件回调函数中直接去请求。

代码语言:javascript
复制
// bad
useEffect(() => {  
  loading && getList()  
}, [loading])


onClick = () => {
  setLoading(true)
}
代码语言:javascript
复制
// good
onClick = () => {
  setLoading(true)
  getList()
}

所以,我又想了一下,还是觉得 useEffect 跟竞态应该不会有什么关系才对,于是我依然坚持我的看法。他也态度比较坚决,说 useEffect 肯定存在竞态问题,于是我就问他,如果存在竞态问题,具体的表现是什么呢?结果他又不说...

这是我们第一次有一点争吵的苗头。

但是谁知道,可能是因为我这个观点,导致他对我的专业性产生了质疑,然后我又一副非常有底气的样子,让他非常不爽,于是接二连三的我们因为这个问题产生观念冲突。

吵到最后,他直接喊我去看某某的文章...

我心里意识到了,这小子瞧不起我。

本来这个事情已经过去了,吵就吵过了,我也没放在心上,可是谁知道,就在这过去的一个月里,断断续续有好几个人跑来跟我说 useEffect 的竞态问题。

我愈发感觉到事情不对劲,但是我还是没想通所谓的竞态问题与 useEffect 的关系在哪。然后昨天又有一个人来跟我说这个问题,还帅气的甩了一篇文章给我,让我好好学好好看

这篇文章果然讲了 useEffect 的竞态问题,如下图。

他的意思就是说,当同时有多个请求时,由于返回的时间可能不一样,不会按照请求的顺序返回结果,造成最后设置的数据不知道是哪一次的。然后我看了他的表达,有点似懂非懂。

然后我又悄悄去搜了一下,发现真的有许多文章在讲 useEffect 的竞态问题,当然许多大佬的文章我也看了。没想到这个问题在一部分人的认知里形成了共识

那这样的话,问题就严重了,我肯定必须得搞懂这是个什么情况了啊。

然后就找到一篇文章,这个就说得比较清楚了。

原来他们一直说的竞态问题,是担心快速交互:例如快速点击,同一个接口多次请求,从而导致 state 可能出现混乱的问题。然后鬼使神差的,有的读者就把竞态问题定位到 useEffect 身上。

我一看这个说法就有问题啊,因为稍微仔细思考一下,就知道如果我们不用 useEffect,就把请求写在 onClick 回调里,这个竞态问题,是不是就消失了?其实不会消失,竞态问题仍然存在。所以这种说法完全就是在强行给 useEffect 上价值,站不住脚。

也无法用这个理由来说明这是 useEffect 的弊端,从而得出应该避免使用 useEffect 的结论。

代码语言:javascript
复制
function onSure() {  
  setLoading(true)  
  api().then(res => {
    setData(res.result)
  })
}

当然异步请求导致的竞态问题确实是存在的,在我的 10 多年的开发经验里,这是一个老生常谈的问题了。他不仅不是 useEffect 的问题,甚至都不是 React 的问题,他就是一个客户端开发的共性问题。我们应该用防重的思路去解决它。

而不应该把这个问题跟 useEffect 强关联在一起,让 useEffect 来背锅!

所以呢,我觉得这个问题也太简单了一点,就担心他们说的 useEffect 竞态问题,不是这个意思。然后我又去找了大量的文章看看有没有不一样的说法。结果国内的文章口径都比较统一,我没找到其他的说辞。甚至确实是有的文章会因此明确的得出不应该使用 useEffect 的结论。

然后我又查阅了群友推给我的英文文章。也去看了许多 Youtube 上的许多英文视频。没有看到新的说辞。看来大家说的 useEffect 竞态问题,确实就是这个了。

我在查阅了大量文章之后发现,国内的主要写 React 的文章中,有的文章里确实明确表示了因为 useEffect 有竞态问题,所以应该避免使用 useEffect,有一部分文章有一些诱导性,把竞态问题与 useEffect 强关联在一起,给人一种不用 useEffect 竞态问题就不存在了的错觉。

例如

也有文章准确的表明了竞问题是由于异步请求导致的。

而主要写 Vue 的文章,则很少看到这样的观念分歧。

在国外写 React 的文章我目前还没有找到明确的表示因为 Race Conditions 而放弃使用 useEffect 的观点。

比如有一篇文章的标题是这样的:

Fixing Race Conditions in React with useEffect。我自己翻译了一下,应该是

使用 useEffect 在 React 中修复竞态条件

应该没有翻译错吧?

和「几行代码解决 useEffect 中的竞态条件」不是一个意思吧?

1、分析

解决这个问题的核心思路,一定是思考如何避免在交互中防止请求的连续发生,而不是弃用 useEffect 就能解决问题。

例如,大多数接口请求连续发生的情况,是在连续点击时会产生。

基础扎实,比较自信的人,都会在交互的地方去避免鼠标连续点击,如:给鼠标设置一个 disabled 的状态,一旦点击发生,就禁用掉鼠标。

代码语言:javascript
复制
const [loading, setLoading] = useState(false)
...
<button 
  onClick={() => {setLoading(true)}} 
  disabled={loading}
>
  点击
</button>

不够自信的人,就会总害怕有什么意外情况会导致上面防止连续点击的手段不生效,比如连续点击点得太快了导致我还来不及设置成 disabled 就触发了两次点击事件,岂不是竹篮打水一场空了吗?

所以他们可能会想到使用防抖或者取消上一次请求的方式来解决这个问题。

个别场景使用防抖/取消上一次请求比较合理,比如在搜索框中连续输入时

比如在一个案例中,它的交互是 tab 左右切换,他的例子中,多个 tab 页,只维护了一份数据,因此在多个页面切换时重新请求并修改数据,修改的是同一个数据。这样,当切换速度过快时,他下面的写法,就有可能会导致数据混乱。

代码语言:javascript
复制
const [data, setData] = useState(null);

useEffect(() => {
  const fetchData = async () => {
    const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
    const newData = await response.json();
    setData(newData);
  };

  fetchData();
}, [id]);

解决办法就是取消上一次请求,利用 useEffect 的机制可以轻松做到这一点。

代码语言:javascript
复制
useEffect(() => {
  const abortController = new AbortController();

  const fetchData = async () => {
    setTimeout(async () => {
      try {
        const response = await fetch(`https://swapi.dev/api/people/${id}/`, {
          signal: abortController.signal,
        });
        const newData = await response.json();

        setFetchedId(id);
        setData(newData);
      } catch (error) {
        if (error.name === 'AbortError') {
          // Aborting a fetch throws an error
          // So we can't update state afterwards
        }
        // Handle other request errors here
      }
    }, Math.round(Math.random() * 12000));
  };


  fetchData();
  return () => {
    abortController.abort();
  };
}, [id]);

也可以设置一个状态,结合 setTimeout 来延后请求的发生。

代码语言:javascript
复制
useEffect(() => {
  let active = true;

  const fetchData = async () => {
    setTimeout(async () => {
      const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
      const newData = await response.json();
      if (active) {
        setFetchedId(props.id);
        setData(newData);
      }
    }, Math.round(Math.random() * 12000));
  };


  fetchData();
  return () => {
    active = false;
  };
}, [id]);

在 React 中,也可以结合 Suspense 来解决竞态问题

后续我会写文章详细分析 Suspense 的具体用法

当然,这个案例更理想的解决方案是,把每个 Tab 页当成单独的实例,各自维护各自的数据,即使你快速切换请求快速发生了,也不会存在相互干扰的问题。竞态条件自然就消失了。

除此之外,这样做的好处很多,例如我们可以轻松做到数据缓存,这是我最愿意采用的方案。

2、结论

竞态条件在前端开发中确实存在。但是大部分竞态问题出现的原因与程序员本身方案设计不合理有关系。合理的方案设计能有效避免竞态问题的出现。

我的观点与与大多数人的观点不同,我认为好的思路是:避免竞态问题的出现。而不是等到竞态问题出现之后,我们再去想用什么办法去解决他。关于这个观点,我曾经也跟某大厂面试官产生过争议,在点击防重的一个面试题里,他坚持认为应该去取消请求做到兜底,我则坚持认为不需要这个兜底,意义并不大,disabled 按钮即可,就算是要做兜底,也应该是在服务端去考虑兜底的问题,前端也做不到兜底

我们不应该因为你看到的异步请求竞态问题是在 useEffect 中出现,就把竞态问题与 useEffect 强绑定在一起去思考,甚至以此来说明这是 useEffect 独有的弊端。竞态问题的出现,与 useEffect 无关

在写文章时,我会结合我多年的开发经验,尽量去保证技术观点的正确性,但我当然做不到能保证我的观点是 100% 正确的,也会偶尔出现错误。所以也欢迎大家跟我探讨技术争议和问题,这也是相互进步的一种比较好的方式。

但是讲道理,有的人比较喜欢用冷嘲热讽、言语攻击、鄙视等态度来沟通争议...

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 这波能反杀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、分析
  • 2、结论
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档