React 前端导航

常见的 React hooks 闭包陷阱,如何避免?

常见的 React hooks 闭包陷阱,如何避免?

React hooks 闭包陷阱是什么,常见场景?

React 提供了一些性能优化函数: React.memo 、useMemo、useCallback。

  • useMemo:缓存值,只有依赖项变更的时候才会重新计算
    const cachedValue = useMemo(fn=>calculateValue, dependencies);
    
  • useCallback:缓存函数,只有依赖项变更的时候才会重新更新
    const cachedFn = useCallback(fn, dependencies);
    
  • React.memo:缓存组件,当 props 不变时,不会执行 render。arePropsEqual 是可选函数,自定义新旧 props 的对比逻辑, 返回 true 缓存,返回 false 不缓存。
    const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
    const arePropsEqual=(oldProps: Props, newProps: Props) => boolean
    

使用上面这些函数来优化性能时,这些函数与外部的 state 形成闭包,导致函数中获取到的 state 值不是最新的,这就是闭包陷阱。

React 闭包陷阱代码演示

如下代码中,包含一个 App (计时器)组件和一个 Child 组件, 当点击 Child 组件时需要返回 App 组件中最新的 state 值;

import React, { useEffect } from "react";
import "antd/dist/antd.css";
import { Button, ButtonProps } from "antd";

const Child = ({ onClick }: ButtonProps) => {
  console.log("render child");
  return (
    <Button onClick={onClick} type="primary">
      Button
    </Button>
  );
};

const App: React.FC = () => {
  const [count, setCount] = React.useState(0);

  useEffect(() => {
    const time = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
    return () => {
      clearInterval(time);
    };
  }, []);

  const handleClick = () => {
    console.log("count:", count);
  };

  return (
    <>
      <h2>{count}</h2>
      <Child onClick={handleClick} />
    </>
  );
};

export default App;

这样没什么问题,但是每次渲染的时候 Child 组件都会执行 render

为了防止 App 组件在更新的时候重复渲染 Child 子组件,我们可以使用 React.memo 包裹下 Child 子组件, handleClick 也使用 useCallback 包裹,这样就能保证 Child 子组件只 render 一次。

const Child = React.memo(({ onClick }: ButtonProps) => {
    console.log("render child");
    return (
      <Button onClick={onClick} type="primary">
        Button
      </Button>
    );
  });
const handleClick = useCallback(() => {
    console.log("count:", count);
  }, []);

这样一来 useCallback 和 state 就形成了一个闭包,每次打印的 state 就是初始化的 state。

为了获得最新的 state 值,必须将 count 参数写进 useCallback 的第二个参数。

const handleClick = useCallback(() => {
    console.log("count:", count);
  }, [ count ]);

但这样,又会导致 Child 组件更新。那么有什么好的解决办法呢?既能防止子组件的更新,又可以获取到最新的 state 值呢?

如何避免

我们可以使用 useRef 来存一个函数,每次更新的时候设置 ref.current 的值,通过函数来获取最新的 state 值。

const App: React.FC = () => {
  const [count, setCount] = React.useState(0);
  const ref = React.useRef<VoidFunction>();

  useEffect(() => {
    const time = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
    return () => {
      clearInterval(time);
    };
  }, []);
 
  const fn = () => {
      console.log("count:", count);
  };

  ref.current = fn;

  const handleClick = useCallback(() => {
    ref.current();
  }, []);

 return (
    <>
      <h2>{count}</h2>
      <Child onClick={handleClick} />
    </>
  );
};

Codesandbox 演示地址

总结

解决闭包陷阱的方法:

  • 当页面更新频率低时,不要使用 useMemo、useCallback 缓存函数来优化页面,在真实影响体验的情况下再来考虑优化;
  • 将更新依赖的参数写进 useCallback 的第二个参数中;
  • 使用 useRef 来保存函数,用一个函数实时获取最新的 state 值;

声明:本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。邮箱:farmerlzj@163.com。 本站原创内容未经允许不得转载,或转载时需注明出处: 内容转载自: React前端网:https://qianduan.shop/blogs/detail/80

#react#闭包

相关推荐

react中实现markdown文件读取展示

react中实现markdown文件读取展示

umi实践问题汇总--持续更新

在使用umi的过程中所遇到问题的记录汇总