PureComponent
我们可以称之为 纯组件
,用于避免不必要的渲染 (render 函数),从而提高效率
我们先看一个栗子
import React, { Component } from 'react'
/* App 引入渲染的组件 */
export default class TaskContainer extends Component {
state = {
tasks: []
}
componentDidMount() {
const tasks = []
for (let i = 0; i < 10; i++) {
tasks.push({
name: `Task ${i}`,
isFinish: Math.random() > 0.5
})
}
this.setState({ tasks })
}
addTask = newTask => {
this.setState({
tasks: [...this.state.tasks, newTask]
})
}
render() {
console.log('TaskContainer Render')
return (
<div>
<AddTask onAdd={this.addTask}/>
<TaskList tasks={this.state.tasks} />
</div>
)
}
}
/* 添加一条新任务 */
class AddTask extends Component {
state = {
name: ''
}
handleChange = e => {
this.setState({ name: e.target.value })
}
addTask = () => {
/*
使用运算符: ?.
可省去: this.props.onAdd &&
*/
this.props.onAdd?.({
name: this.state.name,
isFinish: false
})
}
render() {
console.log('AddTask Render')
return (
<div>
<input type="text" value={this.state.name} onChange={this.handleChange} />
<button onClick={this.addTask}>Add Task</button>
</div>
)
}
}
/* 任务列表 */
class TaskList extends Component {
render() {
console.log('TaskList Render')
const ts = this.props.tasks.map((task, i) => <Task key={i} {...task} />)
return ( <ul> {ts} </ul> )
}
}
/* 单个任务 */
class Task extends Component {
render() {
console.log('Task Render')
const {name, isFinish} = this.props
{/* 通过类名样式区分是否是完成状态 */}
return (
<li className={isFinish ? "finish" : ""}>{name}</li>
)
}
}
// const Task = props => {
// console.log('Task Render')
// const {name, isFinish} = props
// return (
// <li className={isFinish ? "finish" : ""}>{name}</li>
// )
// }
初次渲染与 componentDidMount 获取任务列表后渲染的结果
从这张截图我们可以看到组件挂载完毕后,由于 TaskContainer
组件 state
中任务列表数据变化,所以导致两次 render
确实是正常的结果,但我们可以看到 AddTask
组件本身状态并没有任何的改变,却有一次 多余 的 render
点击按钮添加一条新任务时的渲染结果
从这张截图我们也可以看到,点击按钮时,AddTask
组件的状态其实也没有变动,但由于 TaskContainer
组件中任务列表新增一条任务,从而导致其下的所有组件都重新渲染了一遍,主要是前 10 条 Task
本身没有任何变动,却也依旧重新渲染了一遍
优化思路与优化方案
如果一个组件的
属性
和状态
均未发生变化,则我们可以认为该组件重新渲染是没有必要的
所以,在类组件中,我们就可以借助 shouldComponentUpdate(nextProps, nextState)
生命周期钩子函数进行简单的优化,先写一个浅比较帮助函数 objectEqual(origin, target)
// helper.js
export function objectEqual(origin, target) {
for (let prop in origin) {
// 存在属性值不同
if (!Object.is(origin[prop], target[prop]) {
return false
}
}
// 所有属性值均相同(对象地址相同 & 基础数据类型值相同)
return true
}
那么我们就可以在类组件中添加 shouldComponentUpdate
生命周期函数 (以 Task
组件为例):
import {objectEqual} from '../utils/helper'
class Task extends Component {
shouldComponentUpdate(nextProps, nextState) {
return !(objectEqual(this.props, nextProps) && objectEqual(this.state, nextState))
// 或者好理解一点,这样写:
// if (
// objectEqual(this.props, nextProps)
// && objectEqual(this.state, nextState)
// ) {
// return false
// }
// return true
}
render() {
console.log('Task Render')
const {name, isFinish} = this.props
return (
<li className={isFinish ? "finish" : ""}>{name}</li>
)
}
}
再次 新增任务
时,你就会发现 Task Render
就只会打印 一次
了 (之前的 10 个任务组件就没有重新渲染了)
而这个方案其实并不需要我们自己去实现,因为 React 已经帮我们封装好了这么个优化方案的组件,那就是 PureComponent
,只要我们写的类组件继承自它,它内部默认会在 shouldComponentUpdate
中对属性和状态进行浅比较优化。我们只需要将 extends Component
更改为 extends PureComponent
即可
注意点 —— ·PureComponent· 与 ·memo·
-
PureComponent
进行的是浅比较
,所以,为了效率的提升,书写类组件时尽量使用PureComponent
,而且不要改动之前的状态,永远都要创建新的状态去覆盖之前的状态 (在 React 中运用一般运用Immutable
,即不可变对象保证每次都是产生新的对象)以上面新增任务处理函数为例,如下代码新增一条任务时,数组之中确实会新增一条任务,但并不会运行
render
,因为state 中的 tasks 还是指向之前的对象
,PureComponent
中经过对比发现,对象引用并未改变,则传给TaskList
组件的 tasks 属性并未变化,其下的组件并不会重新渲染,页面上任务列表并不会更新import React, { PureComponent } from 'react' export default class TaskContainer extends PureComponent { state = { tasks: [] } componentDidMount() { // ... get tasks } /* =========== attention =========== */ addTask = newTask => { this.state.tasks.push(newTask) this.setState({ tasks: this.state.tasks }) } /* =========== attention =========== */ render() { console.log('TaskContainer Render, tasks.length:', this.state.tasks.length) return ( <div> <AddTask onAdd={this.addTask}/> <TaskList tasks={this.state.tasks} /> </div> ) } }
-
函数组件则使用
React.memo
函数去创建,如上面的Task组件
,函数纯组件写法:import React, {memo} from 'react' const Task = props => { console.log('Task Render') const {name, isFinish} = props return <li className={isFinish ? "finish" : ""}>{name}</li> } export default memo(Task) // 外面引用它,即和继承 PureComponent 的类组件是同样的效果 /* ====================== 华丽的分割线 ====================== */ // memo 函数的实现方案应该是: function memo(FuncComp) { return class Memo extends PureComponent { render() { return <FuncComp {...this.props} /> } } }
以上,便是本篇对 PureComponent
组件的认识与 React 渲染方面的简单优化方案