React性能优化系列之减少props改变的实现方法


Posted in Javascript onJanuary 17, 2019

React性能优化的一个核心点就是减少render的次数。如果你的组件没有做过特殊的处理(SCU -- shouldComponentUpdate或使用PureComponent),那每次父组件render时,子组件就会跟着一起被重新渲染。通常一个复杂的子组件都会进行一些优化,比如:SCU 使用PureComponent组件。对于SCU基本上进行的也都是浅比较,深比较的代价太高。

对于这些被优化的子组件,我们要减少一些不必要的props改变:比如事件绑定。对于那些依赖于配置项的组件,我们更是减少这些作为props的配置的变化,因为可能一但配置项发生了变化,整个组件都会跟着重新渲染,所以我们要尽可能的减少props的改变

事件绑定

class ClickMe extends React.Component {
  state = {
    value: '3333',
  };

  render() {
    return (
      <Button
        onClick={() => {
          console.log('l am clicked!', this.state.value);
        }}
      >
        click me
      </Button>
    )
  }
}

相信大多数的开发者React都会指出这种写法的缺点:每次ClickMe组件渲染的时候onClick属性与上一次的值相比都是一个不同的匿名函数,如果Button是一个复杂的子组件且内部没有经过任何特殊的处理,那就会造成多余的渲染。对于这种情况的做法一般有两种方式:

  1. 在构造函数内绑定 this
  2. 将箭头函数赋予class的属性
class ClickMe extends React.Component {
  state = {
    value: '3333',
  };

  handleClick = () => {
    console.log('l am clicked!', this.state.value);
  };

  render() {
    return (
      <Button
        onClick={this.handleClick}
      >
        click me
      </Button>
    )
  }
}

// 或
class ClickMe extends React.Component {
  constuctor(props) {
    super(props);
    this.state = {
      value: '3333',
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('l am clicked!', this.state.value);
  }

  render() {
    return (
      <Button
        onClick={this.handleClick}
      >
        click me
      </Button>
    )
  }
}

批量事件绑定

那在考虑下面这种情况,涉及到子组件的批量绑定时:

class MultiClick extends React.Component {
  dataSource = [
    { key: '1', value: '1' },
    { key: '2', value: '2' },
    { key: '3', value: '3' },
    { key: '4', value: '4' },
  ];

  handleClick = key => {
    console.error('key:', key);
  };

  render() {
    return (
      <div>
        {this.dataSource.map(item => (
          <div
            key={item.key}
            onClick={() => {
              this.handleClick(item.key);
            }}
          >
            {item.value}
          </div>
        ))}
      </div>
    );
  }
}

类似于这种需要传递参数的情况,该如何去优化?

这个就需要我们去做数据的缓存,即回调的缓存,上述例子如下:

cacheMap = {};

genClickHandler = key => {
  if (!this.cacheMap[key]) {
    this.cacheMap[key] = () => {
      console.error('key:', key);
    };
  }
  return this.cacheMap[key];
};

// 绑定
<div key={item.key} onClick={this.genClickHandler(item.key)}>
  {item.value}
</div>;

如果多个基本类型的参数可以,将他们拼接成字符串作为cacheMap的key,简单的引用类型可以使用JSON.stringify,不过原则上作为事件绑定的函数 传递的参数简单为好。

作为配置的props缓存

说到数据的缓存,不管光是事件的回调,还有很多 其他情况。比如表格的 columns需要根据属性变化的这种场景:

class TableDemo extends React.Component {
  getColumns = () => {
    const { name } = this.state;
    return [
      {
        key: '1',
        title: `${name}_1`,
      },
      {
        key: '2',
        title: `${name}_2`,
      },
    ];
  };

