useCallback(1)

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进行优化, 将优化之后的函数, 传递给子组件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容