React19新特性

forwardRef 的弃用

传统的 forwardRef 用法

在 React 19 之前,forwardRef 是一个高阶函数,用于让子组件能够接收并转发父组件传递过来的 ref,从而允许父组件直接访问子组件中的 DOM 元素或组件实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useRef, forwardRef } from 'react';

// React 18 及之前:必须使用 forwardRef
const ChildComponent = forwardRef((props, ref) => {
return (
<input
ref={ref}
type="text"
placeholder="请输入内容"
/>
);
});

// 父组件:使用子组件并传递 ref
const ParentComponent = () => {
const inputRef = useRef(null);

const focusInput = () => {
// 通过 ref 直接访问子组件中的 input 元素并调用其方法
inputRef.current.focus();
};

return (
<div>
<ChildComponent ref={inputRef} />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
};

export default ParentComponent;

forwardRef 已废弃

React 19 的改进

在 React 19 中,官网显示 forwardRef 已被弃用。现在我们无需再用 forwardRef 来封装子组件,可以直接在子组件的 props 中接收 ref。

ref 接收

React 19 的新写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useRef } from 'react';

// React 19:直接在 props 中接收 ref
function ChildComponent({ ref, ...props }) {
return (
<input
ref={ref}
type="text"
placeholder="请输入内容"
{...props}
/>
);
}

// 或者使用解构
function ChildComponent(props) {
return (
<input
ref={props.ref}
type="text"
placeholder="请输入内容"
/>
);
}

// 父组件的使用方式保持不变
const ParentComponent = () => {
const inputRef = useRef(null);

const focusInput = () => {
inputRef.current.focus();
};

return (
<div>
<ChildComponent ref={inputRef} />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
};

更复杂的示例:配合 useImperativeHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, { useRef, useImperativeHandle } from 'react';

// React 19:直接接收 ref,无需 forwardRef
function CustomInput({ ref, label }) {
const inputRef = useRef(null);

// 自定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
},
getValue: () => {
return inputRef.current.value;
}
}));

return (
<div>
<label>{label}</label>
<input ref={inputRef} type="text" />
</div>
);
}

function App() {
const customInputRef = useRef(null);

const handleSubmit = () => {
const value = customInputRef.current.getValue();
console.log('输入值:', value);
customInputRef.current.clear();
};

return (
<div>
<CustomInput ref={customInputRef} label="用户名:" />
<button onClick={handleSubmit}>提交</button>
</div>
);
}

迁移指南

如果你的项目需要从旧版本迁移到 React 19:

  1. 简单组件:直接移除 forwardRef 包装,在 props 中接收 ref
  2. 复杂组件:保持 useImperativeHandle 的使用,只需移除 forwardRef
  3. 类型定义(TypeScript):更新类型定义以包含 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// React 18
import { forwardRef, ForwardedRef } from 'react';

interface Props {
label: string;
}

const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
return <input ref={ref} {...props} />;
});

// React 19
interface Props {
label: string;
ref?: React.Ref<HTMLInputElement>;
}

function Input({ ref, label, ...props }: Props) {
return <input ref={ref} {...props} />;
}

新 API:use

use

use 是 React 19 引入的一个全新 API,它可以让你读取类似于 PromiseContext 的资源的值。这是 React 首个可以在条件语句和循环中调用的 Hook。

核心特性

  1. 可以在条件语句中使用(突破了 Hooks 规则的限制)
  2. 支持读取 Promise
  3. 支持读取 Context
  4. 通常搭配 Suspense 和错误边界使用

使用场景一:读取 Context

use 可以替代 useContext,并且更加灵活,可以在条件语句中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { createContext, use } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}

function Form() {
return (
<Panel title="Welcome">
<Button show={true}>Sign up</Button>
<Button show={false}>Log in</Button>
</Panel>
);
}

function Panel({ title, children }) {
const theme = use(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
);
}

