目錄
- 什么是 React Hooks?
- 為什么要?jiǎng)?chuàng)造 Hooks?
- Hooks API 一覽
- Hooks 使用規(guī)則
什么是 React Hooks?
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
- Hooks 是在 React 16.8 版本的新特性,允許你以函數(shù)組件的形式去寫每個(gè)組件
- 每一個(gè) Hook 是由 React 提供的函數(shù),它可以讓你在函數(shù)組件中“鉤”連到一些 React 特性。
Class?生命周期函數(shù)?this?
—— 可以通通say goodbye
Hooks 會(huì)使 React 變得臃腫嗎?
- hook 不包含任何破壞性的更改,它提供了使用我們已知的 React 特性的能力,如 state 、context 和生命周期。
- 將 減少編寫 React 應(yīng)用時(shí)需要考慮的概念數(shù)量。Hooks 可以使得你始終使用函數(shù),而不必在函數(shù)、類、高階組件和 reader 屬性之間不斷切換。
- 隨之而來,代碼量也會(huì)減少。??
為什么要?jiǎng)?chuàng)造 Hooks?
React 的使命:讓開發(fā)者更容易地構(gòu)建好的 UI。??
為了這個(gè)目標(biāo),React 團(tuán)隊(duì)做了哪些努力呢???
- 嘗試簡化復(fù)雜的東西(Suspense)
- 提升性能,嘗試讓 React 本身運(yùn)行的更快(Time Slicing)
- 使用開發(fā)者工具幫助開發(fā)者 debug(Profile)
React 還存在什么糟糕的地方?
- ?? 在組件間復(fù)用狀態(tài)邏輯很難——嵌套地獄
- mixin
- props
- 高階組件
- ?? 龐大的組件,邏輯分散雜亂無章,變得難以理解
- ?? 令人困惑的 Classes
So,有沒有什么方法來解決這三個(gè)問題呢?
Of course!React Hooks!??
內(nèi)置的 Hook

Hook API 介紹
請(qǐng)參照 demo 示例 進(jìn)行學(xué)習(xí)。
useState

傳入值作為state的默認(rèn)值,返回一個(gè)數(shù)組,數(shù)組的第一項(xiàng)是對(duì)應(yīng)的狀態(tài)(默認(rèn)值會(huì)賦予狀態(tài)),數(shù)組的第二項(xiàng)是更新狀態(tài)的函數(shù)。
const [state, setState] = useState(initialState);
setState(newState);
不同于 setState,useState 不會(huì)自動(dòng)合并更新的對(duì)象,需要我們自己用對(duì)象擴(kuò)展的形式合并,如果需要管理包含多個(gè)子值的狀態(tài)對(duì)象,則用 useReducer 更為適合。
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Lazy initial state:如果初始狀態(tài)是一個(gè)昂貴的計(jì)算的結(jié)果,你可以提供一個(gè)函數(shù)來進(jìn)行計(jì)算,它只會(huì)在初始渲染時(shí)執(zhí)行。
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
useEffect
可以在函數(shù)組件中執(zhí)行副作用操作,并且是在函數(shù)渲染DOM完成后執(zhí)行副作用操作,異步執(zhí)行。

