优化 React Functional Component 性能
Functional Component
的引入为 React 的开发带来了极大的便利,然而它并不像 Class Component
那样可以设置 shouldComponentUpdate
。
测试代码如下
import React from "react";
const ItemX = id => {
return props => {
console.log(`Item${id} render`);
const { title } = props;
let timer = null;
return <div>{title}</div>;
};
};
const Item1 = ItemX(1);
class App extends React.Component {
state = {
title1: 1,
x: 3
};
_handleClickIncX = () => {
const { x } = this.state;
this.setState({
x: x + 1
});
};
_handleClickIncTitle1 = () => {
const { title1 } = this.state;
this.setState({
title1: title1 + 1
});
};
render() {
const { title1, title2, title3 } = this.state;
return (
<div>
<Item1 title={title1} />
<button onClick={this._handleClickIncX}>incX</button>
<button onClick={this._handleClickIncTitle1}>incTitle1</button>
</div>
);
}
}
可以发现点击 incX 也会导致 Item1 的执行,这就很尴尬了,那么如何让 Functional Component
也具有 shouldComponentUpdate
的功能呢?
我们可以用 PureComponent
将 Functional Component
包裹起来。
function pure1(component) {
return class extends React.PureComponent {
render() {
const props = this.props;
return component(props);
}
};
}
const Item2 = pure1(ItemX(2));
class App extends React.Component {
state = {
title1: 1,
title2: 2,
x: 3
};
_handleClickIncTitle2 = () => {
const { title2 } = this.state;
this.setState({
title2: title2 + 1
});
};
render() {
const { title1, title2 } = this.state;
return (
<div>
<Item1 title={title1} />
<Item2 title={title2} />
<button onClick={this._handleClickIncX}>incX</button>
<button onClick={this._handleClickIncTitle1}>incTitle1</button>
<button onClick={this._handleClickIncTitle2}>incTitle2</button>
</div>
);
}
}
点击 incX 和 incTitle1 并不会导致 Item2 被执行。
项目开发中可以使用 recompose
中的 pure
函数来包裹 Functional Component
。
然而 pure1
本质上是用一个 Class Component
包裹 Functional Component
,这样似乎并不那么 Functional
,那么换种方法呢?
const hasOwnProperty = Object.prototype.hasOwnProperty;
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
function shallowEqual(objA, objB) {
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;
}
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
function pure2(component) {
let isInitialized = false;
let oldProps = null;
let oldValue = null;
return function(newProps) {
if (!isInitialized) {
isInitialized = true;
oldProps = newProps;
oldValue = component(newProps);
return oldValue;
}
if (shallowEqual(oldProps, newProps)) {
return oldValue;
} else {
oldProps = newProps;
oldValue = component(newProps);
return oldValue;
}
};
}
const Item3 = pure2(ItemX(3));
class App extends React.Component {
state = {
title1: 1,
title2: 2,
title3: 3,
x: 3
};
_handleClickIncTitle3 = () => {
const { title3 } = this.state;
this.setState({
title3: title3 + 1
});
};
render() {
const { title1, title2, title3 } = this.state;
return (
<div>
<Item1 title={title1} />
<Item2 title={title2} />
<Item3 title={title3} />
<button onClick={this._handleClickIncX}>incX</button>
<button onClick={this._handleClickIncTitle1}>incTitle1</button>
<button onClick={this._handleClickIncTitle2}>incTitle2</button>
<button onClick={this._handleClickIncTitle3}>incTitle3</button>
</div>
);
}
}
shallowEqual
这个函数是从 React
源码中 PureComponent
那部分提取出来的。
Nice Work!