UseMemo、UseCallback、React.memo

  前置问题:React中的函数式组件什么时候会重新渲染?
  答:组件中的状态改变(包含Context)、父组件重新渲染导致子组件的重新渲染。

  组件多次重新渲染会导致性能的下降。UseMemo、UseCallback、React.memo都利用缓存机制来提高了组件的性能。
  UseMemo类似于Vue中的计算属性,将组件中计算出的值进行缓存;
  UseCallback将组件中的函数进行缓存;
  React.memo通过监听props的值是否改变来控制子组件是否重新渲染;

UseMemo

官方解释
代码逻辑解释:
  使用useState钩子创建了一个userInfo状态,包含name和gender属性。
  函数formatGender 用来根据用户的gender属性来将其格式化为中文。
  handleClick 方法用来改变userInfo中的name属性。
  gender 是根据函数formatGender 生成的一个值。

不使用UseMemo

  当点击两个按钮,不管是修改name还是修改gender,组件中的状态都发生改变了,所以组件要重新渲染。组件的重新渲染导致formatGender 函数也要重新执行,可以看到函数中的打印语句。
  如果是修改gender导致组件的重新渲染,那么formatGender 函数重新执行,这无可厚非。
  但是如果是修改name导致组件的重新渲染,那么formatGender 函数就不应该重新执行,因为就算它执行了,计算出来的结果与组件重新渲染之前的计算结果还是相同的,这就白白浪费了性能。如果这个计算过程很昂贵,那么性能浪费的更多。

使用UseMemo

  上述的情况可以使用UseMemo这个钩子进行优化,缓存函数上一次的计算结果。只有当特定的状态发生变化时,才会重新进行重新计算,提高了性能。

import { useState, useMemo } from "react";
export const EgOfUseMemo = () => {
  const [userInfo, setUserInfo] = useState({
    name: "nobody",
    gender: "male",
  });

  const formatGender = (gender) => {
    console.log("调用了翻译性别的方法");
    return gender === "male" ? "男" : "女";
  };
  const handleClick = () => {
    setUserInfo({
      ...userInfo,
      name: "lvxiaobu",
    });
  };

  const updateSex = () => {
    setUserInfo({
      ...userInfo,
      gender: userInfo.gender + 'aaa',
    });
  };
  // 优化前,不使用useMemo的情况下,修改其他属性,也会重新调用formatGender方法,浪费计算资源
  // 每次点击按钮,都会调用一次formatGender方法
  // const gender = formatGender(userInfo.gender);

  // 优化后,修改其他属性,不会重新调用formatGender方法,性能提升
  // 每次点击按钮,都不会调用formatGender方法
  const gender = useMemo(() => {
    return formatGender(userInfo.gender);
  }, [userInfo.gender]);

  return (
    <div>
      姓名: {userInfo.name} -- 性别: {gender} <br />
      <button onClick={handleClick}>修改名字</button>
      <button onClick={updateSex}>修改性别</button>
    </div>
  );
};

UseCallback

官方解释

不使用UseCallback

  为方便调试,请在入口文件中关闭React中的严格模式
代码解释:
  使用useState钩子生成两个状态count和name,并定义两个更新状态的函数updateCount 和updateName 。
  为了监听Fn函数是否是新生成的,使用useRef钩子缓存Fn函数的上一次引用;使用useEffect钩子监听Fn函数的变化,并比较Fn函数两次的引用地址是否相同。
  当组件挂载时,useEffect钩子将Fn函数的引用缓存了起来。
  当通过按钮来改变count或name时,组件中的状态发生改变,进行重新渲染。触发useEffect钩子,打印出预料中的结果(Fn的新引用和旧引用不相等,因为重新渲染的时候生成的是一个新函数,虽然新函数和旧函数的逻辑是一样的,但是在内存中的地址不一样)。

import React from "react";
import { useCallback, useState, useEffect, useRef } from "react";
import Mock from "mockjs";

