react的性能优化建议

1. 组件卸载前进行清理操作

在组件中为 window 注册的全局事件, 以及定时器, 在组件卸载前要清理掉, 防止组件卸载后继续执行影响应用性能

使用 useState 保存一个数值,然后开启定时器改变数值,卸载组件时清理定时器

import React, { useState, useEffect } from "react";
function UseState() {
  const [date, setDate] = useState(new Date());
  useEffect(() => {
    const timer = setInterval(() => {
      setDate(new Date());
    }, 1000);
    //卸载时清理
    return () => clearInterval(timer);
  }, [date]);
  return (
    <div>
      <p>{date.toLocaleTimeString()}</p>
    </div>
  );
}
export default UseState;

2. 降低组件重新渲染的频率

1、什么是纯组件?
在渲染前会判断state、props的数据变化,相同的输入 (state、props) 呈现相同的输出. 在输入内容相同的情况下纯组件不会被重新渲染.
2、如何实现纯组件?
React 提供了 PureComponent 类, 类组件在继承它以后, 类组件就变成了纯组件. 纯组件会对 props 和 state 进行浅层比较, 如果上一次的 props、state 和下一次的 props、state 相同, 则不会重新渲染组件.
3、什么是浅层比较
浅比较指的是比较基本数据类型是否具有相同的值,、比较复杂数据类型(数组、对象等)的第一层值是否相同。
4、浅层是否有性能消耗
有性能消耗,但是和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整颗 virtualDOM 树, 而浅层比较值操作当前组件的 state 和 props。

具体的代码实例见之前的博客:组件优化

3. 使用组件懒加载

使用组件懒加载可以减少 bundle 文件大小, 加快组件呈递速度.

路由懒加载
import {BrowserRouter, Link, Redirect, Switch, Route } from "react-router-dom"
import { lazy, Suspense } from "react"
import Home from "./pages/Home"
// import About from "./pages/About"

const About = lazy(() => import(/* webpackChunkName: "about" */ "./pages/About"))

function App() {
  return (
    <BrowserRouter>
      <div>
        <Link to="/home">Home</Link> - <Link to="/about">About</Link>
      </div>
      <Switch>
        <Route path="/home">
          <Home />
        </Route>
        <Route path="/about">
          <Suspense fallback={<div>loading....</div>}>
            <About />
          </Suspense>
        </Route>
        <Route path="/">
          <Redirect to="/home" />
        </Route>
      </Switch>
    </BrowserRouter>
  )
}
export default App
根据条件进行组件懒加载

适用于组件不会随条件频繁切换。好处是可以将两个组件全部从 bundle 中分离出来,减少 bundle 文件的体积。

import { lazy, Suspense } from "react"

export default function About() {
  let LazyComponent = null
  if (true) {
    LazyComponent = lazy(() => import(/* webpackChunkName: "list" */ "../components/List"))
  } else {
    LazyComponent = lazy(() => import(/* webpackChunkName: "news" */ "../components/News"))
  }
  return (
    <div>
      about page works
      <Suspense fallback={<div>loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  )
}

4. 使用 Fragment 以避免额外的标记

React 组件中返回的 jsx 如果有多个同级元素, 多个同级元素必须要有一个共同的父级.

为了满足这个条件我们通常都会在最外层添加一个div, 但是这样的话就会多出一个无意义的标记, 如果每个组件都多出这样的一个无意义标记的话, 浏览器渲染引擎的负担就会加剧.

function App() {
  return (
    <div>
      <div>message a</div>
      <div>message b</div>
    </div>
  )
}

为了解决这个问题, React 推出了 fragment 占位符标记. 使用占位符标记既满足了拥有共同父级的要求又不会多出额外的无意义标记.

import { Fragment } from "react"

function App() {
  return (
    <Fragment>
      <div>message a</div>
      <div>message b</div>
    </Fragment>
  )
}
function App() {
  return (
    <>
      <div>message a</div>
      <div>message b</div>
    </>
  )
}

5. 尽量不要使用内联函数定义

在使用内联函数后, render 方法每次运行时都会创建该函数的新实例, 导致 React 在进行 Virtual DOM 比对时, 新旧函数比对不相等,导致React 总是为元素绑定新的函数实例, 而旧的函数实例又要交给垃圾回收器处理.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  render() {
    return (
      <input
        value={this.state.inputValue}
        onChange={e => this.setState({ inputValue: e.target.value })}
        />
    )
  }
}

正确的做法是在组件中单独定义函数, 将函数绑定给事件.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  setInputValue = e => {
    this.setState({ inputValue: e.target.value })
  }
  render() {
    return (
      <input value={this.state.inputValue} onChange={this.setInputValue} />
    )
  }
}

6. 在构造函数中进行函数this绑定

在类组件中如果使用 fn() {} 这种方式定义事件函数, 事件函数 this 默认指向 undefined. 也就是说函数内部的 this 指向需要被更正.

可以在构造函数中对函数的 this 进行更正, 也可以在行内进行更正, 两者看起来没有太大区别, 但是对性能的影响是不同的.

export default class App extends React.Component {
  handleClick() {
    console.log(this)
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>按钮</button>
  }
}

上面代码的问题是, render 方法每次执行时都会调用 bind 方法生成新的函数实例.

  constructor() {
    super()
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    console.log(this)
  }
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

上面代码 构造函数只执行一次, 所以函数 this 指向更正的代码也只执行一次.

7. 类组件中的箭头函数

在类组件中使用箭头函数不会存在 this 指向问题, 因为箭头函数本身并不绑定 this.

