在React 19中,@eslint-react/no-unstable-default-props和 @eslint-react/no-unstable-context-value这两个规则仍然相关吗?

前端开发 2026-07-09

我注意到ESLint的 React规则 no-unstable-default-propsno-unstable-context-value 在默认情况下是禁用的。它们似乎是有用的规则,关于通过使用 'use memo'useMemo() 来避免不必要的重新渲染。

2024年 12月发布的React 19引入了 React Compiler,它会自动处理记忆化。不过,useMemo 的文档页面对于在何种情形下它仍然相关并不清楚。它只是说React Compiler “减少了手动 useMemo 调用的需要”,然后按旧版React的文档列出所有 useMemo 的用例。在网上搜索时,我发现 useMemo “很少需要”,但具体何时仍然不清楚。

那么,在会触发 “no-unstable-default-props” 和 “no-unstable-context-value” 规则的情形下,useMemo 仍然需要吗?

解决方案

这些规则仍然相关,但应被理解为“引用稳定性”规则,而不是“总是添加 useMemo” 的规则。

React 19本身并不会改变JavaScript对象的身份(引用)。这仍然会在每次渲染时创建一个新的数组:

function List({ items = [] }) {
  // items is a new array whenever the prop is omitted
}

这也会在每次渲染时创建一个新的对象:

<MyContext.Provider value={{ foo: "bar" }}>
  {children}
</MyContext.Provider>

这正是这些规则所警告的点。no-unstable-default-props 规则明确指出,数组/对象/函数的默认值在解构被评估时会每次创建新的标识,并建议改用稳定的引用。no-unstable-context-value 规则同样针对上下文的问题:当提供者的值改变时,消费者会重新渲染,而一个新鲜的字面对象会让值在每次渲染时看起来像是改变了。

因此,在没有使用React Compiler的情况下,旧的建议仍然是正确的:

const EMPTY_ITEMS: Item[] = [];

function List({ items = EMPTY_ITEMS }) {
  // ...
}

以及:

function MyProvider({ children }) {
  const value = useMemo(() => ({ foo: "bar" }), []);

  return (
    <MyContext.Provider value={value}>
      {children}
    </MyContext.Provider>
  );
}

然而,开启React Compiler时,答案会改变。React的文档表示编译器在构建时自动优化React代码,并减少对手动 useMemouseCallbackReact.memo 的需求。useMemo 的文档也说同样的事情:编译器会自动对值和函数进行记忆化,减少对手动 useMemo 调用的需要。

关键在于,编译器并不是因为你安装了React 19就被启用。它是一个构建阶段的工具,你需要安装/配置,例如通过 babel-plugin-react-compiler。React的官方文档也描述了渐进式采用模式,包括仅对带注解的函数进行编译,使用 "use memo"

这也是为什么这些规则并非简单地“无用”。它们在以下情形仍然有用:

  1. 未启用编译器;
  2. 文件/组件未包含在编译采用范围内;
  3. 由于不受支持的代码或React规则违规,编译器跳过了某个组件;
  4. 你正在编写库代码,可能被不对你的源代码进行编译的应用所使用;
  5. 你希望对一些超出React渲染优化范围的东西保持稳定的引用,例如Effect依赖项或第三方API。

规则文档本身现在也承认这一点。no-unstable-default-props 将这视为在组件使用React Compiler的 "use memo" 指令时可以的:

function MyComponent({ items = [] }: MyComponentProps) {
  "use memo";
  return null;
}

规则文档指出,在React 19中,如果组件使用 "use memo",编译器会自动优化默认属性的引用稳定性,因此字面默认值是可以接受的。

同样,no-unstable-context-value 将此视为有效:

function MyComponentProvider() {
  "use memo";

  return (
    <MyContext.Provider value={{ foo: "bar" }}>
      <ExampleConsumer />
    </MyContext.Provider>
  );
}

因为编译器可以为已编译的组件稳定化该值。

因此,实际结论是:

  • 如果你没有使用React Compiler,保留这些规则或手动稳定化这些值。
  • 如果你在所有地方都使用React Compiler,且你的lint工具能够理解 "use memo",这些规则的重要性会降低,可能会显得太过冗杂。
  • 如果你以增量方式使用编译器,请保持启用,或至少保留在未被编译的代码部分。

另外,useMemo 并非在这些规则触发的所有情形下都是“必需”。对于默认属性,比较简单的修复通常是一个模块作用域的常量:

const EMPTY_ARRAY: string[] = [];

function Component({ items = EMPTY_ARRAY }) {
  // ...
}

对于上下文值,useMemo 仍然是一个普通的非编译修复:

function Provider({ children }) {
  const [user, setUser] = useState(null);

  const value = useMemo(() => ({ user, setUser }), [user]);

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

但在已编译的代码中,直接将对象字面量写在内联也可以,前提是组件确实编译成功。

最后一个警告:React Compiler并不能在语义上替代每一个可能的 useMemo。React的编译器文档表示 useMemouseCallback 仍然可以作为兜底方案,当你需要对某些值(尤其是用于Effect依赖项的值)进行精确控制时。编译器也并不会全局对每一个任意函数进行记忆化;它的记忆化是针对组件和钩子,且不会在不相关的组件之间共享。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章