react 无状态组件使用_使用无状态组件优化React性能

react 无状态组件使用

一个由骨头制成的React徽标与恐龙化石一起在博物馆中展出。看一下无状态组件的演变。

这个故事是关于无状态组件的 。 这意味着其中没有任何this.state = { ... }调用的组件。 它们仅处理传入的“道具”和子组件。

首先,超级基础

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

编者注:在本文的演示中,我们正在尝试使用CodeSandbox
让我们知道您的想法!

好极了! 有用。 这确实很基础,但可以设置示例。

注意事项:

  • 它是无状态的。 没有this.state = { ... }
  • console.log在那里,因此您可以了解其使用情况。 特别是,在进行性能优化时,您将希望避免在道具没有实际更改的情况下进行不必要的重新渲染。
  • 那里的事件处理程序是“内联”的。 这是方便的语法,因为它的代码靠近它处理的元素,加上此语法意味着您不必进行任何.bind(this)仰卧起坐。
  • 使用这样的内联函数,由于必须在每个渲染器上创建该函数,因此性能损失很小。 稍后将详细介绍这一点。

这是一个演示组件

现在我们意识到,上面的组件不仅是无状态的,而且实际上是Dan Abramov所谓的呈现组件 。 它只是一个名称,但基本上,它是轻量级的,可以产生一些HTML / DOM,并且不会弄乱任何状态数据。

这样我们就可以使其具有功能! 好极了! 那不仅感觉到“臀部”,而且还使它不那么害怕,因为它更容易推理。 它获取输入,并且与环境无关,始终返回相同的输出。 当然,由于道具之一是可调用函数,因此它可以“回调”。

因此,让我们重写一下:

const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
}

感觉不舒服吗? 感觉就像纯JavaScript,无需考虑所使用的框架就可以编写东西。

