Hook 是 React 16.8 的新增特性。它可以讓你在不編寫(xiě) class 的情況下使用 state 以及其他的 React 特性犬庇。
Hook 是什么键畴??Hook 是一個(gè)特殊的函數(shù)服赎,它可以讓你“鉤入” React 的特性祸轮。例如,useState?是允許你在 React 函數(shù)組件中添加 state 的 Hook哀峻。
react hooks的內(nèi)置hook有:
useState()涡相、useEffect()、useContext()剩蟀、useMemo()催蝗、useCallback()、useRef()育特、useImperativeHandle()丙号、自定義hook
useState()?
先來(lái)看一下用useState()和class組件操作state的不同吧先朦!
使用useState()
使用class操作state
在constructor()構(gòu)造函數(shù)中,通過(guò)this.state對(duì)象進(jìn)行初始化設(shè)置{count:0}把this.state.count的初始值設(shè)置為1犬缨,在函數(shù)組件中喳魏,沒(méi)有this,需要用到useState()Hook來(lái)操作state變量怀薛。
調(diào)用 useState?方法的時(shí)候做了什么??它定義一個(gè) “state 變量”--count刺彩,這是一種在函數(shù)調(diào)用時(shí)保存變量的方式 ,它與 class 里面的?this.state?提供的功能完全相同枝恋。一般來(lái)說(shuō)创倔,在函數(shù)退出后變量就會(huì)”消失”,而 state 中的變量會(huì)被 React 保留 ---- 閉包原理焚碌。
useState?需要哪些參數(shù)畦攘??useState()?方法里面唯一的參數(shù)就是初始 state,在示例中十电,只需使用數(shù)字來(lái)記錄用戶(hù)點(diǎn)擊的次數(shù)知押,所以我們傳了?0?作為變量的初始 state(如果需要在 state 中存儲(chǔ)兩個(gè)不同的變量,只需調(diào)用?useState()?兩次即可)鹃骂。
useState?方法的返回值是什么台盯??返回值為:當(dāng)前 state 以及更新 state 的函數(shù)。這就是我們寫(xiě)?const [count, setCount] = useState()?的原因偎漫。這與 class 里面this.state.count?和?this.setState?類(lèi)似爷恳,唯一區(qū)別就是你需要成對(duì)的獲取它們有缆。
讀取state變量方式的不同:
通過(guò)class定義的state:{ this.state.count }函數(shù)式組件獲取state變量:{ count }
更新state變量方式的不同:
通過(guò)class定義的state:需要調(diào)用?this.setState()?來(lái)更新?count?值
通過(guò)function定義的state:在定義的函數(shù)式組件中已經(jīng)有了?setCount?和?count?變量象踊,所以我們不需要?this,可通過(guò)setCount直接操作state棚壁。
函數(shù)式組件代碼解讀:
第一行:?引入 React 中的?useState?Hook杯矩。它讓我們?cè)诤瘮?shù)組件中存儲(chǔ)內(nèi)部 state。
第四行:?在 Test 組件內(nèi)部袖外,我們通過(guò)調(diào)用?useState?Hook 聲明了一個(gè)新的 state 變量史隆。它返回一對(duì)值給到我們命名的變量上。我們把變量命名為?count曼验,因?yàn)樗鎯?chǔ)的是點(diǎn)擊次數(shù)泌射。我們通過(guò)傳?0?作為?useState?唯一的參數(shù)來(lái)將其初始化為?0。第二個(gè)返回的值本身就是一個(gè)函數(shù)鬓照。它讓我們可以更新?count?的值熔酷,所以我們叫它?setCount。
第八行:?當(dāng)用戶(hù)點(diǎn)擊按鈕后豺裆,我們傳遞一個(gè)新的值給?setCount拒秘。React 會(huì)重新渲染 Test 組件,并把最新的?count?傳給它。
"[ ]"是數(shù)組解構(gòu)的操作
const[fruit,setFruit]=useState('banana'); 等價(jià)于下邊代碼? ? ? ? ? ? ? varfruitStateVariable=useState('banana');// 返回一個(gè)有兩個(gè)元素的數(shù)組varfruit=fruitStateVariable[0];// 數(shù)組里的第一個(gè)值? ? ? ? ? ? ? ? ? ? ? ? ? varsetFruit=fruitStateVariable[1];// 數(shù)組里的第二個(gè)值
useEffect()
在useEffect hook可以在函數(shù)里執(zhí)行一些副作用操作躺酒,如:數(shù)據(jù)獲取的請(qǐng)求押蚤,設(shè)置訂閱(事件監(jiān)聽(tīng))、手動(dòng)更改 React 組件中的 DOM 都屬于useEffect()(副作用)羹应。
我理解的副作用就是在不停的完善通過(guò)腳手架搭建出來(lái)的一個(gè)空架子揽碘,從形式上看,useEffect()=componentDidMount+componentDidUpdate+componentWillUnmount量愧。useEffect中的方法钾菊,是在render()完DOM后再執(zhí)行的,好比于class組件中執(zhí)行完render后再執(zhí)行componentDidMount方法一樣偎肃。
在 React 組件中有兩種常見(jiàn)副作用操作:需要清除的和不需要清除的
無(wú)需清除的 effect:
有時(shí)候煞烫,我們只想在 React 更新 DOM 之后運(yùn)行一些額外的代碼。比如發(fā)送網(wǎng)絡(luò)請(qǐng)求累颂,手動(dòng)變更 DOM滞详,記錄日志,這些都是常見(jiàn)的無(wú)需清除的操作紊馏。
class組件中的副作用操作:
在 React 的 class 組件中料饥,render?函數(shù)是沒(méi)有任何副作用的。這就是為什么在 React class 中朱监,我們把副作用操作放到?componentDidMount?和?componentDidUpdate?函數(shù)中岸啡。如下:
使用function組件實(shí)現(xiàn)dom的加載和更新:
默認(rèn)情況下,useEffect在第一次渲染之后和每次更新之后都會(huì)執(zhí)行擂送。稍后進(jìn)行配置后可以實(shí)現(xiàn)非默認(rèn)操作悦荒。同時(shí)React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢嘹吨。
性能方面:
與?componentDidMount?或?componentDidUpdate?不同搬味,使用?useEffect?調(diào)度的 effect 不會(huì)阻塞瀏覽器更新屏幕,這讓?xiě)?yīng)用看起來(lái)響應(yīng)更快蟀拷。大多數(shù)情況下碰纬,effect 不需要同步地執(zhí)行。在個(gè)別情況下(例如測(cè)量布局)问芬,有單獨(dú)的?useLayoutEffect?Hook 使用悦析,其 API 與?useEffect?相同。
需要清除的 effect副作用:
像訂閱外部數(shù)據(jù)源愈诚,清除工作是非常重要的她按,可以防止引起內(nèi)存泄露牛隅!
使用 Class 的示例:在 React class 中,通常會(huì)在?componentDidMount?中設(shè)置訂閱酌泰,并在?componentWillUnmount?中清除它媒佣。
在 React class 中,你通常會(huì)在?componentDidMount?中設(shè)置訂閱陵刹,并在?componentWillUnmount?中清除它默伍。
使用 Hook 的示例
由于添加和刪除訂閱的代碼的緊密性,所以?useEffect?的設(shè)計(jì)是在同一個(gè)地方執(zhí)行衰琐。如果你的 effect 返回一個(gè)函數(shù)也糊,React 將會(huì)在執(zhí)行清除操作時(shí)調(diào)用它:
為什么要在 effect 中返回一個(gè)函數(shù)??這是 effect 可選的清除機(jī)制羡宙。每個(gè) effect 都可以返回一個(gè)清除函數(shù)狸剃。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分狗热。
React 何時(shí)清除 effect钞馁??React 會(huì)在組件卸載的時(shí)候執(zhí)行清除操作。正如之前學(xué)到的匿刮,effect 在每次渲染的時(shí)候都會(huì)執(zhí)行僧凰。這就是為什么 React?會(huì)在執(zhí)行當(dāng)前 effect 之前對(duì)上一個(gè) effect 進(jìn)行清除。
使用 Hook 其中一個(gè)目的就是要解決 class 中生命周期函數(shù)經(jīng)常包含不相關(guān)的邏輯熟丸,但又把相關(guān)邏輯分離到了幾個(gè)不同方法中的問(wèn)題训措。
可以發(fā)現(xiàn) document.title?的邏輯被分割到?componentDidMount?和?componentDidUpdate?中,訂閱邏輯又被分割到?componentDidMount?和?componentWillUnmount?中光羞。而且?componentDidMount?中同時(shí)包含了兩個(gè)不同功能的代碼绩鸣。當(dāng)功能需求i更多時(shí),邏輯處理非常容易混亂狞山。
那么 Hook 如何解決這個(gè)問(wèn)題呢全闷?可以使用多個(gè) effect叉寂,會(huì)將不相關(guān)邏輯分離到不同的 effect 中:
并不是必須為 effect 中返回的函數(shù)命名屏鳍。這里我們將其命名為?cleanup?是為了表明此函數(shù)的目的勘纯,但其實(shí)也可以返回一個(gè)箭頭函數(shù)或者給起一個(gè)別的名字。
class組件:
從 class 中 props 讀取?friend.id钓瞭,然后在組件掛載后componentDidMount()訂閱好友的狀態(tài)驳遵,并在卸載組件componentWillUnmount()的時(shí)候取消訂閱。但是當(dāng)組件已經(jīng)顯示在屏幕上時(shí)山涡,state變化時(shí)會(huì)發(fā)生什么堤结?-- 我們的組件將繼續(xù)展示原來(lái)的state唆迁。而且我們還會(huì)因?yàn)槿∠嗛啎r(shí)使用錯(cuò)誤的 ID 導(dǎo)致內(nèi)存泄露或崩潰的問(wèn)題,所以在 class 組件中竞穷,我們需要添加?componentDidUpdate?來(lái)解決這個(gè)問(wèn)題:
https://react.docschina.org/docs/hooks-effect.html?--?useEffect hook官網(wǎng)鏈接
并不是每一次的dom渲染都必須執(zhí)行useEffect()的唐责,畢竟渲染是很消耗性能能的,所以在當(dāng)dom加載的數(shù)據(jù)與上次渲染的數(shù)據(jù)一致時(shí)瘾带,應(yīng)當(dāng)跳過(guò)useEffect()操作鼠哥,優(yōu)化性能,那么該如何實(shí)現(xiàn)這樣的操作呢看政?
在 class 組件中朴恳,我們可以通過(guò)在?componentDidUpdate?中添加對(duì)?prevProps?或?prevState?的比較邏輯解決:
如果某些特定值在兩次重渲染之間沒(méi)有發(fā)生變化,你可以通知 React?跳過(guò)對(duì) effect 的調(diào)用允蚣,只要傳遞數(shù)組作為?useEffect?的第二個(gè)可選參數(shù)即可:
而useEffect?默認(rèn)就會(huì)處理更新問(wèn)題于颖,對(duì)前一個(gè)useEffect() 進(jìn)行清理。
同理嚷兔,對(duì)于有清除操作的 effect 同樣適用
1恍飘、僅執(zhí)行一次。給useEffect多傳一個(gè)空數(shù)組[]谴垫,比如:
useEffect(()=>{document.title=`Clicked ${count} times`;},[]);
2章母、選擇性的執(zhí)行。給useEffect多傳一個(gè)[count]翩剪,比如:
useEffect(()=>{document.title=`Clicked ${count} times`;},[count]);
數(shù)組參數(shù)前面的方法乳怎,是否執(zhí)行,依賴(lài)于數(shù)組的值前后兩次是否變化前弯。
3蚪缀、每次刷新都執(zhí)行一遍。不傳任何參數(shù)恕出,比如:
useEffect(()=>{document.title=`Clicked ${count} times`;});
useContext
看下方代碼便可大致了解useContext()的用法啦~
借助React.createContext 和 useContext()询枚,我們擁有了一種 “透?jìng)鳌?的的能力,能將頂層的屬性浙巫,一次傳遞到任意子層級(jí)的組件金蜀,而不需要層層接力式的傳遞。
useReducer:
useState?的替代方案的畴。它接收一個(gè)形如?(state, action) => newState?的 reducer渊抄,并返回當(dāng)前的 state 以及與其配套的?dispatch?方法。
useCallback:
當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過(guò)優(yōu)化的并使用引用相等性去避免非必要渲染(例如?shouldComponentUpdate)的子組件時(shí)丧裁,它將非常有用
useCallback(fn, deps)?相當(dāng)于?useMemo(() => fn, deps)
useMemo:
useMemo能記憶一個(gè)方法執(zhí)行的結(jié)果值护桦,假如下次刷新組件時(shí),依賴(lài)不變煎娇,則useMemo不會(huì)執(zhí)行這個(gè)方法二庵,而是直接拿到上次記憶的值 -- 類(lèi)似于vue中的computed贪染。如果這個(gè)方法是個(gè)耗時(shí)運(yùn)算,或是返回一個(gè)組件催享,當(dāng)依賴(lài)不變抑进,就直接拿記憶值,這樣就能起到性能優(yōu)化的效果睡陪。如果沒(méi)有提供依賴(lài)項(xiàng)數(shù)組寺渗,useMemo?在每次渲染時(shí)都會(huì)計(jì)算新的值。
useRef:
是用對(duì)象引用方式兰迫,用戶(hù)代碼可以用它來(lái)做一般數(shù)據(jù)的緩存信殊。說(shuō)白了還是一種持久化。
seRef?返回一個(gè)可變的 ref 對(duì)象汁果,其?.current?屬性被初始化為傳入的參數(shù)(initialValue)涡拘。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變。
React.memo:
functionMyComponent(props){// render using props}functionareEqual(prevProps,nextProps){// return true if passing nextProps to render would return// the same result as passing prevProps to render,// otherwise return false}exportdefaultReact.memo(MyComponent,areEqual);
總結(jié)一下
1据德、useState和useRef鉤子行為相似鳄乏。
2、useContext具有透?jìng)髂芰?/p>
3棘利、其他鉤子在于依賴(lài)橱野。
4、捕獲值的這個(gè)特性是我們寫(xiě)鉤子最最需要注意的問(wèn)題善玫,它是函數(shù)特有的一種特性水援,并非函數(shù)式組件專(zhuān)有。函數(shù)的每一次調(diào)用茅郎,會(huì)產(chǎn)生一個(gè)屬于那一次調(diào)用的作用域蜗元,不同的作用域之間不受影響。
其他
react-redux的鉤子
狀態(tài)管理方面系冗,React 社區(qū)最有名的工具當(dāng)然是 Redux奕扣。在 react-redux@7.1 中新引用了三個(gè) API:
useSelector。它有點(diǎn)像 connect() 函數(shù)的第一個(gè)參數(shù) mapStateToProps掌敬,把數(shù)據(jù)從 state 中取出來(lái)惯豆;
useStore 。返回 store 本身涝开;
useDispatch循帐。返回 store.dispatch框仔。
關(guān)于測(cè)試
覺(jué)得還是到改了一部分 hooks 寫(xiě)法后舀武,在加單元測(cè)試。現(xiàn)在堆積了很多邏輯的class組件真心難寫(xiě)离斩。如何測(cè)試使用了 Hook 的組件