Ray-D-Song's Blog

如何在 React 中使用 async-await

2024-10-21 3min

我一直是个 Vue 用户,最近才开始使用 React,希望下面的内容可以帮到和我一样对函数式组件感到困惑的人。

Suspanse

首先,在 React 中以下写法是无效的:

async function Child() {
  const data = await fetchData()
  
  return <div>{data}</div>
}

function Parent() {
  return (
    <Suspanse fallback={<div>loading</div>}>
      <Child />
    </Suspanse>
  )
}

这是第一个误区, 在 Vue 中,使用<Suspanse>包裹子组件,就可以在子组件的顶层使用 await。
但是 React 每个组件都是同步组件,Suspanse 只支持以下几种情况:

其中use API可以实现和 Vue 顶层 await 类似的效果,但目前还是实验阶段。

正确方式

useEffect

function AsyncComponent() {
  const [data, setData] = useState(null)

  useEffect(() => {
    async function fetchData() {
      const result = await someAsyncOperation()
      setData(result)
    }
    fetchData()
  }, [])

  if (!data) return <div>loading</div>
  return <div>{data}</div>
}

如果useEffect钩子的第二个参数为空数组,就会在组件首次渲染时,执行 Effect 函数。
在上面的例子中,就是执行 fetchData,请求成功后使用 setData 更新数据,刷新视图。

注意:useEffect 的第二个参数不能省略,省略=每次组件更新都会触发,这会导致无限刷新

自定义 hooks

每一次请求都这么写,会让组件的表达非常啰嗦。
常用的做法是将请求封装成一个自定义 hooks。

function useAsyncData() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchData() {
      try {
        const result = await someAsyncOperation()
        setData(result);
      } catch (error) {
        console.error(error)
      } finally {
        setLoading(false)
      }
    }
    fetchData()
  }, [])

  return { data, loading }
}

使用:

function AsyncComponent() {
  const { data, loading } = useAsyncData()

  if (loading) return <div>loading...</div>
  if (!data) return <div>no data</div>
  return <div>{data}</div>
}

也有一些库可以帮我们完成这个过程,比如react-query、swr、ahooks。

使用 swr:

function AsyncComponent() {
  const { data, isLoading } = useSWR('key', fetcher)

  if (isLoading) return <div>loading...</div>
  if (!data) return <div>no data</div>
  return <div>{data}</div>
}

总结

React async 的核心在于理解「React 组件都是同步组件」。
组件内部可以维护一个异步状态,等到这个异步状态 fulfilled 的时候再用 useState 去更新视图。