前言
本人使用了 hooks 已经有一段时间了,经常使用的 useState
, useEffect
,useRef
基本已经掌握了,但是对优化和其他的 hooks 并没有很好的运用起来,下面简单介绍一下 hooks 的优化使用
优化篇
useCallback
useCallback 常用的场景是缓存一个 function,再 render 过程中不再重新创建。
下面我们看一些例子:
对 useEffect 进行优化:
export default function App() {
const [count, setCount] = useState(0);
const clg = ()=>{
console.log(2)
}
React.useEffect(()=>{
clg()
},[clg])
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
</div>
);
}
我们点击按钮后都会 render 重新创建 clg 函数,从而使 useEffect 再次执行,如果我们并不需要 clg 变化而影响 useEffect 执行,我们该怎么做呢?
export default function App() {
const [count, setCount] = useState(0);
// callback会缓存这个函数
const clg = React.useCallback(()=>{
console.log(2)
},[])
React.useEffect(()=>{
clg()
},[clg])
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
</div>
);
}
我们使用 useCallback 来缓存这个函数,从而 useEffect 不再进行进行无用的执行。
搭配 memo 进行优化:
function One({clg}) {
clg()
return <h2>One</h2>;
}
export default function App() {
const [count, setCount] = useState(0);
const clg = ()=>{
console.log(2)
}
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
<One clg={clg} />
</div>
);
}
我们将 clg 以属性的形式传递给子组件,每次点击按钮后都会使子组件更新。
const MemoOne = React.memo(function One({clg}}) {
clg()
return <h2>MemoOne</h2>;
});
export default function App() {
const [count, setCount] = useState(0);
const clg = React.useCallback(()=>{
console.log(2)
},[])
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
<MemoOne clg={clg} />
</div>
);
}
memo 为我们提供浅层比较方法,与 class 组件的 shouldComponentUpdate 生命周期类似,
useMemo
如果 useCallback 是缓存 function,那 useMemo 就是缓存用来属性或函数结果等具体的值,与 vue 的 computed 类似。
function One({sum}) {
console.log(sum)
return <h2>One</h2>;
}
export default function App() {
const [count, setCount] = useState(0);
const sum = 2 + 3
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
<One sum={sum} />
</div>
);
}
每次 render 都会重新渲染子组件
const MemoOne = React.memo(function One({sum}) {
console.log(sum) // 5
return <h2>MemoOne</h2>;
});
export default function App() {
const [count, setCount] = useState(0);
// 这里注意 useMemo 必须传一个有返回值的函数
const sum = useMemo(()=>{
return 2 + 3
},[])
return (
<div className="App">
<div>{count}</div>
<button
onClick={() => {
setCount((prev)=> prev + 1)
}}
>
Click
</button>
<MemoOne sum={sum} />
</div>
);
}
useMemo 配合 memo 实现子组件无效的渲染
memo
useMemo 与 useCallback 优化后的属性,子组件都需要memo进行包裹,否则无效。因为无状态组件自身并不会浅比较对比属性是否变化。 需要注意的是与class 组件中 [shouldComponentUpdate()](https://zh-hans.reactjs.org/docs/react-component.html#shouldcomponentupdate)
方法不同的是,如果 props 相等,areEqual
会返回 true
;如果 props 不相等,则返回 false
。这与 shouldComponentUpdate
方法的返回值相反
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
合并更新
react 17.0 之后合成事件场景下在进行批量更新
import { useState, useLayoutEffect } from "react";
import * as ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
console.log("=== click ===");
setCount((c) => c + 1);
setFlag((f) => !f);
// 只批量更新一次
}
console.log("ReRender");
return (
<div>
<button onClick={handleClick}>点我</button>
<h1>{count}</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
在异步和 setTimeout 场景会造成 hooks 批量更新失败,导致多次 render
import { useState, useLayoutEffect } from "react";
import * as ReactDOM from "react-dom";
function fetchSomething() {
return new Promise((resolve) => setTimeout(resolve, 100));
}
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClickPromise() {
console.log("=== click ===");
fetchSomething().then(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// 更新两次
});
}
function handleClickSetTimeout() {
setTimeout(() => {
setCount((c) => c + 1); // Causes a re-render
setFlag((f) => !f); // Causes a re-render
// 更新两次
}, 0)
}
console.log("Rerender: 正在重新渲染");
return (
<div>
<button onClick={handleClickPromise}>Promise</button>
<button onClick={handleClickSetTimeout}>SetTimeout</button>
<h1>{count}</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
手动批量更新
react-dom
中提供了unstable_batchedUpdates
方法进行手动批量更新
const handerClick = () => {
Promise.resolve().then(()=>{
unstable_batchedUpdates(()=>{
setCount((c) => c + 1);
setFlag((f) => !f);
// 仅更新一次
})
})
}
参考:
https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo