Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 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()?
先來看一下用useState()和class組件操作state的不同吧!
使用useState()

使用class操作state

在constructor()構(gòu)造函數(shù)中,通過this.state對象進(jìn)行初始化設(shè)置{count:0}把this.state.count的初始值設(shè)置為1,在函數(shù)組件中,沒有this,需要用到useState()Hook來操作state變量。
調(diào)用 useState?方法的時(shí)候做了什么??它定義一個(gè) “state 變量”--count,這是一種在函數(shù)調(diào)用時(shí)保存變量的方式 ,它與 class 里面的?this.state?提供的功能完全相同。一般來說,在函數(shù)退出后變量就會”消失”,而 state 中的變量會被 React 保留 ---- 閉包原理。
useState?需要哪些參數(shù)??useState()?方法里面唯一的參數(shù)就是初始 state,在示例中,只需使用數(shù)字來記錄用戶點(diǎn)擊的次數(shù),所以我們傳了?0?作為變量的初始 state(如果需要在 state 中存儲兩個(gè)不同的變量,只需調(diào)用?useState()?兩次即可)。
useState?方法的返回值是什么??返回值為:當(dāng)前 state 以及更新 state 的函數(shù)。這就是我們寫?const [count, setCount] = useState()?的原因。這與 class 里面this.state.count?和?this.setState?類似,唯一區(qū)別就是你需要成對的獲取它們。
讀取state變量方式的不同:
通過class定義的state:{ this.state.count }函數(shù)式組件獲取state變量:{ count }
更新state變量方式的不同:
通過class定義的state:需要調(diào)用?this.setState()?來更新?count?值
通過function定義的state:在定義的函數(shù)式組件中已經(jīng)有了?setCount?和?count?變量,所以我們不需要?this,可通過setCount直接操作state。
函數(shù)式組件代碼解讀:
第一行:?引入 React 中的?useState?Hook。它讓我們在函數(shù)組件中存儲內(nèi)部 state。
第四行:?在 Test 組件內(nèi)部,我們通過調(diào)用?useState?Hook 聲明了一個(gè)新的 state 變量。它返回一對值給到我們命名的變量上。我們把變量命名為?count,因?yàn)樗鎯Φ氖屈c(diǎn)擊次數(shù)。我們通過傳?0?作為?useState?唯一的參數(shù)來將其初始化為?0。第二個(gè)返回的值本身就是一個(gè)函數(shù)。它讓我們可以更新?count?的值,所以我們叫它?setCount。
第八行:?當(dāng)用戶點(diǎn)擊按鈕后,我們傳遞一個(gè)新的值給?setCount。React 會重新渲染 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ù)獲取的請求,設(shè)置訂閱(事件監(jiān)聽)、手動更改 React 組件中的 DOM 都屬于useEffect()(副作用)。
我理解的副作用就是在不停的完善通過腳手架搭建出來的一個(gè)空架子,從形式上看,useEffect()=componentDidMount+componentDidUpdate+componentWillUnmount。useEffect中的方法,是在render()完DOM后再執(zhí)行的,好比于class組件中執(zhí)行完render后再執(zhí)行componentDidMount方法一樣。
在 React 組件中有兩種常見副作用操作:需要清除的和不需要清除的
無需清除的 effect:
有時(shí)候,我們只想在 React 更新 DOM 之后運(yùn)行一些額外的代碼。比如發(fā)送網(wǎng)絡(luò)請求,手動變更 DOM,記錄日志,這些都是常見的無需清除的操作。
class組件中的副作用操作:
在 React 的 class 組件中,render?函數(shù)是沒有任何副作用的。這就是為什么在 React class 中,我們把副作用操作放到?componentDidMount?和?componentDidUpdate?函數(shù)中。如下:

使用function組件實(shí)現(xiàn)dom的加載和更新:

默認(rèn)情況下,useEffect在第一次渲染之后和每次更新之后都會執(zhí)行。稍后進(jìn)行配置后可以實(shí)現(xiàn)非默認(rèn)操作。同時(shí)React 保證了每次運(yùn)行 effect 的同時(shí),DOM 都已經(jīng)更新完畢。
性能方面:
與?componentDidMount?或?componentDidUpdate?不同,使用?useEffect?調(diào)度的 effect 不會阻塞瀏覽器更新屏幕,這讓應(yīng)用看起來響應(yīng)更快。大多數(shù)情況下,effect 不需要同步地執(zhí)行。在個(gè)別情況下(例如測量布局),有單獨(dú)的?useLayoutEffect?Hook 使用,其 API 與?useEffect?相同。
需要清除的 effect副作用:
像訂閱外部數(shù)據(jù)源,清除工作是非常重要的,可以防止引起內(nèi)存泄露!
使用 Class 的示例:在 React class 中,通常會在?componentDidMount?中設(shè)置訂閱,并在?componentWillUnmount?中清除它。

