perf api

PureComponent 和 memo

业务场景:下面代码每次点击都会导致Tit内重新渲染并执行打印333,但是Tit没有任何变化不必要渲染,但是将 Component 改为 PureComponent 则可避免

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
47
48
49
50
51
52
53
class Tit extends React.Component {
render () {
console.log(333)
return <span>33</span>
}
}

class App extends React.Component {
constructor(props) {
super(props)
}

state = {
count: 0
}

add = () => {
this.setState({
count: 3
})
}

render () {
return (
<div>
<Tit></Tit>
<div onClick={this.add}>pppp</div>
</div>
)
}
}

// 函数组件使用React.memo
const Tit = React.memo(function a() {
console.log('Tip')
return <div>tit</div>
})

function App() {
const [count, setCount] = useState(0)

const add = () => {
console.log('click')
setCount(count + 1)
}

return (
<div>
<Tit /> <span>{count}</span>
<div onClick={add}>click</div>
</div>
)
}

PureComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}

// 只需继承 React.Component 的原型,直接 PureComponent.prototype = new Component(),造成不必要的内存消耗,Component 存在很多不需要继承的属性和方法
// 所以会新建ComponentDummy,只继承Component的原型,不包括constructor,以此来节省内存
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

// 修复 pureComponentPrototype.constructor 构造函数指向
pureComponentPrototype.constructor = PureComponent;

// 但多加一个 Object.assign(),能有效的避免多一次原型链查找
Object.assign(pureComponentPrototype, Component.prototype);

// isPureReactComponent 表示是 PureComponent,后续组件渲染时,react-dom 判断是否浅比较 props、status 实现更新
pureComponentPrototype.isPureReactComponent = true;

checkShouldComponentUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 对 PureComponent 判断
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
// ...
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}

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
function shallowEqual(objA: mixed, objB: mixed): boolean {
// is 其实就是Object.is的实现
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
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;
}

useCallback

  • callback(仅仅是个函数),并把要做事情的函数放在callback函数体内执行

  • deps 依赖参数数组

1
2
3
const handleChildrenCallback = useCallback(() => {
handleChildren()
}, [])
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
const Parent = () => {
const [count, setCount] = useState(0)
const handleChildren = () => {
console.log('child click fun')
}

// 不使用 useCallback 会导致每次click,子组件都会渲染,加了 count 依赖,每次 count 改变会渲染子组件
const childCall = useCallback(() => {
handleChildren()
}, [count])

const handleParent = () => {
console.log('parent')
setCount(preCount => preCount + 1)
}

return (
<div>
<div onClick={handleParent}>Parent:{count} </div>

// 直接传递的方式 <ChildrenComponent handleChildren={handleChildren} /> 会使父级每次点击都会触发子组件渲染
<ChildrenComponent handleChildren={childCall} />
</div>
)
}

// 子组建 child 只打印一次
const ChildrenComponent = React.memo(({ handleChildren }) => {
console.log('child')
return <div onClick={handleChildren}>ChildrenComponent </div>
})

useMemo

返回一个 memoized 值。在依赖参数不变的的情况返回的是上次第一次计算的值

  • callback(仅仅是个函数),并把要做事情的函数放在callback函数体内执行,(需要有返回值)

  • deps 要做事情的函数需要引入的外部参数或者是依赖参数

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
const ComputeComponent = () => {
const [count, setCount] = useState(100)
const [changeNum, setChangeNum] = useState(100)

function calc(arg) {
console.log('calc')
const array = new Array(arg).fill(arg)
return array.reduce((currentTotal, item) => {
return currentTotal + item
}, 0)
}
const handCount = () => {
setCount(preCount => preCount * 2)
}

const changeOther = () => {
console.log('other')
setChangeNum(changeNum + 1)
}

// 不实用 useMemo,other 触发事件也会重新计算 calc,
// const computeValue = calc(count)
const computeValue = useMemo(() => calc(count), [count])

return (
<div>
<div>{computeValue}</div>
<div onClick={handCount}>add: {count} </div>
<div onClick={changeOther}>other</div>
</div>
)
}
  • useCallback 优化针对于子组件渲染

  • useMemo 优化针对于当前组件高开销的计算

返回
顶部