如何在React中使用useCallback优化代码

news/2024/7/15 18:25:16 标签: react.js, javascript, 前端

React中父组件更新,子组件该如何?

首先看一段代码,对于父组件更新状态的时候,子组件是否会更新:

function Child(props){
  console.log('child更新')
  const {childName} = props
  return (
    <div>
      <h5>{childName}</h5>
      <button>changeChildName</button>
    </div>
  )
}

function Parent(){
  const [name,setName] = useState('farther')
  const [childName,setChildName] = useState('child')
  return (
    <div>
      <Child childName={childName} />
      <h4>{name}</h4>
      //更新name,父组件自己的状态
      <button onClick={() => {setName(Math.random())}}>changeFartherName</button>
    </div>
  )
}

在这个例子中,子组件的childName是从父组件的props中拿到的。但是当我点击按钮,修改的是父组件的状态,props并没有改变。
但是,子组件依然会更新。这是React更新的机制。但是在这种情况下,子组件是没有必要重新render一遍的。

基于上面的情况,memo方法出现了。

React.memo

memo是一个Hoc高阶组件,他可以对组件进行封装,而封装后的组件只有在props变化的时候才会render。
针对于上面的代码,就可以通过memo将子组件进行封装:

 function getChild(props) {
  //父组件更新自己的状态时,不会render
  console.log("child更新");
  const { childName } = props;
  return (
    <div>
      <h5>{childName}</h5>
      <button>changeChildName</button>
    </div>
  );
}

const Child = memo(getChild);

function Parent() {
  const [name, setName] = useState("farther");
  const [childName, setChildName] = useState("child");
  return (
    <div>
      <Child childName={childName} setChildName={setChildName} />
      <h4>{name}</h4>
      <button
        onClick={() => {
          setName(Math.random());
        }}
      >
        changeFartherName
      </button>
    </div>
  );
}

通过const Child = memo(getChild);将Child组件进行包装,这个时候,只有更改childName属性的时候,子组件才会更新。

React.memo同时也提供了第二个参数,接收一个回调函数,参数是新旧的props。例如想控制在什么条件下更新,在什么条件下不更新。通过方法的返回值(true || false)来决定组件的更新。

useCallback

举个例子,有以下场景。当父组件将状态值,和改变状态值的方法都传给了子组件。当父组件更新的时候,子组件从props里拿的方法是重复定义的,还是一直用的一个?

 //用来存储父组件传给子组件的setChild方法
 let arr = [];
 function getChild(props) {
   console.log("child更新");
   const { childName, setChild } = props;
   arr.push(setChild);
   //判断每次传入的setChild方法是不是同一个
   if (arr.length > 1) {
     console.log(arr[0] === arr[1]); //false 答案不是同一个
   }
   return (
     <div>
       <h5>{childName}</h5>
       <button>changeChildName</button>
     </div>
   );
 }
 
 const Child = memo(getChild);
 
 function Parent() {
   const [name, setName] = useState("farther");
   const [childName, setChildName] = useState("child");
   //更改状态值的方法,传递给子组件
   const setChild = () => {
     if(Math.random() > 2){
       setChildName(Math.random())
     }
   }
   return (
     <div>
       <Child childName={childName} setChild={setChild} />
       <h4>{name}</h4>
       <button
         onClick={() => {
           setChildName(Math.random());
         }}
       >
         changeFartherName
       </button>
     </div>
   );
 }

执行上面的代码,可以看出来,每次父组件更新,都要定义一个setChild方法。但是似乎并不需要这种重复定义,所以useCallback出现了。

