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

Posted

技术标签:

【中文标题】如果没有 shouldComponentUpdate,React 0.14 的无状态组件将如何提供性能改进?【英文标题】:How will React 0.14's Stateless Components offer performance improvements without shouldComponentUpdate? 【发布时间】:2016-02-15 16:31:15 【问题描述】:

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

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

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

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

【问题讨论】:

【参考方案1】:

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

只对对象进行浅层比较

确实意味着如果您无法通过浅层比较可靠地检测到更改,则必须小心使用此类组件,但如果可以的话,它会使它们非常高效并且可以可能避免对虚拟 DOM 进行大量更改!

查看这里了解更多信息ReactJs.org's 'React.PureComponent' section。

【讨论】:

【参考方案2】:

你可以使用装饰器来组合你的无状态函数组件来执行高阶优化,以确定 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 是多余的,因为它是一个简单的字符串比较,但是一旦你需要一些嵌套的道具,immutable 就是要走的路。您还应该记住,Immutable.fromJS() 是一个繁重的操作,但如果您不需要很多道具(这通常是无状态功能组件的全部意义所在:保留尽可能多的道具以更好地进行代码拆分和可重用性)。

【讨论】:

【参考方案3】:

避免不必要的分配

如果我理解正确的话,无状态的功能组件被转换为常规组件。来自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 库)中轻松实现,因为它可能需要一些关于 props 数据类型的知识 - 例如引用相等检查是否足够分离道具(这适用于不可变类型)或者是否需要对道具实例进行更深入的分析? (我只是简单地浏览了 memoize 的实现,但它似乎假定任何给定实例的数据永远不会改变,是吗?)。 @DanDanDan,我记忆中的东西没有回答你的问题,所以我把它删除了。我正在试验用户级性能调整。问题是,memoize 的特性实际上是依赖于实现的。应该可以制作一个依赖于引用相等的版本,并制作一个进行深度相等检查的版本。 我不太确定我是否得到了 100% 回答我原来的问题的答案 - 但是,那么,这个问题可能还没有答案.. 这个想法可能是为了无状态组件,以便在未来进行更多优化!所以我认为您的答案最接近,因此值得接受 - 谢谢!【参考方案4】:

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

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

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

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

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

【讨论】:

您的示例缓存使用引用相等检查来比较一个 props 实例与另一个实例,这只有在 props 对象的同一实例始终具有相同数据的情况下才会真正起作用 - 如果它基本上是不可变的。这是我原来问题的一部分;你是否认为 React 正在向更严格的要求 props 类型可靠地不可变的飞跃? 你错了,我上面的代码通过JSON.stringify序列化数据来检查严格的数据相等性。你是对的,为了使用比深度数据相等性检查更有效的引用相等性检查,必须确保底层数据是不可变的,即如果必须更改一小部分数据,那么一个新的应创建数据副本。但是您可以通过使用不可变数据结构轻松地在您的应用程序上实现这一点,无需等待 React 团队实现官方解决方案。谢谢。 对不起,你对缓存键序列化的看法是正确的!而且我可以利用自己使用不可变结构的好处,而无需更改任何 React 框架。不过,我的问题仍然存在,React 如何在不假设使用不可变类型的情况下优化这些无状态组件 - 对于有状态组件,我可以使用 ShouldComponentUpdate 为 React 提供更多信息,但我无法使用无状态组件来做到这一点。如果框架尝试 props 序列化或深度相等检查,它无法知道这些过程是否会很昂贵。 你是对的,React 不能使用引用相等,因为他们似乎不可能强迫人们使用不可变的数据结构,或者让他们承诺不改变数据。所以如果他们必须做一些优化,包括缓存,它应该使用深度数据相等。而且我相信如果组件的嵌套层次足够深,这将是一个有用的优化。

以上是关于如果没有 shouldComponentUpdate,React 0.14 的无状态组件将如何提供性能改进?的主要内容,如果未能解决你的问题,请参考以下文章

返回特定行,在列中级联结果 - 如果没有 1 则多个 2,否则如果没有 2 则 3,否则如果没有 3,则多个 4

windows schtasks如果没有打开则打开excel,如果没有打开则打开工作表

如果函数没有明确使用'ret',为啥没有返回值

如果用户没有与动画交互,Actionscript 3 执行一个动作

如果没有你

如果流没有手动关闭,它啥时候关闭?