React Hook是React函數(shù)式組件转锈,它不僅僅有函數(shù)組件的特性,還帶有React框架的特性。所以蟹倾,官網(wǎng)文檔多次強調(diào):
只在 React 函數(shù)中調(diào)用 Hook
不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook培慌。你可以:
- ? 在 React 的函數(shù)組件中調(diào)用 Hook
- ? 在自定義 Hook 中調(diào)用其他 Hook
1. 那么 React 中 Function Component 與 Class Component 有何不同表鳍?
Class Component:
class ProfilePage extends React.Component {
showMessage = () => {
alert("Followed " + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
Function Component:
function ProfilePage(props) {
const showMessage = () => {
alert("Followed " + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return <button onClick={handleClick}>Follow</button>;
}
如下父級組件的調(diào)用方式:
<ProfilePageFunction user={this.state.user} />
<ProfilePageClass user={this.state.user} />
場景:那么當(dāng)點擊按鈕后的 3 秒內(nèi)胁镐,父級修改了 this.state.user笨农,彈出的用戶名是修改前的還是修改后的呢份招?
答案:Class Component 展示的是修改后的值廓旬,F(xiàn)unction Component 展示的是修改前的值
原因:this
在 Class Component 中是可變的十气,當(dāng)組件入?yún)l(fā)生變化時, this.props
同步改變。而 Function Component 不存在this.props
的語法炼蛤,因此 props
總是不可變的絮识。
2. 用Hook 創(chuàng)建函數(shù)組件,會有什么變化呢浅萧?
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
function Example() {
const [count, setCount] = useState(0);
const handleAlertClick = useCallback(()=>{
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000)
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
增加 count
</button>
<button onClick={handleAlertClick}>
顯示 count
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);
場景:點擊 顯示 按鈕徘郭,在 3s 后(模擬耗時任務(wù))會出現(xiàn)彈層闪湾。在這 3s 期間快速點擊 增加 count 按鈕
結(jié)果:3s 后看到的彈層計數(shù)仍舊為 0濒憋!
原因:在 handleAlertClick
函數(shù)執(zhí)行的那個 Render 過程里裆站,count
的值可以看作常量 本姥。執(zhí)行 setCount(count + 1)
時會交由一個全新的 Render 渲染氛赐,所以不會執(zhí)行 handleAlertClick
函數(shù)艰管。
那我們用useEffect
改造一下呢?
const [count, setCount] = useState(0);
useEffect(()=>{
setTimeout(() => {
alert('count: ' + count);
}, 3000)
}, [count]); // 監(jiān)聽count變化
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
增加 count
</button>
</div>
);
}
...
行為:三秒鐘內(nèi)點擊“增加 count”按鈕三次耘擂。
結(jié)果:先顯示0(初始化),然依次顯示1鸽照,2诞外,3,useEffect
被觸發(fā)4次脾拆。
原因:useEffect
也是具有 Capture Value 的特性,每次render都是獨立快照百揭,拿到的 count
都是固化下來的常量爽哎。和上面例子不同之處在于,它監(jiān)聽了count
變化器一,可以被觸發(fā)多次Render课锌。
什么是Capture Value?
每次 Render 的內(nèi)容都會形成一個快照并保留下來,因此當(dāng)狀態(tài)變更而 Rerender 時祈秕,就形成了 N 個 Render 狀態(tài)渺贤,而每個 Render 狀態(tài)都擁有自己固定不變的 Props 與 State。
3. 如何繞過 Capture Value请毛?
利用 useRef
志鞍!
useRef定義
useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)方仿。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變固棚。
ref 類型的變量通常是用來存儲 DOM 元素引用。
但在 react hooks 中仙蚜,它可以存放任何可變數(shù)據(jù)玻孟,并在所有 Render 過程中保持著唯一引用,因此所有對 ref 的賦值或取值鳍征,拿到的都只有一個最終狀態(tài),而不會在每個 Render 間存在隔離面徽。
function Example() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
const handleAlertClick = useCallback(
() => {
setTimeout(() => {
alert("You clicked on: " + countRef.current);
}, 3000);
},
[count]
);
return (
<div>
<p>You clicked {count} times</p>
<button
onClick={() => {
countRef.current = count + 1;
setCount(count + 1);
}}
>
增加 count
</button>
<button onClick={handleAlertClick}>顯示 count</button>
</div>
);
行為:三秒鐘內(nèi)點擊“增加 count”按鈕三次艳丛。
結(jié)果: 先顯示0(初始化),3s 后顯示3次3趟紊。
4. useEffect
搭配useReducer
利用 useEffect
的兄弟 useReducer
函數(shù)氮双,可以將更新與動作解耦。
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: "tick" }); // Instead of setCount(c => c + step);
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
這就是一個局部 “Redux”霎匈,由于更新變成了dispatch({ type: "tick" })
所以不管更新時需要依賴多少變量戴差,在調(diào)用更新的動作里都不需要依賴任何變量。 具體更新操作在 reducer 函數(shù)里寫就可以了
5. useMemo
vs useCallback
按照官網(wǎng)說法铛嘱,兩個的相同點和不同點非常明確(React教程)
- 相同點:
- 都會在組件第一次渲染的時候執(zhí)行
- 依賴的變量發(fā)生改變時再次執(zhí)行
- 返回緩存的值
- 不同點:
-
useMemo
返回緩存的變量 -
useCallback
返回緩存的函數(shù)
-
useCallback(fn, deps)
相當(dāng)于useMemo(() => fn, deps)
5.1 useMemo
實例
export function useDocumentMap() {
const { docState: { sideBarList = [] } = {} } = useStore()
return useMemo(() => getDocumentMap(sideBarList), [sideBarList])
}
getDocumentMap
是一個復(fù)雜計算函數(shù)暖释,sideBarList
為store里存儲的值袭厂。UI顯示的時候,需要做一次model=>UI model
的轉(zhuǎn)換球匕。因為這個轉(zhuǎn)換需要復(fù)雜計算纹磺,所以用useMemo
做緩存,在sideBarList
不變的情況下亮曹,調(diào)用useDocumentMap
獲取UI model
只會計算一次橄杨。
5.2 useCallback
實例
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1);
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<div>
<div>
<Button onClickButton={handleClickButton1}>Button1</Button>
</div>
<div>
<Button onClickButton={handleClickButton2}>Button2</Button>
</div>
</div>
);
}
// Button組件
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
可見Button
組件只依賴onClickButton
和children
。
如果點擊Button1
照卦,因為count1
的值發(fā)生變化式矫,導(dǎo)致容器App
重新渲染,handleClickButton1
變量重新被聲明(又創(chuàng)建了一個新的函數(shù)對象)役耕,從而第一個Button
被再次渲染采转。而handleClickButton2
呢?因為被useCallback
包了一層蹄葱,且依賴count2
氏义,所以,此時handleClickButton2
變量不會被重新聲明图云,還是之前的函數(shù)引用地址惯悠,所以,第二個Button
不會被重復(fù)渲染竣况。
React.memo
這個方法克婶,會對 props 做一個淺層比較,如果 props 沒有發(fā)生改變丹泉,則不會重新渲染此組件
5.3 總結(jié)
- 如果有函數(shù)傳遞給子組件情萤,使用
useCallback
- 如果有值傳遞給子組件,使用
useMemo
-
useMemo摹恨、useCallback
都自帶閉包筋岛,類似useEffect
的Capture Value
能力