返回文章列表

React 19 新特性详解

React 19 是 React 框架的一次重要升级,带来了诸多令人兴奋的新特性。其中最引人注目的包括 React Compiler 自动记忆化、Actions 模式、新的乐观更新 Hook(useOptimistic)、use API、文档元数据支持等。这些特性大幅简化了开发者的日常工作,让代码更简洁、性能更优秀。本文将深入讲解 React 19 的核心新特性,并提供迁移指南。

一、React Compiler 自动记忆化

1.1 背景:手动记忆化的痛点

在 React 19 之前,为了避免不必要的重渲染,开发者需要手动使用 useMemo、useCallback 和 React.memo 来记忆化值、函数和组件。这带来了几个问题:

1.2 React Compiler 解决方案

React Compiler 是一个编译时优化工具,它会自动分析组件代码,在需要的地方自动插入记忆化逻辑。这意味着开发者可以专注于业务逻辑,而无需手动管理 memo:

// React 18:需要手动 memo
import { useMemo, useCallback } from 'react';

function ProductList({ products, filterText }) {
    // 手动 memo 计算
    const filteredProducts = useMemo(() => {
        return products.filter(p =>
            p.name.toLowerCase().includes(filterText.toLowerCase())
        );
    }, [products, filterText]);

    // 手动 memo 函数
    const handleAddToCart = useCallback((product) => {
        addToCart(product);
    }, []);

    return (
        <ul>
            {filteredProducts.map(product => (
                <ProductItem
                    key={product.id}
                    product={product}
                    onAddToCart={handleAddToCart}
                />
            ))}
        </ul>
    );
}

// React 19:无需手动 memo,编译器自动处理
function ProductList({ products, filterText }) {
    // 编译器会自动 memo 这个计算
    const filteredProducts = products.filter(p =>
        p.name.toLowerCase().includes(filterText.toLowerCase())
    );

    // 编译器会自动 memo 这个函数
    const handleAddToCart = (product) => {
        addToCart(product);
    };

    return (
        <ul>
            {filteredProducts.map(product => (
                <ProductItem
                    key={product.id}
                    product={product}
                    onAddToCart={handleAddToCart}
                />
            ))}
        </ul>
    );
}

1.3 启用 React Compiler

// babel 配置
// babel.config.js
module.exports = {
    presets: [
        ['@babel/preset-react', { runtime: 'automatic' }]
    ],
    plugins: [
        // 启用 React Compiler
        ['babel-plugin-react-compiler']
    ]
};

// Vite 配置
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        react({
            babel: {
                plugins: [
                    ['babel-plugin-react-compiler']
                ]
            }
        })
    ]
});

1.4 Compiler 的规则

React Compiler 对代码有一些要求,违反这些规则可能导致编译失败或运行时错误:

// 错误:在渲染过程中修改 props
function BadComponent({ items }) {
    items.push(1); // 不要这样做
    return <div>{items.length}</div>;
}

// 正确:创建新数组
function GoodComponent({ items }) {
    const newItems = [...items, 1];
    return <div>{newItems.length}</div>;
}

二、Actions 与 useActionState

React 19 引入了 Actions 概念,统一了异步操作(如提交表单、数据获取)的处理方式。useActionState 是为此设计的核心 Hook。

2.1 旧方式 vs 新方式

// React 18:手动管理异步状态
import { useState } from 'react';

