目录
Users
React中状态组件和无状态组件的主要区别是什么?
如何使用无状态组件优化我的React应用程序的性能?
什么是PureComponent,它如何帮助优化React性能?
如何将React中的状态组件转换为无状态组件?
在React中使用无状态组件的最佳实践是什么?
无状态组件可以在React中使用生命周期方法吗?
React中无状态组件的局限性是什么?
无状态组件如何提高代码的可读性?
无状态组件可以在React中处理事件吗?
无状态组件如何促进代码的模块化?
首页 web前端 js教程 通过无状态组件优化反应性能

通过无状态组件优化反应性能

Feb 16, 2025 am 11:35 AM

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组件渲染的地方)

另一个问题是,正如你所看到的,当实际执行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

热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

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

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

前端热敏纸小票打印遇到乱码问题怎么办? 前端热敏纸小票打印遇到乱码问题怎么办? Apr 04, 2025 pm 02:42 PM

前端热敏纸小票打印的常见问题与解决方案在前端开发中,小票打印是一个常见的需求。然而,很多开发者在实...

神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

JavaScript难以学习吗? JavaScript难以学习吗? Apr 03, 2025 am 12:20 AM

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? 如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

如何实现视差滚动和元素动画效果,像资生堂官网那样?
或者:
怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? 如何实现视差滚动和元素动画效果,像资生堂官网那样? 或者: 怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? Apr 04, 2025 pm 05:36 PM

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

console.log输出结果差异:两次调用为何不同? console.log输出结果差异:两次调用为何不同? Apr 04, 2025 pm 05:12 PM

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...

See all articles