Skip to content

useExhaustiveDependencies

¥Summary

¥How to configure

biome.json
{
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": "error"
}
}
}
}

¥Description

强制 React Hooks 中正确使用依赖。

¥Enforce correct dependency usage within React hooks.

React 组件可以访问各种 hooks,这些 hooks 可以执行各种操作,例如查询和更新状态。

¥React components have access to various hooks that can perform various actions like querying and updating state.

对于变量更改时触发的钩子(例如 useEffectuseMemo),React 依赖于钩子列出的依赖数组来确定何时重新计算 Effects 并重新渲染页面。

¥For hooks that trigger whenever a variable changes (such as useEffect and useMemo), React relies on the hook’s listed dependencies array to determine when to re-compute Effects and re-render the page.

当依赖指定不正确时,这可能会导致意外行为:

¥This can lead to unexpected behavior when dependencies are incorrectly specified:

function ticker() {
const [count, setCount] = useState(0);
/** Increment the count once per second. */
function onTick() {
setCount(count + 1);
}
// React _thinks_ this code doesn't depend on anything else, so
// it will only use the _initial_ version of `onTick` when rendering the component.
// As a result, our normally-dynamic counter will always display 1!
// This is referred to as a "stale closure", and is a common pitfall for beginners.
useEffect(() => {
const id = setInterval(onTick, 1000);
return () => clearInterval(id);
}, []);
return <h1>Counter: {count}</h1>;
}
function apples() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("We have 0 apples!");
// React _thinks_ this code depends on BOTH `count` and `message`, and will re-run the hook whenever
// `message` is changed despite it not actually being used inside the closure.
// In fact, this will create an infinite loop due to our hook updating `message` and triggering itself again!
useEffect(() => {
setMessage(`We have ${count} apples!`)
}, [count, message]);
}

此规则旨在通过诊断钩子依赖的潜在错误或无效用法来防止此类问题。

¥This rule attempts to prevent such issues by diagnosing potentially incorrect or invalid usages of hook dependencies.

¥Default Behavior

默认情况下,以下钩子(及其对应的 Preact 钩子)的参数将受此规则检查:

¥By default, the following hooks (and their Preact counterparts) will have their arguments checked by this rule:

  • useEffect

  • useLayoutEffect

  • useInsertionEffect

  • useCallback

  • useMemo

  • useImperativeHandle

¥Stable results

当已知某个钩子具有稳定的返回值(即在多次调用中返回值保持不变)时,该值不需要也不应被指定为依赖。例如,React 的 useState hook 返回的 setter 在程序的整个生命周期内都不会改变,因此应该省略。

¥When a hook is known to have a stable return value (one whose identity doesn’t change across invocations), that value doesn’t need to and should not be specified as a dependency. For example, setters returned by React’s useState hook will not change throughout the lifetime of a program and should therefore be omitted.

默认情况下,以下钩子被认为具有稳定的返回值:

¥By default, the following hooks are considered to have stable return values:

  • useState(索引 1)

    ¥useState (index 1)

  • useReducer(索引 1)

    ¥useReducer (index 1)

  • useTransition(索引 1)

    ¥useTransition (index 1)

  • useRef

  • useEffectEvent

如果你想向规则的诊断添加自定义钩子或指定你自己的函数以获得稳定的结果,请参阅 options 部分以了解更多信息。

¥If you want to add custom hooks to the rule’s diagnostics or specify your own functions with stable results, see the options section for more information.

¥Examples

¥Invalid

import { useEffect } from "react";
function component() {
let a = 1;
useEffect(() => {
console.log(a);
}, []);
}
code-block.js:5:3 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook does not specify its dependency on a.

3 │ function component() {
4 │ let a = 1;
> 5 │ useEffect(() => {
^^^^^^^^^
6 │ console.log(a);
7 │ }, []);

This dependency is being used here, but is not specified in the hook dependency list.

4 │ let a = 1;
5 │ useEffect(() => {
> 6 │ console.log(a);
^
7 │ }, []);
8 │ }

React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.

Either include it or remove the dependency array.

Unsafe fix: Add the missing dependency to the list.

7 │ ··},·[a]);
+
import { useEffect } from "react";
function badComponent() {
let a = 1;
useEffect(() => {
console.log(a);
}, "not an array");
}
code-block.js:7:6 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This dependencies list is not an array literal.

5 │ useEffect(() => {
6 │ console.log(a);
> 7 │ }, “not an array”);
^^^^^^^^^^^^^^
8 │ }
9 │

Biome can’t statically verify whether you’ve passed the correct dependencies.
Replace the argument with an array literal and list your dependencies within it.

import { useEffect } from "react";
function component() {
let unused = 1;
useEffect(() => {}, [unused]);
}
code-block.js:5:5 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook specifies more dependencies than necessary: unused.

