mcpField
R
react-19

react-19

github.com/jbmanllr开发178 次安装40 周前更新
代码仓库

关于 react-19

面向编码助手的参考技能,说明 React 19 的变更、API 和迁移注意事项。内容聚焦于相较旧版 React 的差异,并非完整的 React 参考手册。

主要功能

  1. 01React 19 API 变更
  2. 02新 Hooks 和 Form Actions
  3. 03迁移指南与 codemods
  4. 04React 19.2 功能说明
  5. 05TypeScript 与弃用 API 说明

适用场景

  1. 01将应用升级到 React 19
  2. 02查阅 Hooks 和 ref 的变更
  3. 03替换已移除的 React API
  4. 04了解 React 19.2 的功能

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jbmanllr/agent-skills --skill react-19

安装后在 Claude Code 中输入/react-19确认已加载。

详细介绍

React 19 - 主要变更

本技能聚焦于 React 19 中发生了哪些变化,并非完整的 React 参考资料。

从 React 16/17 迁移?

如果从 18 之前的版本升级,这些变更已逐步累积,现在都是必需的:

变更引入版本React 19 状态
createRoot / hydrateRootReact 18必需(已移除 ReactDOM.render)
并发渲染React 18所有 R19 功能的基础
自动批处理React 18默认行为
useId、useSyncExternalStoreReact 18稳定,广泛使用
Hooks(新代码不使用类组件)React 16.8新功能的唯一实现方式
createContext(非旧版 Context)React 16.3必需(已移除旧版 Context)
错误边界React 16现在提供更完善的错误回调

**迁移路径:**先升级到 React 18.3(它会显示弃用警告),然后再升级到 19。

React 19 的思维方式

React 19 从根本上改变了我们思考 React 的方式:

旧思维新思维
默认采用客户端渲染服务端优先(默认使用 RSC)
手动记忆化由编译器处理
使用 useEffect 获取数据异步服务端组件
使用 useState 处理表单表单 Actions
布尔值表示加载状态Suspense 边界
优化所有内容编写正确的代码,由编译器优化

有关思维模型的变化,请参阅 references/paradigm-shifts.md。

有关应停止采用的做法,请参阅 references/anti-patterns.md。

快速参考:新增内容

功能React 18React 19+
记忆化手动(useMemo、useCallback、memo)React Compiler(自动)或手动
转发 refsforwardRef() 包装器将 ref 作为普通属性
Context 提供程序<Context.Provider value={}><Context value={}>
表单状态使用 useState 自行实现useActionState Hook
乐观更新手动管理状态useOptimistic Hook
读取 Promise无法在渲染中读取use() Hook
条件式 Context无法实现在条件语句后使用 use(Context)
表单待处理状态手动跟踪useFormStatus Hook
Ref 清理卸载时传入 null返回清理函数
文档元数据react-helmet 或手动处理原生 <title>、<meta>、<link>
使用状态隐藏/显示 UI卸载/重新挂载(状态会丢失)<Activity> 组件(19.2+)
非响应式 Effect 逻辑添加到依赖项或禁用 lintuseEffectEvent Hook(19.2+)
自定义元素部分支持完全支持(将 props 作为属性处理)
水合错误多条模糊错误单条带差异对比的错误

React Compiler 与记忆化

启用 React Compiler 后,手动记忆化是可选的,并非禁止:

// React Compiler handles this automatically
function Component({ items }) {
  const filtered = items.filter(x => x.active);
  const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
  const handleClick = (id) => console.log(id);
  return <List items={sorted} onClick={handleClick} />;
}

// Manual memoization still works as escape hatch for fine-grained control
const filtered = useMemo(() => expensiveOperation(items), [items]);
const handleClick = useCallback((id) => onClick(id), [onClick]);

在 React Compiler 中使用手动记忆化的场景:

  • 需要稳定引用的 Effect 依赖项
  • 在多个组件间共享开销较大的计算(编译器不会共享)
  • 明确控制何时重新计算

详情请参阅 references/react-compiler.md。

将 ref 作为属性(已弃用 forwardRef)

// React 19: ref is just a prop
function Input({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}

// Usage - no change
const inputRef = useRef(null);
<Input ref={inputRef} placeholder="Enter text" />

// forwardRef still works but will be deprecated
// Codemod: npx codemod@latest react/19/replace-forward-ref

Ref 清理函数

// React 19: Return cleanup function from ref callback
<input
  ref={(node) => {
    // Setup
    node?.focus();
    // Return cleanup (called on unmount or ref change)
    return () => {
      console.log('Cleanup');
    };
  }}
/>

// React 18: Received null on unmount (still works, but cleanup preferred)
<input ref={(node) => {
  if (node) { /* setup */ }
  else { /* cleanup */ }
}} />

将 Context 作为提供程序

const ThemeContext = createContext('light');

// React 19: Use Context directly
function App({ children }) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );
}

