這是 React 進階系列的第一篇文章,這個系列內容會包括一些 React 的新知識以及原理內容,有興趣的能夠持續關注。前端
注意:Hooks 在 React 16.8 版本中才正式發佈 react
以前若是咱們須要抽離一些重複的邏輯,就會選擇 HOC 或者 render props 的方式。可是經過這樣的方式去實現組件,你打開 React DevTools 就會發現組件被各類其餘組件包裹在裏面。這種方式首先提升了 debug 的難度,而且也很難實現共享狀態。git
可是經過 Hooks 的方式去抽離重複邏輯的話,一是不會增長組件的嵌套,二是能夠實現狀態的共享。github
若是咱們須要一個管理狀態的組件,那麼就必須使用 class 的方式去建立一個組件。可是一旦 class 組件變得複雜,那麼四散的代碼就很不容易維護。另外 class 組件經過 Babel 編譯出來的代碼也相比函數組件多得多。數組
Hooks 可以讓咱們經過函數組件的方式去管理狀態,而且也能將四散的業務邏輯寫成一個個 Hooks 便於複用以及維護。閉包
前面說了一些 Hooks 的好處,接下來咱們就進入正題,經過實現一個計數器來學習幾個經常使用的 Hooks。函數
useState
的用法很簡單,傳入一個初始 state
,返回一個 state
以及修改 state
的函數。學習
// useState 返回的 state 是個常量
// 每次組件從新渲染以後,當前 state 和以前的 state 都不相同
// 即便這個 state 是個對象
const [count, setCount] = useState(1)
複製代碼
setCount
用法是和 setState
同樣的,能夠傳入一個新的狀態或者函數。fetch
setCount(2)
setCount(prevCount => prevCount + 1)
複製代碼
useState
的用法是否是很簡單。假如如今須要咱們實現一個計數器,按照以前的方式只能經過 class 的方式去寫,可是如今咱們能夠經過函數組件 + Hooks 的方式去實現這個功能。ui
function Counter() {
const [count, setCount] = React.useState(0)
return (
<div> Count: {count} <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </div>
);
}
複製代碼
如今咱們的計時器需求又升級了,須要在組件更新之後打印出當前的計數,這時候咱們能夠經過 useEffect
來實現
function Counter() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log(count)
})
return (
<div> Count: {count} <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </div>
);
}
複製代碼
以上代碼當咱們改變計數的時候,就會打印出正確的計數,咱們其實基本能夠把 useEffect
當作是 componentDidUpdate
,它們的區別咱們能夠在下一個例子中看到。
另外 useEffect
還能夠返回一個函數,功能相似於 componentWillUnmount
function Counter() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log(count)
return () => console.log('clean', count)
})
// ...
}
複製代碼
當咱們每次更新計數時,都會先打印 clean
這行 log
如今咱們的需求再次升級了,須要咱們在計數器更新之後延時兩秒打印出計數。實現這個再簡單不過了,咱們改造下 useEffect
內部的代碼便可
React.useEffect(() => {
setTimeout(() => {
console.log(count)
}, 2000)
})
複製代碼
當咱們快速點擊按鈕後,能夠在兩秒延時之後看到正確的計數。可是若是咱們將這段代碼寫到 componentDidUpdate
中,事情就變得不同了。
componentDidUpdate() {
setTimeout(() => {
console.log(this.state.count)
}, 2000)
}
複製代碼
對於這段代碼來講,若是咱們快速點擊按鈕,你會在延時兩秒後看到打印出了相同的幾個計數。這是由於在 useEffect
中咱們經過閉包的方式每次都捕獲到了正確的計數。可是在 componentDidUpdate
中,經過 this.state.count
的方式只能拿到最新的狀態,由於這是一個對象。
固然若是你只想拿到最新的 state
的話,你可使用 useRef
來實現。
function Counter() {
const [count, setCount] = React.useState(0)
const ref = React.useRef(count)
React.useEffect(() => {
ref.current = count
setTimeout(() => {
console.log(ref.current)
}, 2000)
})
//...
}
複製代碼
useRef
能夠用來存儲任何會改變的值,解決了在函數組件上不能經過實例去存儲數據的問題。另外你還能夠 useRef
來訪問到改變以前的數據。
function Counter() {
const [count, setCount] = React.useState(0)
const ref = React.useRef()
React.useEffect(() => {
// 能夠在從新賦值以前判斷先前存儲的數據和當前數據的區別
ref.current = count
})
<div>
Count: {count}
PreCount: {ref.current}
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</div>
//...
}
複製代碼
如今需求再次升級,咱們須要經過接口來獲取初始計數,咱們經過 setTimeout
來模擬這個行爲。
function Counter() {
const [count, setCount] = React.useState();
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setLoading(true);
setTimeout(() => {
setCount(1);
setLoading(false);
}, 2000);
});
return (
<div> {!loading ? ( <div> Count: {count} <button onClick={() => setCount(pre => pre + 1)}>+</button> <button onClick={() => setCount(pre => pre - 1)}>-</button> </div> ) : ( <div>loading</div> )} </div>
);
}
複製代碼
若是你去執行這段代碼,會發現 useEffect
無限執行。這是由於在 useEffect
內部再次觸發了狀態更新,所以 useEffect
會再次執行。
解決這個問題咱們能夠經過 useEffect
的第二個參數解決
React.useEffect(() => {
setLoading(true);
setTimeout(() => {
setCount(1);
setLoading(false);
}, 2000);
}, []);
複製代碼
第二個參數傳入一個依賴數組,只有依賴的屬性變動了,纔會再次觸發 useEffect
的執行。在上述例子中,咱們傳入一個空數組就表明這個 useEffect
只會執行一次。
如今咱們的代碼有點醜陋了,能夠將請求的這部分代碼單獨抽離成一個函數,你可能會這樣寫
const fetch = () => {
setLoading(true);
setTimeout(() => {
setCount(1);
setLoading(false);
}, 2000);
}
React.useEffect(() => {
fetch()
}, [fetch]);
複製代碼
可是這段代碼出現的問題和一開始的是同樣的,仍是會無限執行。這是由於雖然你傳入了依賴,可是每次組件更新的時候 fetch
都會從新建立,所以 useEffect
認爲依賴已經更新了,因此再次執行回調。
解決這個問題咱們須要使用到一個新的 Hooks useCallback
。這個 Hooks 能夠生成一個不隨着組件更新而再次建立的 callback,接下來咱們經過這個 Hooks 再次改造下代碼
const fetch = React.useCallback(() => {
setLoading(true);
setTimeout(() => {
setCount(1);
setLoading(false);
}, 2000);
}, [])
React.useEffect(() => {
fetch()
}, [fetch]);
複製代碼
大功告成,咱們已經經過幾個 Hooks + 函數組件完美實現了本來須要 class 組件才能完成的事情。
經過幾個計數器的需求咱們學習了一些經常使用的 Hooks,接下來總結一下這部分的內容。
useEffect
內部有依賴外部的屬性,而且但願依賴屬性不改變就不重複執行 useEffect
的話,能夠傳入一個依賴數組做爲第二個參數另外我還封裝了幾個經常使用的 Hooks API,有興趣的能夠閱讀下代碼,倉庫中的代碼會持續更新。
咱們經過這篇文章學習瞭如何使用 Hooks,若是你還有什麼疑問歡迎在評論區與我互動。
我全部的系列文章都會在個人 Github 中最早更新,有興趣的能夠關注下。今年主要會着重寫如下三個專欄
最後,以爲內容有幫助能夠關注下個人公衆號 「前端真好玩」咯,會有不少好東西等着你。