可以認(rèn)為 useEffect 就是組合了 componentDidMount,componentDidUpdate,以及 componentWillUnmount(在 useEffect 的回調(diào)中),但是又有區(qū)別,useEffect 不會(huì)阻止瀏覽器更新屏幕。
useEffect(() => {
const subscription = props.source.subscribe();
// 清理步驟,本質(zhì)上就是消除副作用
return () => {
subscription.unsubscribe();
};
}, [props.source]);
【注意】:永遠(yuǎn)要對(duì) useEffect 的依賴誠實(shí),被依賴的參數(shù)一定要填上去,否則會(huì)產(chǎn)生非常難以察覺與修復(fù)的 BUG。 (利用 eslint-plugin-react-hooks 自動(dòng)訂正你的代碼中的依賴)
*useLayoutEffect
和
useEffect相同,都是用來執(zhí)行副作用,主要用來讀取DOM布局并觸發(fā)同步渲染,在瀏覽器執(zhí)行繪制之前,useLayoutEffect 內(nèi)部的更新計(jì)劃將被同步刷新,同步調(diào)用 effect。
官網(wǎng)建議還是盡可能的是使用標(biāo)準(zhǔn)的 useEffect 以避免阻塞視覺更新。
useCallback
可以認(rèn)為是對(duì)依賴項(xiàng)的監(jiān)聽,接受一個(gè)回調(diào)函數(shù)和依賴項(xiàng)數(shù)組,返回一個(gè)該回調(diào)函數(shù)的 memoized(記憶)版本,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新。
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
【用途】:解決將函數(shù)抽到 useEffect 外部的問題。
useMemo
主要用于渲染過程優(yōu)化,兩個(gè)參數(shù)依次是計(jì)算函數(shù)(通常是組件函數(shù))和依賴狀態(tài)列表,當(dāng)依賴的狀態(tài)發(fā)生改變時(shí),才會(huì)觸發(fā)計(jì)算函數(shù)的執(zhí)行。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
【好處】:相比于 React.memo,可以做到更細(xì)粒度的優(yōu)化渲染。如函數(shù) Child 整體可能用到了 A、B 兩個(gè) props,而渲染僅用到了 B,使用 React.memo() 方案時(shí),A 的變化會(huì)導(dǎo)致重渲染,而使用 useMemo 的方案則不會(huì)。
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)
useContext
useContext接受一個(gè) context 對(duì)象(由createContext創(chuàng)建)作為參數(shù),并返回Context.Consumer
- 創(chuàng)建一個(gè) Context
const stateContext = createContext('default');
- 在根節(jié)點(diǎn)使用
Store.Provider注入
function Parent() {
const [count, setCount] = useState(0);
return (
<Store.Provider value={{ count }}>
<Child />
</Store.Provider>
);
}
- 在子節(jié)點(diǎn)使用
useContext拿到注入的數(shù)據(jù)
const Child = memo((props) => {
const { count } = useContext(Store)
// ...
});
【問題】:當(dāng)函數(shù)多了,Provider 的 value 會(huì)變得很臃腫。
【解決】:使用 useReducer 解決這個(gè)問題。
useReducer
它和 Redux 的工作方式是一樣的。
useReducer的出現(xiàn)是useState的替代方案,能夠讓我們更好的管理狀態(tài)。
const [state, dispatch] = useReducer(reducer, initialArg, init);
【arg1】:reducer。
【arg2】:指定狀態(tài)的默認(rèn)值。
【arg3】:接受一個(gè)函數(shù)作為參數(shù),并把第二個(gè)參數(shù)當(dāng)作函數(shù)的參數(shù)執(zhí)行。主要作用是初始值的惰性求值,把一些對(duì)狀態(tài)的邏輯抽離出來,有利于重置state。
// 定義一個(gè)init函數(shù)
function init(initialCount) {
return [
...initialCount,
];
}
// useReducer使用
useReducer(reducer,[{id: Date.now(), value: "Hello react"}], init);
useRef
返回一個(gè)可變的 ref 對(duì)象,其 .current 屬性初始化為傳遞的參數(shù)(initialValue)。返回的對(duì)象將持續(xù)整個(gè)組件的生命周期。事實(shí)上 useRef 是一個(gè)非常有用的 API,許多情況下,我們需要保存一些改變的東西,它會(huì)派上大用場(chǎng)的。
const refContainer = useRef(initialValue);
*useImperativeHandle
可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值。
當(dāng)我們使用父組件把 ref 傳遞給子組件的時(shí)候,這個(gè)Hook 允許在子組件中把自定義實(shí)例附加到父組件傳過來的 ref 上,有利于父組件控制子組件。
useImperativeHandle(ref, createHandle, [deps])
*useDebugValue
可用于在 React DevTools 中顯示自定義鉤子的標(biāo)簽。
useDebugValue(value)
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}

?? Hooks 的使用規(guī)則
- 只能在函數(shù)最外層調(diào)用 Hook,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用。(??? Why?)
- 只在React函數(shù)組件中調(diào)用 Hooks(除非是自定義Hooks)。
Hooks 內(nèi)部是如何工作的:
// 偽代碼
let hooks, i;
function useState() {
i++;
if (hooks[i]) {
// 再次渲染時(shí)
return hooks[i];
}
// 第一次渲染
hooks.push(...);
}
// 準(zhǔn)備渲染
i = -1;
hooks = fiber.hooks || [];
// 調(diào)用組件
YourComponent();
// 緩存 Hooks 的狀態(tài)
fiber.hooks = hooks;
第一條規(guī)則可以確保每次組件呈現(xiàn)時(shí)調(diào)用鉤子的順序是相同的。
【注意】自定義 Hooks 從技術(shù)上講并不是 React 的特性。編寫自定義 Hooks 的可行性源自于 Hooks 的設(shè)計(jì)方式。
封裝 http hook
import { useState, useEffect } from 'react';
export const useHttp = (url, dependencies) => {
const [isLoading, setIsLoading] = useState(false);
const [fetchedData, setFetchedData] = useState(null);
useEffect(() => {
setIsLoading(true);
console.log('Sending Http request to URL: ' + url);
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch.');
}
return response.json();
})
.then(data => {
setIsLoading(false);
setFetchedData(data);
})
.catch(err => {
console.log(err);
setIsLoading(false);
});
}, dependencies);
return [isLoading, fetchedData];
};
能在 useEffect 中使用 async/await 嗎?
【結(jié)論】:不能在 useEffect 中返回一個(gè) promise。JavaScript 異步函數(shù)總是返回一個(gè)promise,而 useEffect 應(yīng)該只返回另一個(gè)函數(shù),該函數(shù)用于清除效果。也就是說,如果你在useEffect 中啟動(dòng) setInterval,你將返回一個(gè)函數(shù)(這里有一個(gè)閉包)來清除 interval。
? 參考
- React的今天和明天(第一部分)
- React的今天和明天(第二部分)
- 深入淺出 React Hooks
- 理解 React Hooks
- useEffect 完整指南
- 精讀《Function Component入門》
- 30分鐘精通React Hooks
- React Hooks 詳解 【近 1W 字】+ 項(xiàng)目實(shí)戰(zhàn)
- React16:Hooks總覽,擁抱函數(shù)式 (這大概是最全的React Hooks吧)
- 【原】Under the hood of React’s hooks system
- 【譯】React hooks:它不是一種魔法,只是一個(gè)數(shù)組——使用圖表揭秘提案規(guī)則
- React Hooks 原理
- 【原】Making Sense of React Hooks
- 【譯】理解React Hooks