搜索
首页web前端js教程通过无状态组件优化反应性能

Optimizing React Performance with Stateless Components

Optimizing React Performance with Stateless Components

本文探讨React中的无状态组件。这类组件不包含this.state = { ... }调用,仅处理传入的“props”和子组件。

要点总结

  • React中的无状态组件不包含任何this.state = { … }调用。它们只处理传入的“props”和子组件,这使得它们更简单,也更容易从测试的角度进行分析。
  • 无状态组件可以转换为函数式组件,这更像是纯JavaScript,对所使用的框架依赖性更小。
  • 通过使用React.PureComponent可以优化无状态组件的性能,它具有内置的shouldComponentUpdate方法,可以对每个prop进行浅比较。
  • Recompose是一个用于React中函数组件和高阶组件的实用工具库。它可以用来渲染函数组件,而不会在props不变时重新渲染。
  • 无状态组件可以显着提高React应用程序的性能,因为它们不管理自己的状态或生命周期方法,从而减少了代码量和内存消耗。但是,它们也有一些限制,例如不能使用生命周期方法或管理自己的状态。

基础知识

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

代码运行正常。它非常基础,但建立了示例。

需要注意的是:

  • 它是无状态的,没有this.state = { ... }
  • console.log用于了解其使用情况。特别是在进行性能优化时,如果props实际上没有改变,则需要避免不必要的重新渲染。
  • 这里的事件处理程序是“内联”的。这种语法很方便,因为它的代码靠近它处理的元素,而且这种语法意味着你不需要进行任何.bind(this)操作。
  • 使用这样的内联函数,会有一点性能损失,因为函数必须在每次渲染时创建。稍后将详细介绍这一点。

展示型组件

我们现在意识到上面的组件不仅是无状态的,它实际上是Dan Abramov所说的展示型组件。它只是一个名称,但基本上,它很轻量级,产生一些HTML/DOM,并且不处理任何状态数据。

所以我们可以把它做成一个函数!这不仅感觉“很酷”,而且也使它不那么可怕,因为它更容易理解。它接收输入,并且独立于环境,总是返回相同的输出。当然,它“回调”,因为其中一个prop是一个可调用的函数。

让我们重写它:

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

感觉不错吧?它感觉像是纯JavaScript,你可以编写它而无需考虑你正在使用的框架。

持续重新渲染的问题

假设我们的User组件用在一个随时间变化状态的组件中。但是状态不会影响我们的组件。例如,像这样:

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

运行这段代码,你会注意到,即使什么都没有改变,我们的组件也会重新渲染!现在这还不是什么大问题,但在实际应用中,组件往往会越来越复杂,每一次不必要的重新渲染都会导致网站速度变慢。

如果你现在用react-addons-perf调试这个应用,我肯定你会发现时间浪费在渲染Users->User上了。糟糕!该怎么办?!

似乎一切都在表明我们需要使用shouldComponentUpdate来覆盖React如何认为props不同,而我们确信它们并没有不同。为了添加React生命周期钩子,组件需要成为一个类。。所以我们回到最初的基于类的实现,并添加新的生命周期钩子方法:

回到类组件

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 id="Users">Users</h1>
      {
        this.state.users.map(user => {
          return <User
            name={user.name}
            highlighted={user.highlighted}
            userSelected={() => {
              this.toggleUserHighlight(user)
            }}/>
         })
      }
    </div>
  }
}

注意新增的shouldComponentUpdate方法。这有点难看。我们不仅不能再使用函数,而且还必须手动列出可能更改的props。这涉及到一个大胆的假设,即userSelected函数prop不会改变。这不太可能,但需要注意。

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

React.PureComponent

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

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

尝试一下,你会失望的。它每次都会重新渲染。为什么?!答案是因为userSelected函数在App的render方法中每次都会重新创建。这意味着当基于PureComponent的组件调用它自己的shouldComponentUpdate()时,它会返回true,因为该函数总是不同的,因为它每次都会被创建。

通常情况下,解决这个问题的方法是在包含组件的构造函数中绑定该函数。首先,如果我们这样做,这意味着我们必须键入方法名5次(而之前是1次):

  • this.userSelected = this.userSelected.bind(this)(在构造函数中)
  • userSelected() { ... }(作为方法定义本身)
  • <user ... userselected="{this.userSelected}"></user>(在定义User组件渲染的地方)

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

诚然,这个问题有一个解决方案,那就是首先将userSelected方法绑定到this,然后当调用该方法(在子组件中)时,将user(或其名称)传递回去。这里有一个这样的解决方案。

recompose来救援!

