"list">
{list.map(
(item, index) => {
return <div className='item' key={item}>{item}div> })}
<
/div>
最后需要 loading 显示的部分,我们使用 Suspense 来完成。
<Suspense fallback={<div>loading...div>}>
<Item api={api} setList={setList} />
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 (
<div className={__cls}>{joke.value}div>
)
}
状态 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 (
<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>
)
}
这样之后,我们的目标基本就完成了。接下来,我们需要观察,当我恶意重复点击按钮,会发生什么事情。
01
连续点击
恶意连续点击之前,我根据我以往的经验预测一下可能会发生什么事情。
首先,多次点击会导致多次请求,因此数组中会新增大量的数据。
其次,由于请求太密集,那么点击的先后顺序,与请求成功的先后顺序不一致,因此列表中的顺序也会与点击顺序不同。「竞态问题」
那么我们来试着操作一下,看看该案例会有什么反应。演示结果如下,新增一条数据时,我连续点击了 10 次。
![](http://mmbiz.qpic.cn/sz_mmbiz_gif/Kn1wMOibzLcHkeys7LZdzYcwaLqGcnkonryRkibrHUX5EogGp2KoALzCPD5PbkQothtlZhxAeMh12Gb3lVanGEdQ/640?wx_fmt=gif&from=appmsg)
结果我们发现,点击期间,并没有新的数据渲染到页面上,一直是 loading 的状态。
再来看一下此时的请求情况。