// React 18: Required .Provider (still works, will be deprecated)
<ThemeContext.Provider value="dark">
  {children}
</ThemeContext.Provider>

新 Hooks

useActionState
import { useActionState } from 'react';

function Form() {
  const [error, submitAction, isPending] = useActionState(
    async (prevState, formData) => {
      const result = await saveData(formData.get('name'));
      if (result.error) return result.error;
      redirect('/success');
      return null;
    },
    null // initial state
  );

  return (
    <form action={submitAction}>
      <input name="name" disabled={isPending} />
      <button disabled={isPending}>
        {isPending ? 'Saving...' : 'Save'}
      </button>
      {error && <p className="error">{error}</p>}
    </form>
  );
}
useOptimistic
import { useOptimistic } from 'react';

function Messages({ messages, sendMessage }) {
  const [optimisticMessages, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage) => [...state, { ...newMessage, sending: true }]
  );

  async function handleSubmit(formData) {
    const message = { text: formData.get('text'), id: Date.now() };
    addOptimistic(message); // Show immediately
    await sendMessage(message); // Reverts on error
  }

  return (
    <form action={handleSubmit}>
      {optimisticMessages.map(m => (
        <div key={m.id} style={{ opacity: m.sending ? 0.5 : 1 }}>
          {m.text}
        </div>
      ))}
      <input name="text" />
    </form>
  );
}
use() Hook
import { use, Suspense } from 'react';

// Read promises (suspends until resolved)
function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);
  return comments.map(c => <p key={c.id}>{c.text}</p>);
}

// Usage with Suspense
<Suspense fallback={<Spinner />}>
  <Comments commentsPromise={fetchComments()} />
</Suspense>

// Conditional context reading (not possible with useContext!)
function Theme({ showTheme }) {
  if (!showTheme) return <div>Plain</div>;

  const theme = use(ThemeContext); // Can be called conditionally!
  return <div style={{ color: theme.primary }}>Themed</div>;
}
useFormStatus(react-dom)
import { useFormStatus } from 'react-dom';

