搜索
首页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,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

node.js流带打字稿node.js流带打字稿Apr 30, 2025 am 08:22 AM

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python vs. JavaScript:性能和效率注意事项Python vs. JavaScript:性能和效率注意事项Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript的起源:探索其实施语言JavaScript的起源:探索其实施语言Apr 29, 2025 am 12:51 AM

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

幕后:什么语言能力JavaScript?幕后:什么语言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。

Python和JavaScript的未来:趋势和预测Python和JavaScript的未来:趋势和预测Apr 27, 2025 am 12:21 AM

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

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脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

安全考试浏览器

安全考试浏览器

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