Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术随笔

    • git教程
    • 前端模块化:CommonJS,AMD,CMD,ES6
    • 如何动态加载本地图片
    • GitHub自动部署脚本
    • React生命周期详解
    • 将父组件的props作为组件的初始state的几种方案
    • webpack基础入门
    • StackOver记录:如何在function中使用redux
    • React中使用onClick时的三种性能优化
      • 前言
      • 一、bind
        • 第一种方案:使用bind
        • 第二种方案:使用箭头函数(也是目前使用最多的)
      • 二、思考:是否使用shouldComponentDidUpdate的三个阶段
        • 结论:
      • 三、三种高频操作的性能优化方案
        • debounce(防抖)与throttle(节流)
      • 实操:Talk is cheap,show me code
        • 1. debouce(防抖)
        • 2. throttle(节流)
        • 3. requestAnimationFrame节流
      • 本篇blog未解决的问题
      • 参考文献:
    • 思考React状态管理
    • forEach/for..in/of
    • styled-components使用记录
    • 深入理解forEach与forof
    • Node技术架构
    • Redux、Mobx状态管理杂谈
    • JS中的类型检测
    • 如何判断图片是否在可视区域
    • 如何使用图片的懒加载操作
    • JavaScript模块化:从CommonJS到AMD
    • git查看:修改用户名、密码
    • 保持数据Immutable的几种写法
    • 如何实现一个Promise.all
    • resolve(Promise对象)会多出两个微任务
    • todo_基于Mocha的测试驱动开发
    • 浏览器页面渲染机制
    • todo-响应式编程之数据劫持
    • todo-immer库的介绍
    • React-Hooks 基础原理解析
    • JS基本功
    • 函数闭包与this指针
    • 收集 DUMI 配置过程中遇到的问题
    • typescript 类型工具
    • 统一接口的导入导出
    • 网络安全:如何预防 XSS 攻击
    • 浏览器页面渲染机制【2023】
    • 跨域资源请求
    • 浏览器事件循环机制
    • 如何理解浏览器的 user agent 用户代理的含义?
  • 转载文章

  • Mac相关

  • 前端学习周报

  • 包管理工具

  • 技术随笔
  • 技术随笔
wangjiasheng
2021-12-18
目录

React中使用onClick时的三种性能优化

# 前言

其实这里有点标题党了,实际上是希望解决的问题是,在组件中使用事件函数时可以执行的优化,而最常用的事件处理函数就是onClick函数。

先给上结论与实际使用方法,如果还有兴趣往下看的话,可以继续阅读。


本篇博客的主要结论有:

  • 尽量不要在render函数内定义事件函数

  • 高频操作的性能优化:

    1. 函数防抖(debounce)
    2. 函数节流(throttling)
    3. 增强版节流:requestAnimationFrame

    如果想要知道具体在项目中如何使用上面的优化方法,请跳转至:Talk is cheap,show me code

# 一、bind

既然是对事件函数进行优化,第一步就是事件函数到底该如何定义?

比如,在传统的类组件中,我们是如何将函数绑定到组件实例上呢?

# 第一种方案:使用bind

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

以及 render 内写法

return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
1

# 第二种方案:使用箭头函数(也是目前使用最多的)

class Foo extends Component {
  handleClick = () => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}
// 还有一种写法:感觉用的人不是很多,就是当有参数传入的箭头函数,之前以为是不能改写,其实也可以的。
class Foo extends Component {
  handleClick = ("参数")=>() => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick("参数")}>Click Me</button>;
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

以及 render 内写法

return <button onClick={()=>{}}>Click Me</button>;
1

在React官网 (opens new window)中,对此的评价是:

  1. 在render方法中使用Function.prototype.bind会在每次组件渲染时创建一个新的函数,可能会影响性能(参见下文)
  2. 在render方法中可以使用箭头函数,但会在每次组件渲染时创建一个新的函数,可能会影响性能(参见下文),如果遇到了性能问题,一定要进行优化!
点击查看

传统的函数定义与箭头函数的区别?

结论是:当不使用this指针时,两者基本没有没啥区别。注意这里是基本,像arguments对于箭头函数当然是没有的啦。