export default class App extends React.Component {
  handleClick = () => console.log(this)
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

箭头函数在 this 指向问题上占据优势, 但是同时也有不利的一面.
当使用箭头函数时, 该函数被添加为类的实例对象属性, 而不是原型对象属性. 如果组件被多次重用, 每个组件实例对象中都将会有一个相同的函数实例, 降低了函数实例的可重用性造成了资源浪费.

综上所述, 更正函数内部 this 指向的最佳做法仍是在构造函数中使用 bind 方法进行绑定

8. 避免使用内联样式属性

当使用内联 style 为元素添加样式时, 内联 style 会被编译为 JavaScript 代码, 通过 JavaScript 代码将样式规则映射到元素的身上, 浏览器就会花费时间执行脚本和渲染 UI, 从而增加了组件的渲染时间.

function App() {
  return <div style={{ backgroundColor: "skyblue" }}>App works</div>
}

在上面的组件中, 为元素附加了内联样式, 添加的内联样式为 JavaScript 对象, backgroundColor 需要被转换为等效的 CSS 样式规则, 然后将其应用到元素, 这样涉及到脚本的执行.

更好的办法是将 CSS 文件导入样式组件.

9. 优化条件渲染

频繁的挂载和卸载组件是一项耗性能的操作, 为了确保应用程序的性能, 应该减少组件挂载和卸载的次数.
在 React 中我们经常会根据条件渲染不同的组件. 条件渲染是一项必做的优化操作.

function App() {
  if (true) {
    return (
      <>
        <AdminHeader />
        <Header />
        <Content />
      </>
    )
  } else {
    return (
      <>
        <Header />
        <Content />
      </>
    )
  }
}

减少不必要的挂载和卸载组件,改写为下面的代码

function App() {
  return (
    <>
      {true && <AdminHeader />}
      <Header />
      <Content />
    </>
  )
}

10.render方法中不要更改应用状态

当应用程序状态发生更改时, React 会调用 render 方法, 如果在 render 方法中继续更改应用程序状态, 就会发生 render 方法递归调用导致应用报错.

与其他生命周期函数不同, render 方法应该被作为纯函数. 这意味着, 在 render 方法中不要做以下事情, 比如不要调用
setState 方法, 不要使用其他手段查询更改原生 DOM 元素, 以及其他更改应用程序的任何操作. render
方法的执行要根据状态的改变, 这样可以保持组件的行为和渲染方式一致.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  render() {
    this.setState({name: "李四"})
    return <div>{this.state.name}</div>
  }
}

11.避免数据结构突变

组件中 props 和 state 的数据结构应该保持一致, 数据结构突变会导致输出不一致.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      employee: {
        name: "张三",
        age: 20
      }
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState({
      employee: {
        name: "李四"
      }
    })
  }
  render() {
    const { name, age } = this.state.employee
    return (
      <div>
        <div>{name} {age}</div>
        <button onClick={this.handleClick}>更改</button>
      </div>
    )
  }
}

12.为列表数据添加唯一标识

当需要渲染列表数据时, 我们应该为每一个列表项添加 key 属性, key 属性的值必须是唯一的.

key 属性可以让 React 直接了当的知道哪些列表项发生了变化, 从而避免了 React 内部逐一遍历 Virtual DOM 查找变化所带来的性能消耗. 避免了元素因为位置变化而导致的重新创建.

当列表数据没有唯一标识时, 可以临时使用索引作为 key 属性的值, 但是仅限于列表项是静态的, 不会被动态改变. 比如不会对列表项进行排序或者过滤, 不会从顶部或者中间添加或者删除项目. 当发生以上行为是, 索引会被更改, 并不可靠.

13.节流和防抖

在高频触发事件时,可以以指定的间隔调用事件处理函数。比如当页面滚动到底部要进行获取数据时, 需要使用节流进行限制, 如果不进行限制, 页面滚动到底部时将多次触发事件并向服务器端发送请求导致性能问题.

节流
function throttle(fn, time) {
  let canRun = true
  return function () {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      fn.call(this, ...arguments)
      canRun = true
    }, time)
  }
}
防抖

在触发高频事件时,只在最后一次触发事件后进行响应。比如支付按钮,当用户每点击一次按钮时就会触发一次网络请求, 导致性能问题.

function debounce(fn, time) {
  let timer = null
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.call(this, ...arguments)
    }, time)
  }
}

14.外部资源使用 CDN 加载

15.插件按需加载

如之前整理的例子 ,例子

16.长列表优化 (虚拟列表)

虚拟列表就是按需显示, 不必一次渲染所有列表项, 只显示用户可视区域内的元素, 再根据用户的滚动位置逐步渲染其余数据. 上拉加载是随着滚动请求数据和渲染数据, 虚拟列表是一次请求所有数据, 随着滚动按需渲染.

react-virtualized 可以实现虚拟列表. 它会在页面中生成一块滚动区域, 在区域内进行列表内容的显示. 它会根据可视区域大小生成数据, 比如可视区域内正好可以放置10条数据, 它就会渲染10条数据, 然后再根据用户的滚动位置, 不断的渲染数据并替换区域内数据. 再通过定位的方式设置列表项的位置, 形成滚动的视觉效果.
使用 react-virtualized 实现虚拟列表

下载

react-virtualized npm install react-virtualized

通过 List 组件显示列表数据

import { List } from "react-virtualized"
<List width={400} height={600} rowCount={records.length} rowHeight={44} rowRenderer={rowRenderer} />
// width: 指定窗口宽度
// height: 指定窗口高度
// rowHeight: 指定每一行的高度
// rowCount: 指定数据条数, 用于计算列表的总高度
// rowRenderer: 指定渲染函数, 内部会根据 rowCount 进行遍历, 遍历过程中会不断调用
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值