现代React核心特性指南(截止到19.2)

112 阅读7分钟

近期,React 正式发布了 v19.2 版本,引入了多项备受期待的特性,例如原生的 KeepAlive 组件 <Activity />(其在 v18 的实验版本中名为 <Offscreen>),以及构建时优化工具 React Compiler v1.0。自 v18 发布以来,许多新特性尚未在项目中充分实践。面对 v19 这一重要更新,有必要系统梳理其核心变化,评估哪些特性适合引入现有项目。本文基于 React 官方文档(未涉及服务端及相关实验性特性),对个人认为较关键内容进行梳理与总结。

1. React Compiler:自动化性能优化

React Compiler 是一个构建时工具,能够自动为组件和 Hooks 应用最优的记忆化(Memoization)策略。开发者无需手动使用 memo()useMemo() 或 useCallback(),编译器会自动优化普通函数式组件和 Hooks 中定义的变量与函数,显著降低开发时的心智负担。

  • 优化范围:组件或 Hooks 内部定义的变量、函数及使用的 Hooks 均会被记忆化。
  • 子组件优化:所有子组件会自动记忆化。当父组件重新渲染但未影响子组件时,子组件不会重新渲染。
  • 作用域:记忆化结果仅限于当前组件或 Hooks 内部,不与其他组件或 Hooks 共享。

通过以下示例可以直观看到编译后的记忆化效果: react-compiler-result.png

2. 异步处理:提升用户体验

React 提供了一系列管理异步状态和优化用户体验的特性:

  • 回退界面与加载状态

    • Suspense:在组件等待异步操作时显示回退界面。
    • lazy:延迟加载组件代码,可与 Suspense 配合显示加载状态。
    • use:直接读取异步资源(如 Promise),也可与 Suspense 配合。
  • 非阻塞更新(Transition)

    • startTransition:将更新标记为非紧急,避免阻塞用户交互。
    • useTransition:功能同 startTransition,同时提供过渡状态。
    • useDeferredValue:延迟更新某个值,将更新分为紧急与非紧急部分。

这些特性适用于不同场景,也可组合使用。核心目标是将非紧急更新标记为低优先级,确保 React 能够及时响应用户操作,提升界面流畅度。

<Suspense /> 用于包裹任意层级的异步组件,在异步操作进行时显示备用内容,操作完成后渲染目标组件。其触发条件是子组件树中必须有能够抛出 Promise 的组件<Suspense /> 通过拦截该 Promise 决定显示内容。支持此机制的包括 Next.js 等框架、lazy 加载的组件,以及使用 use 读取 Promise 的组件。

useDeferredValue 和 startTransition 分别用于标记操作(如表单提交、数据请求)为低优先级,使其不阻塞用户的其他交互。它们会使 <Suspense /> 显示旧值而非备用方案(fallback)。React 会在非紧急更新完全准备就绪后直接呈现新内容,避免加载状态闪烁。

注意

  • use 除了读取 Promise,还可便捷地读取 Context 值。读取Context时,可以在 if-else 等非组件顶层的代码中调用,但不可在 try-catch 块中调用。
  • 若 startTransition 中的动作函数包含 await 异步调用,则其后的代码需再次用 startTransition 包裹,以确保被标记为 Transition 状态。

3. <Activity />:原生 KeepAlive 支持

<Activity /> 是 React 官方提供的 KeepAlive 组件,已正式发布(其原实验版本名为 <Offscreen />)。

在多 Tab 页需保持各页面状态的应用(如管理后台)中,以往方案包括:使用简单 Tabs 组件(仅样式隐藏)、依赖 react-activation 库(性能更优,需关闭 <StrictMode>)。现在,官方 <Activity /> 组件可高效实现此功能:

  • 隐藏时:使用 display: "none" 视觉隐藏,并销毁 Effects、清理活动订阅。
  • 隐藏时渲染:子组件仍会根据 props 和 Context 低优先级重新渲染;但 useSyncExternalStore 订阅的外部状态变化不会触发渲染,仅在可见时更新。
  • 再次可见时:恢复子组件先前状态,并重新创建其 Effects。
  • 初始隐藏时:若组件初始渲染即被隐藏,仍会以低优先级渲染,但不会挂载 Effects(若使用 use 加载数据,则数据请求仍会执行)。

服务器渲染优化:React 支持选择性水合(Selective Hydration),<Activity /> 可加速初始 HTML 的分块水合,提升可交互时间。<Suspense> 和 <Activity /> 均能将组件分解为独立单元,帮助 React 优先处理页面的可交互部分,优化应用性能。

4. Refs 的简化使用