function Button({ show, children }) {
// 重点:use 可以在条件语句中调用!
if (show) {
const theme = use(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
return false;
}

对比 useContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ useContext 不能在条件语句中使用
function Button({ show, children }) {
if (show) {
const theme = useContext(ThemeContext); // 错误!违反 Hooks 规则
return <button>{children}</button>;
}
}

// ✅ use 可以在条件语句中使用
function Button({ show, children }) {
if (show) {
const theme = use(ThemeContext); // 正确!
return <button>{children}</button>;
}
}

使用场景二:读取 Promise(数据获取)

use 最强大的功能是可以直接读取 Promise,配合 Suspense 实现优雅的异步数据加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { use, Suspense } from 'react';

// 模拟异步数据获取
function fetchUser(userId) {
return fetch(`/api/users/${userId}`)
.then(res => res.json());
}

function UserProfile({ userPromise }) {
// 直接读取 Promise,组件会自动 suspend
const user = use(userPromise);

return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>{user.bio}</p>
</div>
);
}

function App() {
const userPromise = fetchUser(1);

return (
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}

使用场景三:配合 Suspense 和 ErrorBoundary

完整的错误处理和加载状态管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { use, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer({ messagePromise }) {
return (
<ErrorBoundary fallback={<p>⚠️ Something went wrong</p>}>
<Suspense fallback={<p>⌛ Downloading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</ErrorBoundary>
);
}

// 使用示例
function App() {
const messagePromise = fetch('/api/message')
.then(res => {
if (!res.ok) throw new Error('Failed to fetch');
return res.text();
});

return <MessageContainer messagePromise={messagePromise} />;
}

高级用法:条件数据获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { use, Suspense, useState } from 'react';

function UserData({ userId }) {
// 只在 userId 存在时才获取数据
if (!userId) {
return <div>请选择一个用户</div>;
}

// 在条件语句中使用 use
const user = use(fetchUser(userId));

return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}

function App() {
const [selectedUserId, setSelectedUserId] = useState(null);

return (
<div>
<select onChange={(e) => setSelectedUserId(e.target.value)}>
<option value="">选择用户</option>
<option value="1">用户 1</option>
<option value="2">用户 2</option>
</select>

<Suspense fallback={<div>加载中...</div>}>
<UserData userId={selectedUserId} />
</Suspense>
</div>
);
}

循环中使用 use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function PostList({ postIds }) {
return (
<ul>
{postIds.map(id => {
// 在循环中使用 use
const post = use(fetchPost(id));
return (
<li key={id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</li>
);
})}
</ul>
);
}

function App() {
return (
<Suspense fallback={<div>加载文章列表...</div>}>
<PostList postIds={[1, 2, 3, 4, 5]} />
</Suspense>
);
}

use vs useContext vs 传统数据获取

特性 use useContext useEffect + useState
可在条件语句中使用
可在循环中使用
读取 Promise 需要手动处理
读取 Context
自动 Suspense 需要手动实现
错误处理 自动(配合 ErrorBoundary) 需要手动实现

注意事项

  1. Promise 必须是稳定的:不要在组件内部创建 Promise,应该从 props 接收或使用缓存
  2. 配合 Suspense 使用:读取 Promise 时必须包裹在 Suspense 中
  3. 错误边界:建议配合 ErrorBoundary 处理错误情况
  4. SSR 支持:use API 完全支持服务端渲染

React 19 Actions

Actions 是 React 19 引入的一个重要概念,用于处理数据变更操作(如表单提交、API 调用等)。Actions 自动处理加载状态、错误处理和乐观更新。

什么是 Actions

Actions 是异步函数,用于管理数据提交的整个生命周期:

  • pending 状态:自动跟踪异步操作的进行状态
  • 错误处理:自动捕获和处理错误
  • 乐观更新:在请求完成前先更新 UI
  • 自动重置:表单自动重置

useActionState

useActionState 是用于管理 Action 状态的新 Hook。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { useActionState } from 'react';

async function updateName(previousState, formData) {
const name = formData.get('name');

try {
// 模拟 API 调用
await fetch('/api/update-name', {
method: 'POST',
body: JSON.stringify({ name })
});

return { success: true, message: '更新成功!' };
} catch (error) {
return { success: false, message: '更新失败' };
}
}

function UpdateNameForm() {
const [state, submitAction, isPending] = useActionState(updateName, {
success: null,
message: ''
});

return (
<form action={submitAction}>
<input type="text" name="name" required />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>

{state.message && (
<p style={{ color: state.success ? 'green' : 'red' }}>
{state.message}
</p>
)}
</form>
);
}

完整示例:用户注册表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { useActionState } from 'react';

async function registerUser(previousState, formData) {
const username = formData.get('username');
const email = formData.get('email');
const password = formData.get('password');

// 表单验证
if (password.length < 6) {
return {
success: false,
errors: { password: '密码至少需要6个字符' }
};
}

try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, email, password })
});

if (!response.ok) {
const error = await response.json();
return { success: false, errors: error };
}

const user = await response.json();
return { success: true, user };
} catch (error) {
return {
success: false,
errors: { general: '注册失败,请稍后重试' }
};
}
}

