useCallback(1)

2022-11-04  本文已影响0人  未路过
image.png

用来做性能优化。

import React, { memo, useState } from "react";

const App = memo(() => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={(e) => setCount(count + 1)}>点我加1</button>
    </div>
  );
});

export default App;

import React, { memo, useState } from "react";

const App = memo(() => {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment }>点我加1</button>
    </div>
  );
});

export default App;

有两种方法写点击事件修改state
第一种: <button onClick={(e) => setCount(count + 1)}>点我加1</button>

第二种: <button onClick={increment }>点我加1</button>

这两种写法一样。

当点击按钮,函数执行,state里面的值被修改,函数组件被重新调用。
但是重新执行函数以后,然后的increment函数还是会被再次定义。这次定义的函数和上次渲染定义的increment函数不是一个函数,有不同的内存地址。
第一种: <button onClick={(e) => setCount(count + 1)}>点我加1</button>
这样写也会定义很多次,(e) => setCount(count + 1)也会被定义很多次。
每次渲染组件,函数都会被重新定义一次。
但是在定义新的函数的时候,以前的函数会被销毁掉。因为没有引用指向它,它就会被销毁掉。
button会创建新的reactElemetn,新的reactElement会指向新的函数的。旧的reactElement要么被销毁,要么被重复利用了。不管怎么样,旧的函数是没有引用指向它的了。没有引用指向这个函数,这个函数就会被销毁掉。
如果把funcion incrment(){setCount(count+1)}
放到App函数外面怎么样?只有文件被调用的时候才会执行一次。
但是问题是定义到外面拿不到setCount,所以如果不需要用到局部变量的化,函数定义到外面是没问题的。

useCallback

我们把increment函数作为参数放到useCallback这个函数中,它会返回一个新的函数

  const increment = useCallback(() => {
    setCount(count + 1);
  });

useCallback对里面传入的参数是有记忆的,能保证返回的increment是同一个函数。能保证
<button onClick={increment}>点我加1</button>
这里使用的increment使用的是同一个函数。

但是其实这里是没有做到的,当点击按钮的时候,app函数重新执行,给useCallback传入的这个函数() => {
setCount(count + 1);
}
也是会被重新定义的。返回的也依然是之前的increment函数。但是返回之前的increment是没有价值的,因为还是把这个setCount(count + 1);这个函数重新定义了一次。只不过以前是在最外层定义const increment = () => {
setCount(count + 1);
};
现在是作为参数被定义。参数也没定义了多次。
所以没有做性能优化。

useCallback的使用

如何进行性能的优化呢?
 useCallback会返回一个函数的 memoized(记忆的) 值;
 在依赖不变的情况下,多次定义的时候,返回的值是相同的;
它接受第二个参数,在哪些值改变的时候,我useCallback再返回一个新的值。
如果是空数组的化,就代表不依赖任何数据,它永远返回一个相同的值。

import React, { memo, useCallback, useState } from "react";

const App = memo(() => {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => {
    console.log("increment");
    console.log(count);
    setCount(count + 1);
  }, []);
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>点我加1</button>
    </div>
  );
});

export default App;

但是这么写会引起问题,
点一次,从0到1,然再点击,count的值旧不变了,但是打印是没问题的,count的值一直是0

闭包陷阱

第一次传入的这个setCount的这个函数,在定义的那一刻,它捕获的是count,也就是0.
然后执行以后,count改变,变成1,app函数重新执行,然后setcount(count+1)这个函数又被重新定义,但是useCallback的第二次参数是空数组,不依赖任何数据的改变,useCallback返回的一直都是第一次定义的那个increment。也就是count一直是0的那个。然后点击按钮,执行setcount(count+1),里面的count是0,结果还是setcount(1).真正的count的值没有变化,还是1,组件不会发生渲染!!!

解决方法

给第二个参数传入count

import React, { memo, useCallback, useState } from "react";

const App = memo(() => {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>点我加1</button>
    </div>
  );
});

export default App;

但是这个样子还不如直接定义一个increment函数。
如果

 const increment = useCallback(() => {
    setCount(count + 1);
  }, [count])

这个函数传递给子组件。

import React, { memo, useCallback, useState } from "react";
const HyIncrement = memo((props) => {
  const { increment } = props;
  return (
    <div>
      <button onClick={increment}>increment+1</button>
    </div>
  );
});

const App = memo(() => {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>点我加1</button>
      <hr></hr>
      <HyIncrement increment={increment}></HyIncrement>
    </div>
  );
});

export default App;

这个时候不论是普通的increment还是用来usecallback的increment,子组件都是可以改变state的。

import React, { memo, useCallback, useState } from "react";
const HyIncrement = memo((props) => {
  console.log("HYincrment被渲染");
  const { increment } = props;
  return (
    <div>
      <button onClick={increment}>increment+1</button>
    </div>
  );
});

const App = memo(() => {
  const [count, setCount] = useState(0);
  /*   const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]); */
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>点我加1</button>
      <hr></hr>
      <HyIncrement increment={increment}></HyIncrement>
    </div>
  );
});

export default App;

如果是普通的increment的化,
第一次渲染以后,每一次点击按钮都会被渲染,无论是点击父亲的按钮还是自己的按钮。
因为每次都传入的是新的increment。HYIncrement被重新渲染的逻辑是因为自己的porps发生了改变。
props中的属性发生改变时, 组件本身就会被重新渲染。
如果使用callback的increment,也是会被重新渲染。
因为count发生了改变,每次返回的increment也是会改变的。HYIncrement被重新渲染的逻辑是也是因为自己的porps发生了改变。

如果app里面还有别的state的化,

import React, { memo, useCallback, useState } from "react";
const HyIncrement = memo((props) => {
  console.log("HYincrment被渲染");
  const { increment } = props;
  return (
    <div>
      <button onClick={increment}>increment+1</button>
    </div>
  );
});

const App = memo(() => {
  const [count, setCount] = useState(0);
  const [randomNum, setRandomNum] = useState(0);
  /*   const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]); */
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>点我加1</button>
      <hr></hr>
      <HyIncrement increment={increment}></HyIncrement>
      <hr></hr>
      <h2>randomNum:{randomNum}</h2>
      <button onClick={(e) => setRandomNum(Math.random().toFixed(2))}>
        修改randomNum
      </button>
    </div>
  );
});

export default App;

如果是普通的increment的化,不论是修改number还是count,子组件都是会被重新渲染的,因为number改变,app函数就会重新执行,每一次的increment 都是新的函数。然后子组件的porps改变,它旧会被重新渲染。

但是,如果是callback,不修改count的化,它就会在第一次被加载的时候渲染一次。不论怎么修改number,子组件都是不会被重新渲染的,因为callback的incremnt只依赖于count,count没变,increment就没变,increment没变,子组件的props就没变,子组件就不会被渲染。

如果HYIncrment是一个特别大的组件。里面有100个子组件,如果用的是以前的单独的increment,里面的组件不适用HyIncremetn里面的数据的化,也是不会发生重新渲染的,但是如果使用了HYIncrement里的数据,也是会发生渲染。

性能优化:把这个函数传递给子组件的时候才会做性能优化,自己单独使用的时候是不会有性能优化的。

新的值!改变,组件重新渲染!

// useCallback性能优化的点:
// 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件

上一篇 下一篇

猜你喜欢

热点阅读