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

How will React 0.14's Stateless Components offer performance improvements without shouldComponentUpdate?

自从我阅读有关 React 0.14 的发行说明(和其他相关炒作)以来,这个问题一直在我脑海中盘旋——我是 React 的忠实粉丝,我认为无状态组件 (https://facebook.github.io/react/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components) 是一个很好的想法,无论是为了便于编写这些组件,还是为了在代码中表达这些组件应该 "pure" 一致地呈现相同道具数据的意图。

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

我一直在尝试查找有关此的更多信息,但一无所获。我无法想象 可以 围绕无状态组件 进行哪些性能改进 而无需 假设 props 数据将包含不可变类型..也许一些初步分析尝试猜测 "props" 和 "nextProps" 是否代表相同的数据?

我只是想知道是否有人对此有任何内部信息或一些启发性的见解。如果 React did 开始要求 props 类型为 "fully immutable"(允许引用相等性比较以确认数据没有改变)那么我认为这将是向前迈出的一大步,但它也可能是一个很大的变化。

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

但是,如果您的功能组件读取一些外部变量来组成 return 值,则缓存将无法实现,因此,这些外部变量可能会随着时间的推移而变化,从而使缓存值过时。无论如何,这将违反作为纯函数的规定,它们将不再是纯函数。

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

This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.

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

这里是一个非常直接的缓存实现,用于演示目的:

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];
  }
}

而我目前能想到的纯函数式组件还有其他好的特性:

  • 单元测试友好
  • 比基于 class 的组件更轻量
  • 高度可重用,因为它们只是函数

避免不必要的分配

如果我没理解错的话,stateless functional components 被转换成regular components。来自 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);
}

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

编辑

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

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

假设我们有这种组件:

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 有点矫枉过正,因为它只是一个简单的字符串比较,但是一旦你需要一些嵌套的 props,immutable 就是你要走的路。您还应该记住 Immutable.fromJS() 是一项繁重的操作,但是如果您不需要很多道具也没关系(这通常是无状态功能组件的全部要点:保留尽可能多的道具以获得更好的代码拆分和可重用性)。

终于有答案了!我不确定这是什么版本的 React(我怀疑 0.14 不包括这个,只是奠定了基础)但现在 PureComponent 没有实现“shouldComponentUpdate”,因为它是由 React 自动处理的,其中:

only shallowly compares the objects

确实 意味着如果您不能通过浅层比较可靠地检测到变化,则在使用此类组件时必须小心,但如果可以的话,它会它们非常高效,可以避免对虚拟 DOM!

进行大量更改

查看此处了解更多信息ReactJs.org's 'React.PureComponent' section