1:前言
最近开发中使用到了这几个API,这几个API不经常用,刚看到的时候理解也有点模糊,于是网上找了一些讲解并自己动手做了些实验,算是搞明白了。本文主要讲解React.memo、useMemo、useCallback的知识。
2:React.memo
React.memo为高阶组件。
如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 context 发生变化时,它仍会重新渲染。
以上是React官方文档对React.memo的解释。通俗点说就是:当父组件更新的时候,子组件也会更新,即使这个子组件不需要更新。例如子组件不接受props时,或者接受的props相同渲染结果相同,这种情况下子组件就不需要重新渲染。例如:
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<Memo></Memo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
export default App;
这个时候App组件的a变化:
可以看到Memo组件重新渲染了。但是这个时候Memo组件仅做展示作用,不需要重新渲染。这个时候怎么办呢?用Memo可以解决:
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<AfterMemo></AfterMemo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
结果:
用React.memo包一下即可解决。通过记忆组件渲染结果的方式提高性能。在这种情况下,React将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo仅检查props的变更:
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
return (
<div>
<h1>a: {a}</h1>
<h1>b: {b}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<button onClick={() => setB(b + 1)}>点击b</button>
<AfterMemo b={b}></AfterMemo>
</div>
);
}
//子组件
const Memo = ({ b }) => {
return (
<div>
{ console.log('this is memo Component======', b) }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
结果:
当b变化时,子组件更新。React.memo和React.PureComponent组件功能类似。实际开发中根据需求选择。React.memo官方文档。
3:React.useMemo
React.useMemo和React.memo不是一回事。React.memo是高阶组件,而React.useMemo是React中的Hook,返回一个memoized值。把创建函数和依赖项数组作为参数传入useMemo,他仅会在依赖项变化时才会重新计算memoized的值。通俗理解就是useMemo返回一个缓存的变量,只有当依赖项改变时,useMemo返回值才会变。
上代码:
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
const [name, setName] = useState('Durant')
const [age, setAge] = useState(30)
const durant = { name, age }
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<AfterMemo durant={durant}></AfterMemo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
代码中,durant是一个对象,传递给子组件。此时点击按钮改变a,会发现子组件重新渲染了:
因为每次改变a,父组件重新渲染都会创建一个新的durant对象,所以子组件重新渲染。但是durant对象的属性和值都没有变化,子组件不应该重新渲染。此时useMemo派上用场了。
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
const [name, setName] = useState('Durant')
const [age, setAge] = useState(30)
const durant = React.useMemo(() => ({ name, age }), [name, age])
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<AfterMemo durant={durant}></AfterMemo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
useMemo函数会返回一个对象,只有当name或age变化时返回的值才会改变。此时改变a,子组件接收到的durant没变,不会重新渲染:
这就是useMemo。详细请看useMemo官方文档。
4:React.useCallback
useMemo缓存的是一个对象,而useCallback缓存的是一个函数。
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
const [b, setB] = useState(30)
const printB = () => {
console.log('b========', b)
}
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<AfterMemo printB={printB}></AfterMemo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
此时改变a,父组件重新渲染,创建了一个新的printB函数,子组件刷新:
给printB用useCallback包一层:
import React, { useState } from 'react';
//父组件
function App() {
const [a, setA] = useState(0)
const [b, setB] = useState(30)
const printB = React.useCallback(() => {
console.log('b======', b)
}, [b])
return (
<div>
<h1>a: {a}</h1>
<button onClick={() => setA(a + 1)}>点击a</button>
<AfterMemo printB={printB}></AfterMemo>
</div>
);
}
//子组件
const Memo = () => {
return (
<div>
{ console.log('this is memo Component======') }
</div>
)
}
const AfterMemo = React.memo(Memo)
export default App;
此时再点击按钮改变a:
可以看到,改变a,子组件不再刷新。但当改变b时,子组件刷新。useCallback官方文档
5:总结
1. useCallback(fn, deps)相当于useMemo( () => fn, deps)。
2. 可以把useMemo当作性能优化的手段,但不要把他当成语义上的保证。
3. React.memo仅作为性能优化的方式存在,不要依赖它来阻止重新渲染,这会产生bug。
4. useMemo缓存变量,useCallback缓存函数。