在 React class 中,你通常會在?componentDidMount?中設(shè)置訂閱,并在?componentWillUnmount?中清除它。
使用 Hook 的示例
由于添加和刪除訂閱的代碼的緊密性,所以?useEffect?的設(shè)計(jì)是在同一個(gè)地方執(zhí)行。如果你的 effect 返回一個(gè)函數(shù),React 將會在執(zhí)行清除操作時(shí)調(diào)用它:


為什么要在 effect 中返回一個(gè)函數(shù)??這是 effect 可選的清除機(jī)制。每個(gè) effect 都可以返回一個(gè)清除函數(shù)。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。
React 何時(shí)清除 effect??React 會在組件卸載的時(shí)候執(zhí)行清除操作。正如之前學(xué)到的,effect 在每次渲染的時(shí)候都會執(zhí)行。這就是為什么 React?會在執(zhí)行當(dāng)前 effect 之前對上一個(gè) effect 進(jìn)行清除。
使用 Hook 其中一個(gè)目的就是要解決 class 中生命周期函數(shù)經(jīng)常包含不相關(guān)的邏輯,但又把相關(guān)邏輯分離到了幾個(gè)不同方法中的問題。

可以發(fā)現(xiàn) document.title?的邏輯被分割到?componentDidMount?和?componentDidUpdate?中,訂閱邏輯又被分割到?componentDidMount?和?componentWillUnmount?中。而且?componentDidMount?中同時(shí)包含了兩個(gè)不同功能的代碼。當(dāng)功能需求i更多時(shí),邏輯處理非常容易混亂。
那么 Hook 如何解決這個(gè)問題呢?可以使用多個(gè) effect,會將不相關(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í)會發(fā)生什么?-- 我們的組件將繼續(xù)展示原來的state。而且我們還會因?yàn)槿∠嗛啎r(shí)使用錯(cuò)誤的 ID 導(dǎo)致內(nèi)存泄露或崩潰的問題,所以在 class 組件中,我們需要添加?componentDidUpdate?來解決這個(gè)問題:

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)跳過useEffect()操作,優(yōu)化性能,那么該如何實(shí)現(xiàn)這樣的操作呢?
在 class 組件中,我們可以通過在?componentDidUpdate?中添加對?prevProps?或?prevState?的比較邏輯解決:

如果某些特定值在兩次重渲染之間沒有發(fā)生變化,你可以通知 React?跳過對 effect 的調(diào)用,只要傳遞數(shù)組作為?useEffect?的第二個(gè)可選參數(shù)即可:

而useEffect?默認(rèn)就會處理更新問題,對前一個(gè)useEffect() 進(jìn)行清理。
同理,對于有清除操作的 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í)行,依賴于數(shù)組的值前后兩次是否變化。
3、每次刷新都執(zhí)行一遍。不傳任何參數(shù),比如:
useEffect(()=>{document.title=`Clicked ${count} times`;});
useContext
看下方代碼便可大致了解useContext()的用法啦~

借助React.createContext 和 useContext(),我們擁有了一種 “透傳” 的的能力,能將頂層的屬性,一次傳遞到任意子層級的組件,而不需要層層接力式的傳遞。
useReducer:
useState?的替代方案。它接收一個(gè)形如?(state, action) => newState?的 reducer,并返回當(dāng)前的 state 以及與其配套的?dispatch?方法。

useCallback:

當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如?shouldComponentUpdate)的子組件時(shí),它將非常有用
useCallback(fn, deps)?相當(dāng)于?useMemo(() => fn, deps)
useMemo:
useMemo能記憶一個(gè)方法執(zhí)行的結(jié)果值,假如下次刷新組件時(shí),依賴不變,則useMemo不會執(zhí)行這個(gè)方法,而是直接拿到上次記憶的值 -- 類似于vue中的computed。如果這個(gè)方法是個(gè)耗時(shí)運(yùn)算,或是返回一個(gè)組件,當(dāng)依賴不變,就直接拿記憶值,這樣就能起到性能優(yōu)化的效果。如果沒有提供依賴項(xiàng)數(shù)組,useMemo?在每次渲染時(shí)都會計(jì)算新的值。
useRef:
是用對象引用方式,用戶代碼可以用它來做一般數(shù)據(jù)的緩存。說白了還是一種持久化。
seRef?返回一個(gè)可變的 ref 對象,其?.current?屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對象在組件的整個(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具有透傳能力
3、其他鉤子在于依賴。
4、捕獲值的這個(gè)特性是我們寫鉤子最最需要注意的問題,它是函數(shù)特有的一種特性,并非函數(shù)式組件專有。函數(shù)的每一次調(diào)用,會產(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 中取出來;
useStore 。返回 store 本身;
useDispatch。返回 store.dispatch。
關(guān)于測試
覺得還是到改了一部分 hooks 寫法后,在加單元測試?,F(xiàn)在堆積了很多邏輯的class組件真心難寫。如何測試使用了 Hook 的組件