当前位置:首页 > 科技  > 软件

React 19 出手解决了异步请求的竞态问题,是好事还是坏事?

来源: 责编: 时间:2024-06-07 17:20:19 128观看
导读是的,又是竞态问题。在客户端开发中,这是一个老生常态的问题。一个有经验的前端工程师必定是对这个问题的情况与解决方案如数家珍。因此竞态问题也经常在面试的过程中被讨论。竞态问题指的是,当我们在交互过程中,由于各种

3Gm28资讯网——每日最新资讯28at.com

是的,又是竞态问题。3Gm28资讯网——每日最新资讯28at.com

在客户端开发中,这是一个老生常态的问题。一个有经验的前端工程师必定是对这个问题的情况与解决方案如数家珍。因此竞态问题也经常在面试的过程中被讨论。3Gm28资讯网——每日最新资讯28at.com

竞态问题指的是,当我们在交互过程中,由于各种原因导致同一个接口短时间之内连续发送请求,后发送的请求有可能先得到请求结果,从而导致数据渲染出现预期之外的错误。3Gm28资讯网——每日最新资讯28at.com

有的地方也称为竞态条件3Gm28资讯网——每日最新资讯28at.com

因为防止重复执行可以有效的解决竞态问题,因此许多时候面试官也会直接在面试中问我们如何实现防重。常用的方式就是取消上一次请求,或者设置状态让按钮不能连续点击,想必各位大佬对这些方案都已经非常熟悉,我这里就不展开细说。当然,这个问题虽然被经常讨论,但是要解决好确实需要一点技术功底。3Gm28资讯网——每日最新资讯28at.com

React 19 结合 Suspense 也在竞态问题上,提出了一个自己的解决方案。我们结合新的案例来探讨一下这个问题,看完之后大家感受一下这种方式是好是坏。3Gm28资讯网——每日最新资讯28at.com

一、案例

我们先来看一下本次案例要实现的交互效果。如下图所示。每次点击会新增一条数据到下方的列表中。3Gm28资讯网——每日最新资讯28at.com

3Gm28资讯网——每日最新资讯28at.com

我们来实现一下这个效果,首先定义一个用于请求接口的 promise。3Gm28资讯网——每日最新资讯28at.com

const getApi = async () => {  const res = await fetch('https://api.chucknorris.io/jokes/random')  return res.json()}

然后和前面的案例一样,我们将每次点击的 api 作为状态存储起来,通过 api 的改变来触发更新的执行。3Gm28资讯网——每日最新资讯28at.com

const [api, setApi] = useState(null)

与此同时,我们还需要一个数组作为状态来管理列表。3Gm28资讯网——每日最新资讯28at.com

const [list, setList] = useState([])

有了这个数组之后,我们需要遍历这个数组渲染成 UI。3Gm28资讯网——每日最新资讯28at.com

<div className="list">  {list.map((item, index) => {    return <div className='item' key={item}>{item}</div>  })}</div>

最后需要 loading 显示的部分,我们使用 Suspense 来完成。3Gm28资讯网——每日最新资讯28at.com

<Suspense fallback={<div>loading...</div>}>  <Item api={api} setList={setList} /></Suspense>

需要注意的是,我们这里把 setList 传递进入了子组件。这个细节需要仔细思考我的动因。3Gm28资讯网——每日最新资讯28at.com

我们要考虑的问题是,当我们在 Suspense 之外,需要知道请求成功的状态和数据时,只有在 Suspense 的子组件内部才可以获取到。Suspense 子组件和外面的 Loading 是一个互斥的显示关系。3Gm28资讯网——每日最新资讯28at.com

因此,我们要在子组件内部去获取请求成功的数据结果。3Gm28资讯网——每日最新资讯28at.com

const Item = ({api, setList}) => {  const [show, setShow] = useState(true)  const joke = api ? use(api) : {value: 'nothing'}  useEffect(() => {    if (!api) return    setList((list) => {      if (!list.includes(joke.value)) {        return list.concat(joke.value)      }      return list    })    setShow(false)  }, [])  const __cls = show ? '_03_a_value show' : '_03_a_value'  return (    <div className={__cls}>{joke.value}</div>  )}

状态 show 是为了让最后一条数据在列表中显示,而不在这里显示。3Gm28资讯网——每日最新资讯28at.com

这里我们使用了 useEffect 来表示子组件渲染完成时需要执行的逻辑。注意 React 19 虽然通过很多方式大幅度弱化了 useEffect 的存在感,但是偶尔在合适的时候使用也是必要的。3Gm28资讯网——每日最新资讯28at.com

我在合并 list 的过程中,添加了一个判断。3Gm28资讯网——每日最新资讯28at.com

setList((list) => {  if (!list.includes(joke.value)) {    return list.concat(joke.value)  }  return list})

这个细节在真实项目开发中尤其重要。因为 React 19 严格模式之下,组件会让 useEffect 执行两次,以模拟生产环境的重复请求问题,因此,我这里做了一个判断方式同样的数据连续推送到数组里,从而导致线上 bug 的发生。3Gm28资讯网——每日最新资讯28at.com

一个程序员是否经验丰富,是否成熟,都是体现在这些生产环境的细节中。3Gm28资讯网——每日最新资讯28at.com

完整代码如下:3Gm28资讯网——每日最新资讯28at.com

