使用 PureRenderMixin 遇到的问题及 React.addons.PureRenderMixin 的实现

最近对项目中的组件进行了一些改造对组件添加了 PureRenderMixin,在改造过程中遇到一些问题,在这里做一个简单的记录。

 

示例:

假设有这样一个简单的 React 组件 App,其 state 里面有一个 items 属性为一个数组,render 方法将 items 里面的数据展示出来,并提供一个 按钮,用以添加数据(数据维护中常见的功能)。

// 改造前,注意 _addItem 实现
import React from 'react' import { render } from 'react-dom'
import PureRenderMixin from 'react-addons-pure-render-mixin' class App extends React.Component {   constructor(){     this.state = {       items: []     }
    // 添加 PureRenderMixin 的语句
    // this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate
    // 通过 React.createClass 新建的组件可以直接在其 mixins: 属性中添加 PureRenderMixin 即可
    // mixins: [PureRenderMixin]   }   render() {
    return (<div>
          <button onClick={ this._addItem }> 添加随机数据 </button>
          <ul>             {               this.state.items.map((item, idx)=> {                 return <li key={ idx }> {JSON.stringify(item)} </li>               })             }           </ul>
        </div>)
  }
  _addItem() {
    // 直接对 items 进行 push 操作,
    let { items = [] } = this.state;
    items.push(Math.ceil(Math.random()*1000));
    this.setState({
      items: items
    })
  }
}

上面的代码在未添加 PureRenderMixin 时正常运行,点击按钮正常调用  addItem  方法,并按照组件生命周期调用 render 方法组件刷新正常添加了 item 展示在页面上。

但是当添加了 PureRenderMixin 后 无论怎么点击 "添加随机数据",组件都不会调用 render 方法进行相应的刷新展示完整数据。通过工具查看 state.items 属性却是有正常的添加新值的。通过在 render 方法中打印 log 发现是数据变化后没有调用 render 方法重新渲染组件,所以组件展示没有变化。

 

原因(PureRenderMixin实现):

pureRenderMixin 的实现主要是修改了组件的 shouldComponentUpdate 方法的实现对于组件 state 或 props 变化过后先进行一个 shallowCompare(浅比较)的过程在决定是否需要 render 组件从而优化相关渲染性能。

实现代码主要为:

1 // 源文件地址: https://github.com/facebook/react/blob/master/src/addons/ReactComponentWithPureRenderMixin.js
2 var ReactComponentWithPureRenderMixin = {
3   shouldComponentUpdate: function(nextProps, nextState){
4     return shallowCompare(this, nextProps, nextState);
5   }
6 }

上面的代码使用了一个 shallowCompare 的方法,实现代码如下:

1 // 源文件地址 https://github.com/facebook/react/blob/master/src/addons/shallowCompare.js
2 function shallowCompare(instance, nextProps, nextState) {
3     return (
4         !shallowEqual(instance.props, nextProps) ||
5         !shallowEqual(instance.state, nextState)
6     );
7 }

可以看到主要的实现都放到 shallowEqual 这个方法里面,这个方法使用的是一个第三方库用于浅比较两个对象是否相等。
浅的意义在于,不会去迭代的对对象进行深度比较,只取得对象的 key 的值进行比较,对于基础类型直接比较值是否相等,对于引用类型只比较其引用是否相等。

简版的实现如下:

 1 function shallowEqual(objA, objB) {
 2     if(objA === objB) {
 3         return true;
 4     }
 5     var keyA = Object.keys(objA),
 6           keyB = Object.keys(objB);
 7 
 8     if(keyA.length != keyB.length) {
 9         return false;
10     }
11 
12     for(var idx = 0, len = keyA.length; idx < len; idx++ ) {
13         var key = keyA[idx];
14 
15         if(!objB.hasOwnProperty(key)) {
16             return false;
17         }
18         var valueA = objA[key],
19               valueB = objB[key];
20         // 无差别比较,引用类型比较引用,基础类型直接比较值
21         if(valueA !== valueB) {
22             return false;
23         }
24     }
25     return true;
26 }

完整 shallowEqual 实现代码地址: https://github.com/dashed/shallowequal/blob/master/src/index.js

 

将 PureRenderMixin的实现 结合到上面的例子分析:

我们的 _addItem 方法是直接对 state.items 进行 push 操作的,所以执行完 push 操作后 items === state.items 是一直返回 true 的。然后通过 setState 钩子去修改组件的 state 按照组件生命周期接下来组件将会执行 shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate ,由于我们组件添加了 PureRenderMixin 替换了默认的  shouldComponentUpdate 方法,然后按照前面看到的 PureRenderMixin 代码对于 state 的比较会是通过 shallowEqual 方法返回值。而由于新设置的 items 属性和原本的 state.items 是同一对象所以返回结果为 true , 本例中没有 props 传递,也返回 true , 最终 shouldComponentUpdate 运算结果为 false(!true || !true) 所以没有调用 render 方法更新组件。

 

如何修改:

通过上面的分析我们已经知道问题的原因在于新设置的 items 数组与原来的 state.items 是同一对象保存着同一份引用,所以可以考虑重新构建一个对象使其与原来的  state.items 指向不同的引用即可解决上面的问题。

可选的解决方案: Immutable Data(React爬坑秘籍(一)——提升渲染性能),深拷贝(存在潜在的性能问题), ...(es 6解构,对应于对象的 Object.assign, 数组的 map,与Immutable Data 的思路一样)

这里由于是对数组进行的操作,直接通过 concat 返回一个新的数组对象,然后用这个新的数组对象设置为 items 属性即可。

修改后的 _addItems 方法:

_addItem() {
    // 直接对 items 进行 push 操作,
    let { items = [] } = this.state,
         newItems;
    newItems = items.concat(Math.ceil(Math.random()* 1000));
    // newItems = [...items, Math.ceil(Math.random() * 1000)];
    this.setState({
        items: newItems
    })
}

总结: 

在 JavaScript 中变量是可以是一个具体的值(简单类型)或执行一个引用(对象,数组等),所以在使用引用类型的时候要格外注意,不然可能导致程序出现一些意料之外的问题。

在使用 PureRenderMixin 或者 shallowEqual 时注意其进行的是浅比较,对于引用类型比较的是引用是否相同。

posted @ 2016-10-10 13:47  youngBrain1893  阅读(1253)  评论(0编辑  收藏  举报