首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Hooks】:[组]When to use React.useCallback()

【Hooks】:[组]When to use React.useCallback()

作者头像
WEBJ2EE
发布2021-02-26 16:10:09
4010
发布2021-02-26 16:10:09
举报
文章被收录于专栏:WebJ2EEWebJ2EE
目录
1. useCallback refresher
2. Why does this matter?
3. When is this useful in React?
4. When not to use useCallback?
5. useCallback doesn’t memoize the function result

I write a lot of React components which require code review. Many times during that process someone has dropped the classic line:

I think we should wrap this function in useCallback for performance reasons.

But what is useCallback and when does it make sense to use it?

1. useCallback refresher

To recap, useCallback is a React Hook which returns a memoized version of the callback function it is passed.

This means that the function object returned from useCallback will be the same between re-renders.

2. Why does this matter?

It’s worth recalling that in JavaScript, functions display referential equality. This means they are only considered equal if they point to the same object.

Therefore if you were two declare two functions with identical implementations they would not be equal to each other.

For example:

const firstFunction = function() {
    return 1 + 2; // same as secondFunction
}

const secondFunction = function() {
    return 1 + 2; // same as firstFunction
}

// Same implementation, different objects
firstFunction === secondFunction; // false

// Same objects!
firstFunction === firstFunction; // true

3. When is this useful in React?

Typically useCallback is helpful when passing callback props to highly optimised child components.

For example, if a child component that accepts a callback relies on a referential equality check (eg: React.memo() or shouldComponentUpdate) to prevent unnecessary re-renders when its props change, then it is important that any callback props do not change between renders.

To do this, the parent component can wrap the callback prop in useCallback and be guaranteed that it is passing the same function object down into the optimised child component.

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        // this will return the same function
        // instance between re-renders
    });

    return (
        <MemoizedSubComponent
            handleClick={onHandleClick}
        />
    );
}

4. When not to use useCallback?

You should avoid seeing useCallback as a blanket performance optimisation.

In most cases, it’s simply better to accept that for functions declared inline with React components, each new re-render creates a new function object. This is typically absolutely fine and will not have a detrimental impact on performance.

You should always profile though – just in case!

Let’s look at another example:

import React, { useCallback } from 'react';

function MyComponent() {
  const handleClick = useCallback(() => {
    // handle the click event
  }, []);

  return <MyChild onClick={handleClick} />;
}

function MyChild ({ onClick }) {
  return <button onClick={onClick}>I am a child</button>;
}

Does it make sense to apply useCallback()? Most likely not.

useCallback() hook is called every time MyComponent renders. Even useCallback() returning the same function object, still, the inline function is re-created on every re-rendering (useCallback() just skips it).

This doesn’t bring any benefits because the optimization costs more than not having the optimization.

Don’t forget about the increased code complexity. You have to keep the deps of useCallback(..., deps) in sync with what you’re using inside the memoized callback.

Simply accept that on each re-rendering new functions are created:

import React, { useCallback } from 'react';

function MyComponent() {
  const handleClick = () => {
    // handle the click event
  };

  return <MyChild onClick={handleClick} />;
}

function MyChild ({ onClick }) {
  return <button onClick={onClick}>I am a child</button>;
}

5. useCallback doesn’t memoize the function result

It’s worth noting that applying useCallback doesn’t memoize the result of a function’s invocation. Rather it memoizes the function object itself.

Consider the following:

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        const special = calculatePi();
    });

    return (
        <SubComponent
            handleClick={onHandleClick}
        />
    );
}

In this example, each time the <SubComponent> triggers the onHandleClick callback via its handleClick prop the (presumably expensive!) calculatePi() will be triggered. The result of the arrow function isn’t memoized, only the reference.

If we wanted to avoid re-calculating Pi on each handleClick we’d be better off memorizing calculatePi directly via useMemo():

const memoizedPi = useMemo( calculatePii() );

6. Summary

When thinking about performance tweaks, recall the statement:

Profile before optimizing

Any optimization adds complexity. Any optimization added too early is a risk because the optimized code may change many times.

These considerations apply to useCallback() hook too. Its appropriate use case is to memoize the callback functions that are supplied to memoized heavy child components.

Either way:

  • profile
  • quantify the increased performance (e.g. 150ms vs 50ms render speed increase)

Then ask yourself: does the increased performance, compared to increased complexity, worth using useCallback()?

To enable the memoization of the entire component output I recommend checking my post Use React.memo() wisely.

参考:

When to use React.useCallback()? https://aheadcreative.co.uk/articles/when-to-use-react-usecallback/ Your Guide to React.useCallback(): https://dmitripavlutin.com/dont-overuse-react-usecallback/ When to useMemo and useCallback: https://kentcdodds.com/blog/usememo-and-usecallback


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档