Appearance
页面更新优化
类组件 -shouldComponentUpdate
React 给我们提供了一个生命周期方法 shouldComponentUpdate(很多时候,我们简称为 SCU),这个方法接受参数,并且 需要有返回值:
该方法有两个参数:
参数一:nextProps 修改之后,最新的 props 属性
参数二:nextState 修改之后,最新的 state 属性
该方法返回值是一个 boolean 类型
- 返回值为 true,那么就需要调用 render 方法;
- 返回值为 false,那么久不需要调用 render 方法;
- 默认返回的是 true,也就是只要 state 发生改变,就会调用 render 方法;
比如我们在 App 中增加一个 message 属性:
- jsx 中并没有依赖这个 message,那么它的改变不应该引起重新渲染;
- 但是因为 render 监听到 state 的改变,就会重新 render,所以最后 render 方法还是被重新调用了;
例子
jsx
import React, {Component} from 'react';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
message: "Hello World"
}
}
render() {
// 假设这里是一个计数器
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
shouldComponentUpdate(nextProps, nextState) {
// 如果值不同就更新、值相同就不更新
if (this.state.counter !== nextState.counter) {
return true;
}
return false;
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
changeText() {
this.setState({
message: "你好啊,wjw"
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
PureComponent
如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。
思考:shouldComponentUpdate 中的各种判断的目的是什么? 答案:props 或者 state 中的数据是否发生了改变,来决定 shouldComponentUpdate 返回 true 或者 false;
使用
jsx
import React, { PureComponent } from 'react';
// Main
class Banner extends PureComponent {
render() {
console.log("Banner render函数被调用");
return <h3>我是Banner组件</h3>
}
}
class Main extends PureComponent {
render() {
console.log("Main render函数被调用");
return (
<div>
<Banner/>
</div>
)
}
}
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<Main/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
源码
javascript
// react/blob/main/packages/react/src/ReactBaseClasses.js
/**
* 方便的组件,默认浅相等检查sCU。
*/
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// 如果一个组件有字符串引用,我们将在以后分配一个不同的对象。
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// 避免这些方法的额外原型跳转
assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
渲染处理
javascript
// react/packages/react-reconciler/src/ReactFiberClassComponent.new.js
// 通过判断
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
1
2
3
4
5
6
7
2
3
4
5
6
7
浅对比
javascript
// react/packages/shared/shallowEqual.js
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// 测试A和B不同的key值
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
export default shallowEqual;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
函数化组件 -memo
React.memo() 可接受 2 个参数,第一个参数为纯函数的组件,第二个参数用于对比 props 控制是否刷新,与 shouldComponentUpdate() 功能类似
jsx
import React from "react";
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
function areEqual(prevProps, nextProps) {
if(prevProps.seconds===nextProps.seconds){
return true
}else {
return false
}
}
export default React.memo(Child,areEqual)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
源码
javascript
// react/packages/react/src/ReactMemo.js
export function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
if (__DEV__) {
if (!isValidElementType(type)) {
console.error(
'memo: The first argument must be a component. Instead ' +
'received: %s',
type === null ? 'null' : typeof type,
);
}
}
const elementType = {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
if (__DEV__) {
let ownName;
Object.defineProperty(elementType, 'displayName', {
enumerable: false,
configurable: true,
get: function() {
return ownName;
},
set: function(name) {
ownName = name;
// 在大多数情况下,内部组件不应该继承这个显示名称,
// 因为该组件可能在其他地方使用。
// 但是匿名函数继承这个名字很好,
// 这样我们的组件堆栈生成逻辑将显示它们的框架。
// 匿名函数通常建议采用如下模式:
// React.memo((props) => {...});
// 这种内部功能没有在其他地方使用,所以副作用是可以接受的。
if (!type.name && !type.displayName) {
type.displayName = name;
}
},
});
}
return elementType;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45