incompatible-library

验证是否使用了与 memoization 不兼容的库(手动或自动)。

🌐 Validates against usage of libraries which are incompatible with memoization (manual or automatic).

注意

这些库是在 React 的记忆化规则完全记录之前设计的。当时,他们为了优化在应用状态变化时以最符合人机工程学的方式保持组件适度响应性,做出了正确的选择。虽然这些遗留模式有效,但我们后来发现它与 React 的编程模型不兼容。我们将继续与库作者合作,将这些库迁移到遵循 React 规则的模式。

🌐 These libraries were designed before React’s memoization rules were fully documented. They made the correct choices at the time to optimize for ergonomic ways to keep components just the right amount of reactive as app state changes. While these legacy patterns worked, we have since discovered that it’s incompatible with React’s programming model. We will continue working with library authors to migrate these libraries to use patterns that follow the Rules of React.

规则详情

🌐 Rule Details

一些库使用的模式不被 React 支持。当代码检测工具(linter)检测到使用这些 API 的情况时(来自已知列表),它会根据此规则进行标记。这意味着 React 编译器可以自动跳过使用这些不兼容 API 的组件,以避免破坏你的应用。

🌐 Some libraries use patterns that aren’t supported by React. When the linter detects usages of these APIs from a known list, it flags them under this rule. This means that React Compiler can automatically skip over components that use these incompatible APIs, in order to avoid breaking your app.

// Example of how memoization breaks with these libraries
function Form() {
const { watch } = useForm();

// ❌ This value will never update, even when 'name' field changes
const name = useMemo(() => watch('name'), [watch]);

return <div>Name: {name}</div>; // UI appears "frozen"
}

React 编译器会根据 React 规则自动缓存值。如果使用手动 useMemo 出现问题,编译器的自动优化也会出现问题。这个规则有助于识别这些有问题的模式。

🌐 React Compiler automatically memoizes values following the Rules of React. If something breaks with manual useMemo, it will also break the compiler’s automatic optimization. This rule helps identify these problematic patterns.

深入研究

设计遵循 React 规则的 API

🌐 Designing APIs that follow the Rules of React

在设计库 API 或钩子时需要考虑的一个问题是,调用该 API 是否可以安全地使用 useMemo 进行记忆化。如果不能,那么手动记忆化和 React 编译器的记忆化都会破坏用户的代码。

🌐 One question to think about when designing a library API or hook is whether calling the API can be safely memoized with useMemo. If it can’t, then both manual and React Compiler memoizations will break your user’s code.

例如,其中一种不兼容的模式是“内部可变性”。内部可变性是指对象或函数保持它自己的隐藏状态,该状态会随时间变化,即使对它的引用保持不变。可以把它想象成一个外表看起来一样的盒子,但内部的内容会偷偷地重新排列。React 无法判断有什么变化,因为它只检查你是否提供了不同的盒子,而不关心里面的内容。这会破坏备忘化(memoization),因为 React 依赖外部对象(或函数)的变化来判断其值的部分是否发生了变化。

🌐 For example, one such incompatible pattern is “interior mutability”. Interior mutability is when an object or function keeps its own hidden state that changes over time, even though the reference to it stays the same. Think of it like a box that looks the same on the outside but secretly rearranges its contents. React can’t tell anything changed because it only checks if you gave it a different box, not what’s inside. This breaks memoization, since React relies on the outer object (or function) changing if part of its value has changed.

作为经验法则,在设计 React API 时,考虑一下 useMemo 是否会破坏它:

🌐 As a rule of thumb, when designing React APIs, think about whether useMemo would break it:

function Component() {
const { someFunction } = useLibrary();
// it should always be safe to memoize functions like this
const result = useMemo(() => someFunction(), [someFunction]);
}

相反,设计返回不可变状态并使用显式更新函数的 API:

🌐 Instead, design APIs that return immutable state and use explicit update functions:

// ✅ Good: Return immutable state that changes reference when updated
function Component() {
const { field, updateField } = useLibrary();
// this is always safe to memo
const greeting = useMemo(() => `Hello, ${field.name}!`, [field.name]);

return (
<div>
<input
value={field.name}
onChange={(e) => updateField('name', e.target.value)}
/>
<p>{greeting}</p>
</div>
);
}

无效

🌐 Invalid

此规则的错误代码示例:

🌐 Examples of incorrect code for this rule:

// ❌ react-hook-form `watch`
function Component() {
const {watch} = useForm();
const value = watch('field'); // Interior mutability
return <div>{value}</div>;
}

// ❌ TanStack Table `useReactTable`
function Component({data}) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
// table instance uses interior mutability
return <Table table={table} />;
}

易犯错误

MobX

observer 这样的 MobX 模式也会破坏记忆化假设,但 linter 目前尚未检测到它们。如果你依赖 MobX 并发现你的应用在 React 编译器下无法正常工作,你可能需要使用 "use no memo" directive

🌐 MobX patterns like observer also break memoization assumptions, but the linter does not yet detect them. If you rely on MobX and find that your app doesn’t work with React Compiler, you may need to use the "use no memo" directive.

// ❌ MobX `observer`
const Component = observer(() => {
const [timer] = useState(() => new Timer());
return <span>Seconds passed: {timer.secondsPassed}</span>;
});

有效

🌐 Valid

此规则的正确代码示例:

🌐 Examples of correct code for this rule:

// ✅ For react-hook-form, use `useWatch`:
function Component() {
const {register, control} = useForm();
const watchedValue = useWatch({
control,
name: 'field'
});

return (
<>
<input {...register('field')} />
<div>Current value: {watchedValue}</div>
</>
);
}

一些其他库尚未提供与 React 的记忆化模型兼容的替代 API。如果 linter 没有自动跳过调用这些 API 的组件或钩子,请 提交问题,以便我们将其添加到 linter 中。

🌐 Some other libraries do not yet have alternative APIs that are compatible with React’s memoization model. If the linter doesn’t automatically skip over your components or hooks that call these APIs, please file an issue so we can add it to the linter.