探索React Hooks:原來它們是這樣誕生的!
這篇文章《Where Did Hooks Come From?》主要討論了 React Hooks 的來源和背景。在引入 Hooks 之前,React 類需要擴展 React.Component 或 React.PureComponent,而 React 本身沒有提供共享代碼的 API。因此,React 社區開發人員創建了兩種有效共享組件代碼的模式,分別是高階組件(Higher Order Components,簡稱 HOC)和 Render Props。這些模式在一定程度上解決了代碼重用的問題,但仍然存在一些局限性。為了更好地解決這些問題,React Hooks 被引入,為開發者提供了一種更簡潔、易于理解的方式來共享和重用組件的邏輯。
下面是正文~~
Hooks 是用于在組件之間共享通用邏輯的。明確地說,我們所說的“邏輯”并不是指組件的 UI 部分(JSX)。我們談論的是組件中 JSX 之前的所有內容。在基于類的組件中,我們會說它在生命周期方法和自定義方法中。在功能組件中,它只是 JSX 之上的東西。
在某種程度上,Hooks 的故事與 React 及其先前用于共享代碼的 API 的故事密切相關。所以請耐心聽我從頭說起...
2013:第一個React API:
React 開發者不喜歡 mixins,這是共享邏輯的第一個 API。
最初,React 有一種在組件之間共享通用邏輯的方法,稱為 mixins。這是在 JavaScript 擁有類之前的 React 早期。這些偽類看起來的組件允許“混入”可共享的邏輯。當時,mixins 被指責為社區開始流行的一些反模式的根本原因。因此,當 React 在 2016 年獲得真正的類時,大多數 React 開發人員為 mixins 的 API 消失而歡呼。
2016:類組件
在JavaScript在ES2015(ES6)中獲得類之后,React很快跟進了今天仍然可以使用的類組件。但是,如果你對React較為陌生,可能會想知道為什么普遍認為應該在React中完全避免使用類組件?
主要原因是共享邏輯困難。當我們失去了 mixins 時,我們也失去了一種原始的共享代碼方式。我們可以通過創建一個新組件來共享/重用 UI,以共享 JSX,但是沒有內置方法可以共享生命周期方法,例如 componentDidMount 、 componentDidUpdate 和 componentWillUnmount 。 這些特定方法是我們可能希望管理組件副作用的地方。因此,如果您用某個副作用編寫 ComponentOne ,我們將不得不將該邏輯復制到 ComponentTwo ,從而使邏輯無法以一種只編寫一次的方式抽象。
我們不能用繼承嗎?
class ComponentOne extends SharableStuff {
// ...
}
class ComponentTwo extends SharableStuff {
// ...
}
不,React 不允許我們編寫從其他組件繼承的組件。而且,即使 React 允許你這樣做,你將如何將多個邏輯體共享到 ComponentOne ?不允許多重繼承,所以這不起作用:
class ComponentOne extends SharableStuffA extends SharableStuffB {
// ...
}
React類必須擴展 React.Component 或 React.PureComponent ,并且React本身沒有共享代碼的API。
社區雖然很聰明。React 開發人員創建了兩種模式,有效地在組件之間共享代碼,這兩種模式被稱為高階組件(Hoc)和 Render Props。
無狀態函數組件
在同一時期,React 團隊宣布了一種使用函數而不是類來創建組件的新方法。當時的主要想法是擁有一個僅接受屬性并可以返回 JSX 的組件。沒有狀態或使用類似于類生命周期方法的 React API 的能力。
我們稱之為無狀態函數組件,因為它們也不能有狀態。
不久之后,React 團隊告訴我們不要這樣稱呼它們。我們應該稱之為函數組件,因為...他們有計劃??
2018 Hooks
從本質上講,Hooks 只是我們可以從函數組件中調用的函數。我們可以使用內置的鉤子并編寫自己的:
- 內置鉤子:這些API(如 useState() )使功能組件能夠“掛鉤”到React的所有功能。
- 自定義鉤子:這些只是我們編寫的實現內置鉤子的函數。自定義鉤子的一般概念是為任何想要使用它的組件創建可重用的邏輯。
React 有 useState() ,因此函數組件可以擁有與類狀態類似的自己的本地狀態。但是,如果刷新頁面,所有本地狀態都會重置(就像任何其他 JS 變量一樣)。因此,我們可以創建自己的 useLocalStorageState() ,它可能的工作方式與 useState() 完全相同,但還將狀態保持到 localStorage ,以便在刷新后恢復值。
下面是一個使用自定義鉤子共享數據獲取邏輯的示例。你不必完全了解如何使用 useState 和 useEffect ,只需要了解它們為組件執行一些邏輯,我想共享它。如果另一個組件也想根據 productId 獲取產品,那么需要重新編寫下面高亮的代碼:
這里是相同的邏輯移至自定義鉤子。現在任何組件都可以使用 useFetchProduct 鉤子:
// Custom Hook
function useFetchProduct(productId) {
const [product, setProduct] = useState(null)
useEffect(() => {
fetchProduct(productId).then((product) => setProduct(product))
}, [productId])
return product
}
function BrowseProducts({ productId }) {
const product = useFetchProduct(productId)
// return <div>...</div>
}
這是一個過于簡化的例子,上面的 useEffect 代碼是不完整的。如果你想要一個獲取數據的自定義 Hook,推薦來自 React Query 的自定義鉤子,名為 useQuery() 。
如今,如果你愿意,你仍然可以使用類。如果你覺得它們更容易使用,那完全取決于你。然而,在類之間共享邏輯時,你將會遇到問題。即使你可以接受這些問題,并且你不覺得高階組件(HOC)和 Render Props 混亂,與過去五年開始學習 React 的其他開發者合作或者組隊工作時,你可能會發現困難。
他們在 Hooks 被當作 React 主要方法教授時開始接觸 React。他們可能不了解類組件的“進退維谷”,如何處理這種奇怪的作用域問題,以及何時以及如何使用 HOC 或 Render Props。此外,React 生態系統中絕大多數第三方庫已經放棄了 HOC 和 Render Props,轉而采用了 Hooks。因此,你將無法輕松地使用它們的工具,因為 Hooks 僅適用于函數式組件。
我的一些朋友已經使用 React 很長時間了,他們記得這些討論和權衡。但是我注意到(至少在 Twitter 上),歷史正在重演。有一整代新的 React 開發者不知道這個背景故事,也不知道我們為什么要有 Hooks。我承認,Hooks 的某些部分比類更難,比如我們可能需要記憶化( useMemo 和 useCallback ),但這是一種權衡。你可以選擇使用帶有 HoC 和 Render Props 的類(也不容易),或者使用具有輕松共享代碼能力的 Hooks,但需要理解記憶化的復雜性。