React 19 新特性 - useTransition、useActionState

712 阅读2分钟

在 React 应用中,通常要执行一个数据变更的操作的前后顺序是: 

  1. 用户输入变更数据
  2. 点击提交,发起Api请求
  3. 然后等到服务响应更新状态

而在这个过程中,实际上会产生一些过渡状态或者错误状态,需要我们手动去处理(也就是处理竞态关系)。而 useTransitionuseActionState 的出现,为我们提供了自动处理待定状态、错误的功能。下面我们跟着官方提供的例子一起简单理解一下 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>
  );
}

PixPin_2024-12-23_16-17-57.gif

PixPin_2024-12-23_16-19-09.gif

使用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>
  );
}

PixPin_2024-12-23_16-25-30.gif

PixPin_2024-12-23_16-26-09.gif 在使用 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官网