Hooks
Hooks
是 React
16.8 的新增特性绷耍,能夠在不寫 class
的情況下使用 state
以及其他特性型豁。
動機
- 在組件之間復用狀態(tài)邏輯很難
- 復雜組件變得難以理解
- 難以理解的
class
Hooks
規(guī)則
- 只有在最頂層使用
Hooks
不要再循環(huán)/條件/嵌套函數(shù)中使用` - 只有在
React
函數(shù)中調用Hooks
函數(shù)組件和類組件的不同
函數(shù)組件能夠捕獲到當前渲染的所用的值材泄。
對于類組件來說卷拘,雖然 props
是一個不可變的數(shù)據(jù)看蚜,但是 this
是一個可變的數(shù)據(jù)结窘,在我們渲染組件的時候 this
發(fā)生了改變,所以 this.props
發(fā)生了改變底哗,因此在 this.showMessage
中會拿到最新的 props
值岁诉。
對于函數(shù)組件來說捕獲了渲染所使用的值,當我們使用 hooks
時跋选,這種特性也同樣的試用于 state
上涕癣。
const showMessage = () => {
alert("寫入:" + message);
};
const handleSendClick = () => {
setTimeout(showMessage, 3000);
};
const handleMessageChange = (e) => {
setMessage(e.target.value);
};
如果我們想跳出'函數(shù)組件捕獲當前渲染的所用值‘這個特性,我們可以采用 ref
來追蹤某些數(shù)據(jù)前标。通ref.current
可以獲取到最新的值
const showMessage = () => {
alert("寫入:" + ref.current);
};
const handleSendClick = () => {
setTimeout(showMessage, 3000);
};
const handleMessageChange = (e) => {
setMessage(e.target.value);
ref.current = e.target.value;
};
useEffect
useEffect 能夠在函數(shù)組件中執(zhí)行副作用操作(數(shù)據(jù)獲取/涉及訂閱)坠韩,其實可以把 useEffect
看作是 componentDidMount / componentDidUpdate / componentWillUnMount
的組合
- 第一個參數(shù)是一個
callback
,返回destory
炼列。destory
作為下一個callback
執(zhí)行前調用只搁,用于清除上一次callback
產(chǎn)生的副作用 - 第二個參數(shù)是依賴項,一個數(shù)組俭尖,可以有多個依賴項氢惋。依賴項改變,執(zhí)行上一個
callback
返回的destory
稽犁,和執(zhí)行新的effect
第一個參數(shù)callback
對于 useEffect
的執(zhí)行焰望,React
處理邏輯是采用異步調用
的,對于每一個 effect
的 callback
會像 setTimeout
回調函數(shù)一樣已亥,放到任務隊列里面熊赖,等到主線程執(zhí)行完畢才會執(zhí)行。所以 effect
的回調函數(shù)不會阻塞瀏覽器繪制視圖
- 相關的生命周期替換方案
- componentDidMount 替代方案
React.useEffect(()=>{
//請求數(shù)據(jù)虑椎,事件監(jiān)聽震鹉,操縱DOM
},[]) //dep=[],只有在初始化執(zhí)行
/*
因為useEffect會捕獲props和state捆姜,
所以即使是在回調函數(shù)中我們拿到的還是最初的props和state
*/
-
componentDidUnmount
替代方案
React.useEffect(()=>{
/* 請求數(shù)據(jù) 传趾, 事件監(jiān)聽 , 操縱dom 娇未, 增加定時器墨缘,延時器 */
return function componentWillUnmount(){
/* 解除事件監(jiān)聽器 ,清除定時器,延時器 */
}
},[])/* 切記 dep = [] */
//useEffect第一個函數(shù)的返回值可以作為componentWillUnmount使用
-
componentWillReceiveProps
替代方案
其實兩者的執(zhí)行時機是完全不同的镊讼,一個在render
階段宽涌,一個在commit
階段,useEffect
會初始化執(zhí)行一次蝶棋,但是componentWillReceiveProps
只會在props
變化時執(zhí)行更新
React.useEffect(()=>{
console.log('props變化:componentWillReceiveProps')
},[ props ])
-
componentDidUpdate
替代方案
useEffect
和componentDidUpdate
在執(zhí)行時期雖然有點差別卸亮,useEffect
是異步執(zhí)行,componentDidUpdate
是同步執(zhí)行 玩裙,但都是在commit
階段
React.useEffect(()=>{
console.log('組件更新完成:componentDidUpdate ')
}) //沒有dep依賴項兼贸,沒有第二個參數(shù),那么每一次執(zhí)行函數(shù)組件吃溅,都會執(zhí)行該 effect溶诞。
- 在
useEffect
中[]需要處理什么
只有當函數(shù)(以及它所調用的函數(shù))不引用 props
、state
以及由它們衍生而來的值時决侈,你才能放心地把它們從依賴列表中省略螺垢,使用 eslint-plugin-react-hooks
幫助我們的代碼做一個校驗
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
//只會做一次更新,然后定時器不再轉動
- 是否應該把函數(shù)當做
effect
的依賴
const loadResourceCatalog = async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
}
useEffect(() => {
loadResourceCatalog();
}, [])
//在函數(shù)loadResourceCatalog中使用了templateType這樣的一個state
//在開發(fā)的過程中可能會忘記函數(shù)loadResourceCatalog依賴templateType值
第一個簡單的解法赖歌,對于某些只在 useEffect 中使用的函數(shù)枉圃,直接定義在 effect
中,以至于能夠直接依賴某些 state
useEffect(() => {
const loadResourceCatalog = async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
}
loadResourceCatalog();
}, [templateType])
假如我們需要在很多地方用到我們定義的函數(shù)庐冯,不能夠把定義放到當前的 effect
中孽亲,并且將函數(shù)放到了第二個的依賴參數(shù)中,那這個代碼將就進入死循環(huán)展父。因為函數(shù)在每一次渲染中都返回一個新的引用
const Template = () => {
const getStandardTemplateList = async () => {
const res: any = await API.getStandardTemplateList()
if (!res.success) return;
const { data } = res;
setCascaderOptions(data);
getDefaultOption(data[0])
}
useEffect(()=>{
getStandardTemplateList()
}, [])
}
針對這種情況返劲,如果當前函數(shù)沒有引用任何組件內(nèi)的任何值,可以將該函數(shù)提取到組件外面去定義犯祠,這樣就不會組件每次 render
時不會再次改變函數(shù)引用旭等。
const getStandardTemplateList = async () => {
const res: any = await API.getStandardTemplateList()
if (!res.success) return;
const { data } = res;
setCascaderOptions(data);
getDefaultOption(data[0])
}
const Template = () => {
useEffect(()=>{
getStandardTemplateList()
}, [])
}
如果說當前函數(shù)中引用了組件內(nèi)的一些狀態(tài)值,可以采用 useCallBack
對當前函數(shù)進行包裹
const loadResourceCatalog = useCallback(async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
}, [templateType])
useEffect(() => {
loadResourceCatalog();
}, [loadResourceCatalog])
//通過useCallback的包裹衡载,如果templateType保持不變,那么loadResourceCatalog也會保持不變隙袁,所以useEffect也不會重新運行
//如果templateType改變痰娱,那么loadResourceCatalog也會改變,所以useEffect也會重新運行
useCallback
useCallback(fn, deps)
返回一個 memoized
回調函數(shù)菩收,該回調函數(shù)僅在某個依賴項改變時才會更新
import React, { useCallback, useState } from "react";
const CallBackTest = () => {
const [count, setCount] = useState(0);
const [total, setTotal] = useState(0);
const handleCount = () => setCount(count + 1);
//const handleCount = useCallback(() => setCount(count + 1), [count]);
const handleTotal = () => setTotal(total + 1);
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<div>
<Child onClick={handleCount} label="Increment Count" />
<Child onClick={handleTotal} label="Increment Total" />
</div>
</div>
);
};
const Child = React.memo(({ onClick, label }) => {
console.log(`${label} Child Render`);
return <button onClick={onClick}>{label}</button>;
});
export default CallBackTest;
React.memo
是通過記憶組件渲染結果的方式來提高性能梨睁,memo
是 react16.6
引入的新屬性,通過淺比較
(源碼通過 Object.is
方法比較)當前依賴的 props
和下一個 props
是否相同來決定是否重新渲染娜饵;如果使用過類組件方式坡贺,就能知道 memo
其實就相當于 class
組件中的 React.PureComponent
,區(qū)別就在于 memo
用于函數(shù)組件。useCallback
和 React.memo
一定要結合使用才能有效果遍坟。
使用場景
- 作為
props
拳亿,傳遞給子組件,為避免子元素不必要的渲染愿伴,需要配合React.Memo
使用肺魁,否則無意義 - 作為
useEffect
的依賴項,需要進行比較的時候才需要加上useCallback
useMemo
返回一個 memoized
值
僅會在某個依賴項改變時才重新計算 memoized
值隔节,這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算 useCallback(fn, deps)
相當于useMemo(() => fn, deps)
鹅经,對于實現(xiàn)上,基本上是和 useCallback
相似怎诫,只是略微有些不同
使用場景
- 避免在每次渲染時都進行高開銷的計算
兩個 hooks
內(nèi)置于 React
都有特別的原因:
1.引用相等
當在 React
函數(shù)組件中定義一個對象時瘾晃,它跟上次定義的相同對象,引用是不一樣的(即使它具有所有相同值和相同屬性)
- 依賴列表
React.memo
大多數(shù)時候幻妓,你不需要考慮去優(yōu)化不必要的重新渲染蹦误,因為優(yōu)化總會帶來成本。
- 昂貴的計算
計算成本很高的同步計算值的函數(shù)
總結
本文介紹了 hooks
產(chǎn)生動機涌哲、函數(shù)組件和類組件的區(qū)別以及 useEffect / useCallback / useMemo
等內(nèi)容胖缤。重點介紹了 useEffect
的生命周期替換方案以及是否把函數(shù)作為 useEffect
的第二參數(shù)。
參考鏈接
When to useMemo and useCallback
How to fetch data with React Hooks