useCallback接收两个参数,第一个参数是回调函数,第二参数是一个数组。返回一个处理过的回调函数。
只有数组里的依赖项改变的时候,它才会更新。也就是说上面的代码可以经过useCallback处理:

  let arr = [];
  function getChild(props) {
    console.log("child更新");
    const { childName, setChild } = props;
    arr.push(setChild);
    if (arr.length > 1) {
      console.log(arr[0] === arr[1]); //true 说明拿到的方法都是一个方法的引用
    }
    return (
      <div>
        <h5>{childName}</h5>
        <button>changeChildName</button>
      </div>
    );
  }
  
  const Child = memo(getChild);
  
  function Parent() {
    const [name, setName] = useState("farther");
    const [childName, setChildName] = useState("child");
    //通过useCallback进行处理
    const setChild = useCallback(() => {
      if(Math.random() > 2){
        setChildName(Math.random())
      }
    },[])
    return (
      <div>
        <Child childName={childName} setChild={setChild} />
        <h4>{name}</h4>
        <button
          onClick={() => {
            setChildName(Math.random());
          }}
        >
          changeFartherName
        </button>
      </div>
    );
  }

而useCallback是useMemo的一个语法糖,对于useMemo的hook这里就不细说了。


http://www.niftyadmin.cn/n/414627.html

相关文章

设计菜单界面时应注意的一般性原则

功能组织菜单&#xff0c;合理分类&#xff0c; 并力求简短&#xff0c;前后一致&#xff1b;合理组织菜单界面的结构与层次&#xff1b;按一定的 规则对菜单项进行排序&#xff1b;菜单选项的标题要力求文字简短、含义明确&#xff0c; 并且最好以关键词开始&#xff1b;常…

华为OD机试真题 Java 实现【观看文艺汇演问题】【2023 B卷 100分】,附详细解题思路

一、题目描述 为庆祝中国共产党成立100周年&#xff0c;某公园将举行多场文艺汇演&#xff0c;很多演出都是同时进行。 一个人只能同时观看一场演出&#xff0c;且不能迟到早退。由于演出分散在不同的演出场地&#xff0c;所以连续观看的演出最少要有15分钟的时间间隔。 小明…

2023年系统分析师上午题

全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试 2023年上半年 系统分析师 上午试卷 &#xff08;考试时间 9 : 00&#xff5e;11 : 30 共 150 分钟&#xff09; 1. 在答题卡的指定位置上正确写入你的姓名和准考证号&#xff0c;并用正规 2B 铅笔在你写入…

Java 深拷贝和浅拷贝

Java 中的深拷贝和浅拷贝是针对对象复制而言的。 浅拷贝&#xff08;Shallow Copy&#xff09; 当对象进行浅拷贝时&#xff0c;只会复制对象本身和其中的基本数据类型属性&#xff0c;而不会复制引用对象的实际内容。具体而言&#xff0c;浅拷贝只会创建一个新的对象&#x…

kubernetes 1.24.2实战与源码(6)

kubernetes 1.24.2实战与源码 第37章 k8s hpa和vpa依赖的metrics-server源码解读和kubelet top原理 37.1 metrics-server源码解读 37.2 kubelet top原理 第38章 k8s crd开发 38.1 crd技术介绍和自定义crd需求分析 38.2 使用kubebuilder编写crd代码 38.3 部署crd到k8s中使…

mysql密码遗忘和登陆报错问题

登录密码忘记&#xff0c;其实解决办法很简单&#xff0c;只需要在mysql的主配置文件my.cnf里添加一行“跳过授权表”的参数选择即可&#xff01; 在my.cnf中添加下面一行&#xff1a; [roottest-huanqiu ~]# vim /etc/my.cnf //在[mysqld]区域里添加 ........ s…

基于jsp+mysql+Spring+mybatis+Springboot的SpringBoot停车场停车位管理系统

运行环境: 最好是java jdk 1.8&#xff0c;我在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以&#xff0c;如果编译器的版本太低&#xff0c;需要升级下编译器&#xff0c;不要弄太低的版本 tomcat服务器环…

暴力递归到动态规划(三)

⭐️前言⭐️ 本篇文章是从暴力递归到动态规划的第三章。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评论区留言 &#x1f349;博客中涉及源码及…