React中的一些Hooks的理解

React中的一些Hooks的理解

注意:react18 的话可以取消严格模式,再运行以下代码否则会出现状态改变一次组件重新渲染两次的问题(好像是函数式组件遇到的问题 具体分析可以看 https://juejin.cn/post/7092696529105846303#heading-2)
参考:https://juejin.cn/post/6844904093824073742#heading-5

这两个钩子一般用于性能的优化,而且有时会用于搭配使用


一、useMemo 的使用

useMemo

  1. 应用场景:一般应用于优化子组件,减少子组件不必要的重render
  2. 作用:当父组件的数据发生改变的时候,但是这些数据的改变可能与子组件无关,为了避免子组件不必要的重新渲染可以在子组件上包裹useMemo

下面看一下示例

首先是不使用 useMemo 早成子组件不必要渲染的

父组件定义

import "./styles.css";
import Info from "./Info";
import { useState } from "react";

export default function App() {
 const [count1, setCount1] = useState(0);
 const [s, changes] = useState(false);
 return (
   <div className="App">
     <h1>Hello CodeSandbox</h1>
     <div>
       <button
         onClick={() => {
           changes(!s);
         }}
       >
         改变S
       </button>
     </div>
     <button
       onClick={() => {
         setCount1(count1 + 1);
       }}
     >
       +1
     </button>
     <Info info={count1} name={"第一个"} />
   </div>
 );
}

子组件定义

const Info = ({ name, info }) => {
  console.log(name + " -> 我被重新渲染了");
  return <div>我是子组件:{info}</div>;
};
export default Info;

页面效果:
在这里插入图片描述

在这个案例里面我们我们定义了按钮改变s去改变s 的数据,但是s的值与子组件的状态无关。当我们点击按钮改变S 的时候子组件就会重新渲染,结果如下图,我们没有点击 +1 按钮,而子组件就已经重新渲染了8次,这显然是不合理的,因此我们修改一下子组件的代码可以解决这种问题。

在这里插入图片描述
使用memo 的子组件

const Info = ({ name, info }) => {
  console.log(name + " -> 我被重新渲染了");
  return <div>我是子组件:{info}</div>;
};
export default Info;

在这里插入图片描述

此时我们再重复点击按钮改变S则不会出现子组件重新渲染的问题,因为使用memo包裹后当父组件状态改变并不会直接触发子组件的重新渲染,而是会判断前后的props是否发生改变,若发生改变才触发重新渲染,因此当props中含有Object 的类型的时候可能会失效。

失效的原因:

const a = {};
const b = {};
a === b; // false

二、useCallback 的使用

1.引入库

useCallback 用于对数组进行缓存,用于缓存一个函数。
应用场景:避免函数的重复创建、当父组件给子组件传递函数的时候和usememo搭配使用避免子组件重复渲染

修改上例代码:
父组件

import "./styles.css";
import Info from "./Info";
import { useCallback, useState } from "react";

export default function App() {
  const [count1, setCount1] = useState(0);
  const [s, changes] = useState(false);
  const getCount1 = () => {
    return count1;
  };
  const callbackGetCount1 = useCallback(() => {
    return count1;
  }, [count1]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <div>
        <button
          onClick={() => {
            changes(!s);
          }}
        >
          改变S
        </button>
      </div>
      <button
        onClick={() => {
          setCount1(count1 + 1);
        }}
      >
        +1
      </button>
      <Info info={getCount1} name={"第一个"} />
      <Info info={callbackGetCount1} name={"第二个"} />
    </div>
  );
}

子组件

import react from "react";

const Info = ({ name, info }) => {
  console.log(name + " -> 我被重新渲染了");
  return (
    <div>
      我是{name}子组件:{info()}
    </div>
  );
};
export default react.memo(Info);

页面
在这里插入图片描述
重复点击改变S按钮
在这里插入图片描述

当我们点击改变S按钮的时候发现虽然子组件都用useMemo包裹了,但是子组件1被重新渲染,而子组件2没有被重新渲染。这就是前面提到的useMemo 对props 的浅比较引起的,对于子组件1我们每次都会传给它一个新的getCount1函数,虽然内容是一样的但是并不相等,如下证明两个函数不相等。

失效的原因:

const a = () => {};
const b = () => {};
a === b; // false

那么子组件2 为什么没有被重复渲染呢?这是因为我们给子组件2传递的函数是通过 useCallback包裹过的callbackGetCount1,它会对函数进行缓存,在这个案例中只有当count1(也就是useCallback 通过第二个参数中监听到的数据)改变的时候这个函数才会重新创建从而引起子组件的useMemo的失效。我们通过一个实例来验证这个说法。

只修改上述案例中的父组件的useCallback部分的代码为

  // 修改前
  // const callbackGetCount1 = useCallback(() => {
  //   return count1;
  // }, [count1]);
  // 修改后
  const callbackGetCount1 = useCallback(() => {
    return count1;
  }, [s]);

这个时候当我们再去重复的点击按钮改变S发现子组件二也会跟着发生重新渲染。

useCallback好像会对值进行深度拷贝,也就是缓存,这刚好和它接受第二个参数去监听数据变化想匹配。
额外实验
修改上述的父组件

import "./styles.css";
import Info from "./Info";
import { useCallback, useState } from "react";

export default function App() {
 const [count1, setCount1] = useState(0);
 const getCount1 = () => {
   return count1;
 };
 const callbackGetCount1 = useCallback(() => {
   console.log(count1);
   setCount1(count1 + Math.random());
 }, []);

 return (
   <div className="App">
     <h1>Hello CodeSandbox</h1>
     <div>
       <button
         onClick={() => {
           callbackGetCount1();
         }}
       >
         触发callback函数
       </button>
     </div>
     <button
       onClick={() => {
         setCount1(count1 + 1);
       }}
     >
       +1
     </button>
     <Info info={getCount1} name={"第一个"} />
   </div>
 );
}

页面:
在这里插入图片描述

我们可以进行以下操作,比如重复点击触发callback函数这个按钮,发现控制台输出如下
在这里插入图片描述
发现count1 的值并没有改变,但是页面上的子组件中对应显示的count1发生改变。也就是说我们在外面通过useState创建的count1useCallback中的count1不是一个同一个东西,但是setCount1却是一样的,所以我猜测这里应该是useCallback对传入的函数中的那些变量进行了一次值拷贝才出现这样的情况(还没没看过源码具体原理还不是很清楚).


三、useRef 的两种用法

1 、获取DOM元素的对象

import { useEffect, useRef } from "react";
import "./styles.css";

export default function App() {
  const myRef = useRef<any>(HTMLInputElement);

  useEffect(() => {}, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <input ref={myRef} />
      <button
        onClick={() => {
          console.log(myRef);
          console.log(myRef.current.value);
        }}
      >
        输出
      </button>
    </div>
  );
}

页面
在这里插入图片描述

我们通过myRef跟input 框绑定,方便我们从其他地方获取input框中的内容(而不是通过js获取dom元素的值)。

**点击输出按钮控制台的结果
在这里插入图片描述

2 、缓存数值

直接生命存储数据的案例

import { useState } from "react";
import "./styles.css";

export default function App() {
 const [uodate, setUpdate] = useState(false);
 let num = 0;

 return (
   <div className="App">
     <h1>Hello CodeSandbox</h1>
     <input
       onChange={(e) => {
         setUpdate(!uodate);
       }}
     />
     <button
       onClick={() => {
         num++;
       }}
     >
       点我+ num
     </button>
     <button
       onClick={() => {
         console.log(num);
       }}
     >
       输出
     </button>
   </div>
 );
}

页面
在这里插入图片描述

首先是关于页面内容的说明:

  • 出入框搭配useState实现当输入内容变化的时候组件重新渲染
  • 点我 + num 按钮可以实现增加 num 的值
  • 输出按钮则可以在控制台输出num 的值
    实验的步骤:
  1. 连续点击2(没有要求,点了就行)次 点我 + num 按钮
  2. 点击输出按钮
  3. 向输入框中输入一个值
  4. 再次点击输出按钮
  5. 观察控制台输出
    控制台结果
    在这里插入图片描述
    在这个实验中,我们通过步骤三确定了num可以成功加上,但是由于给输入框输入值的时候触发了页面的刷新导致 let num = 0;语句重新执行,第二次点击输出按钮的时候输出的num 的值又变回了0

使用 useRef 存储数据的案例

import { useRef, useState } from "react";
import "./styles.css";

export default function App() {
 const [uodate, setUpdate] = useState(false);
 const num = useRef(0);

 return (
   <div className="App">
     <h1>Hello CodeSandbox</h1>
     <input
       onChange={(e) => {
         setUpdate(!uodate);
       }}
     />
     <button
       onClick={() => {
         num.current++;
       }}
     >
       点我+ num
     </button>
     <button
       onClick={() => {
         console.log(num.current);
       }}
     >
       输出
     </button>
   </div>
 );
}

页面的布局和作用跟上面那个相同

首先是关于页面内容的说明:

  • 出入框搭配useState实现当输入内容变化的时候组件重新渲染
  • 点我 + num 按钮可以实现增加 num 的值
  • 输出按钮则可以在控制台输出num 的值
    实验的步骤:
  1. 连续点击2(没有要求,点了就行)次 点我 + num 按钮
  2. 点击输出按钮
  3. 向输入框中输入一个值
  4. 再次点击输出按钮
  5. 观察控制台输出
    控制台结果
    在这里插入图片描述
    输出的结果符合预期

其实上面的还可以通过在函数外面var一个num变量也可以避免这种问题的出现,不过其实用哪种方式存储数据,还是要根据我们的场景,有时候我们可能就是需要那种每次刷新变量重新赋值的场景

总结

useMemo可以理解成缓存组件的props 的,只有组件的props变化才会触发组件重新渲染
useCallback可以理解成缓存函数的,只有当这个useCallback对应监听的数据发生改变的时候,才会重新创建这个函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值