跳到主要内容

React 高级 Hooks

本章将介绍 React 中的高级 Hooks,包括 useReduceruseLayoutEffectuseIduseTransitionuseDeferredValueuseImperativeHandle


1. useReducer:复杂状态管理

它是 useState 的替代方案,特别适合处理多个子状态或下一个状态依赖前一个状态的场景。

实时编辑器
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div style={{ textAlign: 'center', padding: '20px', border: '1px solid #ddd' }}>
      <h1>{state.count}</h1>
      <button onClick={() => dispatch({ type: 'decrement' })} style={{ marginRight: '5px' }}>-1</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
    </div>
  );
}

render(<Counter />);
结果
Loading...

2. useTransition:非阻塞渲染 (React 18+)

允许将某些更新标记为非紧急,让用户界面在重负载下仍能保持响应。

实时编辑器
function App() {
  const [isPending, startTransition] = useTransition();
  const [input, setInput] = useState("");
  const [list, setList] = useState([]);

  function handleChange(e) {
    const value = e.target.value;
    setInput(value); // 紧急更新

    // 将非紧急更新包裹在 startTransition 中
    startTransition(() => {
      const slowList = Array.from({ length: 5000 }, (_, i) => `${value} - Item ${i}`);
      setList(slowList);
    });
  }

  return (
    <div>
      <input value={input} onChange={handleChange} placeholder="输入内容感受差异..." />
      {isPending ? (
        <p>正在计算列表...</p>
      ) : (
        <ul style={{ height: '150px', overflow: 'auto', opacity: 1 }}>
          {list.slice(0, 100).map((item, i) => <li key={i}>{item}</li>)}
        </ul>
      )}
    </div>
  );
}
结果
Loading...

3. useLayoutEffect:执行 DOM 测量

它在浏览器绘制屏幕之前同步执行,通常用于测量布局并同步重新渲染,以避免闪烁。

实时编辑器
function LayoutDemo() {
  const [value, setValue] = useState(0);
  const ref = useRef(null);

  useLayoutEffect(() => {
    if (ref.current) {
      console.log("测量高度: ", ref.current.getBoundingClientRect().height);
    }
  }, [value]);

  return (
    <div>
      <div ref={ref} style={{ height: `${value}px`, background: 'orange' }}>
        高度: {value}px
      </div>
      <button onClick={() => setValue(v => v + 50)}>增加高度</button>
    </div>
  );
}
结果
Loading...

4. useDeferredValue:延迟更新

useTransition 类似,但它直接返回一个“延迟版”的值。

实时编辑器
const List = React.memo(({ text }) => {
  // 模拟慢速渲染
  const items = [];
  for (let i = 0; i < 2000; i++) {
    items.push(<li key={i}>{text} - 第 {i} 个内容</li>);
  }
  return <ul style={{ height: '100px', overflow: 'auto' }}>{items}</ul>;
});

function App() {
  const [text, setText] = useState("");
  const deferredText = useDeferredValue(text);

  return (
    <div>
      <p>输入框能立即响应,因为列表渲染被推迟了:</p>
      <input value={text} onChange={e => setText(e.target.value)} />
      <List text={deferredText} />
    </div>
  );
}

render(<App />);
结果
Loading...

5. React 19: useActionState (预览)

用于简化表单状态管理,自动处理 pending 和反馈。

// 在 React 19 环境中:
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
// 处理逻辑
return null;
},
null
);

6. React 19: use API

use 可以在渲染中读取资源(如 Promise 或 Context)。它是唯一可以在条件/循环语句中使用的 Hook。

function MessageContent({ messagePromise }) {
const message = use(messagePromise);
return <p>{message}</p>;
}

��据变更操作。它会自动处理待定状态和错误状态。

import { useActionState } from 'react';

function UpdateNameForm() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const name = formData.get('name');

// 调用 API 更新名字
const error = await updateName(name);

if (error) {
return error; // 返回错误信息
}

// 成功后跳转
redirect('/profile');
return null;
},
null // 初始状态
);

return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
{isPending ? '更新中...' : '更新名字'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}

参数说明:

  • 第一个参数:Action 函数,接收 (previousState, payload) 参数
  • 第二个参数:初始状态值
  • 第三个参数(可选):用于设置 action 的 permalink

返回值:

  • state:Action 返回的最后结果
  • dispatch:用于触发 action 的函数
  • isPending:是否处于待定状态

useOptimistic

useOptimistic 用于实现乐观更新,即在异步操作完成前就显示预期结果。

import { useOptimistic } from 'react';

function TodoList({ todos, onUpdateTodo }) {
const [optimisticTodos, setOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => {
// 乐观更新:立即显示新状态
return state.map(todo =>
todo.id === newTodo.id ? { ...todo, ...newTodo } : todo
);
}
);

async function updateTodo(id, completed) {
// 立即显示乐观更新
setOptimisticTodo({ id, completed });

// 发送请求
await updateTodoAPI(id, completed);

// 成功后更新实际数据
onUpdateTodo(id, completed);
}

return (
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={(e) => updateTodo(todo.id, e.target.checked)}
/>
{todo.title}
</li>
))}
</ul>
);
}

使用场景:

  • 点赞/收藏操作
  • 待办事项状态切换
  • 评论发送
  • 任何需要即时反馈的用户操作

use API

use 是 React 19 新增的 API,用于在渲染过程中读取资源(Promise 或 Context)。