// Must be used inside a <form> - reads parent form status
function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();
  return (
    <button disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

function Form() {
  return (
    <form action={serverAction}>
      <input name="email" />
      <SubmitButton /> {/* Reads form status via context */}
    </form>
  );
}

完整的 API 详情请参阅 references/new-hooks.md。

表单 Actions

// Pass function directly to form action
<form action={async (formData) => {
  'use server';
  await saveToDatabase(formData);
}}>
  <input name="email" type="email" />
  <button type="submit">Subscribe</button>
</form>

// Button-level actions
<form>
  <button formAction={saveAction}>Save</button>
  <button formAction={deleteAction}>Delete</button>
</form>

// Manual form reset
import { requestFormReset } from 'react-dom';
requestFormReset(formElement);

文档元数据

// Automatically hoisted to <head> - works in any component
function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="description" content={post.excerpt} />
      <meta name="author" content={post.author} />
      <link rel="canonical" href={post.url} />
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

资源预加载

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';

function App() {
  // DNS prefetch
  prefetchDNS('https://api.example.com');

  // Establish connection early
  preconnect('https://fonts.googleapis.com');

  // Preload resources
  preload('https://example.com/font.woff2', { as: 'font' });
  preload('/hero.jpg', { as: 'image' });

  // Load and execute script eagerly
  preinit('https://example.com/analytics.js', { as: 'script' });

  return <main>...</main>;
}

样式表支持

// precedence controls insertion order and deduplication
function Component() {
  return (
    <>
      <link rel="stylesheet" href="/base.css" precedence="default" />
      <link rel="stylesheet" href="/theme.css" precedence="high" />
      <div className="styled">Content</div>
    </>
  );
}

// React ensures stylesheets load before Suspense boundary reveals
<Suspense fallback={<Skeleton />}>
  <link rel="stylesheet" href="/feature.css" precedence="default" />
  <FeatureComponent />
</Suspense>

自定义元素支持

React 19 全面支持自定义元素(Web Components)。

// Props matching element properties are assigned as properties
// Others are assigned as attributes
<my-element
  stringAttr="hello"           // Attribute (string)
  complexProp={{ foo: 'bar' }} // Property (object)
  onCustomEvent={handleEvent}  // Property (function)
/>

**客户端:**React 会检查元素实例上是否存在某个属性。如果存在,则将其赋值为属性;否则,将其作为特性赋值。

**服务端(SSR):**基本类型(字符串、数字)会渲染为特性。对象、函数和符号会从 HTML 中省略。

// Define custom element
class MyElement extends HTMLElement {
  constructor() {
    super();
    this.data = undefined; // React will assign to this property
  }

  connectedCallback() {
    this.textContent = JSON.stringify(this.data);
  }
}
customElements.define('my-element', MyElement);

// Use in React
<my-element data={{ items: [1, 2, 3] }} />

水合改进

更清晰的错误消息

React 19 会显示一条包含差异的错误,而不是多条含糊的错误:

Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.

<App>
  <span>
+   Client
-   Server
兼容第三方脚本

React 19 能妥善处理浏览器扩展或第三方脚本插入的元素:

  • 跳过 <head> 和 <body> 中意外出现的标签(不会出现不匹配错误)
  • 重新渲染期间会保留扩展添加的样式表
  • 对扩展注入的内容,无需添加 suppressHydrationWarning

已移除的 API(破坏性变更)

已移除迁移方式
ReactDOM.render()createRoot().render()
ReactDOM.hydrate()hydrateRoot()
unmountComponentAtNode()root.unmount()
ReactDOM.findDOMNode()使用 refs
propTypes使用 TypeScript 或移除
defaultProps(函数)ES6 默认参数
字符串 refs回调 refs 或 useRef
旧版 ContextcreateContext
React.createFactoryJSX
react-dom/test-utils使用来自 'react' 的 act

迁移指南请参阅 references/deprecations.md。

TypeScript 变更

// useRef requires argument
const ref = useRef<HTMLDivElement>(null); // Required
const ref = useRef(); // Error in React 19

// Ref callbacks must not return values (except cleanup)
<div ref={(node) => { instance = node; }} /> // Correct
<div ref={(node) => (instance = node)} />    // Error - implicit return

// ReactElement props are now unknown (not any)
type Props = ReactElement['props']; // unknown in R19, any in R18

// JSX namespace - import explicitly
import type { JSX } from 'react';

codemod 请参阅 references/typescript-changes.md。

迁移 Codemod

# Run all React 19 codemods
npx codemod@latest react/19/migration-recipe

# Individual codemods
npx codemod@latest react/19/replace-reactdom-render
npx codemod@latest react/19/replace-string-ref
npx codemod@latest react/19/replace-act-import
npx codemod@latest react/19/replace-use-form-state
npx codemod@latest react/prop-types-typescript

# TypeScript types
npx types-react-codemod@latest preset-19 ./src

导入(最佳实践)

// Named imports (recommended)
import { useState, useEffect, useRef, use } from 'react';
import { createRoot } from 'react-dom/client';
import { useFormStatus } from 'react-dom';

// Default import still works but named preferred
import React from 'react'; // Works but not recommended

错误处理变更

// React 19 error handling options
const root = createRoot(container, {
  onUncaughtError: (error, errorInfo) => {
    // Errors not caught by Error Boundary
    console.error('Uncaught:', error, errorInfo.componentStack);
  },
  onCaughtError: (error, errorInfo) => {
    // Errors caught by Error Boundary
    reportToAnalytics(error);
  },
  onRecoverableError: (error, errorInfo) => {
    // Errors React recovered from automatically
    console.warn('Recovered:', error);
  }
});

Suspense 模式和错误边界请参阅 references/suspense-streaming.md。

React 19.2+ 功能

Activity 组件(19.2)

隐藏/显示 UI,同时保留状态(类似后台标签页):

import { Activity } from 'react';

// State preserved when hidden, Effects cleaned up
<Activity mode={isVisible ? 'visible' : 'hidden'}>
  <ExpensiveComponent />
</Activity>
useEffectEvent Hook(19.2)

从 Effects 中提取非响应式逻辑,无需添加依赖项:

import { useEffect, useEffectEvent } from 'react';

function Chat({ roomId, theme }) {
  // Reads theme without making it a dependency
  const onConnected = useEffectEvent(() => {
    showNotification(`Connected!`, theme);
  });

  useEffect(() => {
    const conn = connect(roomId);
    conn.on('connected', onConnected);
    return () => conn.disconnect();
  }, [roomId]); // theme NOT in deps - won't reconnect on theme change
}

完整的 19.1+ 和 19.2 功能请参阅 references/react-19-2-features.md。

参考文档

文档内容
paradigm-shifts.md心智模型变化——如何在 React 19 中进行思考
anti-patterns.md应停止的做法——过时模式
react-19-2-features.mdReact 19.1+ 和 19.2 功能(Activity、useEffectEvent)
new-hooks.md19.0 Hooks 的完整 API
server-components.mdRSC、Server Actions、指令
suspense-streaming.mdSuspense、流式传输、错误处理
react-compiler.md自动记忆化详解
deprecations.md已移除的 API 及迁移指南
typescript-changes.md类型变更和 codemod

评论

支持 Markdown