function RegistrationForm() {
const [state, submitAction, isPending] = useActionState(registerUser, {
success: null,
errors: {}
});

return (
<form action={submitAction}>
<div>
<label htmlFor="username">用户名:</label>
<input
id="username"
name="username"
type="text"
required
disabled={isPending}
/>
</div>

<div>
<label htmlFor="email">邮箱:</label>
<input
id="email"
name="email"
type="email"
required
disabled={isPending}
/>
{state.errors?.email && (
<span className="error">{state.errors.email}</span>
)}
</div>

<div>
<label htmlFor="password">密码:</label>
<input
id="password"
name="password"
type="password"
required
disabled={isPending}
/>
{state.errors?.password && (
<span className="error">{state.errors.password}</span>
)}
</div>

<button type="submit" disabled={isPending}>
{isPending ? '注册中...' : '注册'}
</button>

{state.errors?.general && (
<p className="error">{state.errors.general}</p>
)}

{state.success && (
<p className="success">注册成功!欢迎 {state.user.username}</p>
)}
</form>
);
}

useActionState 参数详解

1
const [state, action, isPending] = useActionState(fn, initialState, permalink?)
  • fn: Action 函数,接收 (previousState, formData) 参数
  • initialState: 初始状态
  • permalink(可选): 用于渐进增强的 URL
  • 返回值:
    • state: 当前状态
    • action: 可以传递给表单的 action 属性
    • isPending: 布尔值,表示操作是否正在进行

useOptimistic

useOptimistic 用于实现乐观更新,在服务器响应前先更新 UI,提供即时反馈。

基本概念

乐观更新:先假设操作会成功,立即更新 UI,如果失败再回滚。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { useOptimistic, useState } from 'react';

function LikeButton({ postId, initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(currentLikes, amount) => currentLikes + amount
);

async function handleLike() {
// 乐观更新:立即显示 +1
addOptimisticLike(1);

try {
// 实际的 API 调用
const response = await fetch(`/api/posts/${postId}/like`, {
method: 'POST'
});
const data = await response.json();

// 更新真实数据
setLikes(data.likes);
} catch (error) {
// 如果失败,乐观更新会自动回滚
console.error('点赞失败', error);
}
}

return (
<button onClick={handleLike}>
❤️ {optimisticLikes} 个赞
</button>
);
}

完整示例:待办事项列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { useOptimistic, useState } from 'react';

function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React 19', completed: false },
{ id: 2, text: '写博客文章', completed: false }
]);

const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, newTodo) => {
if (newTodo.type === 'add') {
return [...currentTodos, newTodo.todo];
} else if (newTodo.type === 'toggle') {
return currentTodos.map(todo =>
todo.id === newTodo.id
? { ...todo, completed: !todo.completed }
: todo
);
} else if (newTodo.type === 'delete') {
return currentTodos.filter(todo => todo.id !== newTodo.id);
}
return currentTodos;
}
);

async function addTodo(text) {
const newTodo = {
id: Date.now(),
text,
completed: false
};

// 乐观更新
addOptimisticTodo({ type: 'add', todo: newTodo });

try {
const response = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
});
const savedTodo = await response.json();
setTodos(prev => [...prev, savedTodo]);
} catch (error) {
console.error('添加失败', error);
}
}

async function toggleTodo(id) {
// 乐观更新
addOptimisticTodo({ type: 'toggle', id });

try {
await fetch(`/api/todos/${id}/toggle`, { method: 'POST' });
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
} catch (error) {
console.error('切换失败', error);
}
}

async function deleteTodo(id) {
// 乐观更新
addOptimisticTodo({ type: 'delete', id });

try {
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
setTodos(prev => prev.filter(todo => todo.id !== id));
} catch (error) {
console.error('删除失败', error);
}
}

