前言
本人使用了 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><buttononClick={() => {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><buttononClick={() => {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><buttononClick={() => {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><buttononClick={() => {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 + 3return (<div className="App"><div>{count}</div><buttononClick={() => {setCount((prev)=> prev + 1)}}>Click</button><One sum={sum} /></div>);}
每次 render 都会重新渲染子组件
const MemoOne = React.memo(function One({sum}) {console.log(sum) // 5return <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><buttononClick={() => {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-rendersetFlag((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
