對 react hooks 的理解
阿新 • • 發佈:2022-03-11
通常情況下,React 會有多餘的 render。
1. 常用的場景一:子元件依賴父元件資料,當父元件資料更新時,會重新渲染子元件。
// index.tsx function Index() { const [count, setCount] = useState(0); const handleAdd = () => { setCount((i) => i + 1); }; return ( <div className="container"> <h2>{count}</h2> <Button type="primary" size="large" onClick={handleAdd}> 點選 +1 </Button> <Child data={count} /> </div> ); } // child.tsx function Child(props: any) { console.log("child 被渲染了!!"); return ( <div className="container"> <Card>child 被渲染了({props.data})</Card> </div> ); }
2. 常見的場景二:子元件依賴父元件資料,當父元件其他資料(非子元件依賴的資料)更新時,子元件也會重新渲染!
// index.tsx // const data = 0; // 定義一個常量 function Index() { const [count, setCount] = useState(0); const [data, setData] = useState(0); const handleAdd = () => { setCount((i) => i + 1); }; return ( <div className="container"> <h2>{count}</h2> <Button type="primary" size="large" onClick={handleAdd}> 點選 +1 </Button> <Child data={data} /> </div> ); } // child.tsx function Child(props: any) { console.log("child 被渲染了!!"); return ( <div className="container"> <Card>child 被渲染了({props.data})</Card> </div> ); }
3. 常見的場景三:子元件不依賴父元件資料,當父元件資料更新時,子元件也會重新渲染!!
// index.tsx function Index() { const [count, setCount] = useState(0); const handleAdd = () => { setCount((i) => i + 1); }; return ( <div className="container"> <h2>{count}</h2> <Button type="primary" size="large" onClick={handleAdd}> 點選 +1 </Button> <ChildPure /> </div> ); } // child.tsx function ChildPure() { console.log("childPure 被渲染了!!"); return ( <div className="container"> <Card>childPure 被渲染了</Card> </div> ); }
【總結】不論子元件是否有依賴父元件的資料,當父元件資料更新時(也就是呼叫 setXXX()),react 會重新渲染整個檢視,其中包括了子元件。多數情況下,這種渲染元件操作是多餘的、浪費的,react.memo() 可以用來解決這個問題。
react.memo
將程式碼裡的 Child 用 React.memo(Child) 包裹,當 props 沒有更新時,React 會跳過渲染元件的操作並直接服用最近一次渲染的結果。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
return (
<div className="container">
<h2>{count}</h2>
<Button type="primary" size="large" onClick={handleAdd}>
點選 +1
</Button>
<ChildPureMemo />
</div>
);
}
// child.tsx
function ChildPure() {
console.log("childPure 被渲染了!!");
return (
<div className="container">
<Card>childPure 被渲染了</Card>
</div>
);
}
export const ChildPureMemo = React.memo(ChildPure);
但是這裡還有個bug,當給元件添加了監聽事件後,又回到了最初的重複渲染狀態。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = () => {};
return (
<div className="container">
<h2>{count}</h2>
<Button type="primary" size="large" onClick={handleAdd}>
點選 +1
</Button>
<ChildMemo data={data} onClick={handleData} />
</div>
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
<div className="container">
<Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
</div>
);
}
export const ChildMemo = React.memo(Child);
[總結] 使用 react.memo() 包裹的子元件,當 props 沒有變化時,不會重新渲染子元件。但是監聽事件在父元件重新渲染時,雖然功能一樣,但引用地址變化了!!所以對於子元件來說,props發生了變化,進行重新渲染。這種情況下,需要使用 useCallback 解決。
useCallback
useCallback 的第二個引數是依賴項,當依賴發生變化時,才會重新計算新的value,如果依賴不變,就重用之前的value。
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = useCallback(() => {
console.log(111);
setData((i) => i + 1);
}, []);
return (
<div className="container">
<h2>{count}</h2>
<Button type="primary" size="large" onClick={handleAdd}>
點選 +1
</Button>
<ChildMemo data={data} onClick={handleData} />
</div>
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
<div className="container">
<Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
</div>
);
}
export const ChildMemo = React.memo(Child);
useMemo
useMemo 的用途和 useCallback 一樣,只是第一個引數的型別不一樣。useCallback(value, []) => useMemo(()=>value,[])
如果 value 是一個函式時,useMemo 的寫法會相對不易理解
// index.tsx
function Index() {
const [count, setCount] = useState(0);
const handleAdd = () => {
setCount((i) => i + 1);
};
const handleData = useMemo(
() => () => {
console.log(111);
setData((i) => i + 1);
},
[]
);
return (
<div className="container">
<h2>{count}</h2>
<Button type="primary" size="large" onClick={handleAdd}>
點選 +1
</Button>
<ChildMemo data={data} onClick={handleData} />
</div>
);
}
// child.tsx
function Child(props: any) {
console.log("child 被渲染了!!");
return (
<div className="container">
<Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
</div>
);
}
export const ChildMemo = React.memo(Child);