const getApi = async () => {  const res = await fetch('https://api.chucknorris.io/jokes/random')  return res.json()}export default function Index() {  const [api, setApi] = useState(null)  const [list, setList] = useState([])  function __clickToGetMessage() {    setApi(getApi())  }  return (    <div>      <div id='tips'>点击按钮新增一条数据,该数据从接口中获取</div>      <button onClick={__clickToGetMessage}>新增数据</button>      <div className="content">        <div className="list">          {list.map((item, index) => {            return <div className='item' key={item}>{item}</div>          })}        </div>                <Suspense fallback={<div>loading...</div>}>          <Item api={api} setList={setList} />        </Suspense>      </div>    </div>  )}const Item = ({api, setList}) => {  const [show, setShow] = useState(true)  const joke = api ? use(api) : {value: 'nothing'}  useEffect(() => {    if (!api) return    setList((list) => {      if (!list.includes(joke.value)) {        return list.concat(joke.value)      }      return list    })    setShow(false)  }, [])  const __cls = show ? '_03_a_value show' : '_03_a_value'  return (    <div className={__cls}>{joke.value}</div>  )}

这样之后,我们的目标基本就完成了。接下来,我们需要观察,当我恶意重复点击按钮,会发生什么事情。3Gm28资讯网——每日最新资讯28at.com

二、连续点击

恶意连续点击之前,我根据我以往的经验预测一下可能会发生什么事情。3Gm28资讯网——每日最新资讯28at.com

首先,多次点击会导致多次请求,因此数组中会新增大量的数据。3Gm28资讯网——每日最新资讯28at.com

其次,由于请求太密集,那么点击的先后顺序,与请求成功的先后顺序不一致,因此列表中的顺序也会与点击顺序不同。「竞态问题」3Gm28资讯网——每日最新资讯28at.com

那么我们来试着操作一下,看看该案例会有什么反应。演示结果如下,新增一条数据时,我连续点击了 10 次。3Gm28资讯网——每日最新资讯28at.com

图片3Gm28资讯网——每日最新资讯28at.com

结果我们发现,点击期间,并没有新的数据渲染到页面上,一直是 loading 的状态。3Gm28资讯网——每日最新资讯28at.com

再来看一下此时的请求情况。3Gm28资讯网——每日最新资讯28at.com

3Gm28资讯网——每日最新资讯28at.com

请求的顺序被严格控制了:上一个请求请求成功之后,下一个请求才开始发生。此时是一个串行的请求过程。3Gm28资讯网——每日最新资讯28at.com

react 19 使用这种思路解决了竞态问题。与此同时,反馈到数据上,虽然前面多次的请求已经成功,但是对于组件状态来说,这个中间过程中一直有请求在发生,此时 React 认为中间的请求产生的数据为无效数据。只会把最后一个请求成功的数据作为最终的返回结果。3Gm28资讯网——每日最新资讯28at.com

三、是好是坏

很显然,仅从 UI 结果上来说,这样的处理方式确实是非常合理的,我们不需要过多的干涉数据的处理,非常的轻松。但问题是,每次请求都成功发生。3Gm28资讯网——每日最新资讯28at.com

当我点击 10 次,就会有 10 次请求,由于使用串行的策略来解决竞态问题,导致最后一次的请求结果需要等待很长实践才会返回。这无疑极大的降低了开发体验。3Gm28资讯网——每日最新资讯28at.com

和取消上一次的请求相比,无论是从体验上,还是从效率上来说,无疑都是更差的一种方案。3Gm28资讯网——每日最新资讯28at.com

因此,我们可以简单基于目前的代码,使用禁用按钮的方式,来防止重复请求。3Gm28资讯网——每日最新资讯28at.com

在父组件中定义一个状态用于控制按钮的禁用状态。3Gm28资讯网——每日最新资讯28at.com

const [disabled, setDisabled] = useState(false)

并将其传递给按钮 button 组件的 disabled 属性。3Gm28资讯网——每日最新资讯28at.com

<button   disabled={disabled}   onClick={__clickToGetMessage}>新增数据</button>

点击时,我们将其设置为 true,此时一个新的请求会发生。3Gm28资讯网——每日最新资讯28at.com

function __clickToGetMessage() {  setDisabled(true);  setApi(getApi())}

请求成功之后,我们在子组件的 useEffect 中,将其设置为 false。子组件代码调整如下:3Gm28资讯网——每日最新资讯28at.com

const Item = ({api, setList, setDisabled}) => {  const [show, setShow] = useState(true)  const joke = api ? use(api) : {value: 'nothing'}  useEffect(() => {    if (!api) return+   setDisabled(false)    setList((list) => {      if (!list.includes(joke.value)) {        return list.concat(joke.value)      }      return list    })    setShow(false)  }, [])  const __cls = show ? '_03_a_value show' : '_03_a_value'  return (    <div className={__cls}>{joke.value}</div>  )}

演示效果如下:3Gm28资讯网——每日最新资讯28at.com

3Gm28资讯网——每日最新资讯28at.com

这种方式也可以比较合理的解决竞态问题。3Gm28资讯网——每日最新资讯28at.com

后续我们通过别的案例,再来演示通过取消上一次的接口请求方式是如何实现的。3Gm28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-92747-0.htmlReact 19 出手解决了异步请求的竞态问题,是好事还是坏事?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 大厂真实案例,CPU 升高问题如何排查?五分钟掌握

下一篇: 从 Dapper 到 OpenTelemetry:分布式追踪的演进之旅

标签:
  • 热门焦点
Top