在 React 应用中,通常要执行一个数据变更的操作的前后顺序是:
- 用户输入变更数据
- 点击提交,发起Api请求
- 然后等到服务响应更新状态
而在这个过程中,实际上会产生一些过渡状态或者错误状态,需要我们手动去处理(也就是处理竞态关系)。而 useTransition 和 useActionState 的出现,为我们提供了自动处理待定状态、错误的功能。下面我们跟着官方提供的例子一起简单理解一下 useTransition、useActionState 这个两个Hooks。
实操案例
举个例子:跟上面我们描述的一致,假设用户需要更新自己的姓名,我们期望用户数据更变的数据,点击提交后页面有加载效果,如果修改成功则页面则显示修改成功的姓名,否则打印错误信息。
首先模拟一个api请求:
export type Result = {
state: '0' | '1'
message: string
data: Record<string, any>
}
export async function updateName(data: Record<string, any>): Promise<Result> {
console.log('run...', {data});
return new Promise((resolve) => {
setTimeout(() => {
if(Math.random() > 0.3) {
resolve({
state: '1',
message: 'success',
data
})
} {
resolve({
state: '0',
message: 'failed',
data
})
}
}, 2000)
})
}
简单写一下主体代码:
import { useState } from 'react';
import { updateName } from '../../../apis';
export function UpdateNamePage() {
const [name, setName] = useState<string>("");
const [databaseData, setDatabaseData] = useState<Record<string, any>>()
const [error, setError] = useState<string>('');
const [isPending, setIsPending] = useState<boolean>(false);
const handleSubmit = async () => {
// to do someThings...
};
return (
<div>
<h3>useState</h3>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
<p>{isPending ? 'pending...' : ''}</p>
<p>name: {databaseData?.name || '-' }</p>
{error && <p style={{ color: 'red' }}>{`error: ${error}`}</p>}
</div>
);
}
使用useState实现功能
使用 useState 中处理挂起和错误状态:
import { useState } from 'react';
import { updateName } from '../../../apis';
export function BeforeActions() {
const [name, setName] = useState<string>("");
const [databaseData, setDatabaseData] = useState<Record<string, any>>()
const [error, setError] = useState<string>('');
const [isPending, setIsPending] = useState<boolean>(false);
const handleSubmit = async () => {
// to do someThings...
setIsPending(true);
const res = await updateName({ name });
setIsPending(false);
if (res.state === '0') {
setError(res.message);
return;
}
setError('')
setDatabaseData(res.data)
};
return (
<div>
<h3>useState</h3>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
<p>{isPending ? 'pending...' : ''}</p>
<p>name: {databaseData?.name || '-' }</p>
{error && <p style={{ color: 'red' }}>{`error: ${error}`}</p>}
</div>
);
}
使用useTransition实现功能
在 React 19 中,添加了对在转换中使用异步函数的支持,以自动处理挂起状态、错误、表单和乐观更新。我们可以使用 useTransiton 来处理挂起状态:
import { useState, useTransition } from 'react';
import { updateName } from '../../../apis';
export function UseTransitionPage() {
const [name, setName] = useState<string>("");
const [databaseData, setDatabaseData] = useState<Record<string,string>>()
const [error, setError] = useState<string>('');
/** 使用 startTransition 自动处理挂起状态,也就是竞态处理 */
const [isPending, startTransition] = useTransition();
console.log({isPending});
const handleSubmit = () => {
/** update */
startTransition(async () => {
const res = await updateName({ name });
if (res.state === '0') {
setError(res.message);
return;
}
setError('')
setDatabaseData(res.data)
return
})
};
return (
<div>
<h3>useTransition: 使用 startTransition 自动处理挂起状态,也就是竞态处理</h3>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{isPending && <p>pending...</p>}
<p>name: {databaseData?.name || '-' }</p>
{error && <p style={{ color: 'red' }}>{`error: ${error}`}</p>}
</div>
);
}
在使用
useTransiton 时,异步转换会立即将isPending状态设置为 true ,发出异步请求,并在任何转换后将isPending切换为 false。这使用户可以在数据变更时保持当前 UI 的响应能力和交互性。
使用useActionState实现功能
上面的我们使用了 useTransition 完成了功能,但只自动处理了挂起状态,跟我们预期达到自动处理挂起状态和错误状态还有些差距。由此我们可以借助 react 19 新的hooks: useActionState 来处理表单操作常见的情况,所以上面的例子可以简化为:
import { useState, useActionState } from 'react';
import { updateName } from '../../../apis';
export function UseActionStatePage() {
const [databaseData, setDatabaseData] = useState<Record<string,any>>()
const [ error, submitAction, isPending ] = useActionState<string | null, FormData>(
async ( previousState, formData ) => {
const res = await updateName({ name: formData.get('name') });
if (res && res.state === '1') {
setDatabaseData(res.data)
return null
}
return res.message
},
null
)
return (
<div>
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>Update</button>
</form>
{
isPending ? <p>pending...</p> :
<>
<p>name: {databaseData?.name || '-' }</p>
{error && <p className='text-red-400'>{`error: ${error}`}</p>}
</>
}
</div>
);
}
useActionState接受一个函数(“Action”),并返回一个包装的 Action 来调用。这是有效的,因为 Action 是组合的。当调用包装的 Action 时, useActionState将返回 Action 的最后结果作为data ,并将 Action 的挂起状态返回为pending 。
更多详细的内容
更多详细的内容参考 React官网