React 16.8 正式推出了 Hooks
的特性,這期間也從一些方面了解嘗試過 Hooks,謹參考多篇文章來談談 Hooks
。
Hooks
可以讓你不需要通過類來使用React
的state
以及其它特性。你也可以自定義自己的Hooks
來在組件之間共享可復用的狀態邏輯 。
起源#
當下,組件以及自頂向下的數據流可以幫助我們將大型 UI 組織成小型、獨立可復用的部分。但是很多時候,我們的邏輯是帶有狀態的,因此不能進一步打破複雜的組件,也不能拆分出一個函數或是另一個組件。這些情況包括動畫、表單控件、連接外部數據源 等等是非常普遍的 。
雖然現在對於 React
應用我們已經有很多種方式來復用這些邏輯,比如寫簡單的函數來調用,或是通過組件(函數或類的形式),但是對於非可視化的邏輯它就不是那麼方便了,所以我們又提出了一些複雜的模式,比如 render props
和 高階組件 。
如果 React
只有一種通用的方式來復用邏輯是不是會更簡單呢?
對於代碼復用,函數似乎是一種完美的機制,但是函數並不能包含本地 React
的狀態,我們不能簡單從類組件提取一些行為,比如 依據 window 的大小來更新狀態 或是 讓一個值隨時間變化。我們通常都需要重構這部分代碼或者是採用抽象的模式比如觀察者。這些都破壞了 React
的簡潔性 。
基於以上的思考,React
開發團隊提出了 Hooks
。它可以讓我們通過函數來使用 React
的特性(比如狀態),並且 React
官方也提供了一些內置的與 React
構建模塊相關的 Hooks
: 狀態、生命週期、上下文,因為Hooks
就是普通的 JavaScript
函數,因此我們也可以自定義 Hooks
來使用 。
Demo#
讓我們看一個例子:隨 window 大小的調整來更新 React
的狀態 。
import { useState, useEffect } from 'react'
function useWindowWidth () {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
上述代碼用到了 React
自帶最常用的兩個 Hooks
: useState
和 useEffect
:
React
基礎的Hooks
還有一個 useContext 。
同時我們還有一個基於內置 Hooks
的自定義 Hooks
: useWindowWidth
。
我們來看看 useWindowWidth
做了什麼:傳入 window 的初始大小,返回狀態值 width
和可以更新 width
的 setWidth
,在 useEffect
內部我們定義了一個通過 setWidth
設置 window 大小的 handleResize
函數,並且監聽了 window resize 事件來更新 window 的大小,最終返回取消監聽的函數 。
這就是一個可以隨 window 大小來調整佈局的 Hooks
。
試想一下如果我們用類組件的方式來實現會是怎樣?
class setWidth extends React.Component {
constructor (props) {
super(props)
this.state = {
width: window.innerWidth
}
}
handleResize = () => this.setState({ width: window.innerWidth })
componentDidMount () {
window.addEventListener('resize', handleResize)
}
componentWillUnmount () {
window.removeEventListener('resize', handleResize)
}
render () {
return <SomeUI />
}
}
對比之下我們很容易感覺到類組件的寫法我們要設置 state
、結合繁瑣的生命週期以及必須要渲染 UI (當然可以是 null
),並且更大的問題是如果我們需要復用這個組件同時 <SomeUI />
又不能相同,我們往往就會採用高階組件的寫法,這又引入了抽象的模式。所以對比下來, Hooks
讓開發者可以有更小的學習成本,同時也更貼合 React
簡潔的理念 。
這裡有一個類組件與
Hooks
更直觀的對比的視頻 : https://twitter.com/i/status/1056960391543062528 。
同時我們也能感受到自定義 Hooks
的魅力,隨著 Hooks
的正式提出,之後將會有越來越多的 Hooks
npm 包來更好的幫助開發者 。
這裡有一個各類自定義
Hooks
實現的網站 : https://usehooks.com/ 。
深入#
作為一個 React
開發者,Hooks
剛提出時我是感到很驚艷的,React
從一開始的定位就是為了更好地構建 UI,這就帶給了我兩個疑問 :
Hooks
的出現代表了什麼?Hooks
是怎麼實現的?
在 對 React Hooks 的一些思考 中有一句話 : "有狀態的組件沒有渲染,有渲染的組件沒有狀態"。React
團隊想要推行的正確理念就是 "狀態與 UI 分開"。而這其實也正與 React Hooks
的特性相合,我們來看一下使用 Hooks
的 兩個規則 :
- 只在函數頂部調用
Hooks
。 - 只在
React
函數中調用Hooks
。
Hooks
必須集中在函數頂部來寫,這其實很容易養成書寫無狀態 UI 組件的習慣,因此也會更容易踐行 "狀態與 UI 分開" 這個理念。個人覺得這也是 React
團隊將 Hooks
視為 狀態共享問題 長久以來最完美的解決方案的原因之一 。
那麼為什麼 Hooks
必須在函數頂部被調用?是為了防止我們用條件判斷來包裹 Hooks
。為什麼不能用條件判斷包裹就涉及到 Hooks
的實現問題,簡單解釋一下就是 : Hooks
並不是通過 Proxy
或者 getters
實現的,而是通過類似數組的實現方式,因此 Hooks
的正常調用與下標順序有關,每次 useState
都會改變下標,如果在條件判斷中使用了 Hooks
,可能就會影響下標造成 Hooks
調用出錯,因此官方也建議在 Hooks
內部進行條件判斷是更正確的做法 。
具體原因可以見 官方文檔 。
Hooks
的原理可以見 React hooks: not magic, just arrays 。如果你想更深入地了解
Hooks
體系,可以見 Under the hood of React’s hooks system 。
可能有人會感覺到 Hooks
是帶有一些限制的,但是更正確的理解是我們應該遵循 Hooks
的約定,它也是 React
官方第一次將 "約定優先" 的理念引入 React
框架當中,有了限制,但也有了更好的便利性 。
在
Next.js
和Umi
中都有 "約定路由" 的功能,這大大降低了路由配置的複雜度,而Hooks
就像代碼級別的約定,大大降低了代碼的複雜度,因此我們可以考慮這是不是未來很重要的一個趨勢 。
更多思考#
Hooks
的設計並不是綁定在 React
上的,現在也已經有了 Vue
、Web Components
甚至是原生 JavaScript
函數的實現(實驗性的 API 設計)。
你可能會有這樣的疑問 : Vue
需要 Hooks
嗎? Vue
確實沒有像 React
類組件一樣的問題,如果是需要復用邏輯的話,Mixins
也可以解決。但是從本質上看的話,Vue
的確需要 Hooks
來解決 狀態共享 的問題,相比於 Mixins
,Hooks
可以幫助 Vue
解決兩個主要問題 :
- 實現狀態的共享 。
- 更清晰地展示邏輯的來向 。
目前 Vue
團隊也計劃在 Vue
3.0 中集成 Hooks
,但是會偏離 React
的 API 而貼合 Vue
的思想去設計,並且很有可能會成為 Mixins
的替代品,因此也是十分值得探索的 。
Vue 實驗性質的實現 : https://github.com/yyx990803/vue-hooks
參考 :