use() 是 React19 提升异步开发体验最重要的 hook。也是让 useEffect 重要性大幅度削弱的主要原因。

我们可以利用 use 读取 Promise 中的值。

1
const value = use(promise)
2
// 读取到的 value 值是 promise 中 resolve 出来的值

也可以使用 use 读取 context 中的资源,后续详细介绍该能力

1、正确理解 promise

这里我们需要特别注意的是,Promise 是指的一个已经创建好的 Promise 对象,并且,在该 promise 对象中已经有了确定的 resolve 的结果,use 读取的是 resolve 的值。

注意观察一下下面两种写法

第一种是已经有了结果状态的 Promise 对象

1
const _api2 = new Promise((resolve) => {
2
resolve({ value: '_api2' })
3
})
4
5
// good
6
const result = use(_api2)

第二种是函数运行创建 Promise 对象,此时我们需要注意,虽然 _api3 执行之后会立即返回一个带有 resolve 结果状态的 Promise,但是 use 并不能第一时间读取到其值。

1
const _api3 = () => {
2
return new Promise(resolve => {
3
resolve({ value: '_api3' })
4
})
5
}
6
7
// bad: get an error
8
const result = use(_api3())

如果我们直接使用第二种,那么运行之后,React19 会给你如下一个报错。

async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

一个完整的案例代码以及演示效果如下

预览
index.tsx
message.tsx
1
import { use } from 'react'
2
import { createRandomMessage } from 'utils/index'
3
import Message from './message'
4
5
const __api = new Promise<{ value: string }>((resolve) => {
6
resolve({ value: createRandomMessage() })
7
})
8
9
export default function Demo01() {
10
const result = use(__api)
11
return (
12
<Message message={result.value} />
13
)
14
}

2、在条件判断中使用

这是一个反模式,并不建议在实践中真的这样使用

和其他 hook 一样,use() 必须在函数组件中使用。但是很不一样的是,use 可以在循环和条件判断语句中使用。

1
if (!loading) {
2
result = use(_api2)
3
}

完整的代码与最终的演示效果如下,你可以在演示案例中多次点击切换按钮查看交互效果。

预览
index.tsx
message.tsx
1
import { use, useState } from 'react'
2
import { createRandomMessage } from 'utils/index'
3
import Skeleton from 'components/ui/skeleton'
4
import Message from './message'
5
6
const __api = new Promise<{ value: string }>((resolve) => {
7
resolve({ value: createRandomMessage() })
8
})
9
10
export default function Demo02() {
11
const [loading, setLoading] = useState(false)
12
let result = { value: '' }
13
if (!loading) {
14
result = use(__api)
15
}
16
17
return (
18
<>
19
{loading ? <Skeleton /> : <Message message={result.value} />}
20
<div className='mt-4 text-right'>
21
<button className='button' onClick={() => setLoading(!loading)}>切换</button>
22
</div>
23
</>
24
)
25
}
26

3、在异步请求中使用

通常,我们在处理异步请求时,也会结合 promise 来使用。但是我们并不能直接使用 use 来读取异步请求中的 promise,因为我们已经非常明确,use 只能读取有确定 resolve 结果的 promise 中的值。但是有可能第一时间异步请求包装的 promise 状态为 pending。因此在这种情况下,我们必须结合 Suspense 来使用。

当然,为了加强对 use 的理解,我们也准备了一个不顾任何风险提示,强行等 promise 请求完成之后使用 use 去读取它的值的案例。代码与演示效果如下。

预览
index.tsx
message.tsx
api.ts
1
import React, { useState, use, useRef } from 'react';
2
import Skeleton from 'components/ui/skeleton'
3
import Message from './message'
4
import { getMessage } from './api'
5
6
export default function Demo03() {
7
let [loading, setLoading] = useState(true)
8
9
const promise = useRef(getMessage().then(res => {
10
setLoading(false)
11
return res
12
}))
13
14
let result = { value: '' }
15
16
if (!loading) {
17
result = use(promise.current)
18
return <Message message={result.value} />
19
}
20
21
return <Skeleton />
22
}

点击预览工具栏中的刷新按钮可以重新加载执行该组件。

我们可以观察一下效果,在目前的 React 版本中,并不能合理的处理好这种存在风险的读取方式,虽然我们最终读取到了 promise 中的值,内容也顺利渲染出来了,但是中间存在一次明显的闪烁。这种体验非常糟糕。

因此,在实践中,如果我们要读取异步请求的 promise 中的值,必须结合 Suspense 来处理。

接下来我们要学习 Suspense 的详细知识。

专栏首页
到顶
专栏目录