面试题中经常会遇到,React.PureComponent
和 React.Component
有什么区别, React官网也已经给出了答案
React.PureComponent
与React.Component
很相似。两者的区别在于React.Component
并未实现shouldComponentUpdate()
,而React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
可以整理出两个点
-
React.PureComponent
内部实现了shouldComponentUpdate
,如果赋予 React 组件相同的 props 和 state,则该组件不会重新渲染 -
新旧 props/state 都只是浅比较
概念听来终觉浅,本文就利用几个小demo来实际检验一下他们之间的区别,加深下印象,同时也加上了和函数式组件的对比。毕竟React hooks
出来之后,函数式组件已经一统天下了
所有Demo的代码可以在这里获取
这里利用React Developer Tools来观察React组件render的情况
1 赋予 React 组件相同的 props 和 state
DEMO 1 新旧props相同
在这个Demo中,写了两个文件,parent.js
和children.js
。
前者设置了一个定时器,每一秒执行一次this.setState
,值得注意的是这里每次设置的parentCount
都是 0,也就是不作更改。
然后将parentCount
传入到三个子组件中,这三个子组件分别是 React.PureComponet
、React.Component
和 函数式组件。
// parent.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { ComponentChild, FunctionalChild, PureChild } from "./components/children";
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
parentCount: 0
}
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({
number: 0 // 设置和初始值相等的值
})
}, 1000)
}
componentWillUnmount () {
clearInterval(this.interval)
}
render() {
const { parentCount } = this.state
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{/* React.PureComponent */}
<PureChild parentCount={parentCount} />
{/* React.Component */}
<ComponentChild parentCount={parentCount} />
{/* 函数式组件 */}
<FunctionalChild parentCount={parentCount} />
</header>
</div>
);
}
}
export default Parent;
复制代码
// children
import React, { Component, PureComponent } from "react";
class PureChild extends PureComponent {
render() {
console.log("PureChild 组件 render");
return <span>{`Pure Child Component ${new Date().valueOf()}`}-{this.props.parentCount}</span>;
}
}
class ComponentChild extends Component {
render() {
console.log("ComponentChild 组件 render");
return <span>{`Child Component - ${new Date().valueOf()}`}-{this.props.parentCount}</span>;
}
}
function FunctionalChild(props) {
console.log("FunctionalChild 组件render");
return (
<span>{`Functional Child Component - ${new Date().valueOf()}`}-{props.parentCount}</span>
);
}
export {
FunctionalChild,
PureChild,
ComponentChild
}
复制代码
可以看到除了 Pure Component 组件之外,其他都会重新进行渲染,无论传入的parentCount
这个prop是否更改. 这种情况下的重渲完全没有必要,白白浪费了性能。
对于函数式组件,可以使用 React.memo 对其进行性能优化
DEMO 2 新旧state相同
更改DEMO 1的代码,将定时设置state的操作移入到子组件内部,每次设置的值也都是一样
// parent.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { ComponentChild, FunctionalChild, PureChild } from "./components/children";
class Parent extends React.Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<PureChild />
<ComponentChild />
<FunctionalChild />
</header>
</div>
);
}
}
export default Parent;
复制代码
// children.js
import React, { Component, PureComponent, useState, useEffect } from "react";
class PureChild extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: 0
}
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({
counter: 0
})
}, 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
console.log("PureChild 组件 render");
return <span>{`Pure Child Component ${new Date().valueOf()}`}</span>;
}
}
class ComponentChild extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({
counter: 0
})
}, 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
console.log("ComponentChild 组件 render");
return <span>{`Child Component - ${new Date().valueOf()}`}</span>;
}
}
function FunctionalChild() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(0)
}, 1000);
return () => clearInterval(interval)
}, [])
console.log("FunctionalChild 组件render");
return (
<span>{`Functional Child Component - ${new Date().valueOf()}`}</span>
);
}
export {
FunctionalChild,
PureChild,
ComponentChild
}
复制代码
最终发现只有 React.Component
写法的组件更新了。其实这里函数式组件没有更新是因为hooks
的原因。调用 State Hook
的更新函数并传入当前的state
时,React
将跳过子组件的渲染及 effect
的执行。(React 使用 Object.is 比较算法 来比较state
)
2 新旧props/state 都只是浅比较
DEMO 3 props浅比较
同样是两个文件,这里将父组件中的parentCount
更改为了一个数组parentArray
,并且每隔一秒push
入一个值,然后在子组件中打印数组的长度
// parent.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { ComponentChild, FunctionalChild, PureChild, FunctionalChildMemo } from "./components/children-props-shallow-compare";
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
parentArray: [1, 2, 3, 4]
}
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(() => {
const { parentArray } = this.state;
this.setState({
parentArray: [...parentArray, parentArray.length + 1]
})
}, 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const { parentArray } = this.state
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<PureChild parentArray={parentArray} />
<ComponentChild parentArray={parentArray} />
<FunctionalChild parentArray={parentArray} />
<FunctionalChildMemo parentArray={parentArray} />
</header>
</div>
);
}
}
export default Parent;
复制代码
// children.js
import React, { Component, PureComponent } from "react";
class PureChild extends PureComponent {
render() {
console.log("PureChild 组件 render");
return <span>{`Pure Child Component ${this.props.parentArray.length}`}</span>;
}
}
class ComponentChild extends Component {
render() {
console.log("ComponentChild 组件 render");
return <span>{`Child Component - ${this.props.parentArray.length}`}</span>;
}
}
function FunctionalChild(props) {
console.log("FunctionalChild 组件render");
return (
<span>{`Functional Child Component - ${props.parentArray.length}`}</span>
);
}
function FunctionalChild2(props) {
console.log("React.memo FunctionalChild 组件render");
return (
<span>{`React.memo Functional Child Component - ${props.parentArray.length}`}</span>
);
}
const FunctionalChildMemo = React.memo(FunctionalChild2)
export {
FunctionalChild,
FunctionalChildMemo,
PureChild,
ComponentChild
}
复制代码
可见只有 Pure Component 和使用 React.memo包裹的组件都没有更新。这里如果是将数组渲染成一个列表,就会发生数据和视图对应不上的问题。当然这里只是为了验证浅比较使用demo,实际开发中应该会是类似下面这样的写法,就不会有上述所说的问题
// ...
const { parentArray } = this.state;
this.setState({
parentArray:[...parentArray, parentArray.length + 1]
})
// ...
复制代码
DEMO 4 state 浅比较
这里将数组更改移入子组件内部
// parent.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { ComponentChild, FunctionalChild, PureChild } from "./components/children-state-shallow-compare";
class Parent extends React.Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<PureChild />
<ComponentChild />
<FunctionalChild />
</header>
</div>
);
}
}
export default Parent;
复制代码
// children js
import React, { Component, PureComponent, useState, useEffect } from "react";
class PureChild extends PureComponent {
constructor(props) {
super(props)
this.state = {
array: [1,2,3,4]
}
}
componentDidMount() {
setInterval(() => {
const { array } = this.state;
array.push(array.length + 1)
this.setState({
array
})
}, 1000)
}
render() {
console.log("PureChild 组件 render");
return <span>{`Pure Child Component ${this.state.array.length}`}</span>;
}
}
class ComponentChild extends Component {
constructor(props) {
super(props)
this.state = {
array: [1,2,3,4]
}
}
componentDidMount() {
setInterval(() => {
const { array } = this.state;
array.push(array.length + 1)
this.setState({
array
})
}, 1000)
}
render() {
console.log("ComponentChild 组件 render");
return <span>{`Child Component - ${this.state.array.length}`}</span>;
}
}
function FunctionalChild(props) {
const [array, setArray] = useState([1,2,3,4]);
useEffect(() => {
setInterval(() => {
array.push(array.length + 1)
setArray(array)
}, 1000)
}, [])
console.log("FunctionalChild 组件render");
return (
<span>{`Functional Child Component - ${array.length}`}</span>
);
}
export {
FunctionalChild,
PureChild,
ComponentChild
}
复制代码
可以看到这里只有 React.Component
组件进行了重新渲染。这里值得注意的是函数式组件也没有更新,验证了State hook的更新函数使用的Object.is()
也是浅比较
参考