【问题标题】:How to let react consider returned value of custom hook as stable identity?如何让反应将自定义钩子的返回值视为稳定的身份?
【发布时间】:2021-01-26 09:02:27
【问题描述】:

在react文档中,我们可以找到:

注意

React 保证 setState 函数的身份是稳定的,并且不会在重新渲染时改变。这就是为什么从 useEffect 或 useCallback 依赖列表中省略是安全的。

有时候自定义hook也可以保证返回的函数身份是稳定的,能不能让react知道?


与 Jayce444 讨论后添加:

如果 react 不将自定义 hook 的返回值视为稳定标识,但我们将其从其他 hooks 的依赖列表中省略,npm 将报告警告

【问题讨论】:

  • 如果我理解你的问题,你不需要“让 React 知道”。您可以安全地从依赖项列表中省略该函数。 React 所做的只是查看依赖项。如果你的自定义钩子没有改变,那么 React 会看到
  • 但是 npm 会报告“缺少依赖项”的警告,“让反应知道”意味着让 npm 不报告任何警告
  • 你是说 Eslint 吗?这听起来像是 eslint 生成的警告。在这种情况下,您只需 disable eslint 对应该特定行。
  • @TaoChen 哦,我明白了。你可能不想这样做,我会写一个答案来说明原因
  • 所以问题应该是“如何通知 ESLint curom hook 的返回值是稳定的?”

标签: reactjs react-hooks eslint


【解决方案1】:

在您的情况下,您并不想仅仅隐藏自定义代码的警告。 React 为 setState 函数这样做是因为它引用了它自己的库中的某些东西。正如评论者所提到的,您可以禁用该特定行的 linting 规则,但最好只包含此依赖项。

当您编写代码时,您通常希望它与其上下文松散耦合,不对它的确切使用位置做出任何假设。虽然在您当前的用例中,您知道钩子中的函数没有改变,但将来可能会改变。考虑这个例子:

const useCustomHook = () => {
    const calculate = useCallback((number) => {
        // Do stuff here
    }, []);
    return ({ calculate });
};


const MyComponent = () => {
    const [number, setNumber] = useState(0);
    const { calculate } = useCustomHook();

    useEffect(() => {
        calculate(number);
    }, [number]);

    // rest of the component
};

简单的例子,你有一个从自定义钩子返回的记忆的calculate 函数,以及你的组件状态中的一个数字。当数字改变时,重新计算。您可以看到,我们已将 calculate 排除在 useEffect 依赖项之外,正如您希望在您的用例中所做的那样。

但是假设这发生了变化,我们将自定义钩子替换为这个:

const useCustomHook = () => {
    const someValue = useContext(someRandomContext);

    const calculateOne = (number) => {/* some code */};
    const calculateTwo = (number) => {/* some code */};

    const calculate = useCallback(someValue ? calculateOne : calculateTwo, [someValue]);

    return ({ calculate });
};

现在,当上下文值改变时,计算函数也会改变。但是,如果您的组件的 useEffect 中没有该依赖项,则计算实际上不会被触发,并且您现在的状态中将有一个陈旧/不正确的值。

虽然目前从技术上讲,这种依赖对您来说可能是多余的,但如果您进行防御性编程,您将避免像这样的错误,这些错误可能会让人头疼。特别是因为在使用自定义钩子时可以获得依赖链,以及使用其他钩子的钩子等。它实际上是依赖数组中的一些额外字符,最好只添加它并避免潜在的麻烦.

【讨论】:

    【解决方案2】:

    @Jayce444,非常感谢,我知道你的选择。

    我的目标是非常像useState这样声明hook,ThisHook = useState + immer(https://github.com/immerjs/immer)

    这是我的自定义钩子

    import produce, { Draft } from "immer";
    import { useCallback, useState } from "react";
    
    export type DraftMutation<T> = (draft: Draft<T>) => void;
    
    export function useImmerState<T>(
        initialValue: T
    ): [T, (mutation: DraftMutation<T>) => void] {
        const [value, setValue] = useState(initialValue);
        const setValueByImmer = useCallback((mutation: DraftMutation<T>) => {
            setValue(oldValue => {
                return produce(oldValue, draft => mutation(draft));
            });
        }, []);
        return [value, setValueByImmer];
    }

    然后,我们来讨论如何使用它。

    Setp 1,定义一个简单的类型,像这样:

    interface Point { 
        readonly x: number;
        readonly y: number;
    }

    Setp 2,使用我的自定义钩子是功能组件

    const [point, setPoint] = useImmerState<Point>({x: 0, y: 0});
    const onButtonClick = useCallback(() => {
        setPoint(draft => {
            draft.x++;
            draft.y++;
        });
    }, []); //Need not add 'setPoint' into the dependency list, and no eslint warining should appear

    你的demo非常棒,但是这个钩子可以保证它没有问题。这个钩子看起来很像useState,这就是我想要这个未来的原因。如果开发人员知道他/她在做什么,React 应该支持它。

    【讨论】:

      猜你喜欢
      • 2020-06-01
      • 2021-03-15
      • 1970-01-01
      • 1970-01-01
      • 2020-08-20
      • 1970-01-01
      • 2019-06-15
      • 2020-03-08
      • 2021-05-25
      相关资源
      最近更新 更多