从 v19 开始,ref 可直接作为子组件的 props 传递,无需使用 forwardRef 包裹子组件。useImperativeHandle 可用于暴露自定义内容至 ref,而非直接暴露内部 DOM。

function MyForm() {
    const inputRef = useRef()
    useEffect(()=>{
        inputRef.current.focus();
    },[]);
    return <form><MyInput ref={inputRef}/></form>
}

// 子组件直接将 ref 作为 props 传递,对外暴露子组件dom元素
function MyInput({ ref }) {  
    return <div><input ref={ref} /></div>;  
}

// 使用useImperativeHandle指定暴露内容,而不是直接暴露子组件的dom
function MyInput2({ ref }) {
    const myInputRef = useRef();
    
    useImperativeHandle(ref, () => {
        // 对外暴露一个对象,可任意定义
        return {
            focus() {  
                myInputRef.current.focus();  
            },   
        };  
    }, []);
    
    return <div><input ref={myInputRef} /></div>;  
}

最佳实践:应优先使用 props 进行组件间通信,仅在必要时(如聚焦、滚动等 imperative 操作)使用 ref

5. 状态管理方案

React 提供了多种粒度状态管理工具:

  • useContext / createContext:跨组件层级传递全局状态。
  • useState:管理组件或 Hooks 内部单一状态。
  • useReducer:整合状态与更新逻辑,适用于复杂状态。
  • useSyncExternalStore:订阅外部数据源。
  • useActionState:处理表单提交状态(特定场景)。
  • use:加载异步数据(特定场景)。

其中,useActionState 和 use 适用于特定场景,通常不视为通用数据模型方案。

对于全局状态,可使用 Context 存储,并通过 useContext 或 use 在子组件中访问。组件内部简单状态适用 useState,复杂状态可使用 useReducer。为进一步简化不可变数据操作,可引入 Immer,使用 useImmer 或 useImmerReducer

还可组合 ContextReducer 与 State:使用 Context + Reducer 管理全局状态,组件内部使用 useState 管理局部状态。

useSyncExternalStore 可用于订阅外部数据源,也可基于此封装自定义状态管理 Hook(需处理监听、触发与取消订阅逻辑)。对于复杂全局状态,建议使用成熟状态管理库(如 Redux Toolkit、Zustand)。

在新版本中已经废弃了Context.Provider

// 创建Context
const ThemeContext = createContext('light');

// 在组件中使用,不再用ThemeContext.Provider
<ThemeContext value="dark">
   <Form />  
</ThemeContext>

6. Effect 的最佳实践

  • useEffect:将组件与外部系统同步,在屏幕渲染后执行。
  • useEffectEvent:将 Effect 中的非响应式逻辑提取为可复用函数,该函数始终可访问最新的 props 和 state(仅可在 useEffect 内部调用)。
  • useLayoutEffect:在浏览器重新绘制屏幕前触发(可能影响性能)。
  • useInsertionEffect:在 useLayoutEffect 触发前将元素插入 DOM,主要用于库开发。

useEffect 易被误用,React 官方文档对此有详细说明。每个 useEffect 应对应一个独立的行为过程,其依赖变量应仅与该行为相关。具体执行逻辑或涉及更多依赖变量时,应使用 useEffectEvent。即:useEffect 仅负责对外部事务的订阅与清理,具体业务逻辑交由 useEffectEvent 处理。

useEffect 应仅用于执行  “组件显示给用户时需要执行的代码” ,以下为常见误用场景及纠正方案:

  • 避免基于 state 调整其他 state:应在事件处理函数或渲染期间计算。
  • 避免 props 变化时调整 state:应在渲染期间直接计算。
  • 避免转换渲染数据:数据转换应在渲染前完成。
  • 避免处理用户事件:应使用事件处理函数。

7. 其他

  • 严格模式<StrictMode> 在开发环境下会重复执行渲染、Effect 和 refs,以检测非纯函数导致的错误。
  • useId:用于生成唯一 ID,不可用于列表 key(key 应由数据生成)。
  • 内置组件支持:支持所有 HTML 和 SVG 组件。
  • 文档元数据<link><meta><script><style><title> 可直接在组件中使用,React 会自动将其移至 DOM 头部。
  • 表单增强:优化服务器端传输,提供配套 Hooks:
    • action 属性支持函数,可与 useActionState 配合。
    • useActionState:管理表单提交状态。
    • useFormStatus:获取最近 <form> 的状态与数据。
    • useOptimistic:乐观更新界面,错误时自动回滚。
  • 类型区分ReactNode 包含基础类型,ReactElement 特指 createElement 创建的组件。

以上内容基于个人对 React 新特性的理解与总结,如有疏漏或更新,请以官方文档为准。