react-19
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact 19 - Key Changes
React 19 - 核心变更
This skill focuses on what changed in React 19. Not a complete React reference.
本内容聚焦于React 19中的变更点,并非完整的React参考手册。
Coming from React 16/17?
从React 16/17升级?
If upgrading from pre-18 versions, these changes accumulated and are now mandatory:
| Change | Introduced | React 19 Status |
|---|---|---|
| React 18 | Required ( |
| Concurrent rendering | React 18 | Foundation for all R19 features |
| Automatic batching | React 18 | Default behavior |
| React 18 | Stable, commonly used |
| Hooks (no classes for new code) | React 16.8 | Only path for new features |
| React 16.3 | Required (legacy Context removed) |
| Error Boundaries | React 16 | Now with better error callbacks |
Migration path: Upgrade to React 18.3 first (shows deprecation warnings), then to 19.
如果从18之前的版本升级,以下累积的变更现在是强制要求:
| 变更内容 | 引入版本 | React 19 状态 |
|---|---|---|
| React 18 | 必须使用( |
| 并发渲染 | React 18 | 所有R19功能的基础 |
| 自动批处理 | React 18 | 默认行为 |
| React 18 | 稳定版,广泛使用 |
| 钩子(新代码不再使用类组件) | React 16.8 | 新功能的唯一实现方式 |
| React 16.3 | 必须使用(遗留Context已移除) |
| 错误边界 | React 16 | 现在支持更完善的错误回调 |
迁移路径: 先升级到React 18.3(该版本会显示废弃警告),再升级到19。
The React 19 Mindset
React 19 的思维转变
React 19 represents fundamental shifts in how to think about React:
| Old Thinking | New Thinking |
|---|---|
| Client-side by default | Server-first (RSC default) |
| Manual memoization | Compiler handles it |
| async Server Components |
| Form Actions |
| Loading state booleans | Suspense boundaries |
| Optimize everything | Write correct code, compiler optimizes |
See references/paradigm-shifts.md for the mental model changes.
See references/anti-patterns.md for what to stop doing.
React 19代表了React开发思路的根本性转变:
| 旧思维 | 新思维 |
|---|---|
| 默认客户端优先 | 服务端优先(RSC默认模式) |
| 手动记忆化 | 编译器自动处理 |
用 | 异步服务端组件 |
用 | Form Actions |
| 用布尔值管理加载状态 | Suspense边界 |
| 优化所有内容 | 先编写正确的代码,编译器负责优化 |
查看 references/paradigm-shifts.md 了解思维模型的具体变化。
查看 references/anti-patterns.md 了解需要摒弃的过时模式。
Quick Reference: What's New
快速参考:新增内容
| Feature | React 18 | React 19+ |
|---|---|---|
| Memoization | Manual ( | React Compiler (automatic) or manual |
| Forward refs | | |
| Context provider | | |
| Form state | Custom with | |
| Optimistic updates | Manual state management | |
| Read promises | Not possible in render | |
| Conditional context | Not possible | |
| Form pending state | Manual tracking | |
| Ref cleanup | Pass | Return cleanup function |
| Document metadata | | Native |
| Hide/show UI with state | Unmount/remount (state lost) | |
| Non-reactive Effect logic | Add to deps or suppress lint | |
| Custom Elements | Partial support | Full support (props as properties) |
| Hydration errors | Multiple vague errors | Single error with diff |
| 特性 | React 18 | React 19+ |
|---|---|---|
| 记忆化 | 手动实现( | React Compiler自动处理或手动实现 |
| 转发ref | | |
| Context提供者 | | |
| 表单状态 | 用 | |
| 乐观更新 | 手动状态管理 | |
| 读取Promise | 渲染中无法实现 | |
| 条件式Context | 无法实现 | 可在条件判断后使用 |
| 表单 pending 状态 | 手动跟踪 | |
| Ref清理 | 在卸载时传入 | 返回清理函数 |
| 文档元数据 | | 原生支持 |
| 用状态控制UI显示/隐藏 | 卸载/重新挂载(状态丢失) | |
| 非响应式Effect逻辑 | 添加依赖或抑制lint提示 | |
| 自定义元素 | 部分支持 | 完整支持(属性作为对象属性传递) |
| 水合错误 | 多个模糊错误 | 单个带差异对比的错误 |
React Compiler & Memoization
React Compiler 与记忆化
With React Compiler enabled, manual memoization is optional, not forbidden:
tsx
// 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]);When to use manual memoization with React Compiler:
- Effect dependencies that need stable references
- Sharing expensive calculations across components (compiler doesn't share)
- Explicit control over when re-computation happens
See references/react-compiler.md for details.
启用React Compiler后,手动记忆化是可选操作,而非被禁止:
tsx
// React Compiler会自动处理以下代码
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} />;
}
// 手动记忆化仍可作为细粒度控制的兜底方案
const filtered = useMemo(() => expensiveOperation(items), [items]);
const handleClick = useCallback((id) => onClick(id), [onClick]);启用React Compiler后仍需手动记忆化的场景:
- 需要稳定引用的Effect依赖项
- 组件间共享的昂贵计算(编译器不会共享计算结果)
- 需要显式控制重新计算时机的场景
查看 references/react-compiler.md 获取详细信息。
ref as Prop (forwardRef Deprecated)
ref 作为属性(forwardRef 已废弃)
tsx
// 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-reftsx
// React 19: ref 只是一个普通属性
function Input({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// 使用方式无变化
const inputRef = useRef(null);
<Input ref={inputRef} placeholder="Enter text" />
// forwardRef 仍可使用但会被废弃
// 代码迁移工具:npx codemod@latest react/19/replace-forward-refRef Cleanup Functions
Ref 清理函数
tsx
// 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 */ }
}} />tsx
// React 19: 从ref回调中返回清理函数
<input
ref={(node) => {
// 初始化操作
node?.focus();
// 返回清理函数(在组件卸载或ref变更时调用)
return () => {
console.log('Cleanup');
};
}}
/>
// React 18: 在卸载时传入null(仍可工作,但推荐使用清理函数)
<input ref={(node) => {
if (node) { /* 初始化 */ }
else { /* 清理 */ }
}} />Context as Provider
Context 直接作为提供者
tsx
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>tsx
const ThemeContext = createContext('light');
// React 19: 直接使用Context
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
// React 18: 必须使用.Provider(仍可工作,但会被废弃)
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>New Hooks
新钩子
useActionState
useActionState
tsx
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>
);
}tsx
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 // 初始状态
);
return (
<form action={submitAction}>
<input name="name" disabled={isPending} />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}useOptimistic
useOptimistic
tsx
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>
);
}tsx
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); // 立即显示
await sendMessage(message); // 出错时回滚
}
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
use() Hook
tsx
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>;
}tsx
import { use, Suspense } from 'react';
// 读取Promise(会挂起直到解析完成)
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(c => <p key={c.id}>{c.text}</p>);
}
// 结合Suspense使用
<Suspense fallback={<Spinner />}>
<Comments commentsPromise={fetchComments()} />
</Suspense>
// 条件式读取Context(useContext无法实现!)
function Theme({ showTheme }) {
if (!showTheme) return <div>Plain</div>;
const theme = use(ThemeContext); // 可在条件判断后调用!
return <div style={{ color: theme.primary }}>Themed</div>;
}useFormStatus (react-dom)
useFormStatus (react-dom)
tsx
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>
);
}See references/new-hooks.md for complete API details.
tsx
import { useFormStatus } from 'react-dom';
// 必须在<form>内部使用 - 读取父表单的状态
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 /> {/* 通过Context读取表单状态 */}
</form>
);
}查看 references/new-hooks.md 获取完整API详情。
Form Actions
Form Actions
tsx
// 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);tsx
// 直接将函数传递给form的action属性
<form action={async (formData) => {
'use server';
await saveToDatabase(formData);
}}>
<input name="email" type="email" />
<button type="submit">Subscribe</button>
</form>
// 按钮级别的action
<form>
<button formAction={saveAction}>Save</button>
<button formAction={deleteAction}>Delete</button>
</form>
// 手动重置表单
import { requestFormReset } from 'react-dom';
requestFormReset(formElement);Document Metadata
文档元数据
tsx
// 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>
);
}tsx
// 会自动提升到<head> - 可在任意组件中使用
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>
);
}Resource Preloading
资源预加载
tsx
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>;
}tsx
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
function App() {
// DNS 预获取
prefetchDNS('https://api.example.com');
// 提前建立连接
preconnect('https://fonts.googleapis.com');
// 预加载资源
preload('https://example.com/font.woff2', { as: 'font' });
preload('/hero.jpg', { as: 'image' });
// 提前加载并执行脚本
preinit('https://example.com/analytics.js', { as: 'script' });
return <main>...</main>;
}Stylesheet Support
样式表支持
tsx
// 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>tsx
// precedence 控制插入顺序和去重
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 确保样式表在Suspense边界显示前加载完成
<Suspense fallback={<Skeleton />}>
<link rel="stylesheet" href="/feature.css" precedence="default" />
<FeatureComponent />
</Suspense>Custom Elements Support
自定义元素支持
React 19 adds full support for Custom Elements (Web Components).
tsx
// 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)
/>Client-side: React checks if a property exists on the element instance. If yes, assigns as property; otherwise, as attribute.
Server-side (SSR): Primitive types (string, number) render as attributes. Objects, functions, symbols are omitted from HTML.
tsx
// 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 添加了对自定义元素(Web Components)的完整支持。
tsx
// 匹配元素属性的props会作为对象属性赋值
// 其他props会作为HTML属性赋值
<my-element
stringAttr="hello" // HTML属性(字符串类型)
complexProp={{ foo: 'bar' }} // 对象属性(对象类型)
onCustomEvent={handleEvent} // 对象属性(函数类型)
/>客户端: React会检查元素实例是否存在对应的属性。如果存在,则作为对象属性赋值;否则作为HTML属性赋值。
服务端(SSR): 原始类型(字符串、数字)会渲染为HTML属性。对象、函数、符号类型会从HTML中省略。
tsx
// 定义自定义元素
class MyElement extends HTMLElement {
constructor() {
super();
this.data = undefined; // React会将值赋值给该属性
}
connectedCallback() {
this.textContent = JSON.stringify(this.data);
}
}
customElements.define('my-element', MyElement);
// 在React中使用
<my-element data={{ items: [1, 2, 3] }} />Hydration Improvements
水合优化
Better Error Messages
更友好的错误提示
React 19 shows a single error with a diff instead of multiple vague errors:
Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.
<App>
<span>
+ Client
- ServerReact 19会显示单个带差异对比的错误,而非多个模糊错误:
Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.
<App>
<span>
+ Client
- ServerThird-Party Script Compatibility
第三方脚本兼容性
React 19 gracefully handles elements inserted by browser extensions or third-party scripts:
- Unexpected tags in and
<head>are skipped (no mismatch errors)<body> - Stylesheets from extensions are preserved during re-renders
- No need to add for extension-injected content
suppressHydrationWarning
React 19可优雅处理浏览器扩展或第三方脚本插入的元素:
- 忽略和
<head>中的意外标签(不会触发不匹配错误)<body> - 保留扩展注入的样式表在重新渲染时不丢失
- 无需为扩展注入的内容添加
suppressHydrationWarning
Removed APIs (Breaking)
已移除的API(重大变更)
| Removed | Migration |
|---|---|
| |
| |
| |
| Use refs |
| TypeScript or remove |
| ES6 default parameters |
| String refs | Callback refs or |
| Legacy Context | |
| JSX |
| |
See references/deprecations.md for migration guides.
| 已移除API | 迁移方案 |
|---|---|
| |
| |
| |
| 使用refs替代 |
| 使用TypeScript或移除 |
| ES6默认参数 |
| 字符串refs | 回调refs或 |
| 遗留Context | |
| JSX |
| 从 |
查看 references/deprecations.md 获取迁移指南。
TypeScript Changes
TypeScript 变更
tsx
// 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';See references/typescript-changes.md for codemods.
tsx
// useRef 必须传入参数
const ref = useRef<HTMLDivElement>(null); // 必须传入
const ref = useRef(); // React 19中会报错
// Ref回调不能返回值(清理函数除外)
<div ref={(node) => { instance = node; }} /> // 正确
<div ref={(node) => (instance = node)} /> // 错误 - 隐式返回
// ReactElement props 类型现在为unknown(之前为any)
type Props = ReactElement['props']; // R19中为unknown,R18中为any
// JSX 命名空间 - 需要显式导入
import type { JSX } from 'react';查看 references/typescript-changes.md 获取代码迁移工具。
Migration Codemods
迁移代码工具
bash
undefinedbash
undefinedRun all React 19 codemods
运行所有React 19迁移工具
npx codemod@latest react/19/migration-recipe
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
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
TypeScript 类型迁移
npx types-react-codemod@latest preset-19 ./src
undefinednpx types-react-codemod@latest preset-19 ./src
undefinedImports (Best Practice)
导入方式(最佳实践)
tsx
// 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 recommendedtsx
// 命名导入(推荐)
import { useState, useEffect, useRef, use } from 'react';
import { createRoot } from 'react-dom/client';
import { useFormStatus } from 'react-dom';
// 默认导入仍可工作,但推荐使用命名导入
import React from 'react'; // 可工作但不推荐Error Handling Changes
错误处理变更
tsx
// 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);
}
});See references/suspense-streaming.md for Suspense patterns and error boundaries.
tsx
// React 19 错误处理选项
const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// 未被Error Boundary捕获的错误
console.error('Uncaught:', error, errorInfo.componentStack);
},
onCaughtError: (error, errorInfo) => {
// 被Error Boundary捕获的错误
reportToAnalytics(error);
},
onRecoverableError: (error, errorInfo) => {
// React自动恢复的错误
console.warn('Recovered:', error);
}
});查看 references/suspense-streaming.md 获取Suspense模式和错误边界相关内容。
React 19.2+ Features
React 19.2+ 新特性
Activity Component (19.2)
Activity 组件(19.2)
Hide/show UI while preserving state (like background tabs):
tsx
import { Activity } from 'react';
// State preserved when hidden, Effects cleaned up
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<ExpensiveComponent />
</Activity>隐藏/显示UI的同时保留状态(类似后台标签页的行为):
tsx
import { Activity } from 'react';
// 隐藏时保留状态,Effect会被清理
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<ExpensiveComponent />
</Activity>useEffectEvent Hook (19.2)
useEffectEvent 钩子(19.2)
Extract non-reactive logic from Effects without adding dependencies:
tsx
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
}See references/react-19-2-features.md for complete 19.1+ and 19.2 features.
从Effect中提取非响应式逻辑,无需添加依赖项:
tsx
import { useEffect, useEffectEvent } from 'react';
function Chat({ roomId, theme }) {
// 读取theme但无需将其作为依赖项
const onConnected = useEffectEvent(() => {
showNotification(`Connected!`, theme);
});
useEffect(() => {
const conn = connect(roomId);
conn.on('connected', onConnected);
return () => conn.disconnect();
}, [roomId]); // theme不在依赖项中 - 主题变更时不会重新连接
}查看 references/react-19-2-features.md 获取19.1+和19.2的完整特性。
Reference Documentation
参考文档
| Document | Content |
|---|---|
| paradigm-shifts.md | Mental model changes - how to think in React 19 |
| anti-patterns.md | What to stop doing - outdated patterns |
| react-19-2-features.md | React 19.1+ and 19.2 features (Activity, useEffectEvent) |
| new-hooks.md | Complete API for 19.0 hooks |
| server-components.md | RSC, Server Actions, directives |
| suspense-streaming.md | Suspense, streaming, error handling |
| react-compiler.md | Automatic memoization details |
| deprecations.md | Removed APIs with migration guides |
| typescript-changes.md | Type changes and codemods |
| 文档 | 内容 |
|---|---|
| paradigm-shifts.md | 思维模型变更 - React 19的思考方式 |
| anti-patterns.md | 需要摒弃的过时模式 |
| react-19-2-features.md | React 19.1+和19.2特性(Activity, useEffectEvent) |
| new-hooks.md | 19.0新钩子的完整API |
| server-components.md | RSC、Server Actions、指令 |
| suspense-streaming.md | Suspense、流式渲染、错误处理 |
| react-compiler.md | 自动记忆化详情 |
| deprecations.md | 已移除API及迁移指南 |
| typescript-changes.md | 类型变更及代码迁移工具 |