如何让ESLint的react-hooks/exhaustive-deps检测无点式函数依赖问题
react-hooks/exhaustive-deps Detect Indirect Closure Dependencies Great question! I’ve run into similar frustrations with ESLint’s react-hooks/exhaustive-deps rule missing indirect dependencies like the one in your example. Let’s break down why this happens and what you can do about it without rewriting your existing code.
Why ESLint Isn’t Catching This
The react-hooks/exhaustive-deps rule relies on static code analysis—it can only track variables that are directly referenced in the callback function or explicitly listed in the dependency array. In your code, always(value) returns a closure that references value, but ESLint can’t peek into the implementation of the external always function to detect this indirect dependency. It just sees a function call as the first argument to useCallback, and doesn’t dig into what that returned function captures.
Solutions to Trigger Alerts Without Rewriting Code
1. Write a Custom ESLint Rule (Most Precise)
This is the most robust way to handle your specific scenario. You can create a custom ESLint rule that scans for useCallback (or useEffect) calls where the first argument is a function invocation (like always(value)), then checks if any of the arguments passed to that function are component state/props variables. If they are, the rule can throw an error reminding you to add those variables to the dependency array.
Here’s a rough outline of how to implement this:
- Set up a new ESLint plugin project (using tools like
yo eslint:pluginor manually) - Traverse the AST to find
useCallbackcall expressions - Check if the first argument is a
CallExpression(i.e., a function being invoked) - Extract the arguments of that call expression and verify if they’re local state/props variables
- If yes, report an error requiring those variables to be added to the dependency array
2. Enforce Arrow Functions for useCallback Arguments
You can use a custom ESLint rule (or extend an existing one) to force useCallback’s first argument to be an arrow function or a locally defined function, instead of a call to an external function like always. This eliminates the scenario where ESLint can’t track indirect dependencies altogether.
For example, you could add a rule like this to your .eslintrc:
{ "rules": { "custom/use-callback-arrow-only": ["error", { "allowLocalFunctions": true }] } }
This rule would flag useCallback(always(value), []) as invalid, pushing you to use useCallback(() => always(value)(), [value])—which ESLint’s built-in rule will then correctly analyze.
3. Use TypeScript as a Safety Net (Optional)
If your project uses TypeScript, you can add type annotations to functions like always to make dependencies more explicit. While this won’t trigger ESLint alerts directly, it can help catch unintended closure captures during development and make it easier to reason about which variables should be in your dependency arrays.
A Note on the "Expected Behavior"
This is indeed expected behavior for the built-in react-hooks/exhaustive-deps rule. Static analysis has limits, and tracking indirect closures from external functions isn’t something it’s designed to handle out of the box. The team behind the rule has mentioned that deep closure analysis would add too much complexity and performance overhead for general use cases.
内容的提问来源于stack exchange,提问作者Jemi Salo

