 闽公网安备 35020302035485号
                
                闽公网安备 35020302035485号
                
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
<div className="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 传递进入了子组件。这个细节需要仔细思考我的动因。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 的存在感,但是偶尔在合适的时候使用也是必要的。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>
  )
}
这样之后,我们的目标基本就完成了。接下来,我们需要观察,当我恶意重复点击按钮,会发生什么事情。

const [disabled, setDisabled] = useState(false)并将其传递给按钮 button 组件的 disabled 属性。
<button 
  disabled={disabled} 
  onClick={__clickToGetMessage}
>新增数据</button>
点击时,我们将其设置为 true,此时一个新的请求会发生function __clickToGetMessage() {
  setDisabled(true);
  setApi(getApi())
}
请求成功之后,我们在子组件的 useEffect 中,将其设置为 false。子组件代码调整如下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)
  }, [])
  //堆代码 duidaima.com
  const __cls = show ? '_03_a_value show' : '_03_a_value'
  return (
    <div className={__cls}>{joke.value}</div>
  )
}
演示效果如下
这种方式也可以比较合理的解决竞态问题。后续我们通过别的案例,再来演示通过取消上一次的接口请求方式是如何实现的。
(全文完)