本文作者:IMWeb lhyt 原文出处:IMWeb社区 未经同意,禁止转载

16.6之前,函数组件没有像 shouldComponentUpdate这样的方法,也没有类似 PureComponent这种解决方案,避免不了函数组件里面所有的代码再次的执行,要依靠外面的条件渲染来控制,或者是高阶组件。之前的话,选择使用函数组件的情况是一些比较简单的又比较纯的组件,只是负责展示的。而且函数组件最终编译babel结果是只执行 createElement那一步;class组件一样有生命周期要实例化,最终经过Babel成es5代码的时候还很长

React.memo

当16.6的memo问世,函数组件就有了类似 PureComponent和 shouldComponentUpdate的解决方案,memo的使用方法:

const C = (props) => {

return 那一夜{props.name}真美section>

}

export default React.memo(C)

当父组件执行render的时候,避免不了C组件的渲染和C函数的执行(如果不在外面加判断的话: {isShowC&&<C/>})。当到了C组件的时候,会浅比较C组件前后props值。如果props每一个属性值都一样,会跳过函数组件C的执行,减少了不必要的渲染,达到了性能优化。

memo第二个参数

第二个参数,是一个函数,该函数传入参数是新props和上次props,我们可以在函数里面做判断逻辑,控制返回值。当我们让函数return true的时候,告诉了react这两个props是一样的,不用重新执行整个函数组件;反之false的时候会重新执行该组件

memo(IfEqual, () => false);

比如这行代码,判断函数一直返回false, memo包住的 IfEqual组件无论怎样都会重新执行

当我们用上了memo,就可以根据业务来进行优化了:

React.memo(C, (nextProps, prevProps) => {

// 做我们想做的事情,类似shouldComponentUpdate

})

函数组件中传入的props值为函数时

我们都知道,js中函数不是简单数据类型,也就是说 function(){}和 function(){}是不一样的,与 {}和 {}不一样同理。那么我们传入 props.onClick(即使是长得一样的内容完全一样),前后 props.onClick都不能划上等号

onClick={() => {}} />

觉得inline function不好看,那前面定义一下,实际上还是逃不了同一个事情:它们是不一样的。这次是因为,函数组件的渲染,也就是执行,每一次重新执行,函数作用域里面一切都是重新开始。这就相当于上一次组件渲染 consthandleClick=()=>{},后面渲染又一次 consthandleClick=()=>{},它们都不是同一个东西

export default () => {

const handleClick = () => {}

return (

<IfEqual onClick={handleClick} />

div>

)

}

这种情况下,我们可以用memo第二个参数来拯救多余一次的渲染的局面:

// props: { a: 1, onClick: () => {} }

// 在我们知道onClick是做同一个事情的函数的前提下,不比较onClick

React.memo(C, (nextProps, prevProps) => nextProps.a === prevProps.a)

最后,前后props的 onClick,它们只有一种情况是一样的——把声明抽到组件外面去

const handleClick = () => {}

export default () => {

return (

<IfEqual onClick={handleClick} />

div>

)

}

这时候,有没有想起class组件里面总是 onClick={this.handleClick}呢? this.handleClick一直都是同一个函数。这种情况,子组件为函数组件的时候,包一层memo就可以实现purecomponent的效果

useCallback

函数组件把函数定义写在外面,是可以解决问题。但是,如果handleClick依赖组件内部的一些变量,那handleClick又不得不写在里面(当然利用引用类型可以解决)。或者还是正常写,靠memo第二个参数来控制要不要重新渲染子函数组件。但是无论怎样,都存在一个问题,就是那一块代码写在里面呢,都无法避免代码的执行和函数的重新定义,比如

function a(){

const b = function(){

console.log(1)

// 很多很多代码

}

}

a()

a() // 函数b又被定义了一次

如果我们通过依赖来确定前后两次是不是同一个函数,我们可以用函数记忆来实现整个功能

// 半伪代码

let prev

let prevDeps

function memorize(fn, deps) {

// 前后依赖一致,不用重新计算直接返回上次结果

if (prev && isEqual(deps, prevDeps)) {

return prev

}

prevDeps = deps

prev = fn

return fn

}

function a(){

const b = memorize(function(){

console.log(1)

// 很多很多代码

}, [])

}

a()

a() // 函数b是同一个

类似函数记忆的原理,后来有了 useCallback的出现,多了一种新的解决方案,根据依赖生成一个函数:

