react 性能优化

  1. 使用 React Fragments 避免额外标记 片段不会向组件引入任何额外标记,但它仍然为两个相邻标记提供父级,因此满足在组件顶级具有单个父级的条件.

export default class NestedRoutingComponent extends React.Component {
    render() {
        return (
            <>
                <h1>This is the Header Component</h1>
                <h2>Welcome To Demo Page</h2>
            </>
        )
    }
}
  1. 不要使用内联函数定义

上面的函数创建了内联函数。每次调用 render 函数时都会创建一个函数的新实例,render 函数会将该函数的新实例绑定到该按钮。

  1. 避免 componentWillMount() 中的异步请求 在检索数据时 React 会触发组件的 render 函数。因此第一个调用的渲染仍然不包含它所需的数据。

这样一开始渲染组件没有数据,然后检索数据,调用 setState,还得重新渲染组件。在 componentWillMount 阶段进行 AJAX 调用没有好处可言。

我们应避免在此函数中发出 Async 请求。这些函数和调用可以延迟到 componentDidMount 生命周期事件里。

import React from "react";
import axios from "axios";

export default class UsingAsyncInComponentWillMount extends React.Component {

  constructor() {
    this.state = {
      userData: null
    }
  }
  
  componentWillMount() {
    axios.get("someResourceUrl").then((data) => {
      this.setState({
        userData: data
      });
    });
  }
  
  render() {
    return (
      <>
        <b>UserName: {this.state.name}</b>
        <b>UserAge: {this.state.age}</b>
      </>
    )
  }
}
  1. 在 Constructor 的早期绑定函数

每次调用 render 函数时都会创建并使用绑定到当前上下文的新函数,但在每次渲染时使用已存在的函数效率更高。优化方案如下:

import React from "react";

export default class DelayedBinding extends React.Component {
  constructor() {
    this.state = {
      name: "Mayank"
    }
    this.handleButtonClick = this.handleButtonClick.bind(this)
  }
  
  handleButtonClick() {
    alert("Button Clicked: " + this.state.name)
  }
  
  render() {
    return (
      <>
        <input type="button" value="Click" onClick={this.handleButtonClick} />
      </>
    )
  }
}
  1. 箭头函数与构造函数中的绑定 处理类时的标准做法就是使用箭头函数。使用箭头函数时会保留执行的上下文。

我们调用它时不需要将函数绑定到上下文。

import React from "react";

export default class DelayedBinding extends React.Component {
  constructor() {
    this.state = {
      name: "Mayank"
    }
  }
  
  handleButtonClick = () => {
    alert("Button Clicked: " + this.state.name)
  }
  
  render() {
    return (
      <>
        <input type="button" value="Click" onClick={this.handleButtonClick} />
      </>
    )
  }
}
  1. 避免使用内联样式属性

import React from "react";

export default class InlineStyledComponents extends React.Component {
  render() {
    return (
      <>
        <b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
      </>
    )
  }
}

内联样式需要话费更多时间来处理脚本和渲染,映射传递给实际 css 属性的所有样式规则.添加的内联样式是 JavaScript 对象而不是样式标记. 样式 backgroundColor 需要转换为等效的 css 样式属性,然后才应用样式.

  1. 优化 React 中的条件渲染 减少组件的安装和卸载. 条件渲染进行提升.

  2. 不要在 render 方法中导出数据

import React from "react";

export default class RenderFunctionOptimization extends React.Component {
  constructor() {
    this.state = {
      name: "Mayank"
    }
  }
  
  render() {
    this.setState({
      name: this.state.name + "_"
    });

    return (
      <div>
        <b>User Name: {this.state.name}</b>
      </div>
    );
  }
}

更新组件状态的问题在于,当状态更新时会触发另一个 render 循环,后者在内部会再触发一个 render 循环,以此类推。