return (
<div>
<h2>待办事项</h2>
<ul>
{optimisticTodos.map(todo => (
<li
key={todo.id}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
opacity: todo.id > 1000000000000 ? 0.6 : 1 // 新添加的项稍微透明
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}

聊天应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { useOptimistic, useState } from 'react';

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(currentMessages, newMessage) => [...currentMessages, newMessage]
);

async function sendMessage(text) {
const tempMessage = {
id: Date.now(),
text,
sender: 'me',
status: 'sending' // 发送中状态
};

// 乐观更新:立即显示消息
addOptimisticMessage(tempMessage);

try {
const response = await fetch(`/api/rooms/${roomId}/messages`, {
method: 'POST',
body: JSON.stringify({ text })
});
const savedMessage = await response.json();

// 替换临时消息为服务器返回的消息
setMessages(prev => [...prev, savedMessage]);
} catch (error) {
// 失败会自动回滚
alert('消息发送失败');
}
}

return (
<div>
<div className="messages">
{optimisticMessages.map(msg => (
<div
key={msg.id}
className={`message ${msg.status === 'sending' ? 'pending' : ''}`}
>
<strong>{msg.sender}:</strong> {msg.text}
{msg.status === 'sending' && <span></span>}
</div>
))}
</div>
<MessageInput onSend={sendMessage} />
</div>
);
}

useFormStatus

useFormStatus 用于获取表单的提交状态,特别适合在表单的子组件中使用。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { useFormStatus } from 'react-dom';

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

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

function MyForm() {
async function handleSubmit(formData) {
const name = formData.get('name');
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ name })
});
}

return (
<form action={handleSubmit}>
<input type="text" name="name" />
<SubmitButton />
</form>
);
}

完整示例:带加载状态的表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { useFormStatus } from 'react-dom';
import { useActionState } from 'react';

// 提交按钮组件
function SubmitButton({ children }) {
const { pending } = useFormStatus();

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

// 表单字段组件
function FormFields() {
const { pending } = useFormStatus();

return (
<>
<input
type="email"
name="email"
placeholder="邮箱"
disabled={pending}
required
/>
<input
type="password"
name="password"
placeholder="密码"
disabled={pending}
required
/>
</>
);
}

// 主表单组件
async function login(previousState, formData) {
const email = formData.get('email');
const password = formData.get('password');

try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});

if (!response.ok) {
return { error: '登录失败,请检查邮箱和密码' };
}

return { success: true };
} catch (error) {
return { error: '网络错误,请稍后重试' };
}
}

function LoginForm() {
const [state, action] = useActionState(login, {});

return (
<form action={action}>
<FormFields />
<SubmitButton>登录</SubmitButton>

{state.error && (
<p className="error">{state.error}</p>
)}
{state.success && (
<p className="success">登录成功!</p>
)}
</form>
);
}

高级示例:多步骤表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { useFormStatus } from 'react-dom';
import { useState } from 'react';

function StepIndicator({ currentStep, totalSteps }) {
const { pending } = useFormStatus();

return (
<div className="steps">
{Array.from({ length: totalSteps }, (_, i) => (
<div
key={i}
className={`step ${i < currentStep ? 'completed' : ''} ${
i === currentStep ? 'active' : ''
} ${pending ? 'disabled' : ''}`}
>
步骤 {i + 1}
</div>
))}
</div>
);
}

function NavigationButtons({ currentStep, totalSteps, onBack }) {
const { pending } = useFormStatus();

return (
<div className="navigation">
{currentStep > 0 && (
<button
type="button"
onClick={onBack}
disabled={pending}
>
上一步
</button>
)}

<button type="submit" disabled={pending}>
{pending ? (
'处理中...'
) : currentStep === totalSteps - 1 ? (
'完成'
) : (
'下一步'
)}
</button>
</div>
);
}

function MultiStepForm() {
const [step, setStep] = useState(0);

async function handleSubmit(formData) {
// 处理表单提交
await new Promise(resolve => setTimeout(resolve, 1000));

if (step < 2) {
setStep(step + 1);
} else {
// 最后一步,提交表单
console.log('表单提交完成');
}
}

return (
<form action={handleSubmit}>
<StepIndicator currentStep={step} totalSteps={3} />

{step === 0 && <Step1Fields />}
{step === 1 && <Step2Fields />}
{step === 2 && <Step3Fields />}

<NavigationButtons
currentStep={step}
totalSteps={3}
onBack={() => setStep(step - 1)}
/>
</form>
);
}