const handleClick = useCallback(() => {

console.log(dep)

}, [dep])

当dep不变,每一次函数组件的执行,handleClick都是同一个函数。如果dep变了,那么handleClick又是一个新的函数

export default () => {

// 没有依赖,永远是同一个函数

const handleClick = useCallback(() => {}, []);

// 依赖a,重新执行函数组件,a不变的,是同一个函数

// a变了handleClick是新的函数

const handleClick1 = useCallback(() => {}, [a]);

return (

<div>

<IfEqual onClick={handleClick} />

div>

)

}

react组件也是一个函数,那其实 useCallback还可以做一个函数组件:

export default () => {

const handleClick = useCallback(() => {}, []);

const Cpn = useCallback(({ name }) => {

return <button onClick={handleClick}>{name}button>

}, [handleClick]);

return (

<Cpn name="hi" />

div>

)

}

当然这只是一个简单的场景,如果用了hooks,还没有解决问题或者暂时没有想到优雅的封装技巧,想用高阶组件的时候,不妨尝试一下 useCallback

useMemo

const a = useMemo(() => memorizeValue, deps)

当deps不变,a的值还是上次的memorizeValue,省去了重新计算的过程。如果memorizeValue是一个函数,和useCallback是一样的效果:

useCallback(fn, inputs) <=> useMemo(() => fn, inputs)

我们可以试一下同步执行的代码,当时间非常长的时候,useMemo可以发挥它的作用了:

// 强行更新组件

const useForceUpdate = () => {

const forceUpdate = useState(0)[1];

return () => forceUpdate(x => x + 1);

}

// 一个很耗时间的代码

function slowlyAdd(n) {

console.time('add slowly')

let res = n;

for (let i = 0; i < 2000000000; i++) {

res += 1;

}

console.timeEnd('add slowly')

return res;

}

// useMemo记忆结果的一个自定义hook

function useSlowlyAdd(n) {

const res = useMemo(() => {

return slowlyAdd(n);

}, [n])

return res;

}

export default () => {

const [count, add] = useState(1);

const forceUpdate = useForceUpdate();

const handleClick = useCallback(() => {}, []);

useSlowlyAdd(count) // 第一次这里会耗很多时间,页面卡死一阵

return (

<>

<button onClick={forceUpdate}>更新页面button>

<button onClick={() => add(count + 1)}>+button>

>

)

}

第一次进来,页面暂时没有任何反应一阵,这是因为slowlyAdd占用了js主线程。当我们点击‘更新页面’更新的时候,页面并没有卡死,而且组件也重新渲染执行了一次。当我们点击+,页面又开始卡死一阵。

这是因为点击+的时候,修改了useMemo的依赖n,n变了重新计算,计算耗费时间。如果点击更新页面,没有修改到依赖n,不会重新计算,页面也不会卡

当然,useMemo也可以做高阶组件,用起来的时候,可以写成reactElement的形式了:

const HOC = useMemo(() => <C />, deps)

最后

有如下的组件,Big是一个10w个节点的组件,每一个节点都绑定事件

const handleClick = useCallback(() => {}, []);

export default () => {

return (

<Big onClick={handleClick} />

div>

)

}

如果Big组件没有memo包住,首次挂载和再次渲染父组件性能如下: 

如果Big组件有memo包住而且props被认为是一样的情况下,首次挂载和再次渲染父组件性能如下: 

但是性能优化不是免费午餐,不是所有的函数组件都包memo,组件里面的函数都包usecallback就好了。因为具有memorize,没有优化的意义的情况下强行优化,反而是性能恶化。

总结一下对于props的某个属性值为函数的时候,如何做到子组件不重新执行多余渲染:

