React Hooks
在 React@16.8 版本正式釋出。我最近在一兩個公司的內部專案中也開始用起來嚐嚐鮮。
不瞭解 Hooks
的同學先擼一遍文件。本文不對 Hooks
做詳細介紹,只闡述一種使用 Hooks
的思路。
一般我們寫 React
如果不是特別大的應用,前後端資料互動邏輯不復雜,這樣我們直接按照正常流程寫元件就能滿足簡單的業務場景。隨著業務場景的深入漸漸地我們元件變大變多,元件與元件之間的資料通訊(也就是狀態管理,不過我更願意稱之為資料通訊)變得越來越複雜。所以我們引入了 Redux
來維護我們日趨複雜的資料通訊。
思路
秉承著這種思路,我在開發應用的時候是沒有一開始就引入 Redux
,因為一開始我覺得就是個小專案。隨著深入專案的開發,其實並沒有這麼簡單。
但是也沒有太複雜,這時我把眼光放到了 Context
身上。Context
本意是上下文,它提供一個 Provider
和一個 Consumer
,這裡和 Angular
裡的 Provider
有點類似,也就是生產者/消費者模式,在某個頂層提供一個 Provider
,下面的子元素通過 Consumer
來消費 Provider
裡的資料和方法。
通過這個概念,我們把不同層級裡的元件共享同一個頂層 Provider
,並且元件內部使用 Consumer
來消費共享資料。
當我們能共享資料後,還剩一個問題就是如何更改 Provider
裡的資料呢?答案是:useReducer
。
好,有了思路,我們來實現一下。
例項
假設我們在某一個層級有個需要共享狀態的父級元素,我們稱它為 Parent,在 Parent 下面不同層級之間有兩個 Child。這裡為了簡單舉例假設兩個 Child 內都是共同的邏輯。
import React from "react"
function Parent() {
const colors = ['red', 'blue']
return (
<>
<Child1 color={colors[0]} />
<Child2 color={colors[1]} />
</>
)
}
function Child1(props) {
return (
<div style={{ background: props.color }}>I am {props.color}</div>
)
}
function Child2(props) {
return (
<div style={{ background: props.color }}>I am {props.color}</div>
)
}
複製程式碼
我們現在已經構造出了這樣的一個上下級結構,目前通過給子元件傳遞屬性,可以實現父元件的狀態共享。但是這裡如果層級加深,我們傳遞屬性的層級也要跟著加深。這樣顯然不是我們想要的。
現在我們來引入 Context
。
首先通過 createContext
方法初始化我們需要的 Context
。
import React, { createContext } from "react"
const Context = createContext({
colors: ['red', 'blue']
})
複製程式碼
然後我們在 Parent 和 Child 裡引入剛才的 Context,並且使用 useContext
拿到共享的資料:
import React, { useContext, createContext } from "react"
const Context = createContext({
colors: []
})
function Parent() {
const initState = {
colors: ["red", "blue"]
}
return (
<Context.Provider value={{ colors: initState.colors }}>
<>
{/* 假裝這些地方有著不同的層級 */}
<Child1 />
<Child2 />
</>
</Context.Provider>
)
}
function Child1(props) {
const { colors } = useContext(Context);
return (
<div style={{ background: colors[0] }}>
I am {colors[0]}
</div>
)
}
// 省略 Child2 程式碼,同 Child1 一致
複製程式碼
現在只是拿到了資料並且進行渲染,再進一步,通過點選元素,修改顏色。在這裡我們就需要用 useReducer
來模擬觸發改變。
首先我們需要一個 reducer 來處理觸發的改變。
function reducer(state, action) {
const { colors } = action
if (action.type === "CHANGE_COLOR") {
return { colors: colors }
} else {
throw new Error()
}
}
複製程式碼
這裡我簡化了 action 的處理,當然你也可以進行擴充套件。
現在,我們給 Provider
加上提供改變的方法 dispatch。
import React, { useContext, createContext } from "react"
const Context = createContext({
colors: []
})
function Parent() {
const initState = {
colors: ["red", "blue"]
}
const [state, dispatch] = useReducer(reducer, initState)
return (
<Context.Provider value={{ colors: state.colors, dispatch: dispatch }}>
<>
{/* 假裝這些地方有著不同的層級 */}
<Child1 />
<Child2 />
</>
</Context.Provider>
)
}
複製程式碼
然後子元件觸發改變:
function Child1(props) {
const { colors, dispatch } = useContext(Context)
return (
<div
style={{ background: colors[0] }}
onClick={() =>
dispatch({
type: "CHANGE_COLOR",
colors: ["yellow", "blue"]
})
}
>
I am {colors[0]}
</div>
)
}
// 省略 Child2 程式碼,同 Child1 一致
複製程式碼
至此,這個小型的狀態共享便完成了。這便是我們擺脫 Redux
之後實現的狀態共享思路的雛形。完整的程式碼及例子見 tiny redux。
進階
在實際的應用中,我們的業務場景會更復雜,比如我們的資料是動態獲取的。
這種情況下你可以把 Provider
抽出來,當 Parent 資料回來之後再初始化 Context。
function Provider (props) {
const { colors } = props
const initState = {
colors,
}
const [state, dispatch] = useReducer(reducer, initState)
return (
<Context.Provider value={{ colors: state.colors, dispatch: dispatch }}>
{props.children}
</Context.Provider>
)
}
複製程式碼
然後我們在 Parent 中做非同步操作,並把動態資料傳給 Provider :
import React, { useState, useEffect } from "react"
function Parent (props) {
const [data, setData] = useState()
const [url, setUrl] = useState('https://example.com')
useEffect(() => {
fetch(url).then(res => setData(data))
}, [url])
if (!data) return <div>Loading ...</div>
return (
<Provider colors={data}>
<>
{/* 假裝這些地方有著不同的層級 */}
<Child1 />
<Child2 />
</>
</Provider>
)
}
複製程式碼
結語
這樣小型的狀態管理機制你甚至可以放在某個元件裡,而不用放到如 Redux
全域性的環境中去。這樣使得我們寫的應用更加靈活,而不是一味的往 store
裡丟狀態。當然你也可以寫一個 AppProvider 來管理全域性的狀態,React Hooks
+ Context
給了我們這樣的便利。
Hooks 真香!