如何让反应将自定义钩子的返回值视为稳定身份?

时间:2020-10-12 03:12:32

标签: reactjs react-hooks eslint

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

注意

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

有时,自定义钩子还可以确保返回的函数身份稳定,是否可以让React知道呢?


在与Jayce444讨论后添加:

如果反应没有将自定义钩子的返回值视为稳定的身份,但是我们从其他钩子的依赖项列表中忽略了它,则npm将报告警告

2 个答案:

答案 0 :(得分:1)

在您的情况下,您实际上并不想只为自定义代码隐藏该警告。 React为setState函数执行此操作,因为它引用了自己库中的某些内容。正如评论者所提到的,您可以为该特定行禁用掉毛规则,但是最好包括此依赖项。

编写代码时,通常希望将代码与其上下文松散地耦合在一起,而不会对实际使用位置进行任何假设。在当前用例中,您知道钩子中的函数没有更改,将来可能会更改。考虑以下示例:

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 });
};

现在,当上下文值更改时,calculate函数也更改。但是,如果您组件的useEffect中没有这种依赖关系,则实际上不会触发计算,并且您的状态现在将具有陈旧/不正确的值。

尽管从技术上讲,目前具有这种依赖关系可能是多余的,但如果您进行防御性编程,则可以避免类似的错误,而这些错误可能会使您难以追查。尤其是由于有依赖关系链,使用自定义钩子以及使用其他钩子的钩子等都可以获得。从字面上看,您的依赖项数组中有一些额外的字符,最好只是添加它,避免日后出现麻烦

答案 1 :(得分:0)

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

我的目标是非常像useState一样声明钩子,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

您的演示非常出色,但是此挂钩可以保证没有问题。这个钩子看起来非常像useState,这就是为什么我想要这个未来。如果开发人员知道他/她在做什么,React应该支持它。