他们说,它一直在重新渲染:(

假设我们的小User使用在状态随时间变化的组件中。 但是状态不会影响我们的组件。 例如,如下所示:

import React, { Component } from 'react'

class Users extends Component {
  constructor(props) {
    super(props)
    this.state = {
      otherData: null,
      users: [{name: 'John Doe', highlighted: false}]
    }
  }

  async componentDidMount() {
    try {
      let response = await fetch('https://api.github.com')
      let data = await response.json()
      this.setState({otherData: data})
    } catch(err) {
      throw err
    }
  }

  toggleUserHighlight(user) {
    this.setState(prevState => {
      users: prevState.users.map(u => {
        if (u.name === user.name) {
          u.highlighted = !u.highlighted
        }
        return u
      })
    })
  }

  render() {
    return <div>
      <h1>Users</h1>
      {
        this.state.users.map(user => {
          return <User
            name={user.name}
            highlighted={user.highlighted}
            userSelected={() => {
              this.toggleUserHighlight(user)
            }}/>
         })
      }
    </div>
  }
}

如果运行此命令,您会注意到即使没有任何更改,我们的小组件也会重新渲染! 现在这不是什么大问题,但是在实际的应用程序中,组件的复杂性趋于增长,并且每次不必要的重新渲染都会导致站点变慢。

如果您现在要使用react-addons-perf调试此应用程序,我相信您会发现浪费时间渲染Users->User 。 不好了! 该怎么办?!

一切似乎都指向一个事实,我们需要使用shouldComponentUpdate来覆盖React在确定它们没有区别时认为道具不同的方式。 要添加一个React生命周期挂钩,该组件需要成为一个类。 感叹 。 因此,我们回到原始的基于类的实现,并添加新的生命周期挂钩方法:

返回成为类组件

import React, { Component } from 'react'

class User extends Component {

  shouldComponentUpdate(nextProps) {
    // Because we KNOW that only these props would change the output
    // of this component.
    return nextProps.name !== this.props.name || nextProps.highlighted !== this.props.highlighted
  }

  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

请注意应该添加新的shouldComponentUpdate方法。 这有点丑陋。 我们不仅不能再使用功能,还必须手动列出可能更改的道具。 这涉及一个大胆的假设,即userSelected函数prop不会更改。 这不太可能,但是需要提防。

但是请注意,这只会渲染一次! 即使包含的App组件重新渲染。 因此,这对性能有好处。 但是,我们可以做得更好吗?

那React.PureComponent呢?

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

从React 15.3开始,有一个新的组件基类。 它称为PureComponent ,它具有内置的shouldComponentUpdate方法,该方法对每个道具进行“浅相等”比较。 大! 如果使用此方法,我们可以丢弃必须列出特定道具的自定义shouldComponentUpdate方法。

import React, { PureComponent } from 'react'

class User extends PureComponent {

  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

试试看,您会失望的。 每次都重新渲染。 为什么?! 答案是因为每次在Apprender方法中都会重新创建功能userSelected 。 这意味着,当基于PureComponent的组件调用其自己的shouldComponentUpdate()它将返回true,因为函数每次创建后总是不同的。

通常,解决方案是将函数绑定到包含组件的构造函数中。 首先,如果要这样做,则意味着我们必须键入5次方法名称(而之前是1次):

  • this.userSelected = this.userSelected.bind(this) (在构造函数中)
  • userSelected() { (作为方法定义本身)
  • <User userSelected={this.userSelected} ... (在定义呈现User组件的位置时)

如您所见,另一个问题是,当实际执行该userSelected方法时,它依赖于闭包。 特别是,它依赖于this.state.users.map()迭代器的作用域变量user

诚然,是解决这一并且对第一绑定的userSelected方法来this ,然后调用该方法(从子组件中)传递用户(或它的名字)回来的时候。 这是一个这样的解决方案

recompose救援!

首先,要迭代,我们想要什么:

  1. 编写功能组件会更好,因为它们是功能。 这立即告诉代码读取器它没有任何状态。 从单元测试的角度来看,他们很容易推理。 而且,他们觉得JavaScript不太冗长和纯净(当然有了JSX)。
  2. 我们懒得绑定所有传递给子组件的方法。 当然,如果方法很复杂,最好将它们重构出来而不是即时创建它们。 动态创建方法意味着我们可以在使用它们的位置附近编写其代码,而不必给它们起一个名字并在3个不同的地方提及5次。
  3. 除非更改了它们的道具,否则子组件绝对不能重新渲染。 对于微小的快照而言,可能并不重要,但对于现实世界中的应用程序而言,当您拥有很多这样的东西时,多余的渲染会在可能的情况下烧毁CPU。

(实际上,我们理想的情况是组件只渲染一次。为什么React无法为我们解决这个问题?那么关于“如何使React快速”的博客文章就会减少90%。)

recompose“ React Utility Belt,用于功能组件和高阶组件。 可以把它想成React的lodash。” 根据文档。 有很多在这个库的探索,但现在我们要显示我们的功能组件,而这些被重新呈现时的道具不会改变。

我们第一次尝试将其重新写回功能组件,但使用recompose.pure看起来像这样:

import React from 'react'
import { pure } from 'recompose'

const User = pure(({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
})

export default User

您可能会注意到,如果运行此命令,即使道具( namehighlighted键)没有变化, User组件仍会重新渲染。

让我们把它提高一个档次。 而不是使用recompose.pure我们将使用recompose.onlyUpdateForKeys这是一个版本recompose.pure ,但您指定的道具键把重点放在明确:

import React from 'react'
import { onlyUpdateForKeys } from 'recompose'

const User = onlyUpdateForKeys(['name', 'highlighted'])(({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
})

export default User

运行该命令时,您会注意到,只有在道具namehighlighted更改发生时,它才会更新。 如果父组件重新呈现,则User组件不会重新呈现。

欢呼! 我们找到了金子!

讨论区

首先,问问自己,优化组件是否值得进行性能测试。 也许这比价值还多。 无论如何,您的组件都应该轻巧,也许您可​​以将任何昂贵的计算移出组件,或者将其移至外部可记忆的函数中,或者您可以重新组织组件,以免在某些数据仍然无法使用时不浪费渲染组件。 。 例如,在这种情况下,您可能不希望在完成该fetch之后才呈现User组件。

这不是一个坏的解决方案编写代码为提供最便捷的方式,然后启动你的东西,然后从那里,重复,以使其更好的性能。 在这种情况下,要使性能发挥作用,您需要从以下位置重写功能组件定义:

const MyComp = (arg1, arg2) => {
...
}

…至…

const MyComp = pure((arg1, arg2) => {
...
})

理想情况下,最好的解决方案是显示React的新补丁,而不是展示如何破解事物的方法,这是对shallowEqual的巨大改进,它能够“自动”破译所传递和比较的内容是函数,并且仅仅因为它不相等并不意味着它实际上是不同的。

入场! 除了必须弄乱构造函数中的绑定方法和每次都重新创建的内联函数之外,还有一种中间立场。 这是公共类字段 。 这是Babel stage-2功能,因此您的设置很可能支持它。 例如, 这是一个使用它的叉子 ,它不仅更短,而且现在也意味着我们不需要手动列出所有非功能道具。 该解决方案必须放弃关闭。 但是,仍然需要理解并了解recompose.onlyUpdateForKeys ,这是很好的。

有关React的更多信息,请查看我们的课程React The ES6 Way

杰克·富兰克林Jack Franklin )对这篇文章进行了同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/optimizing-react-performance-stateless-components/

react 无状态组件使用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值