在日常开发中,两者的区别在于:主要是this指针的执行不同,箭头函数能正常访问到外部的上下文(外部变量)。

会发生this差异的场景:

  • 最常见的是点击事件中的this,一般指向函数的调用者,此时this指向button按钮。
  • 还有情况是,定义的事件函数要传递给其他组件使用。

⭐️在React中经常遇到一个问题:父组件 →\rightarrow→ 子组件

无论子组件的接受父组件的props中的值是否改变,都会触发子组件的渲染。

当然,一般情况下,不去处理也没啥太大的问题,因为这种渲染不会带来太大的性能问题。

render函数的执行 ≠\ne​= Dom render。

当然,实在看不爽,我们可以也使用PureComponent或者ShouldComponentDidUpata去优化。

但是对于这种性能优化,我的编程方式经历了三个阶段:

# 二、思考:是否使用shouldComponentDidUpdate的三个阶段

使用shouldComponentDidUpdate →\rightarrow→ 不用 →\rightarrow→ 使用

  1. 使用shouldComponentDidUpdate

    刚学的时候,什么都不会,才发现父组件改变了,子组件的render函数每次都会重新执行一遍。于是我做了下面的这个蠢事:

    使用JSON.stringify()去校验子组件的props是否改变。props中一定会存在复杂数据类型,简单的浅比较还是渲染子组件。

    但是这种深比较太费性能了,远比重复渲染带来更到的性能问题。

  2. 不用shouldComponentDidUpdate

    所以第二阶段我干脆不用了shouldComponentDidUpdate,因为什么都不做要比瞎优化好的多,或者只是单纯控制props中的几个关键变量的变化。

  3. 再次使用shouldComponentDidUpdate

    前者说了render函数的执行 ≠\ne​= Dom render。但是,如果我们的render中有一个超大变量的定义时,或者在render函数中存在非常昂贵的性能操作了。

    此时可能gpu不爆炸了,可能cpu要爆炸了。(这种说法肯定是错的,但是记忆还是不错的)

# 结论:

需要非常清楚,影响性能消耗的原因。尽量不要在render中做太多的事情。

比如:可以在函数中定义的就在函数中定义,有些请求能在constructor或者其他生命周期中完成的,就完全没必要放在render函数中。

# 三、三种高频操作的性能优化方案

# debounce(防抖)与throttle(节流)

这一块主要是针对高频操作的事件函数,如onScoll、或者高频的onClick操作的优化。

这里不使用中文防抖与节流来作为标题,主要是原先的两个名称实在是太难记忆了。

现在给出官方定义:

节流 (Throttle):节流是阻止函数在给定时间内被多次调用,以时间频率更新状态。——解决调用太快。

防抖 (debounce):抖确保函数上次执行后的一段时间内,不会再次执行。——解决调用太多次。

还是看图比较直观:throttle和debounce的可视化页面 (opens new window)。

image-20211218161650163

根据上面这张图就很轻松判断当前事件函数使用哪儿个方案解决,比方说:

  • 频繁点击:希望只保留最后一次,但就用debounce
  • 滚动触发:只希望滚动按照一定节奏更新视图:就用throttle

# requestAnimationFrame节流

这种方案是节流的扩展,在节流中我们肯定是用setInterval去实现的,那一般间隔的秒数肯定是有我们决定的,对于像滚动这类操作,我们只需要保证人眼视力捕获动态的物体的最小帧数,即每秒24帧。一般写代码的时候,我们尽力确保每秒60帧。

但是这里存在一个问题,如果有些设备的最高帧数只有30帧怎么办呢?那60帧与30帧则没有任何区别。

React官网对此的解释:

使用requestAnimationFrame来节流是一种有用的技术,它可以防止在一秒中进行60帧以上的更新。如果要求一秒钟内完成100次更新,则会为浏览器创建额外的负担,而用户无法看到这些工作(不能区别每秒100帧和60帧的区别)。

# 实操:Talk is cheap,show me code

以上三个方法,都已经有第三库集成实现了。

  • throttling: 基于时间的频率来进行更改 (例如 _.throttle (opens new window))
  • debouncing: 一段时间的不活动之后进行更改 (例如 _.debounce (opens new window))
  • requestAnimationFrame:基于requestAnimationFrame (opens new window)来进行更改 (例如 raf-schd (opens new window))

