短信预约-IT技能 免费直播动态提醒
React 19 出手解决了异步请求的竞态问题,是好事还是坏事?
是的,又是竞态问题。
在客户端开发中,这是一个老生常态的问题。一个有经验的前端工程师必定是对这个问题的情况与解决方案如数家珍。因此竞态问题也经常在面试的过程中被讨论。
竞态问题指的是,当我们在交互过程中,由于各种原因导致同一个接口短时间之内连续发送请求,后发送的请求有可能先得到请求结果,从而导致数据渲染出现预期之外的错误。
有的地方也称为竞态条件
因为防止重复执行可以有效的解决竞态问题,因此许多时候面试官也会直接在面试中问我们如何实现防重。常用的方式就是取消上一次请求,或者设置状态让按钮不能连续点击,想必各位大佬对这些方案都已经非常熟悉,我这里就不展开细说。当然,这个问题虽然被经常讨论,但是要解决好确实需要一点技术功底。
React 19 结合 Suspense 也在竞态问题上,提出了一个自己的解决方案。我们结合新的案例来探讨一下这个问题,看完之后大家感受一下这种方式是好是坏。
一、案例
我们先来看一下本次案例要实现的交互效果。如下图所示。每次点击会新增一条数据到下方的列表中。
我们来实现一下这个效果,首先定义一个用于请求接口的 promise。
const getApi = async () => {
const res = await fetch('https://api.chucknorris.io/jokes/random')
return res.json()
}
然后和前面的案例一样,我们将每次点击的 api 作为状态存储起来,通过 api 的改变来触发更新的执行。
const [api, setApi] = useState(null)
与此同时,我们还需要一个数组作为状态来管理列表。
const [list, setList] = useState([])
有了这个数组之后,我们需要遍历这个数组渲染成 UI。
{list.map((item, index) => {
return
{item}
})}
最后需要 loading 显示的部分,我们使用 Suspense 来完成。
}>
需要注意的是,我们这里把 setList 传递进入了子组件。这个细节需要仔细思考我的动因。
我们要考虑的问题是,当我们在 Suspense 之外,需要知道请求成功的状态和数据时,只有在 Suspense 的子组件内部才可以获取到。Suspense 子组件和外面的 Loading 是一个互斥的显示关系。
因此,我们要在子组件内部去获取请求成功的数据结果。
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 (
{joke.value}
)
}
状态 show 是为了让最后一条数据在列表中显示,而不在这里显示。
这里我们使用了 useEffect 来表示子组件渲染完成时需要执行的逻辑。注意 React 19 虽然通过很多方式大幅度弱化了 useEffect 的存在感,但是偶尔在合适的时候使用也是必要的。
我在合并 list 的过程中,添加了一个判断。
setList((list) => {
if (!list.includes(joke.value)) {
return list.concat(joke.value)
}
return list
})
这个细节在真实项目开发中尤其重要。因为 React 19 严格模式之下,组件会让 useEffect 执行两次,以模拟生产环境的重复请求问题,因此,我这里做了一个判断方式同样的数据连续推送到数组里,从而导致线上 bug 的发生。
一个程序员是否经验丰富,是否成熟,都是体现在这些生产环境的细节中。
完整代码如下:
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 (
点击按钮新增一条数据,该数据从接口中获取
{list.map((item, index) => {
return
{item}
})}
loading... }>
)
}
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 (
{joke.value}
)
}
react 19 使用这种思路解决了竞态问题。与此同时,反馈到数据上,虽然前面多次的请求已经成功,但是对于组件状态来说,这个中间过程中一直有请求在发生,此时 React 认为中间的请求产生的数据为无效数据。只会把最后一个请求成功的数据作为最终的返回结果。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341