react hooks性能优化
memo
使用memo包裹组件,使组件的props进行浅比较。可以避免父组件重新渲染,而子组件中不需要重新渲染的问题。
import { memo, useState } from 'react';
function App() {
const [val,setVal] = useState(true);
const onClick = () => {
setVal(!val);
console.log('点击了按钮');
}
return (
<div>
<Child />
<MemoChilds />
<button onClick={onClick}>刷新App</button>
</div>
)
}
export default App;
function Child() {
console.log('child 渲染了');
return (
<div>
child
</div>
)
}
function MemoChild(){
console.log('memochild 渲染了');
return (
<div>
memochild
</div>
)
}
const MemoChilds = memo(MemoChild)
useMemo
使用useMemo包裹状态值的计算,避免重复的计算。它接收两个参数,第一项是回调函数,useMemo正是返回的该回调函数的返回值,第二项是一个数组,数组里面是依赖项,当依赖项改变的时候,useMemo中的回调函数才会重新计算值。
import { useMemo, useState } from 'react';
function App() {
const [list] = useState([1,2,3]);
const [val,setVal] = useState(true);
//普通计算list的和
console.log('普通计算list的和');
const sum = list.reduce((prev,curr)=>prev+curr);
//缓存计算list的和
const memoSum = useMemo(()=>{
console.log('缓存计算list的和');
return list.reduce((prev,curr)=>prev+curr);
},[list])
const onClick = () => {
setVal(!val);
}
return (
<div>
<div>sun: {sum}</div>
<div>memoSun: {memoSum}</div>
<button onClick={onClick}>重新渲染</button>
</div>
)
}
export default App;
useCallback
我们刚说了使用memo来包裹组件,使组件进行浅比较。但是如果我们给组件传递的是一个函数,当函数触发state值更新后,函数就会重新改变,所以子组件接收的函数会改变,这就使得子组件重复渲染。我们可以给函数加上useCallback,让函数只更新一次。所以,新手一般都会给每个函数都加上useCallback,这是没必要的。如果每个函数都加上useCallback,那么每个函数都要进行依赖项的判断,造成了额外的性能消耗。使用useCallback有以下两种情况
-
组件内部定义的函数,需要作为其他hooks的依赖项。
-
组件内部定义的函数,需要传递给子组件,并且子组件被memo包裹。
组件内部定义的函数,需要作为其他hooks的依赖项。
import React, { useEffect, useState } from 'react';
function App() {
const [count, setCount] = useState(1);
const add = () => {
setCount((count) => count + 1);
};
useEffect(() => {
add();
}, [add]);
return <div className="App">count: {count}</div>;
}
export default App;
组件内部定义的函数,需要传递给子组件,并且子组件被memo包裹。
import { memo, useState } from 'react';
function App() {
const [val,setVal] = useState(true);
const onClick = () => {
setVal(!val);
console.log('点击了按钮');
}
return (
<div>
<MemoChilds onClick={onClick} />
<button onClick={onClick}>刷新App</button>
</div>
)
}
export default App;
function MemoChild({onClick}){
console.log('memochild 渲染了');
return (
<div>
memochild
</div>
)
}
const MemoChilds = memo(MemoChild)
import { memo, useCallback, useState } from 'react';
function App() {
const [val,setVal] = useState(true);
const onClick = useCallback(() => {
setVal(!val);
console.log('点击了按钮');
},[])
return (
<div>
<MemoChilds onClick={onClick} />
<button onClick={onClick}>刷新App</button>
</div>
)
}
export default App;
function MemoChild({onClick}){
console.log('memochild 渲染了');
return (
<div>
memochild
</div>
)
}
const MemoChilds = memo(MemoChild)
使用memo,props进行浅比较的时候是怎么比较的呢?
-
如果都是基本类型,直接比较结果,结果正确则返回true。
-
比较是否都是对象,如果有一个不是则返回false。
-
比较两个对象的属性的数量是否一样,不一样则返回false。
-
比较两者的属性是否相同,属性值是否相同。
function shallowEqual (objA: mixed, objB: mixed): boolean {
// 下面的 is 相当于 === 的功能,只是对 + 0 和 - 0,以及 NaN 和 NaN 的情况进行了特殊处理
// 第一关:基础数据类型直接比较出结果
if (is (objA, objB)) {
return true;
}
// 第二关:只要有一个不是对象数据类型就返回 false
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;
}
参考资料
[1]React Hooks 性能优化
[2]关于React Hooks和Immutable性能优化的实践,我写了一本掘金小册