首次应用 React Hook 开发时,可能不怎么会应用 useCallback,以事件回调为例:

const MyComponent: FC = () => {    // 间接创立函数,不应用 useCallback 包裹    const handleClick = () => {        // ...    };    return (        <div>            <ChildComponent onClick={handleClick} />        </div>    );}

下面示例中,在代码逻辑上,写法天然是正确的,代码运行时,也大概率不会呈现问题。

为什么应用 useCallback

在函数组件中,每次渲染时都会从新执行一次函数,因而上例中每次都会新建一个 handleClick 并传递给 ChildComponent 组件。

而对于 ChildComponent 来说,每次渲染时,作为其 props 的 onClick 函数都是新定义的函数,就会导致 ChildComponent 从新渲染。

不必要的渲染状况增多势必会升高网页的性能,那 useCallback 有什么用呢?

useCallback(fn, deps) 写法能够了解为 useMemo(() => fn, deps) ,就是应用 useCallback 能够“记忆”一个函数。也就是说,每次 MyComponent 渲染时,让其中的 handleClick 函数都是同一个函数,这样对于 ChildComponent 来说,其 props.onClick 也就没有变动。

const MyComponent: FC = () => {    // 应用 useCallback 包裹,每次取得的 handleClick 函数都是同一个    const handleClick = useCallback(() => {        // ...    }, []);    return (        <div>            <ChildComponent onClick={handleClick} />        </div>    );}

应用 useCallback 的问题

既然 useCallback 有优化性能的作用,那么为所有函数都包上 useCallback 不就好了?

首先,代码是给人看的,在代码中充斥大量 useCallback 状况下,会给开发人员减少肯定的浏览累赘,这种问题对不同人影响水平不同,也会有开发者感觉不可承受,认为是一种适度优化。

其次,和 useMemo 一样的问题,useCallback 应用时须要认真填写 deps 依赖,一旦写错可能会造成奇怪的 bug,减少了开发工作量,也缩小了头发数量。

例如,作为老手开发最为常见的一个问题就是:

const MyComponent: FC = () => {    const [count, setCount] = useState(0);    const handleClick = useCallback(() => {        // 这里获取到的 count 永远都是0,这往往让 React Hook 老手感到蛊惑        setCount(count + 1);    }, []);    return (        <div>            <ChildComponent onClick={handleClick} />        </div>    );}

老手往往会发现其应用 useCallback 包裹函数中的变量不更新,永远都是初始值。这种状况的一个解决办法就是将 count 作为 deps 依赖之一。

因而 useCallback 的应用可能没有最佳实际,开发者只能在性能、可读性、开发进度间进行均衡取舍。

useCallback 的依赖问题

在理论开发中,可能会呈现 deps 依赖十分复杂的状况,比方一个函数可能依赖 4 到 5 个变量的状况,当依赖多了,依赖的更新也更加频繁了,useCallback 的“记忆”成果可能会变差。

官网文档中针对这种状况进行了记录,举荐浏览,也能够看这个 issue ,看看大家针对相似问题的探讨。

官网文档中次要应用名为 useEventCallback 的 hook:

function useEventCallback(fn, dependencies) {    const ref = useRef(() => {        throw new Error('Cannot call an event handler while rendering.');    });    // 依据依赖去更新 ref ,保障最终调用的函数是最新的    useEffect(() => {        ref.current = fn;    }, [fn, ...dependencies]);    // useCallback 返回的后果不会扭转    return useCallback(() => {        const fn = ref.current;        return fn();    }, [ref]);}

即应用 useRef 来保留函数,防止 useCallback 所包裹的函数重复变动的问题。

另外能够查看 material-ui 的实现,也是差不多的原理。

非凡状况 useConstCallback

甚至能够看看一些更非凡的状况:

export function useConstCallback(callback) {    var ref = useRef(callback);    return ref.current;}const MyComponent: FC = () => {    const [value, setValue] = useState(false);        const setTrue = useConstCallback(function () {        return setValue(true);    });}

其实我了解这和 useCallback(fn, []) 没有区别,但在一些库中也见到这种写法。