首先,让我们回顾一下我们的目标:

  1. 编写函数式组件感觉更好,因为它们是函数。这立即告诉代码阅读者它不保存任何状态。它们很容易从单元测试的角度进行推理。而且它们感觉更简洁,更像纯JavaScript(当然还有JSX)。
  2. 我们太懒了,不想绑定所有传递给子组件的方法。当然,如果方法很复杂,最好重构它们,而不是动态创建它们。动态创建方法意味着我们可以直接在它们使用的地方编写它们的代码,我们不必为它们命名,也不必在三个不同的地方提及它们5次。
  3. 子组件应该只在props改变时才重新渲染。对于小型快速的组件来说,这可能无关紧要,但对于实际应用来说,当你有许多这样的组件时,所有这些额外的渲染都会在可以避免的情况下浪费CPU。

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

根据文档,recompose是“一个用于函数组件和高阶组件的React实用工具库。可以把它想象成React的lodash。”。这个库有很多东西可以探索,但现在我们想渲染我们的函数组件,而不会在props没有改变时重新渲染。

我们第一次尝试用recompose.pure重写它回到函数组件,看起来像这样:

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

你可能会注意到,如果你运行这段代码,User组件仍然会重新渲染,即使props(namehighlighted键)没有改变。

让我们更进一步。我们不使用recompose.pure,而是使用recompose.onlyUpdateForKeys,它是recompose.pure的一个版本,但你可以明确地指定要关注的prop键:

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

运行这段代码后,你会注意到,只有当namehighlighted props改变时,它才会更新。如果父组件重新渲染,User组件不会。

万岁!我们找到了金子!

讨论

首先,问问自己是否值得对组件进行性能优化。也许这比它值得的要多。你的组件应该很轻量级,也许你可以将任何昂贵的计算从组件中移出,并将它们移到外部的可记忆化函数中,或者你可以重新组织你的组件,以便在某些数据不可用时不浪费渲染组件。例如,在本例中,你可能不想在fetch完成之前渲染User组件。

以对你来说最方便的方式编写代码,然后启动你的程序,然后从那里迭代以使其性能更好,这不是一个坏的解决方案。在本例中,为了提高性能,你需要将函数组件的定义从:

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

…改为…

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

理想情况下,与其展示绕过问题的方法,最好的解决方案是React的一个新补丁,它对shallowEqual进行了巨大的改进,能够“自动”地识别正在传入和比较的是一个函数,并且仅仅因为它不相等并不意味着它实际上不同。

承认!对于不得不处理在构造函数中绑定方法和每次重新创建的内联函数,存在一种折中的替代方案。那就是公共类字段。它是Babel中的一个第二阶段特性,所以你的设置很可能支持它。例如,这里有一个使用它的分支,它不仅更短,而且现在也意味着我们不需要手动列出所有非函数props。这个解决方案必须放弃闭包。尽管如此,当需要时,了解和意识到recompose.onlyUpdateForKeys仍然是好的。

更多关于React的信息,请查看我们的课程《React The ES6 Way》。

本文由Jack Franklin同行评审。感谢所有SitePoint的同行评审者,使SitePoint的内容尽善尽美!

关于使用无状态组件优化React性能的常见问题

React中状态组件和无状态组件的主要区别是什么?

状态组件(也称为类组件)维护关于组件状态随时间变化的内存。它们负责组件的行为和渲染方式。另一方面,无状态组件(也称为函数组件)没有自己的状态。它们以props的形式从父组件接收数据并渲染它。它们主要负责组件的UI部分。

如何使用无状态组件优化我的React应用程序的性能?

无状态组件可以显著提高React应用程序的性能。由于它们不管理自己的状态或生命周期方法,因此它们的代码更少,这使得它们更容易理解和测试。它们还可以减少应用程序消耗的内存量。为了优化性能,你可以尽可能地将状态组件转换为无状态组件,并使用React的PureComponent来避免不必要的重新渲染。

什么是PureComponent,它如何帮助优化React性能?

PureComponent是一种特殊的React组件,可以帮助优化应用程序的性能。它使用浅层prop和状态比较来实现shouldComponentUpdate生命周期方法。这意味着它只有在状态或props发生变化时才会重新渲染,这可以显着减少不必要的重新渲染并提高性能。

如何将React中的状态组件转换为无状态组件?

将状态组件转换为无状态组件包括从组件中删除任何状态或生命周期方法。组件应该只通过props接收数据并渲染它。这是一个例子:

// 状态组件 class Welcome extends React.Component { render() { return

Hello, {this.props.name}
; } }

// 无状态组件 function Welcome(props) { return

Hello, {props.name}
; }

在React中使用无状态组件的最佳实践是什么?