const EgOfUseCallback = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("zhangsan");
  const previousCacheFnRef = useRef(null);
  const updateCount = () => {
    setCount(count + 1);
  };
  const updateName = () => {
    setName(Mock.mock("@name"));
  };
  const Fn = () => {
    console.log("count变化了 ", count);
  };
  
  useEffect(() => {
    if (previousCacheFnRef.current !== null) {
      console.log("Fn 引用发生变化");
      console.log(
        "新引用和旧引用是否相等:",
        previousCacheFnRef.current === cacheFn
      );
    }
    previousCacheFnRef.current = cacheFn;
  }, [Fn]);
  return (
    <div>
      <p>count : {count}</p>
      <button onClick={updateCount}>updateCount</button>
      <p>name : {name}</p>
      <button onClick={updateName}>updateName</button>
      <br />
      <button onClick={cacheFn}>cacheFn</button>
    </div>
  );
});
export default EgOfUseCallback;
使用UseCallback

 &emsp使用了useCallback之后,只有组件中特定的状态发生变化时,组件才会在重新渲染的时候生成一个新函数,否则就返回缓存的函数。可以通过useEffect钩子中的打印语句进行验证。

  ......
  //仅仅需要将函数体用useCallback钩子进行包裹
  const cacheFn = useCallback(() => {
    console.log("count变化了 ", count);
  }, [count]);
  ......

React.memo

官方描述
  一般来说,当父组件中的状态变更之后,父组件中引用的子组件也要进行刷新。
  但是如果子组件中并没有用到父组件中变更的这个状态,那么理论上子组件是不需要进行刷新的。

不使用memo

代码解释:
  在父组件中定义了两个状态,count和name。仅仅传递count的状态给子组件。
  但是无论在父组件中改变哪个状态,都会触发子组件的重新渲染,可以通过子组件中useEffect钩子中的打印语句加以验证。

//App.jsx
import { useState } from "react";
import Mock from "mockjs";
function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState(Mock.mock("@name"));
  return (
    <>
      <span>APP中的name:{name}</span>
      <button
        onClick={() => {
          setName(Mock.mock("@name"));
        }}
      >
        改变name
      </button>
      <button onClick={() => setCount(count + 1)}>count+1</button>
      <Child count={count}></Child>
    </>
  );
}
export default App;
//Child.jsx
const Child = ({ count }) => {
  useEffect(() => {
    console.log("组件重新渲染了");
  });
  console.log("refesh");
  return (
    <div>
      <p>App传递过来的count:{count}</p>
    </div>
  );
};
export default Child;
使用memo

  如果使用memo,就可以让子组件选择性的进行刷新。只有父组件传递给子组件的状态发生改变了,才会重新渲染子组件。
将上述子组件中的代码用memo进行包裹:

//Child.jsx
const Child = React.memo(({ count }) => {
  useEffect(() => {
    console.log("组件重新渲染了");
  });
  console.log("refesh");
  return (
    <div>
      <p>App传递过来的count:{count}</p>
    </div>
  );
});
Child.displayName = 'Child'
export default Child;

总结

  上述两个钩子和一个API都是围绕状态来展开的。根据依赖的状态是否变化,选择性的进行值、函数或者组件的刷新。
  虽然通过缓存可以提升性能,但是使用时也会造成额外的代价,比如增加内存用量、额外监听特定的状态是否变化了等等,所以在使用之前要确定自己是否真的需要它。

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`React.memo` 是 React 中的一个高阶组件,用于优化组件的性能。它与 `React.PureComponent` 类似,但适用于函数组件而不是类组件。 使用 `React.memo` 可以避免在某些情况下不必要的重渲染。当组件的 props 发生变化时,React 会重新渲染该组件。但如果 props 没有发生变化,就没有必要重新渲染组件。这时,`React.memo` 就可以发挥作用。 `React.memo` 的基本用法如下: ```jsx import React from 'react'; function MyComponent(props) { // 组件的渲染逻辑 } export default React.memo(MyComponent); ``` 在这个例子中,我们将 `MyComponent` 组件使用 `React.memo` 包裹起来,这样就可以实现组件的记忆功能。当 `MyComponent` 组件的 props 发生变化时,`React.memo` 会比较前后两个 props 是否相等,如果相等就不会重新渲染组件,否则就会重新渲染组件。 需要注意的是,`React.memo` 只会对 props 进行浅比较,因此如果 props 是一个对象或数组,而且它们的值发生了变化,但引用没有变化,`React.memo` 仍然会认为它们相等。在这种情况下,你需要手动处理 props 的深层次变化,或者使用其他的优化工具,例如 `useMemo`、`useCallback` 等等。 另外,需要注意的是,虽然 `React.memo` 可以帮助我们优化组件的性能,但也不应该滥用。在大多数情况下,React 自身已经能够很好地处理组件的渲染,并且重渲染通常也不会带来太大的性能问题。只有在某些情况下,例如组件渲染比较频繁,或者组件的渲染成本比较高时,才需要考虑使用 `React.memo` 进行优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值