useFormStatus 返回值

1
const { pending, data, method, action } = useFormStatus();
  • pending: 布尔值,表示表单是否正在提交
  • data: FormData 对象,包含表单数据
  • method: 字符串,表单的 method(’get’ 或 ‘post’)
  • action: 函数引用,表单的 action

注意事项

  1. 必须在表单内部使用useFormStatus 必须在 <form> 的子组件中调用
  2. 只能读取父表单状态:不能读取同级或子表单的状态
  3. 配合 Actions 使用:最佳实践是配合 useActionState 使用

其他 React 19 新特性

1. 文档元数据支持

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function BlogPost({ post }) {
return (
<article>
{/* 直接在组件中添加元数据 */}
<title>{post.title} - 我的博客</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<meta property="og:image" content={post.coverImage} />
<link rel="canonical" href={`https://myblog.com/posts/${post.slug}`} />

<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}

2. 样式表优先级

React 19 改进了样式表的加载和优先级管理。

1
2
3
4
5
6
7
8
9
10
11
function Component() {
return (
<>
{/* React 会自动处理样式表的优先级 */}
<link rel="stylesheet" href="/styles/base.css" precedence="default" />
<link rel="stylesheet" href="/styles/theme.css" precedence="high" />

<div className="content">内容</div>
</>
);
}

3. 异步脚本支持

1
2
3
4
5
function Analytics() {
return (
<script async src="https://analytics.example.com/script.js" />
);
}

4. 资源预加载

React 19 提供了新的 API 用于资源预加载。

1
2
3
4
5
6
7
8
9
10
11
import { preload, preinit } from 'react-dom';

function MyComponent() {
// 预加载资源
preload('/api/data.json', { as: 'fetch' });

// 预初始化脚本
preinit('/scripts/analytics.js', { as: 'script' });

return <div>内容</div>;
}

5. ref 回调的清理函数

ref 回调现在可以返回清理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function VideoPlayer() {
return (
<video
ref={(node) => {
if (node) {
// 设置
node.play();

// 返回清理函数
return () => {
node.pause();
};
}
}}
src="/video.mp4"
/>
);
}

6. Context 作为 Provider

不再需要 .Provider,直接使用 Context 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createContext } from 'react';

const ThemeContext = createContext('light');

// React 19: 直接使用 Context
function App() {
return (
<ThemeContext value="dark">
<Page />
</ThemeContext>
);
}

// React 18: 需要使用 .Provider
function App() {
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}

7. useDeferredValue 的初始值

1
2
3
4
5
6
7
8
import { useDeferredValue } from 'react';

function SearchResults({ query }) {
// 可以提供初始值
const deferredQuery = useDeferredValue(query, '');

return <Results query={deferredQuery} />;
}

总结

React 19 带来了许多激动人心的新特性:

核心改进

  1. ref 简化:不再需要 forwardRef,直接在 props 中接收 ref
  2. use Hook:革命性的新 Hook,可在条件语句和循环中使用
  3. Actions:优雅的异步操作管理方案
  4. 表单增强useActionStateuseFormStatus 简化表单处理

性能优化

  1. useOptimistic:乐观更新提升用户体验
  2. 文档元数据:原生支持 SEO 标签
  3. 资源管理:改进的样式表和脚本加载

开发体验

  1. 更简洁的 API:Context 不再需要 .Provider
  2. 更好的类型支持:TypeScript 集成更完善
  3. 向后兼容:平滑的迁移路径

迁移建议

  1. 渐进式升级:不需要一次性重写所有代码
  2. 从新功能开始:在新组件中尝试 React 19 特性
  3. 移除 forwardRef:逐步移除 forwardRef 包装
  4. 使用 Actions:用 Actions 替代手动的加载状态管理
  5. 采用 use Hook:在合适的场景使用 use 简化代码

React 19 是一个重大更新,它简化了开发流程,提升了性能,并引入了更符合现代 Web 开发需求的新特性。建议开发者尽早熟悉这些新特性,为项目升级做好准备。


React19新特性
https://zouhualu.github.io/20250401/React19新特性/
作者
花鹿
发布于
2025年4月1日
许可协议