提问者:小点点

如何应对0.14的无状态组件在不更新shouldComponentUpdate的情况下提供性能改进?


自从我读了React 0.14的发行说明(和其他相关的炒作)后,这个问题就在我的脑海中反复出现——我是React的超级粉丝,我认为无状态组件(https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components)是一个极好的主意编写这些组件的难易程度,以及在代码中表达这些组件在为相同的道具数据一致呈现方面应该是“纯”的意图。

问题是:React如何能够优化这些无状态组件功能,而不必全力以赴,并假设props引用不仅不应在组件内进行操作,而且在组件生命周期之外也永远不会更改,因此props引用是不变的?“常规”组件(又名有状态组件——换句话说,贯穿整个生命周期的组件;componentWillMount、getInitialState等)的原因有一个可选的“shouldComponentUpdate”函数,即React并不假设所有的props和state引用都是完全不可变的。渲染组件后,props引用的某些属性可能会更改,因此同一个“props”实例稍后可能具有不同的内容。这就是为什么人们对使用完全不变的结构感到非常兴奋的部分原因,也是为什么有人说将Om与React结合使用可以带来巨大的性能提升的原因;因为这里使用的不可变结构保证了任何对象的任何给定实例都不会发生变异,所以shouldComponentUpdate可以对props和state执行非常便宜的引用相等性检查(http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/).

我一直试图找到更多关于这方面的信息,但一无所获。如果不假设道具数据将由不可变类型组成,我无法想象围绕无状态组件可以进行哪些性能改进...也许对非不可变道具类型进行一些初步分析,试图猜测“道具”和“下一个道具”是否代表相同的数据?

我只是想知道是否有人对此有任何内幕消息或一些有启发性的见解。如果React确实开始要求道具类型“完全不可变”(允许引用相等性比较来确认数据没有改变),那么我认为这将是向前迈出的一大步,但也可能是一个巨大的变化。


共3个答案

匿名用户

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

但是,如果您的函数组件读取一些外部变量来组成返回值,那么缓存将不可能,因此,这些外部变量可能会随着时间的推移而改变,从而使缓存的值过时。不管怎样,这都违反了纯函数的性质,它们也不再是纯函数了。

在v0上进行反应。14发布候选页面,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];
  }
}

我现在还能想到纯功能组件的其他好特性:

  • 单元测试友好

匿名用户

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

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

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

实施生命周期方法需要进行如下检查:

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

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

删除了记忆化的内容,这些内容没有很好地回答问题或解释问题。

匿名用户

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

假设我们有这样的组件:

可点击的欢迎者。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;

如果名称不变,我们希望做出不渲染的反应。我正在使用一个简单的decorator,它使用immutable库来创建道具和nextrops的不可变表示,并执行一个简单的相等性检查:

pureImmutableRenderCorator。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.fromJS()是一个繁重的操作,但是如果您不需要很多道具(这通常是无状态函数组件的全部意义:保留尽可能多的道具可以实现更好的代码拆分和可重用性)。