button执行onclick函数_可能你的react函数组件从来没有优化过相关推荐

  1. button执行onclick函数_千万别再一直无脑使用ES6的箭头函数了,它虽然很有用但并不是万能的...

    相信很多小伙伴自从知道了ES6的箭头函数以后,都疯狂得使用,渐渐的淡忘了普通函数的使用.不过确实,箭头函数看起来比较简洁,用起来也舒服,不过它的出现是为了解决某一部分问题的,并不是用来替代普通函数的, ...

  2. python中函数提高代码执行速度吗_为什么Python代码在一个函数中运行得更快?

    匿名用户 除了局部/全局变量存储时间外,操作码预测使函数更快. 正如其他答案所解释的,该函数在循环中使用store_fast操作码.下面是函数循环的字节码:>> 13 FOR_ITER 6 ...

  3. react中纯函数_如何在纯React中创建电子邮件芯片

    react中纯函数 by Andreas Remdt 由Andreas Remdt 如何在纯React中创建电子邮件芯片 (How to create email chips in pure Reac ...

  4. python while函数_详解python while 函数及while和for的区别

    1.while循环(只有在条件表达式成立的时候才会进入while循环) while 条件表达式: pass while 条件表达式: pass else: pass 不知道循环次数,但确定循环条件的时 ...

  5. 中计算散度的函数_荷畔微风 - 在函数计算FunctionCompute中使用WebAssembly

    WebAssembly 是一种新的W3C规范,无需插件可以在所有现代浏览器中实现近乎原生代码的性能.同时由于 WebAssembly 运行在轻量级的沙箱虚拟机上,在安全.可移植性上比原生进程更加具备优 ...

  6. hive substr函数_数据分析工具篇——HQL函数及逻辑

    本篇文章我们梳理一下hive常用的函数,对于hive而言,常用的函数并不是特别多,往往记住关键几个,就可以解决80%的问题,这也是大家喜欢hive的原因,那么,常用的函数有哪些呢? 时间函数 1)时间 ...

  7. python四大高阶函数_详谈Python高阶函数与函数装饰器(推荐)

    一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首 ...

  8. python中怎么调用函数_浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数–封装独立的功能 2.调用函数–享受封装的成果 函数的作用:在开发时 ...

  9. python强制结束函数_为什么Python没有 main 函数?终于有人给出了正确答案!

    毫无疑问,Python中没有所谓的 main 入口函数,但在网上经常有文章提到"Python中的main函数"和"建议编写main函数"等. 他们的目的可能是模 ...

  10. scala 函数中嵌套函数_如何在Scala中将函数转换为部分函数?

    scala 函数中嵌套函数 First, let's see what is a function and a partial function, and then we will see their ...

最新文章

  1. 文件操作示例脚本 tcl
  2. mysql 数据库 xtrabackup (完全备份恢复,恢复后重启失败总结)
  3. Unity中实现Hololens的三维空间映射Spatial mapping
  4. leetcode算法题--数字序列中某一位的数字
  5. vue-router 的基本使用
  6. c语言实现两个有序链表的合并(代码示例)
  7. .net中使用XPath语言在xml中判断是否存在节点值的方法
  8. Android--Service完全解析,关于服务你所需知道的一切(下)
  9. olcd12864的u8g2库_U8G2 软件包单色1.3寸OLED屏驱动在 RT-Thread 移植问题
  10. 索引 | 学堂原创推文汇总-v1
  11. Excel2003和Excel2007的区别
  12. oracle 表字段拆分,oracle 字段拆分
  13. 瞒不住了,Prefetch 就是一个大谎言
  14. python是面向对象开发_Python之面向对象
  15. 服饰美妆新品 | 阿迪达斯可循环跑鞋第三代LOOP系列发布;赫丽尔斯X吃豆人跨界限定系列推出...
  16. CentOS6怎样开启MySQL远程访问
  17. CynosDB for PostgreSQL 一主多读架构设计及优化[内附独家PPT]
  18. 第四章 专业统计(上)-统计实务
  19. 错题本——数据库系统工程师 2014
  20. dac0832三角波c语言程序,单片机驱动dac0832输出方波三角波正玄波程序

热门文章

  1. ZendFramework-2.4 源代码 - 整体架构(类图)
  2. 项目中初试PHP单元测试
  3. Android OpenGL ES(十二):三维坐标系及坐标变换初步 .
  4. DFS ZOJ 1002/HDOJ 1045 Fire Net
  5. Ubuntu16.04安装Unity3d2019.2.6
  6. 20200620每日一句
  7. Atitit 知识聚合的方法大总结 目录 1. 什么是聚合 汇聚 1 2. 聚合化应用场景 2 2.1. 一站式 2 3. 知识聚合的历史与趋势
  8. Atitit 长距离无线通信法 LoRa NB-IoT NB-CIoT LoRa是Semtech公司的创新发明,该技术向用户提供显著的长距离、低功耗、安全数据传输机制。使用LoRa技术构建的公用网
  9. Atitit 身份证与银行卡校验规则
  10. atitit.跨架构 bs cs解决方案. 自定义web服务器的实现方案 java .net jetty  HttpListener