# 1. debouce(防抖)

import _ from 'lodash';

class Searchbox extends React.Component {
  constructor(props) {
    super(props);
    // bind 绑定好评!
    this.handleChange = this.handleChange.bind(this);
    this.emitChangeDebounced = _.debounce(this.emitChange, 250);
  }

  componentWillUnmount() {
    this.emitChangeDebounced.cancel();
  }

  render() {
    // 控制输入的更新速度
    return <input onChange={this.handleChange}>; 
  }

  handleChange(e) {
    // 原先的话,直接写:this.emitChange(e.target.value)
    
    // 使用lodash的话,直接包一层就可以了。
    this.emitChangeDebounced(e.target.value);
  }

  emitChange(value) {
    // 实际触发函数
    this.props.onChange(value);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 2. throttle(节流)

import throttle from 'lodash.throttle';

class LoadMoreButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleClickThrottled = throttle(this.handleClick, 1000);
  }

  componentWillUnmount() {
    this.handleClickThrottled.cancel();
  }

  render() {
    return <button onClick={this.handleClickThrottled}>Load More</button>;
  }

  handleClick() {
    this.props.loadMore();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3. requestAnimationFrame节流

官网给出的:控制scroll的方法,使用方法同节流。

只是多了一步,需要解除监听,将rafSchedule返回值保留下来,用于取消监听。

import rafSchedule from 'raf-schd';

class ScrollListener extends React.Component {
  constructor(props) {
    super(props);

    this.handleScroll = this.handleScroll.bind(this);
    this.scheduleUpdate = rafSchedule(
      point => this.props.onScroll(point)
    );
  }

  handleScroll(e) {
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
  }

  componentWillUnmount() {
    this.scheduleUpdate.cancel();
  }

  render() {
    return (
      <div
        style={{ overflow: 'scroll' }}
        onScroll={this.handleScroll}
      >
        <img src="/my-huge-image.jpg" />
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
点击查看

但是上面的代码没办法直接运行,这里给出的代码可以直接执行

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <style>
      #e {
        width: 100px;
        height: 100px;
        background: brown;
        position: absolute;
        left: 0;
        top: 0;
        zoom: 1;
      }
    </style>
  </head>
  <body>
    <div id="e"></div>
    <script>
      var e = document.getElementById("e");
      var flag = true;
      var left = 0;

      function render() {
        if (flag == true) {
          if (left >= 100) {
            flag = false;
          }
          e.style.left = ` ${left++}px`;
        } else {
          if (left <= 0) {
            flag = true;
          }
          e.style.left = ` ${left--}px`;
        }
      }
      //requestAnimationFrame效果
      (function animloop(time) {
        console.log(time, Date.now());
        render();
        rafId = requestAnimationFrame(animloop);
        //如果left等于50 停止动画
        if (left == 50) {
          cancelAnimationFrame(rafId);
        }
      })();
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 本篇blog未解决的问题

  1. 手写节流与防抖,此部分会单独另开一个blog文章记录。

  2. 在requestAnimationFrame函数的可控操作中,与目前接受的项目中的一个函数非常像,当时一直没有看懂,看了第二篇参考博客才知道这是为了解决:同步服务端时间与本地时间不一致的问题。

    比如:服务端时间:A、客户端时间B1和B2。

    前端去请求数据的时候,服务端会返回:A-B1,请求成功的客户端时间为B2。

    只需要将时间差相加则有:

    A−B1+B2=A+ΔB=A+ΔAA-B1+B2=A+\Delta B = A+ \Delta A A−B1+B2=A+ΔB=A+ΔA

    则本地请求结束后,就拿到服务端目前的真实时间了。

# 参考文献:

  1. React官网:在组件中使用事件处理函数 (opens new window)
  2. 简书:requestAnimationFrame详解 (opens new window)
编辑 (opens new window)
#React
上次更新: 2022/04/06, 15:04:00
StackOver记录:如何在function中使用redux
思考React状态管理

← StackOver记录:如何在function中使用redux 思考React状态管理→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式