3 │ function component() {
4 │ let unused = 1;
> 5 │ useEffect(() => {}, [unused]);
^^^^^^^^^
6 │ }
7 │

This dependency can be removed from the list.

3 │ function component() {
4 │ let unused = 1;
> 5 │ useEffect(() => {}, [unused]);
^^^^^^
6 │ }
7 │

React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.

Unsafe fix: Remove the extra dependencies from the list.

5 │ ····useEffect(()·=>·{},·[unused]);
------
import { useEffect, useState } from "react";
function component() {
const [name, setName] = useState();
useEffect(() => {
console.log(name);
setName("i never change and don't need to be here");
}, [name, setName]);
}
code-block.js:5:3 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook specifies more dependencies than necessary: setName.

3 │ function component() {
4 │ const [name, setName] = useState();
> 5 │ useEffect(() => {
^^^^^^^^^
6 │ console.log(name);
7 │ setName(“i never change and don’t need to be here”);

This dependency can be removed from the list.

6 │ console.log(name);
7 │ setName(“i never change and don’t need to be here”);
> 8 │ }, [name, setName]);
^^^^^^^
9 │ }
10 │

React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.

Unsafe fix: Remove the extra dependencies from the list.

8 │ ··},·[name,·setName]);
---------
import { useEffect, useState } from "react";
function component() {
const name = "foo"
// name doesn't change, so specifying it is redundant
useEffect(() => {
console.log(name);
}, [name]);
}
code-block.js:6:3 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook specifies more dependencies than necessary: name.

4 │ const name = “foo”
5 │ // name doesn’t change, so specifying it is redundant
> 6 │ useEffect(() => {
^^^^^^^^^
7 │ console.log(name);
8 │ }, [name]);

This dependency can be removed from the list.

6 │ useEffect(() => {
7 │ console.log(name);
> 8 │ }, [name]);
^^^^
9 │ }
10 │

React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.

Unsafe fix: Remove the extra dependencies from the list.

8 │ ··},·[name]);
----
import { useEffect } from "react";
function component() {
let a = 1;
const b = a + 1;
useEffect(() => {
console.log(b);
}, []);
}
code-block.js:6:3 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook does not specify its dependency on b.

4 │ let a = 1;
5 │ const b = a + 1;
> 6 │ useEffect(() => {
^^^^^^^^^
7 │ console.log(b);
8 │ }, []);

This dependency is being used here, but is not specified in the hook dependency list.

5 │ const b = a + 1;
6 │ useEffect(() => {
> 7 │ console.log(b);
^
8 │ }, []);
9 │ }

React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.

Either include it or remove the dependency array.

Unsafe fix: Add the missing dependency to the list.

8 │ ··},·[b]);
+

¥Valid

import { useEffect } from "react";
function component() {
let a = 1;
useEffect(() => {
console.log(a);
}, [a]);
}
import { useEffect } from "react";
function component() {
const SECONDS_PER_DAY = 60 * 60 * 24;
useEffect(() => {
console.log(SECONDS_PER_DAY);
});
}
import { useEffect, useState } from "react";
function component() {
const [name, setName] = useState();
useEffect(() => {
console.log(name);
setName("");
}, [name]);
}

默认情况下,未从 React 导入的 Hooks 将被忽略(除非在 规则选项 中指定)。

¥Hooks not imported from React are ignored by default (unless specified inside rule options)

import type { EffectCallback, DependencyList } from "react";
// custom useEffect function
declare function useEffect(cb: EffectCallback, deps?: DependencyList): void;
function component() {
let name = "John Doe";
useEffect(() => {
console.log(name);
}, []);
}

¥Ignoring a specific dependency

有时你可能希望忽略有关特定依赖的诊断,而不禁用该钩子的所有 linting。为此,你可以在括号之间指定特定依赖的名称,如下所示:

¥Sometimes you may wish to ignore a diagnostic about a specific dependency without disabling all linting for that hook. To do so, you may specify the name of a specific dependency between parentheses, like this:

import { useEffect } from "react";
function component() {
let a = 1;
// biome-ignore lint/correctness/useExhaustiveDependencies(a): suppress dependency a
useEffect(() => {
console.log(a);
}, []);
}

如果你想忽略多个依赖,可以添加多个注释,并为每个注释添加原因:

¥If you wish to ignore multiple dependencies, you can add multiple comments and add a reason for each:

import { useEffect } from "react";
function component() {
let a = 1;
let b = 1;
// biome-ignore lint/correctness/useExhaustiveDependencies(a): suppress dependency a
// biome-ignore lint/correctness/useExhaustiveDependencies(b): suppress dependency b
useEffect(() => {
console.log(a, b);
}, []);
}

¥Options

允许指定自定义钩子(来自库或内部项目),这些钩子需要检查其依赖和/或已知具有稳定的返回值。

¥Allows specifying custom hooks (from libraries or internal projects) whose dependencies should be checked and/or which are known to have stable return values.