在React中使用无状态组件的一些最佳实践包括:尽可能多地使用它们来提高性能和可读性;保持它们小巧,专注于单一功能;避免使用状态或生命周期方法。还建议使用props的解构来编写更简洁的代码。

无状态组件可以在React中使用生命周期方法吗?

不可以,无状态组件不能在React中使用生命周期方法。生命周期方法只适用于类组件。但是,随着React 16.8中Hooks的引入,你现在可以向函数组件添加状态和生命周期特性。

React中无状态组件的局限性是什么?

虽然无状态组件有很多优点,但它们也有一些局限性。它们不能使用生命周期方法或管理自己的状态。但是,这些限制可以通过使用React Hooks来克服。

无状态组件如何提高代码的可读性?

无状态组件通过简洁明了来提高代码的可读性。它们不管理自己的状态或使用生命周期方法,这使得它们更简单、更容易理解。它们还鼓励使用小型、可重用的组件,这可以使代码更井然有序,更容易维护。

无状态组件可以在React中处理事件吗?

可以,无状态组件可以在React中处理事件。事件处理程序可以作为props传递给无状态组件。这是一个例子:

function ActionLink(props) { return ( Click me ); }

无状态组件如何促进代码的模块化?

无状态组件通过促进使用小型、可重用的组件来促进代码的模块化。每个组件都可以独立开发和测试,这使得代码更易于维护和理解。它还鼓励关注点分离,其中每个组件负责单一功能。

以上是通过无状态组件优化反应性能的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
在JavaScript中替换字符串字符在JavaScript中替换字符串字符Mar 11, 2025 am 12:07 AM

JavaScript字符串替换方法详解及常见问题解答 本文将探讨两种在JavaScript中替换字符串字符的方法:在JavaScript代码内部替换和在网页HTML内部替换。 在JavaScript代码内部替换字符串 最直接的方法是使用replace()方法: str = str.replace("find","replace"); 该方法仅替换第一个匹配项。要替换所有匹配项,需使用正则表达式并添加全局标志g: str = str.replace(/fi

8令人惊叹的jQuery页面布局插件8令人惊叹的jQuery页面布局插件Mar 06, 2025 am 12:48 AM

利用轻松的网页布局:8个基本插件 jQuery大大简化了网页布局。 本文重点介绍了简化该过程的八个功能强大的JQuery插件,对于手动网站创建特别有用

构建您自己的Ajax Web应用程序构建您自己的Ajax Web应用程序Mar 09, 2025 am 12:11 AM

因此,在这里,您准备好了解所有称为Ajax的东西。但是,到底是什么? AJAX一词是指用于创建动态,交互式Web内容的一系列宽松的技术。 Ajax一词,最初由Jesse J创造

如何创建和发布自己的JavaScript库?如何创建和发布自己的JavaScript库?Mar 18, 2025 pm 03:12 PM

文章讨论了创建,发布和维护JavaScript库,专注于计划,开发,测试,文档和促销策略。

使用AJAX动态加载盒内容使用AJAX动态加载盒内容Mar 06, 2025 am 01:07 AM

本教程演示了创建通过Ajax加载的动态页面框,从而可以即时刷新,而无需全页重新加载。 它利用jQuery和JavaScript。将其视为自定义的Facebook式内容框加载程序。 关键概念: Ajax和JQuery

10个JQuery Fun and Games插件10个JQuery Fun and Games插件Mar 08, 2025 am 12:42 AM

10款趣味横生的jQuery游戏插件,让您的网站更具吸引力,提升用户粘性!虽然Flash仍然是开发休闲网页游戏的最佳软件,但jQuery也能创造出令人惊喜的效果,虽然无法与纯动作Flash游戏媲美,但在某些情况下,您也能在浏览器中获得意想不到的乐趣。 jQuery井字棋游戏 游戏编程的“Hello world”,现在有了jQuery版本。 源码 jQuery疯狂填词游戏 这是一个填空游戏,由于不知道单词的上下文,可能会产生一些古怪的结果。 源码 jQuery扫雷游戏

如何为JavaScript编写无曲奇会话库如何为JavaScript编写无曲奇会话库Mar 06, 2025 am 01:18 AM

此JavaScript库利用窗口。名称属性可以管理会话数据,而无需依赖cookie。 它为浏览器中存储和检索会话变量提供了强大的解决方案。 库提供了三种核心方法:会话

jQuery视差教程 - 动画标题背景jQuery视差教程 - 动画标题背景Mar 08, 2025 am 12:39 AM

本教程演示了如何使用jQuery创建迷人的视差背景效果。 我们将构建一个带有分层图像的标题横幅,从而创造出令人惊叹的视觉深度。 更新的插件可与JQuery 1.6.4及更高版本一起使用。 下载

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),