  render() {
    const { dataSource } = this.props;
    return <Table dataSource={dataSource} columns={this.getColumns()} />;
  }
}

这种情况每次组件render的时候,getColumns都会被调用一次,而这个函数每次的返回值都是不一样的 ,及时这两次的name值都相等,原因大家可以类比[] !== []这里就不过多叙述了。

有一种做法是,将columns作为一个this.state的一个属性,在初始化和每次 this.state.name改变的时候同步改变this.state.columns的值,但如果有多个 类似于this.state.name的变量控制this.state.columns的值时候,发现每个变量变化的时候都要调用生成columns的方法, 十分的烦琐易造成错误。

使用缓存可以很好的解决这个问题,在参数较为复杂的时候,我们选择只缓存上一次的值。先看代码再说:

首先我们写一个缓存的函数

function cacheFun(cb) {
  let preResult = null
  let preParams = null
  const equalCb = cb || shallowEqual
  return (fun, params) => {
    if (preResult && equalCb(preParams, params)) {
      return preResult
    }
    preResult = fun(params)
    preParams = params
    return preResult
  }
}

这个缓存函数是一个闭包函数,保存了上一次的参数和上一次的结果,主要的实现就是比较两次的参数,相同则返回上一次结果,不同则返回 调用函数的新结果。当然 对于某些特殊的情况只需要根据传入特定的某几个参数做出判断,这种情况你可以传入自定义的比较函数。先看一下上面的实现:

cacheFun函数第一个参数为选填的选项,是你比较两次参数的 方法,如果你不传入则仅进行 浅比较(与 React 的浅比较相似)。

返回函数的第一个参数为你的 生成columns的回调,params 为你需要的 变量,如果你的变量比较多,你可以将他们 作为一个对象传入;那么代码就类似如下:

const params = { name, time, handler };
cacheFun(this.getColumns, params, cb);

在类中的使用为:

class TableDemo extends React.Component {
  getColumns = name => {
    return [
      {
        key: '1',
        title: `${name}_1`,
      },
      {
        key: '2',
        title: `${name}_2`,
      },
    ];
  };

  getColumnsWrapper = () => {
    const { name } = this.state;
    return cacheFun()(this.getColumns, name);
  };

  render() {
    const { dataSource } = this.props;
    return (
      <Table dataSource={dataSource} columns={this.getColumnsWrapper()} />
    );
  }
}

假如你不喜欢对象的传值方式,那你可以 对这个缓存函数进行更改:

function cacheFun(cb) {
  let preResult = null;
  let preParams = null;
  const equalCb = cb || shallowEqual;
  return (fun, ...params) => {
    if (preResult) {
      const isEqual = params.ervey((param, i) => {
        const preParam = preParams && preParams[i];
        return equalCb(param, preParam);
      });
      if (isEqual) {
        return preResult;
      }
    }
    preResult = fun(params);
    preParams = params;
    return preResult;
  };
}

你这可以这样使用:

cacheFun()(this.getColumns, name, key, param1, params2);
// 或者
cacheFun()(this.getColumns, name, key, { param1, params2 });

这样配置也就被缓存优化了,当TableDemo组件因非name属性render时,这时候你的columns还是返回上一次缓存的值,是的Table这个组件减少了一次因columns引用不同产生的render。如果Table的dataSource数据量很大,那这次对应用的优化就很可观了。

数据的缓存

数据的缓存在原生的内部也有使用cacheFun的场景,如对于一个list 根据 searchStr模糊过滤对于的subList

大致代码如下:

class SearchList extends React.Component {
  
  state = {
    list: [
      { value: '1', key: '1' },
      { value: '11', key: '11' },
      { value: '111', key: '111' },
      { value: '2', key: '2' },
      { value: '22', key: '22' },
      { value: '222', key: '222' },
      { value: '2222', key: '2222' },
    ],
    searchStr: '',
  }

  // ...