对于每个需要验证其依赖的钩子,你必须同时指定使用依赖的闭包的索引以及要验证的依赖数组的索引。

¥For every hook whose dependencies you want validated, you must specify the index of both the closure using the dependencies and the dependencies array to validate it against.

¥Example

biome.json
{
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": {
"options": {
"hooks": [
{
"name": "useLocation",
"closureIndex": 0,
"dependenciesIndex": 1
},
{
"name": "useQuery",
"closureIndex": 2,
"dependenciesIndex": 0
}
]
}
}
}
}
}
}

这将允许对以下代码片段进行检查:

¥This would enable checks on the following code snippets:

function Foo() {
let stateVar = 1;
useLocation(() => {console.log(stateVar)}, []);
}
code-block.js:3:3 lint/correctness/useExhaustiveDependencies  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook does not specify its dependency on stateVar.

1 │ function Foo() {
2 │ let stateVar = 1;
> 3 │ useLocation(() => {console.log(stateVar)}, []);
^^^^^^^^^^^
4 │ }
5 │

This dependency is being used here, but is not specified in the hook dependency list.

1 │ function Foo() {
2 │ let stateVar = 1;
> 3 │ useLocation(() => {console.log(stateVar)}, []);
^^^^^^^^
4 │ }
5 │

React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.

Either include it or remove the dependency array.

Unsafe fix: Add the missing dependency to the list.

3 │ ··useLocation(()·=>·{console.log(stateVar)},·[stateVar]);
++++++++
function Foo() {
let stateVar = 1;
useQuery([stateVar], "smthng", () => {console.log(stateVar)});
}

¥Configuring stable results

如前所述,lint 规则会考虑所谓的 稳定结果,并确保任何此类变量都不会被指定为依赖。

¥As previously discussed, the lint rule takes into account so-called stable results and will ensure any such variables are not specified as dependencies.

你可以通过以下四种方式之一指定自定义函数返回稳定结果:

¥You can specify custom functions as returning stable results in one of four ways:

  1. "stableResult": true - 将返回值标记为稳定。可以像这样配置的 React 钩子的一个例子是 useRef()

    ¥"stableResult": true — marks the return value as stable. An example of a React hook that would be configured like this is useRef().

  2. "stableResult": [1] — 期望返回值是一个数组,并将给定的索引标记为稳定。可以像这样配置的 React 钩子的一个例子是 useState()

    ¥"stableResult": [1] — expects the return value to be an array and marks the given indices as stable. An example of a React hook that would be configured like this is useState().

  3. "stableResult": 1 — 选项 2 ("stableResult": [1]) 的简写。适用于只有一个稳定返回值的 Hooks。

    ¥"stableResult": 1 — shorthand for option 2 ("stableResult": [1]). Useful for hooks that only have a single stable return.

  4. "stableResult": ["setValue"] — 期望返回值是一个对象,并将具有给定键的属性标记为稳定。

    ¥"stableResult": ["setValue"] — expects the return value to be an object and marks the properties with the given keys as stable.

¥Example

biome.json
{
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": {
"options": {
"hooks": [
{
"name": "useDispatch",
"stableResult": true
}
]
}
}
}
}
}
}

使用此配置,以下内容有效:

¥With this configuration, the following is valid:

const dispatch = useDispatch();
// No need to list `dispatch` as dependency since it doesn't change
const doAction = useCallback(() => dispatch(someAction()), []);

如果设置为 false,则该规则不会对传递给未使用依赖的钩子的未使用依赖触发诊断。

¥If set to false, the rule will not trigger diagnostics for unused dependencies passed to hooks that do not use them.

默认:true

¥Default: true

¥Example

biome.json
{
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": {
"options": {
"reportUnnecessaryDependencies": false
}
}
}
}
}
}
import { useEffect } from "react";
function Foo() {
let stateVar = 1;
// not used but still OK
useEffect(() => {}, [stateVar]);
}

如果启用此规则,它还会对完全缺少依赖数组的钩子触发诊断,要求任何缺少依赖的钩子显式指定一个空数组。

¥If enabled, the rule will also trigger diagnostics for hooks that lack dependency arrays altogether, requiring any hooks lacking dependencies to explicitly specify an empty array.

默认:false

¥Default: false

¥Example

biome.json
{
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": {
"options": {
"reportMissingDependenciesArray": true
}
}
}
}
}
}
function noArrayYesProblem() {
let stateVar = 1;
React.useEffect(() => {});
}
code-block.jsx:3:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

This hook does not have a dependencies array.

1 │ function noArrayYesProblem() {
2 │ let stateVar = 1;
> 3 │ React.useEffect(() => {});
^^^^^^^^^
4 │ }
5 │

React relies on hook dependencies to determine when to re-compute Effects.
Add an explicit array (i.e. []) and list the callback’s dependencies inside it.

¥Related links