注: 「いつ使うべきなのか、どう重要なのか」は多くの先人が解説してくださっているため、この記事は具体例をもとに挙動を端的に理解することを目的にしています。

参考にした記事:
https://qiita.com/uehaj/items/99f7cd014e2c0fa1fc4e
https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2

サンプルコード

import React, { useCallback, useState } from "react";

// React.memo〇
const MemoizationWithGood: React.FC<{ func: () => void }> = React.memo(
  ({ func }) => {
    console.log("---render good---");
    return <button onClick={func}>good</button>;
  }
);

// React.memo〇 / useCallback×
const MemoizationWithBad: React.FC<{ func: () => void }> = React.memo(
  ({ func }) => {
    console.log("---render bad---");
    return <button onClick={func}>bad</button>;
  }
);

// React.memo× / useCallback〇
const Component: React.FC<{ func: () => void }> = ({ func }) => {
  console.log("---render---");
  return <button onClick={func}>normal</button>;
};

export default function App() {
  const [countA, setCountA] = useState<number>(0);
  const [countB, setCountB] = useState<number>(0);

  const goodFunc = useCallback(() => {
    console.log("func called");
  }, []);

  const withDependencyFunc = useCallback(() => {
    console.log(countB);
  }, [countB]);

  const badFunc = () => {
    console.log("func called");
  };

  return (
    <div>
      <p>
        A: {countA} B: {countB}
      </p>

      <div>
        <MemoizationWithGood func={goodFunc} />
        <MemoizationWithGood func={withDependencyFunc} />
        <MemoizationWithBad func={badFunc} />
        <Component func={goodFunc} />
      </div>

      <div>
        <button onClick={() => setCountA(countA + 1)}>count up A</button>
        <button onClick={() => setCountB(countB + 1)}>count up B</button>
      </div>
    </div>
  );
}

codesandboxで触ってみると、挙動がちょっとわかると思います。
https://codesandbox.io/s/dark-dew-bjs4p?file=/src/App.tsx

コードの内容ですが、

定義されている関数は

  1. useCallbackを使った関数
  2. useCallbackを使わない関数
  3. stateに依存した関数

利用しているコンポーネントは

  1. React.memoを使ったコンポーネント
  2. React.memoを使っているが、useCallbackを使わない関数を受け取る予定のコンポーネント
  3. React.memoを使わないコンポーネント

となっています。

それぞれを組み合わせていて、stateが更新されたときにどのコンポーネントが再描画されるかわかるようになっています。

各ログ出力の内容を自分にわかりやすいように変えてみたり、各コンポーネントに渡す関数を変えてみたり、依存をいじってみたりするとよりよく分かるんじゃないかと思います。