跳到主要内容

PureComponent

一、认识


PureComponentpropsstate 进行浅层或者引用地址比较。通过比较后,如果前后发生变化触发组件重新渲染,如果前后没有变化则不重新渲染。

二、背景


在日常开发中,有很多React 父组件引用React 子组件的场景,React 父组件 状态的更新,会触发React 子组件重新渲染,无论父组件是否有传值给子组件。如下所示:

import React from "react";
import ReactDOM from "react-dom";

class ParentComponent extends React.Component {
state = { number: 0 };
handleClick = (e) => {
this.setState({ number: this.state.number + 1 });
};
render() {
console.log("父组件");
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>增加</button>
<ChildrenComponent />
</div>
);
}
}
class ChildrenComponent extends React.Component {
render() {
console.log("子组件");
return <div></div>;
}
}
ChildrenComponent = React.memo(ChildrenComponent);

ReactDOM.render(<ParentComponent />, document.getElementById("root"));

现象: 每次点击按钮,父组件状态发生变化,子组件都没有接收父组件的任何状态,但是子组件也还是触发更新了。这样对于性能是很不好的

解决: 我们希望的情形是: React 父组件 某一个状态更新,只有接收这个状态的子组件会触发更新渲染,如果没有接收这个状态,则不会触发更新渲染。

三、语法


import React from "react";
import ReactDOM from "react-dom";

class ParentComponent extends React.PureComponent {
state = { number: 0 };
handleClick = (e) => {
this.setState({ number: this.state.number + 1 });
};
render() {
console.log("父组件");
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>增加</button>
<ChildrenComponent />
</div>
);
}
}
class ChildrenComponent extends React.PureComponent {
render() {
console.log("子组件");
return <div></div>;
}
}

ReactDOM.render(<ParentComponent />, document.getElementById("root"));

四、对比


4.1 React.PureComponent 与 React.Component 对比

  • Component 特点

    • 1. Rect.Component 没有实现 shouldComponentUpdate()

    • 2. 如果子组件使用 React.Component,只要父组件重新渲染,那么就会触发子组件渲染

  • PureComponent 特点

    • 1. React.PureComponent 中以浅层对比 propstate 的方式来实现了 shouldComponentUpdate

    • 2. 如果子组件使用 React.PureComponent , 父组件中只有传入子组件的状态发生变化时才会触发子组件重新渲染

五、注意事项


5.1 PureComponent 组件避免使用箭头函数

描述: 父组件render PureComponent子组件时, 不可使用箭头函数,如下所示:

import React, { PureComponent,Component } from "react";
import ReactDOM from "react-dom";

class Child extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log("Child 组件渲染");
return (
<div>
<h3>Child 组件</h3>
</div>
);
}
}

class App extends Component {
render() {
return (
<div>
<h3>App 组件</h3>
<Child callback={() => {}} />
</div>
);
}
}

ReactDOM.render(<App />, document.getElementById("root"));

结论: 如果是箭头函数绑定的话,都会重新生成一个新的箭头函数,PureComponent 对比新老 props 时候,因为是新的函数,所以会判断不相等,而让组件直接渲染,PureComponent 作用终会失效

5.2 PureComponent 组件的父组件为函数组件时,传递的函数要用 useCallback 或者 useMemo 处理

描述: 如果父组件是函数,子组件是 PureComponent ,那么绑定函数要小心,因为函数组件每一次执行,如果不处理,还会声明一个新的函数,所以 PureComponent 对比同样会失效, 如下所示:

import React, { PureComponent } from "react";
import ReactDOM from "react-dom";

class Child extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log("Child 组件渲染");
return (
<div>
<h3>Child 组件</h3>
</div>
);
}
}

function App() {
return (
<div>
<h3>App 组件</h3>
<Child />
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));

所以,可以用 useCallback 或者 useMemo 解决这个问题,useCallback 首选,这个 hooks 初衷就是为了解决这种情况的

使用useCallback来缓存函数:

import React, { PureComponent, useCallback, useState } from "react";
import ReactDOM from "react-dom";

class Child extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { callback } = this.props;
console.log(callback);
console.log("Child 组件渲染");
return (
<div>
<h3>Child 组件</h3>
</div>
);
}
}

function App() {
const [num, setNum] = useState(0);
const callback = useCallback(() => {
console.log("哈哈");
}, []);
return (
<div>
<h3>App 组件</h3>
{num}
<button onClick={() => setNum(num + 1)}>改变</button>
<Child callback={callback} />
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));

使用useMemo来缓存函数:

import React, { PureComponent, useMemo, useState } from "react";
import ReactDOM from "react-dom";

class Child extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { callback } = this.props;
console.log(callback);
console.log("Child 组件渲染");
return (
<div>
<h3>Child 组件</h3>
</div>
);
}
}

function App() {
const [num, setNum] = useState(0);
const callback = useMemo(() => {
return () => {
console.log("哈哈");
};
}, []);
return (
<div>
<h3>App 组件</h3>
{num}
<button onClick={() => setNum(num + 1)}>改变</button>
<Child callback={callback} />
</div>
);
}

ReactDOM.render(<App />, document.getElementById("root"));