You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+TS中如何让自定义Hook返回的更新函数无需加入依赖数组?

问题:自定义Hook的更新函数触发ESLint依赖警告,如何让它像useState的setState一样免依赖检查?

在React + TypeScript环境中,我希望创建一个自定义Hook,使其返回的更新函数拥有稳定的引用(类似useState返回的setState或useDispatch返回的dispatch),从而不会被ESLint的react-hooks/exhaustive-deps规则要求加入依赖数组。

现有自定义Hook代码如下:

import { useRef, useState, useEffect, useCallback } from 'react';

export type SetStateWithCallback<T> = (newState: React.SetStateAction<T>, callback?: Function) => void;

export function useStateWithCallback<T>(initState: T): [T, SetStateWithCallback<T>] {
    const [state, setState] = useState<T>(initState);
    const callbackRef = useRef<Function | null>(null);
    const setStateWithCallback = useCallback((newState: React.SetStateAction<T>, callback?: Function) => {
        callbackRef.current = typeof callback === 'function' ? callback : null;
        setState(newState);
    }, []);

    useEffect(() => {
        if (callbackRef.current) {
            callbackRef.current?.(state);
            callbackRef.current = null;
        }
    }, [state]);

    return [state, setStateWithCallback];
}

但使用该Hook时,ESLint仍会要求将返回的更新函数加入依赖数组,和内置setState的处理不同:

function MyTestComponent() {
    const [state, setState] = useState({});
    const [customState, setCustomState] = useStateWithCallback({});

    const test = useCallback(() => setState({}), []); // 无警告,正常
    const testCustom = useCallback(() => setCustomState({}), []); // 警告:React Hook useCallback has a missing dependency: 'setCustomState'. Either include it or remove the dependency array. eslint(hooks/exhaustive-deps)

    return null;
}

如何修改setCustomState,让它达到和setState相同的效果?


解决方案

ESLint的react-hooks/exhaustive-deps规则对React内置Hook返回的函数(如setState、dispatch)有特殊豁免,认为它们的引用始终稳定。但自定义Hook返回的函数,即使通过useCallback绑定了空依赖数组,规则仍会触发警告,因为它无法自动识别该函数的稳定性。

我们可以通过用useRef存储更新逻辑,并返回一个稳定的代理函数来解决这个问题,确保返回的更新函数引用永远不变,同时让ESLint认可其稳定性:

修改后的自定义Hook代码:

import { useRef, useState, useEffect, useCallback } from 'react';

export type SetStateWithCallback<T> = (newState: React.SetStateAction<T>, callback?: () => void) => void;

export function useStateWithCallback<T>(initState: T): [T, SetStateWithCallback<T>] {
    const [state, setState] = useState<T>(initState);
    const callbackRef = useRef<(() => void) | null>(null);
    // 用ref存储更新逻辑,确保每次渲染都能拿到最新的逻辑
    const updateStateRef = useRef<SetStateWithCallback<T>>();

    // 更新ref中的逻辑(每次渲染都会执行,但ref的引用不变)
    updateStateRef.current = (newState, callback) => {
        callbackRef.current = callback || null;
        setState(newState);
    };

    // 返回一个稳定的代理函数,依赖为空数组,引用永远不变
    const setStateWithCallback = useCallback((...args) => {
        updateStateRef.current?.(...args);
    }, []);

    useEffect(() => {
        if (callbackRef.current) {
            callbackRef.current();
            callbackRef.current = null;
        }
    }, [state]);

    return [state, setStateWithCallback];
}

原理说明

  • updateStateRef存储更新逻辑:每次组件渲染时,我们更新updateStateRef.current为最新的更新函数逻辑,但updateStateRef本身的引用在组件生命周期内始终不变。
  • 稳定的代理函数:通过useCallback创建一个依赖为空的代理函数,它只负责调用updateStateRef.current的逻辑。这个代理函数的引用永远不会改变,符合React内置更新函数的特性。
  • ESLint豁免:现在当你在useCallback或useEffect中使用setStateWithCallback时,ESLint不会再要求将其加入依赖数组,因为它的引用是稳定的,且依赖数组为空的useCallback已经明确了这一点。

替代方案(快速解决)

如果你不想修改Hook实现,也可以在使用时通过ESLint注释直接禁用该警告:

const testCustom = useCallback(() => setCustomState({}), []); // eslint-disable-next-line react-hooks/exhaustive-deps

但这种方法不够优雅,推荐优先使用上面的Hook修改方案。

内容的提问来源于stack exchange,提问作者Gerson Goulart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:13:15