读取 Promise

import { use, Suspense } from 'react';

function Comments({ commentsPromise }) {
// use 会暂停直到 Promise 解析完成
const comments = use(commentsPromise);

return (
<ul>
{comments.map(comment => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}

function Post({ postPromise }) {
return (
<Suspense fallback={<div>加载评论中...</div>}>
<Comments commentsPromise={postPromise.comments} />
</Suspense>
);
}

读取 Context

import { use } from 'react';
import ThemeContext from './ThemeContext';

function Heading({ children }) {
if (children == null) {
return null;
}

// use 可以在条件语句中调用(与 useContext 不同)
const theme = use(ThemeContext);

return (
<h1 style={{ color: theme.color }}>
{children}
</h1>
);
}

use 与 useContext 的区别:

  • useContext 必须在组件顶层调用
  • use 可以在条件语句和循环中调用
  • use 可以读取 Promise,而 useContext 只能读取 Context

useFormStatus

useFormStatus 用于获取父级 <form> 的状态,常用于设计系统中的按钮组件。

import { useFormStatus } from 'react-dom';

function SubmitButton() {
const { pending, data, method, action } = useFormStatus();

return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}

function ContactForm() {
async function handleSubmit(formData) {
await sendEmail(formData);
}

return (
<form action={handleSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<SubmitButton />
</form>
);
}

返回值:

  • pending:表单是否正在提交
  • data:FormData 对象
  • method:表单方法(get/post)
  • action:表单的 action 属性值

React 19 其他改进

ref 作为 prop

React 19 中,ref 可以直接作为 prop 传递,不再需要 forwardRef

// React 19 新写法
function Input({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}

// 使用
<Input ref={inputRef} placeholder="输入内容" />

Context 简化

React 19 中可以直接使用 <Context> 作为提供者:

const ThemeContext = createContext('light');

// React 19 新写法
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}

ref 清理函数

React 19 支持在 ref 回调中返回清理函数:

<input
ref={(ref) => {
// ref 创建时
if (ref) {
ref.addEventListener('focus', handleFocus);
}

// 新特性:返回清理函数
return () => {
if (ref) {
ref.removeEventListener('focus', handleFocus);
}
};
}}
/>

文档元数据支持

React 19 支持在组件中渲染 <title><meta><link> 标签:

function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta name="keywords" content={post.keywords} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}

完整示例:使用 React 19 新特性的表单

import { useActionState, useOptimistic } from 'react';
import { useFormStatus } from 'react-dom';

// 提交按钮组件
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '保存中...' : '保存'}
</button>
);
}

// 用户资料表单
function UserProfile({ user }) {
const [optimisticName, setOptimisticName] = useOptimistic(user.name);

const [error, formAction] = useActionState(
async (prevState, formData) => {
const newName = formData.get('name');

// 乐观更新
setOptimisticName(newName);

try {
await updateUser(user.id, { name: newName });
return { success: true };
} catch (err) {
return { error: err.message };
}
},
null
);

return (
<form action={formAction}>
<div>
<label>用户名:</label>
<input
name="name"
defaultValue={optimisticName}
required
/>
</div>

<div>
<label>邮箱:</label>
<input
name="email"
type="email"
defaultValue={user.email}
required
/>
</div>

<SubmitButton />

{error?.error && <p className="error">{error.error}</p>}
{error?.success && <p className="success">保存成功!</p>}
</form>
);
}

Hooks 使用规则

在使用所有 Hooks 时,必须遵循以下规则:

1. 只在顶层调用

// ❌ 错误:在条件语句中调用
if (condition) {
const [value, setValue] = useState(0);
}

// ❌ 错误:在循环中调用
for (let i = 0; i < items.length; i++) {
const [value, setValue] = useState(items[i]);
}

// ✅ 正确:在组件顶层调用
function Component() {
const [value, setValue] = useState(0);
// ...
}

2. 只在 React 函数中调用

// ❌ 错误:在普通函数中调用
function handleClick() {
const [value, setValue] = useState(0); // 错误!
}

// ✅ 正确:在组件或自定义 Hook 中调用
function useCustomHook() {
const [value, setValue] = useState(0);
return value;
}

function Component() {
const value = useCustomHook();
// ...
}

3. use API 的特殊规则

use API 是例外,它可以在条件语句中调用:

function Component({ condition, promise, context }) {
if (condition) {
// ✅ use 可以在条件中调用
const value = use(promise);
const theme = use(context);
}
}

小结

本章我们学习了 React 中的高级 Hooks:

  1. useReducer:适合管理复杂状态逻辑,通过 action 描述状态更新
  2. useLayoutEffect:在浏览器绘制前执行,用于 DOM 测量
  3. useId:生成唯一 ID,用于无障碍属性
  4. useTransition:将状态更新标记为非阻塞,保持 UI 响应
  5. useDeferredValue:延迟值的更新,优化性能
  6. useImperativeHandle:自定义暴露给父组件的 ref 方法

练习

  1. 使用 useReducer 实现一个完整的待办事项列表(增删改查)
  2. 使用 useLayoutEffect 实现一个自适应位置的弹出菜单
  3. 使用 useId 创建一个包含多个表单字段的组件
  4. 使用 useTransition 或 useDeferredValue 优化搜索列表的输入体验
  5. 使用 useImperativeHandle 实现一个可由父组件控制的多步骤表单