每次渲染时,都会在内存中创建一个新函数(因为它是在 render 函数中创建的),并将对内存中新地址的新引用传递给 ,虽然输入完全没有变化,该 Button 组件还是会重新渲染。不要在 render 中 定义这些函数官方推荐: createAlertBox = () => { alert(this.props.message); }; <Button onClick={createAlertBox} />通过传递 createAlertBox 方法,它就和 SomeComponent 重新渲染无关了,甚至和 message 这个属性是否修改也没有关系。createAlertBox 内存中的地址不会改变,这意味着 Button 不需要重新渲染,节省了处理时间并提高了应用程序的渲染速度class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // 此语法确保 `handleClick` 内的 `this` 已被绑定。 return ( <button onClick={() => this.handleClick()}> Click me </button> ); }}此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。显示与隐式参数传递<button onClick = { (e)=> this.handleClick( id,e ) }></button><button onClick = { this.handleClick.bind( this,id ) }></button>上述两种方式是等价的,分别通过 箭头函数 和 Function.prototype.bind 来实现。在上面两种情况下,React的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。神奇的 childrenimport React, { useContext, useState } from "react";const ThemeContext = React.createContext();function ChildNonTheme() { console.log("不关心皮肤的子组件渲染了"); return <div>我不关心皮肤,皮肤改变的时候别让我重新渲染!</div>;}function ChildWithTheme() { const theme = useContext(ThemeContext); return <div>我是有皮肤的哦~ {theme}</div>;}function ThemeApp({ children }) { const [theme, setTheme] = useState("light"); const onChangeTheme = () => setTheme(theme === "light" ? "dark" : "light"); return ( <ThemeContext.Provider value={theme}> <button onClick={onChangeTheme}>改变皮肤</button> {children} </ThemeContext.Provider> );}export default function App() { return ( <ThemeApp> <ChildWithTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> </ThemeApp> );}这本质上是由于 React 是自上而下递归更新,<ChildNonTheme /> 这样的代码会被 babel 翻译成 React.createElement(ChildNonTheme) 这样的函数调用,React官方经常强调 props 是immutable 的,所以在每次调用函数式组件的时候,都会生成一份新的 props 引用。通过 children 传入后直接渲染,由于 children 从外部传入的,也就是说 ThemeApp 这个组件内部不会再有 React.createElement 这样的代码,那么在 setTheme 触发重新渲染后,children 完全没有改变,所以可以直接复用。让我们再看一下被 ThemeApp 包裹下的 <ChildNonTheme />,它会作为 children 传递给 ThemeApp,ThemeApp 内部的更新完全不会触发外部的 React.createElement,所以会直接复用之前的 element 结果:import React, { useContext, useState } from "react";const ThemeContext = React.createContext();export function ChildNonTheme() { console.log("不关心皮肤的子组件渲染了"); return <div>我不关心皮肤,皮肤改变的时候别让我重新渲染!</div>;}export function ChildWithTheme() { const theme = useContext(ThemeContext); return <div>我是有皮肤的哦~ {theme}</div>;}function ThemeApp({ children }) { const [theme, setTheme] = useState("light"); const onChangeTheme = () => setTheme(theme === "light" ? "dark" : "light"); return ( <ThemeContext.Provider value={theme}> <button onClick={onChangeTheme}>改变皮肤</button> {children} </ThemeContext.Provider> );}export default function App() { return ( <ThemeApp> <ChildWithTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> <ChildNonTheme /> </ThemeApp> );}总结下来,就是要把渲染比较费时,但是不需要关心状态的子组件提升到「有状态组件」的外部,作为 children 或者props传递进去直接使用,防止被带着一起渲染。React.memo神奇的 children组合 Providersconst StateProviders = ({ children }) => ( <LogProvider> <UserProvider> <MenuProvider> <AppProvider> {children} </AppProvider> </MenuProvider> </UserProvider> </LogProvider>)function App() { return ( <StateProviders> <Main /> </StateProviders> )}function composeProviders(...providers) { return ({ children }) => providers.reduce( (prev, Provider) => <Provider>{prev}</Provider>, children, )}const StateProviders = ({ children }) => ( <LogProvider> <UserProvider> <MenuProvider> <AppProvider> {children} </AppProvider> </MenuProvider> </UserProvider> </LogProvider>)function App() { return ( <StateProviders> <Main /> </StateProviders> )}尽量提升渲染无关的子组件元素到「有状态组件」的外部。 在需要的情况下对 Context 进行读写分离。 包装Context 的使用,注意错误处理。 组合多个 Context,优化代码。

最后更新于

这有帮助吗?