  render() {
    const { searchStr, list } = this.state
    const dataSource = list.filter(it => it.indexOf(searchStr) > -1)
    return (
      <div>
        <Input onChange={this.handleChange} />
        <List dataSource={dataSource} />
      </div>
    )
  }
}

对于此情景的优化使用cacheFun也可以实现

const dataSource = cacheFun()((plist, pSearchStr) => {
  return plist.filter(it => it.indexOf(pSearchStr) > -1)
}, list, searchStr)

但是有大量的类似于此的衍生值的时候,这样的写法又显得不够。社区上出现了许多框架如配合react-redux使用reselect(当然也可以单独使用,不过配合redux使用简直就是前端数据管理的一大杀手锏),还有mobx的衍生概念等。这些后续会单独介绍,这里就稍微提一下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript 获取用户客户端操作系统版本
Aug 25 Javascript
Prototype源码浅析 Enumerable部分之each方法
Jan 16 Javascript
jquery.ajax的url中传递中文乱码问题的解决方法
Feb 07 Javascript
一个CSS+jQuery实现的放大缩小动画效果
Feb 19 Javascript
jquery实现ajax提交form表单的方法总结
Mar 03 Javascript
JS实现淡入淡出图片效果的方法分析
Dec 20 Javascript
Vue.js对象转换实例
Jun 07 Javascript
Vue项目中设置背景图片方法
Feb 21 Javascript
JavaScript数组基于交换的排序示例【冒泡排序】
Jul 21 Javascript
如何从零开始手写Koa2框架
Mar 22 Javascript
写给新手同学的vuex快速上手指北小结
Apr 14 Javascript
使用Vue.js和MJML创建响应式电子邮件
Mar 23 Vue.js
vue 项目接口管理的实现
Jan 17 #Javascript
详解VUE单页应用骨架屏方案
Jan 17 #Javascript
Jquery获取radio选中值实例总结
Jan 17 #jQuery
js中call()和apply()改变指针问题的讲解
Jan 17 #Javascript
js中怎么判断两个字符串相等的实例
Jan 17 #Javascript
js中null与空字符串&quot;&quot;的区别讲解
Jan 17 #Javascript
vue中$nextTick的用法讲解
Jan 17 #Javascript
You might like
php编程实现获取excel文档内容的代码实例
2011/06/28 PHP
整理的一些实用WordPress后台MySQL操作命令
2013/01/07 PHP
php中stream(流)的用法
2014/03/25 PHP
PHP+jQuery 注册模块开发详解
2014/10/14 PHP
php使用pecl方式安装扩展操作示例
2019/08/12 PHP
常用js脚本
2006/12/03 Javascript
javascript 特殊字符串
2009/02/25 Javascript
window.dialogArguments 使用说明
2011/04/11 Javascript
javascript对象的使用和属性操作示例详解
2014/03/02 Javascript
JavaScript eval() 函数介绍及应用示例
2014/07/29 Javascript
JavaScript实现select添加option
2015/07/03 Javascript
jQuery拖动元素并对元素进行重新排序
2015/12/30 Javascript
JavaScript Ajax编程 应用篇
2016/07/02 Javascript
js基于cookie记录来宾姓名的方法
2016/07/19 Javascript
关于javascript的一些知识以及循环详解
2016/09/12 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
js实现的xml对象转json功能示例
2016/12/24 Javascript
在使用JSON格式处理数据时应该注意的问题小结
2017/05/20 Javascript
Angularjs实现下拉框联动的示例代码
2017/08/22 Javascript
nodeJS微信分享
2017/12/20 NodeJs
对Vue beforeRouteEnter 的next执行时机详解
2018/08/25 Javascript
如何使用JavaScript实现栈与队列
2019/06/24 Javascript
[45:52]完美世界DOTA2联赛PWL S3 Forest vs INK ICE 第二场 12.09
2020/12/12 DOTA
Python调用C/C++动态链接库的方法详解
2014/07/22 Python
Python实现过滤单个Android程序日志脚本分享
2015/01/16 Python
Python抓取电影天堂电影信息的代码
2016/04/07 Python
python中的协程深入理解
2019/06/10 Python
pytorch 实现张量tensor,图片,CPU,GPU,数组等的转换
2020/01/13 Python
Python基于time模块表示时间常用方法
2020/06/18 Python
Tensorflow中批量读取数据的案列分析及TFRecord文件的打包与读取
2020/06/30 Python
html5使用canvas画空心圆与实心圆
2014/12/15 HTML / CSS
详解如何获取localStorage最大存储大小的方法
2020/05/21 HTML / CSS
英国户外装备和冒险服装零售商:alloutdoor
2018/01/30 全球购物
世界领先的艺术图书出版社:TASCHEN
2018/07/23 全球购物
计算机毕业生自荐信
2014/06/12 职场文书
使用react-virtualized实现图片动态高度长列表的问题
2021/05/28 Javascript