如果没有shouldComponentUpdate,React 0.14的无状态组件如何提供性能提升?

时间:2015-11-14 00:30:58

标签: reactjs

自从我阅读React 0.14的发布说明(以及其他相关宣传)后,这个问题一直在我脑海中转移 - 我是React的忠实粉丝,我认为是无状态组件({{3}这是一个很好的想法,既可以方便地编写这些组件,也可以用于在代码中表达这些组件应该是纯粹的#34;在对相同的道具数据进行一致渲染方面。

问题是:React如何能够优化这些无状态组件函数而不需要完全占用并假设道具引用不仅是不可变的,因为它们不应该在组件内被操纵,而是< em>还他们永远不会在组件生命周期之外进行更改? &#34;常规&#34;组件(也称为有状态组件 - 换句话说,贯穿整个生命周期的组件; componentWillMount,getInitialState等等)具有可选的&#34; shouldComponentUpdate&#34;函数是React 假设所有的props和state引用都是完全不可变的。在渲染组件之后,道具引用的某些属性可能会发生变化,因此相同的&#34;道具&#34;实例以后可能会有不同的内容。这就是为什么对使用完全不可变结构感到非常兴奋的原因以及为什么使用Om和React可以提供很好的性能提升;因为那里使用的不可变结构保证任何对象的任何给定实例都不会被变异,所以shouldComponentUpdate可以对props和state(https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components)执行非常便宜的引用相等性检查。

我一直在努力寻找更多有关此事的信息,但无处可去。我无法想象可以围绕无状态组件进行哪些性能改进没有假设道具数据将由不可变类型组成..也许是一些非不可变的初步分析道具类型试图猜测&#34;道具&#34;和&#34; nextProps&#34;代表相同的数据?

我只是想知道是否有人有任何内幕消息或其他一些有启发性的见解。如果React 开始要求道具类型是&#34;完全不可变的&#34; (允许引用相等比较以确认数据没有改变)然后我认为这将是一个很大的进步,但它也可能是一个很大的变化。

3 个答案:

答案 0 :(得分:3)

由于您的组件只是其参数的纯函数,因此可以直接缓存它。这是因为纯函数的众所周知的属性,对于相同的输入,它们将始终返回相同的输出。因为它们仅依赖于它们的参数,而不是某些内部或外部状态。除非您在该函数中明确引用了一些可能被解释为状态更改的外部变量。

但是,如果函数组件读取一些外部变量来组成返回值,则无法进行缓存,因此,这些外部变量可能会随着时间的推移而发生变化,从而使缓存的值过时。无论如何,这将违反纯粹的功能,而且它们不再是纯粹的。

React v0.14 Release Candidate页面上,Ben Alpert说:

  

此模式旨在鼓励创建应包含大部分应用的简单组件。将来,我们还可以通过避免不必要的检查和内存分配来优化这些组件的性能

我很确定他的意思是纯功能组件的可缓存性。

这是一个非常简单的缓存实现,用于演示目的:

let componentA = (props) => {
  return <p>{ props.text }</p>;
}

let cache = {};
let cachedA = (props) => {
  let key = JSON.stringify(props); // a fast hash function can be used as well
  if( key in cache ) {
    return cache[key];
  }else {
    cache[key] = componentA(props);
    return cache[key];
  }
}

目前我还能想到其他纯功能组件的优点:

  • 单元测试友好
  • 比基于类的组件更轻量级
  • 高度可重复使用,因为它们只是功能

答案 1 :(得分:1)

避免不必要的分配

如果我理解正确,无状态功能组件将转换为常规组件。来自the source

function StatelessComponent(Component) {
}
StatelessComponent.prototype.render = function() {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  return Component(this.props, this.context, this.updater);
};

创建无状态组件的实例时,将分配新对象。此新对象具有生命周期方法,例如componentWillMountcomponentWillReceiveProps。我猜这个计划是创建这些对象。 不创建对象将避免不必要的分配。

避免不必要的检查

实现生命周期方法需要进行大量检查:

if (inst.componentWillUpdate) {
  inst.componentWillUpdate(nextProps, nextState, nextContext);
}

可以假定无状态功能组件没有这些生命周期方法。这可能是文档所指的内容,但我不确定。

修改

删除了备忘录中的内容,这些内容没有回答问题或解释好问题。

答案 2 :(得分:0)

您可以使用装饰器组合无状态函数组件以执行高阶优化,以确定React是否应该呈现此组件。我正在使用immutable在道具之间执行严格的相等检查。

假设我们有这样的组件:

ClickableGreeter.js

const ClickableGreeter = (props) => (
    <div onClick={(e) => props.onClick(e)}>
        {"Hello " + props.name}
    </div>
)

ClickableGreeter.propTypes = {
    onClick: React.PropTypes.func.isRequired,
    name: React.PropTypes.text.isRequired
}

export default ClickableGreeter;

如果名称没有改变,我们希望React不呈现它。我正在使用一个简单的装饰器,它使用immutable库来创建props和nextProps的不可变表示,并执行简单的相等检查:

pureImmutableRenderDecorator.js

import React from 'react'
import Immutable from 'immutable';

const pureComponent = (Component, propsToRemove = []) => {

    class PureComponent extends React.Component {
        constructor(props) {
            super(props);
            this.displayName = 'PureComponent';
        }
        comparator(props, nextProps, state, nextState) {
            return (
                !Immutable.is(Immutable.fromJS(props), Immutable.fromJS(nextProps)) ||
                !Immutable.is(Immutable.fromJS(state), Immutable.fromJS(nextState))
            )
        }
        removeKeysFromObject(obj, keys) {
            var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target;
        }
        shouldComponentUpdate(nextProps, nextState) {
            let propsToCompare = this.removeKeysFromObject(this.props, propsToRemove),
                nextPropsToCompare = this.removeKeysFromObject(nextProps, propsToRemove);

            return this.comparator(propsToCompare, nextPropsToCompare, this.state, nextState)
        }
        render() {
            return <Component {...this.props} {...this.state} />
        }
    }

    return PureComponent;

}

export default pureComponent;

然后,您可以通过执行以下操作创建PureClickableGreeter组件:

const PureClickableGreeter = pureComponent(ClickableGreeter, ['onClick']) 
//we do not want the 'onClick' props to be compared since it's a callback

当然,在这里使用immutable是过度的,因为它是一个简单的字符串比较,但只要你需要一些嵌套的道具,immutable就是你要走的路。你还应该记住,Immutable.fromJS()是一个繁重的操作,但如果你没有很多道具(这通常是无状态功能组件的全部要点:保持尽可能多的道具以获得更好的代码,那就没问题了)分裂和可重用性。)