function FeedbackForm() {
    const [message, setMessage] = useState('');
    const [error, setError] = useState(null);
    const [isPending, setIsPending] = useState(false);

    async function handleSubmit(event) {
        event.preventDefault();
        setIsPending(true);
        setError(null);
        try {
            const response = await submitFeedback(message);
            if (response.error) {
                setError(response.error);
            } else {
                setMessage('');
                alert('提交成功!');
            }
        } catch (err) {
            setError(err.message);
        } finally {
            setIsPending(false);
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <textarea
                value={message}
                onChange={(e) => setMessage(e.target.value)}
            />
            {error && <p className="error">{error}</p>}
            <button type="submit" disabled={isPending}>
                {isPending ? '提交中...' : '提交'}
            </button>
        </form>
    );
}

// React 19:使用 useActionState 简化
import { useActionState } from 'react';

function FeedbackForm() {
    const [state, formAction, isPending] = useActionState(
        async (previousState, formData) => {
            const message = formData.get('message');
            try {
                const response = await submitFeedback(message);
                if (response.error) {
                    return { error: response.error };
                }
                return { success: true };
            } catch (err) {
                return { error: err.message };
            }
        },
        { error: null } // 初始状态
    );

    return (
        <form action={formAction}>
            <textarea name="message" />
            {state?.error && <p className="error">{state.error}</p>}
            {state?.success && <p className="success">提交成功!</p>}
            <button type="submit" disabled={isPending}>
                {isPending ? '提交中...' : '提交'}
            </button>
        </form>
    );
}

2.2 与表单原生集成

React 19 让 <form> 元素可以直接使用 action 属性,无需 onSubmit:

import { useActionState } from 'react';

function LoginForm() {
    const [state, loginAction, isPending] = useActionState(
        async (prev, formData) => {
            const username = formData.get('username');
            const password = formData.get('password');

            const result = await api.login(username, password);
            if (!result.success) {
                return { error: result.message };
            }
            // 登录成功后跳转
            window.location.href = '/dashboard';
            return null;
        },
        null
    );

    return (
        <form action={loginAction}>
            <input type="text" name="username" required />
            <input type="password" name="password" required />
            <button type="submit" disabled={isPending}>
                {isPending ? '登录中...' : '登录'}
            </button>
            {state?.error && <p>{state.error}</p>}
        </form>
    );
}

三、useOptimistic 乐观更新

useOptimistic 是 React 19 的另一个重要 Hook,用于实现乐观更新(Optimistic Updates)。乐观更新指的是在异步操作完成前,先假设操作成功并更新 UI,如果操作失败再回滚。这种模式能给用户更流畅的体验。

3.1 基本用法

import { useOptimistic, useState } from 'react';

function TodoApp() {
    const [todos, setTodos] = useState([
        { id: 1, text: '学习 React 19', completed: false }
    ]);

    // useOptimistic 提供乐观更新状态
    const [optimisticTodos, addOptimisticTodo] = useOptimistic(
        todos,
        (state, newTodo) => [
            ...state,
            { id: 'temp-' + Date.now(), text: newTodo, completed: false, pending: true }
        ]
    );

    async function addTodo(formData) {
        const text = formData.get('todo');

        // 立即显示乐观更新(UI 立即变化)
        addOptimisticTodo(text);

        // 异步发送请求
        try {
            const result = await api.addTodo(text);
            // 请求成功后用真实数据替换乐观数据
            setTodos(prev => [...prev, result]);
        } catch (err) {
            // 请求失败,useOptimistic 会自动回滚
            console.error('添加失败:', err);
        }
    }

    return (
        <div>
            <form action={addTodo}>
                <input name="todo" placeholder="添加待办事项" />
                <button type="submit">添加</button>
            </form>

            <ul>
                {optimisticTodos.map(todo => (
                    <li
                        key={todo.id}
                        style={{ opacity: todo.pending ? 0.5 : 1 }}
                    >
                        {todo.text}
                        {todo.pending && <span> (添加中...)</span>}
                    </li>
                ))}
            </ul>
        </div>
    );
}

3.2 点赞功能示例

import { useOptimistic, useState } from 'react';

function LikeButton({ initialCount, postId }) {
    const [likeCount, setLikeCount] = useState(initialCount);

    // 乐观更新:点击立即 +1,失败回滚
    const [optimisticCount, addOptimistic] = useOptimistic(
        likeCount,
        (state, delta) => state + delta
    );

    async function handleLike() {
        addOptimistic(1); // 立即显示 +1
        try {
            const result = await api.like(postId);
            setLikeCount(result.count); // 用真实数据替换
        } catch (err) {
            // 自动回滚到 likeCount
            console.error('点赞失败:', err);
        }
    }

    return (
        <button onClick={handleLike}>
            ❤ {optimisticCount}
        </button>
    );
}

四、use API

use 是 React 19 新增的一个 API(注意它不是 Hook,而是一个普通函数),用于读取 Promise 或 Context 的值。与 Hook 不同,use 可以在条件语句中调用。

4.1 读取 Promise

import { use, Suspense } from 'react';

// 异步数据获取函数
async function fetchUser(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
}

function UserProfile({ userPromise }) {
    // use 会暂停组件渲染,直到 Promise resolve
    // 需要在 Suspense 边界内使用
    const user = use(userPromise);

    return (
        <div>
            <h1>{user.name}</h1>
            <p>{user.email}</p>
            <p>注册时间:{user.createdAt}</p>
        </div>
    );
}

// 父组件
function App({ userId }) {
    // 在父组件创建 Promise
    const userPromise = fetchUser(userId);

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

4.2 读取 Context(可在条件语句中调用)

import { use, createContext } from 'react';

const ThemeContext = createContext('light');
const UserContext = createContext(null);

function Header({ isAuthenticated }) {
    // use 可以在条件语句中调用(Hook 不行)
    const theme = use(ThemeContext);

    let greeting = '游客';
    if (isAuthenticated) {
        // 在 if 中使用 use 读取 Context
        const user = use(UserContext);
        greeting = `你好,${user.name}`;
    }

    return (
        <header className={`header header-${theme}`}>
            {greeting}
        </header>
    );
}

4.3 结合 Suspense 处理加载状态

import { use, Suspense } from 'react';

// 多个异步请求并行处理
async function fetchProfile() {
    const [user, posts, friends] = await Promise.all([
        fetch('/api/user').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/friends').then(r => r.json())
    ]);
    return { user, posts, friends };
}

function ProfileContent({ profilePromise }) {
    const { user, posts, friends } = use(profilePromise);

    return (
        <div>
            <h1>{user.name} 的主页</h1>
            <section>
                <h2>文章 ({posts.length})</h2>
                {posts.map(post => <Article key={post.id} post={post} />)}
            </section>
            <section>
                <h2>好友 ({friends.length})</h2>
                {friends.map(friend => <UserCard key={friend.id} user={friend} />)}
            </section>
        </div>
    );
}

function ProfilePage() {
    return (
        <Suspense fallback={<div className="loading">加载中...</div>}>
            <ProfileContent profilePromise={fetchProfile()} />
        </Suspense>
    );
}

五、文档元数据支持

React 19 原生支持在组件中渲染 <title>、<meta>、<link> 等元数据标签,React 会自动将它们提升到 <head> 中。这消除了对 react-helmet 等第三方库的需求。

// React 19:直接在组件中写元数据
function BlogPost({ post }) {
    return (
        <article>
            {/* 这些标签会被自动提升到 <head> */}
            <title>{post.title} - 我的博客</title>
            <meta name="description" content={post.excerpt} />
            <meta name="keywords" content={post.tags.join(', ')} />
            <meta property="og:title" content={post.title} />
            <meta property="og:description" content={post.excerpt} />
            <meta property="og:image" content={post.coverImage} />
            <link rel="canonical" href={`https://example.com/blog/${post.slug}`} />

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

// 还可以用于动态样式表和脚本
function PageWithCustomStyles() {
    return (
        <div>
            <link rel="stylesheet" href="/page-specific.css" />
            <style>{`
                .hero { background: linear-gradient(135deg, #667eea, #764ba2); }
            `}</style>

            <section className="hero">欢迎</section>
        </div>
    );
}

六、ref 作为 prop 传递

React 19 中,ref 可以直接作为普通 prop 传递给函数组件,无需再使用 forwardRef。

// React 18:需要使用 forwardRef
import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
    return <input ref={ref} {...props} />;
});

// 使用
function App() {
    const inputRef = useRef(null);
    return <MyInput ref={inputRef} />;
}

// React 19:直接通过 props 传递 ref
function MyInput({ ref, ...props }) {
    return <input ref={ref} {...props} />;
}

// 使用方式不变
function App() {
    const inputRef = useRef(null);
    return <MyInput ref={inputRef} placeholder="请输入" />;
}

对于需要暴露自定义方法给 ref 的情况,使用 useImperativeHandle:

import { useRef, useImperativeHandle } from 'react';

function CustomInput({ ref }) {
    const inputRef = useRef(null);

    // 暴露自定义方法
    useImperativeHandle(ref, () => ({
        focus: () => inputRef.current?.focus(),
        clear: () => {
            if (inputRef.current) inputRef.current.value = '';
        },
        getValue: () => inputRef.current?.value
    }));

    return <input ref={inputRef} />;
}

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

    function handleFocus() {
        inputRef.current?.focus();
    }

    function handleClear() {
        inputRef.current?.clear();
    }

    return (
        <div>
            <CustomInput ref={inputRef} />
            <button onClick={handleFocus}>聚焦</button>
            <button onClick={handleClear}>清空</button>
        </div>
    );
}

七、异步脚本支持

React 19 改进了对异步脚本(<script async>)的处理。你可以在组件内部渲染 <script> 标签,React 会自动去重并正确处理加载顺序:

// React 19:在组件中使用异步脚本
function MapWidget() {
    return (
        <div>
            {/* 即使多个组件都渲染这个脚本,React 也只会加载一次 */}
            <script async src="https://maps.googleapis.com/maps/api/js?key=API_KEY" />

            <div id="map" style={{ width: '100%', height: '400px' }}>
                地图加载中...
            </div>
        </div>
    );
}

// 内联脚本
function Analytics() {
    return (
        <script>{`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', 'GA_TRACKING_ID');
        `}</script>
    );
}

八、其他改进

8.1 Context 简化

// React 18:需要 Provider
const ThemeContext = createContext('light');

function App() {
    return (
        <ThemeContext.Provider value="dark">
            <Page />
        </ThemeContext.Provider>
    );
}

// React 19:可以直接使用 Context 作为 Provider
const ThemeContext = createContext('light');

function App() {
    return (
        <ThemeContext value="dark">
            <Page />
        </ThemeContext>
    );
}

8.2 ref 回调函数清理

// React 19:ref 回调可以返回清理函数
function InputWithCleanup() {
    return (
        <input
            ref={(element) => {
                // 设置逻辑
                element?.focus();

                // 返回清理函数(React 19 新增)
                return () => {
                    console.log('元素被卸载,执行清理');
                };
            }}
        />
    );
}

8.3 错误信息改进

React 19 提供了更清晰的错误信息,并附带原始错误堆栈,方便调试:

// 自定义错误处理
function RootErrorBoundary({ children }) {
    const [error, setError] = useState(null);

    useEffect(() => {
        const handler = (event) => {
            setError(event.error);
            event.preventDefault();
        };
        window.addEventListener('error', handler);
        return () => window.removeEventListener('error', handler);
    }, []);

    if (error) {
        return (
            <div className="error-boundary">
                <h2>出错了</h2>
                <p>{error.message}</p>
                <details>
                    <summary>错误堆栈</summary>
                    <pre>{error.stack}</pre>
                </details>
                <button onClick={() => setError(null)}>重试</button>
            </div>
        );
    }

    return children;
}

九、迁移指南

9.1 升级到 React 19

# 使用 npm
npm install react@19 react-dom@19

# 使用 yarn
yarn add react@19 react-dom@19

# 使用 pnpm
pnpm add react@19 react-dom@19

9.2 代码迁移清单

9.3 渐进式迁移示例

// 迁移前:React 18 写法
import { forwardRef, useMemo, useCallback, useState } from 'react';

const SearchInput = forwardRef(function SearchInput({ value, onChange }, ref) {
    return <input ref={ref} value={value} onChange={onChange} />;
});

function SearchBar({ items }) {
    const [query, setQuery] = useState('');
    const inputRef = useRef(null);

    const filteredItems = useMemo(() => {
        return items.filter(item =>
            item.name.includes(query)
        );
    }, [items, query]);

    const handleChange = useCallback((e) => {
        setQuery(e.target.value);
    }, []);

    return (
        <div>
            <SearchInput ref={inputRef} value={query} onChange={handleChange} />
            <ul>
                {filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
            </ul>
        </div>
    );
}

// 迁移后:React 19 写法(启用 Compiler)
function SearchInput({ ref, value, onChange }) {
    return <input ref={ref} value={value} onChange={onChange} />;
}

function SearchBar({ items }) {
    const [query, setQuery] = useState('');
    const inputRef = useRef(null);

    // 无需 useMemo,编译器自动优化
    const filteredItems = items.filter(item =>
        item.name.includes(query)
    );

    // 无需 useCallback,编译器自动优化
    const handleChange = (e) => {
        setQuery(e.target.value);
    };

    return (
        <div>
            <SearchInput ref={inputRef} value={query} onChange={handleChange} />
            <ul>
                {filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
            </ul>
        </div>
    );
}

十、总结

React 19 带来的新特性显著改善了开发体验:

这些特性让 React 代码更加简洁、直观,同时保持了优秀的性能。建议在新项目中直接使用 React 19,现有项目可以参考迁移指南逐步升级。React Compiler 虽然是可选的,但一旦启用,将大幅减少模板代码,让开发者真正专注于业务逻辑的实现。

动手挑战

学到这里,不妨动手试一试以下练习,巩固你的理解:

  1. 基础练习:回顾本文核心概念,用自己的话总结关键知识点。
  2. 进阶实践:将文中的示例代码运行一遍,尝试修改参数观察变化。
  3. 拓展思考:想一想这个技术/方法还能应用在哪些场景中?

小贴士:遇到问题时,先独立思考,再查阅资料,最后请教他人——这是成长最快的学习方式。

赞赏支持

本文更新于 2026-08-22,环境 Python 3.12