The unspoken rules of React hooks
React hooks 的潜规则
Updated to include links to documentation that does exist on the react.dev site. I do think that it’s hard to discover and this concept is underemphasized relative to how important it is for working with React hooks, but as the team correctly points out, there are some docs for it.
更新以包括对 react.dev 网站上存在的文档的链接。我确实认为这些文档很难被发现,并且与其在使用 React 钩子时的重要性相比,这个概念被低估了,但正如团队正确指出的那样,确实有一些相关文档。
React hooks… I have mixed feelings about them. But they are a part of the system I work with, so here are some additional notes that it’s weird that some of the React docs don’t cover. Mostly this is about useEffect
.
React hooks……我对此有些复杂的感受。但它们是我工作中系统的一部分,所以这里有一些额外的笔记,奇怪的是一些 React 文档没有涉及。主要是关于useEffect
的。
The “rule of useEffect dependencies” is, in shorthand, that the dependency array should contain all of the variables referenced within the callback. So if you have a useEffect
like
“useEffect 依赖项的规则”简而言之就是,依赖数组应包含回调中引用的所有变量。因此,如果你有一个useEffect
像这样
const [x, setX] = useState(0);
useEffect(() => {
console.log(x);
}, []);
It is bad and wrong! You are referencing x
, and you need to include it in the deps array:
这很糟糕,错了!你正在引用 x
,你需要将它包含在 deps 数组中:
const [x, setX] = useState(0);
useEffect(() => {
console.log(x);
}, [x]);
But, this is not a universal rule. Some values are “known to be stable” and don’t need to be included in your dependencies array. You can see some of these in the eslint rule implementation:
但是,这不是一个普遍规则。有些值是“已知为稳定的”,不需要包含在你的依赖数组中。你可以在eslint 规则实现中看到其中的一些:
const [state, setState] = useState() / React.useState()
// ^^^ true for this reference
const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ true for this reference
const [state, dispatch] = useActionState() / React.useActionState()
// ^^^ true for this reference
const ref = useRef()
// ^^^ true for this reference
const onStuff = useEffectEvent(() => {})
// ^^^ true for this reference
False for everything else.
So, state setters, reducer dispatchers, action state dispatchers, refs, and the return value of useEffectEvent: these are all things you shouldn’t put in dependencies arrays, because they have stable values. Plus the startTransition
method you get out of a useTransition hook - that’s also stable, just not included in that source comment.
所以,状态设置器、reducer 派发器、动作状态派发器、refs,以及 useEffectEvent 的返回值:这些都是你不应该放入依赖数组中的东西,因为它们的值是稳定的。此外,从 useTransition 钩子中获得的 startTransition
方法也是稳定的,只是没有包含在那个源注释中。
Honestly, this is one of the things that annoys me most about hooks in React, which I touched on in my note about Remix: the useEffect
and useMemo
hooks rely heavily on the idea of object identity and stability. The difference between a function that changes per-render versus one whose identity stays the same is vast: if you plug an ever-changing method into useEffect
dependencies, the effect runs every render. But React’s documentation is underwhelming when it comes to documenting this - it isn’t mentioned in the docs for useEffect, useState, or any of the other hook API pages.
老实说,这是关于 React 中 hooks 让我最烦恼的事情之一,我在关于 Remix 的笔记中提到过:useEffect
和 useMemo
hooks 非常依赖于对象的身份和稳定性。每次渲染时变化的函数与身份保持不变的函数之间的差异是巨大的:如果将一个不断变化的方法插入到useEffect
的依赖项中,则每次渲染都会执行该效果。但是 React 的文档在这方面表现平平 - 在 useEffect、useState 或其他 hooks API 页面中没有提到这一点。
As Ricky Hanlon notes, there is a note about the set
method from useState being stable in the ‘Synchronizing with Effects’ documentation, and a dedicated guide to useEffect behavior. This is partly a discovery problem: I, and I think others, expected the stability (or instability) of return values of built-in hooks to be something documented alongside the hooks in their API docs, instead of in a topical guide.
正如Ricky Hanlon指出的,在“同步与效应”的文档中,有关于 useState 的set
方法稳定性的说明,还有关于 useEffect 行为的专门指南。这部分是一个发现问题:我,以及我认为其他人,都期望内置钩子返回值的稳定性(或不稳定性)能在其 API 文档中与钩子一同记录,而不是在主题指南中。
And what about third-party hooks? I use and enjoy Jotai, which provides a hook that looks a lot like useState
called useAtom
. Is the state-setter I get from Jotai stable, like the one I get from useState? I think so, but I’m not sure. What about the return values of useMutation
in tanstack-query, another great library. That is analogous to the setter function and, maybe it’s stable? I don’t know!
那么第三方钩子呢?我使用并喜欢Jotai,它提供了一个看起来很像useState
的钩子,叫做useAtom
。我从 Jotai 获得的状态设置器是否像从 useState 获得的那样稳定?我觉得是的,但我不确定。那么在tanstack-query中useMutation
的返回值呢?这与设置函数类似,也许它是稳定的?我不知道!
It’s both a pretty critical part of understanding React’s “dependencies” system, but it’s hard to know what’s stable and what’s not. On the bright side, if you include some stable value in a dependencies array, it’s fine - it’ll stay the same and it won’t trigger the effect to run or memo to recompute. But I wish that the stability or instability of the return values of hooks was more clearly part of the API guarantee of both React and third-party hook frameworks, and we had better tools for understanding what will, or won’t change.
理解 React 的“依赖”系统时,这是一个相当关键的部分,但很难知道什么是稳定的,什么不是。好消息是,如果你在依赖数组中包含了一些稳定的值,那没关系——它会保持不变,并不会触发效果运行或重新计算记忆。但是我希望钩子返回值的稳定性或不稳定性更清晰地成为 React 和第三方钩子框架的 API 保证的一部分,我们也希望有更好的工具来了解什么会变化,什么不会变化。