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 相关文章推荐
JS类库Bindows1.3中的内存释放方式分析
Mar 08 Javascript
在IE中调用javascript打开Excel的代码(downmoon原作)
Apr 02 Javascript
解析使用JS 清空File控件的路径值
Jul 08 Javascript
jQuery实现级联菜单效果(仿淘宝首页菜单动画)
Apr 10 Javascript
javascript常用代码段搜集
Dec 04 Javascript
最丑的时钟效果!js canvas时钟制作方法
Aug 15 Javascript
ionic由于使用了header和subheader导致被遮挡的问题的两种解决方法
Sep 22 Javascript
详解JavaScript的闭包、IIFE、apply、函数与对象
Dec 21 Javascript
Angular组件化管理实现方法分析
Mar 17 Javascript
vue.js利用defineProperty实现数据的双向绑定
Apr 28 Javascript
微信小程序 蓝牙的实现实例代码
Jun 27 Javascript
Vue中nprogress页面加载进度条的方法实现
Nov 13 Javascript
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新手上路(二)
2006/10/09 PHP
PHP数字格式化
2006/12/06 PHP
php 随机数的产生、页面跳转、件读写、文件重命名、switch语句
2009/08/07 PHP
ThinkPHP 整合Bootstrap Ajax分页样式
2016/12/23 PHP
PHP输出多个元素的排列或组合的方法
2017/03/14 PHP
学习并汇集javascript匿名函数
2010/11/25 Javascript
Prototype的Class.create函数解析
2011/09/22 Javascript
jQuery中andSelf()方法用法实例
2015/01/08 Javascript
PHP+jQuery+Ajax实现多图片上传效果
2015/03/14 Javascript
js实现跨域的方法实例详解
2015/06/24 Javascript
在AngularJS框架中处理数据建模的方式解析
2016/03/05 Javascript
基于Javascript倒计时效果
2016/12/22 Javascript
微信小程序 本地存储及登录页面处理实例详解
2017/01/11 Javascript
Mongoose学习全面理解(推荐)
2017/01/21 Javascript
利用express启动一个server服务的方法
2017/09/17 Javascript
解决vue动态路由异步加载import组件,加载不到module的问题
2020/07/26 Javascript
详解ES6 中的Object.assign()的用法实例代码
2021/01/11 Javascript
Vue多选列表组件深入详解
2021/03/02 Vue.js
[05:31]DOTA2英雄梦之声_第04期_光之守卫
2014/06/23 DOTA
[01:05]主宰至宝剑心之遗
2017/03/16 DOTA
[02:42]岂曰无衣,与子同袍!DOTA2致敬每一位守护人
2020/02/17 DOTA
python调用shell的方法
2013/11/20 Python
使用Python生成随机密码的示例分享
2016/02/18 Python
Django中使用Whoosh进行全文检索的方法
2019/03/31 Python
Python turtle绘画象棋棋盘
2019/08/21 Python
python3 实现调用串口功能
2019/12/26 Python
Pandas 解决dataframe的一列进行向下顺移问题
2019/12/27 Python
面向新手解析python Beautiful Soup基本用法
2020/07/11 Python
Python+unittest+requests+excel实现接口自动化测试框架
2020/12/23 Python
凯撒娱乐:Caesars Entertainment
2018/02/23 全球购物
护士辞职信模板
2014/01/20 职场文书
文化与传播毕业生求职信
2014/03/09 职场文书
指导教师评语
2014/04/26 职场文书
中学生运动会口号
2014/06/07 职场文书
工作收入证明范本
2015/06/12 职场文书
互联网创业商业模式以及赚钱法则有哪些?
2019/10/12 职场文书