useCallback & useMemo
useCallback 与 useMemo
function Button({ onClick, color, children }) {
  const textColor = slowlyCalculateTextColor(this.props.color);
  return (
    <button
      onClick={onClick}
      className={"Button-" + color + " Button-text-" + textColor}
    >
      {children}
    </button>
  );
}
export default React.memo(Button); // ✅ Uses shallow comparison
不过在实际场景下,很多的 Callback 还是会被重新生成,我们还是需要在子组件中进行精细地 shouldComponentUpdate 控制。
闭包冻结
在 useCallback 的使用中,有时候我们会碰到所谓的闭包冻结的现象,即如下所示:
let Test = () => {
  /** Search base infos */
  const [searchID, setSearchID] = useState(0);
  /** Search info action */
  const onSearchInfos = useCallback(() => {
    let fetchUrl = "/api/getSearchInfos";
    let fetchParams = { searchID };
    fetch(fetchUrl, {
      method: "POST",
      body: JSON.stringify(fetchParams),
    })
      .then((res) => res.json())
      .then((res) => {
        console.log(res);
      });
  }, []);
  return (
    <>
      <button
        onClick={() => {
          setSearchID(searchID + 1);
        }}
      >
        button1
      </button>
      <button
        onClick={() => {
          onSearchInfos();
        }}
      >
        button2
      </button>
    </>
  );
};
点击 button1 按钮,searchID 的值加 1,点击 button2 发送一个请求。然而问题是,当我们点击了四次 button1,把 searchID 的值更改到了 4,然后点击 button2,会发现,发送出去的请求,searhID 的值是 0。
原因分析
我们可以使用如下代码来理解:
storage = { a: 1 };
(() => {
  let b = storage.a;
  storage.f = () => {
    console.log(b);
  };
})();
storage.a = 2;
storage.f();
参考 Hooks 的实现原理,我们可以模拟写出如下的代码:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
  if (storage.data === undefined) {
    storage.data = init;
  }
  return [storage.data, (value) => (storage.data = value)];
}
function useCallback(fn) {
  // The real version would check dependencies here, but since our callback
  // should only update on the first render, this will suffice.
  if (storage.callback === undefined) {
    storage.callback = fn;
  }
  return storage.callback;
}
function MyComponent() {
  const [data, setData] = useState(0);
  const callback = useCallback(() => data);
  // Rather than outputting DOM, we'll just log.
  console.log("data:", data);
  console.log("callback:", callback());
  return {
    increase: () => setData(data + 1),
  };
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
解决方案
添加依赖
const onSearchInfos = useCallback(() => {
  // ...
}, [searchID]);
使用 Ref
interface IRef {
  current: any;
}
let Test = () => {
  /** Search base infos */
  const [searchID, setSearchID] = useState(0);
  /** 解决闭包问题 */
  const fetchRef: IRef = useRef(); // hooks为我们提供的一个通用容器,里面有一个current属性
  fetchRef.current = {
    //  为current这个属性添加一个searchID,每当searchID状态变更的时候,Test都会进行重新渲染,从而current能拿到最新的值
    searchID,
  };
  /** Search info action */
  const onSearchInfos = useCallback(() => {
    let fetchUrl = "/api/getSearchInfos";
    let fetchParams = { ...fetchRef.current }; // 解构参数,这里拿到的是外层fetchRef的引用
    fetch(fetchUrl, {
      method: "POST",
      body: JSON.stringify(fetchParams),
    })
      .then((res) => res.json())
      .then((res) => {
        console.log(res);
      });
  }, []);
  return (
    <>
      <button
        onClick={() => {
          setSearchID(searchID + 1);
        }}
      >
        button1
      </button>
      <button
        onClick={() => {
          onSearchInfos();
        }}
      >
        button2
      </button>
    </>
  );
};
useMemo
useMemo 的用法类似 useEffect,常常用于缓存一些复杂计算的结果。useMemo 接收一个函数和依赖数组,当数组中依赖项变化的时候,这个函数就会执行,返回新的值。
const sum = useMemo(() => {
  // 一系列计算
}, [count]);
