mcpField
R
react-19

react-19

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

关于 react-19

面向 React 开发者的 React 19 变更指南,涵盖新 API、范式变化与迁移要点,并链接到各主题的详细参考文档。

主要功能

  1. 01React 19 新特性速览
  2. 02新 Hooks 与表单 Actions
  3. 03React Compiler 与记忆化
  4. 04已移除 API 的迁移说明
  5. 05TypeScript 变化与 codemod

适用场景

  1. 01了解 React 19 的新 Hooks 和表单模式
  2. 02将 React 18 项目迁移到 React 19
  3. 03查找已弃用 API 的替代方案

安装

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

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

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
deprecations.md
new-hooks.md
paradigm-shifts.md
react-19-2-features.md
react-compiler.md
server-components.md
suspense-streaming.md
typescript-changes.md

详细介绍

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