原文地址javascript
對於想要跳過文章直接看結果的人,我已經把我寫的內容製做成了一個庫:use-simple-state,無任何依賴(除了依賴 react ),只有3kb,至關輕量。html
const state = {};
export const getState = () => state;
export const setState = nextState => {
state = nextState;
};
複製代碼
一個全局可用的用於展示咱們應用狀態的值:state;java
讀取狀態的能力:getState;react
更新狀態的能力:setState。git
context
API,它可讓數據在整個 react 應用可用。context
有兩部分:provider (生產者) 和 consumer (消費者),provider 就像它的名字所說的那樣,給應用提供 context
(data 數據),消費者意指當咱們讀取 context
時,咱們就是消費者。context
:若是說 props
是顯式的傳送數據,那麼 context
就是隱式的傳送數據。如今咱們知道了須要哪些工具,如今只要把它們合在一塊兒就能夠了。咱們準備建立一個上下文環境來存放全局狀態,而後把它的 provider 包裹在一個有狀態組件中,而後用 provider 來管理狀態。github
首先,咱們使用 React.createContext
來建立上下文,它能夠給咱們提供 provider 和 consumer。web
import { createContext } from 'react';
const { Provider, Consumer } = createContext();
複製代碼
接下來咱們須要用有狀態組件包裹咱們的 provider,利用它進行應用狀態的管理。咱們也應該把 consumer 導出爲一個更加準確的名稱。express
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
export const StateConsumer = Consumer;
export class StateProvider extends Component {
static defaultProps = {
state: {}
};
state = this.props.state;
render () {
return (
<Provider value={this.state}> {this.props.children} </Provider>
);
}
}
複製代碼
在上面的例子中,StateProvider
是接收一個 state
來做爲初始狀態的組件,而且使組件樹中當前組件下面的任何組件均可以訪問到這個屬性。若是沒有提供 state
,默認會有一個空對象代替。redux
用咱們的 StateProvider
包裹住根組件:api
import { StateProvider } from './stateContext';
import MyApp from './MyApp';
const initialState = {
count: 0
};
export default function Root () {
return (
<StateProvider state={initialState}> <MyApp /> </StateProvider>
);
}
複製代碼
在咱們完成上述代碼以後,就能夠做爲一個消費者從 MyApp
的任何地方得到應用的狀態。在這裏咱們會初始化咱們的狀態爲一個有一個 count
屬性的對象,因此不管何時咱們想要當即獲取應用的狀態,咱們就能夠從這裏得到。
消費者使用 render 屬性 來傳遞上下文數據,咱們能夠經過下面的一個函數做爲 StateConsumer
的子組件的例子來查看。state
參數傳遞給函數用以展示咱們應用的當前狀態,做爲咱們的初始狀態,state.count
爲 0.
import { StateConsumer } from './stateContext';
export default function SomeCount () {
return (
<StateConsumer> {state => ( <p> Count: {state.count} </p> )} </StateConsumer>
);
}
複製代碼
關於 StateConsumer
咱們須要知道的很重要一點是在上下文中它會自動訂閱狀態的改變,所以當咱們的狀態改變後會從新渲染以顯示更新。這就是消費者的默認行爲,咱們暫時還沒作可以用到這個特性的功能。
目前爲止咱們已經能夠讀取應用的狀態,以及在狀態改變時自動更新。如今咱們須要一種更新狀態的方法,爲了作到這一點咱們僅僅只須要在 StateProvider
裏面更新狀態。
你以前可能已經注意到了,咱們給 StateProvider
傳遞了一個 state
屬性,也就是以後會傳遞給組件的 state
屬性。咱們將使用 react 內置的 this.setState
來更新:
export class StateProvider extends Component {
static defaultProps = {
state: {}
};
state = this.props.state;
render () {
return (
<Provider value={{ state: this.state, setState: this.setState.bind(this) }}> {this.props.children} </Provider>
);
}
複製代碼
繼續保持簡單的風格,咱們只給上下文傳遞 this.setState
,這意味着咱們須要稍微改變咱們的上下文傳值,不僅是傳遞 this.state
,咱們如今同時傳遞 state
和 setState
。
當咱們用 StateConsumer
時能夠用解構賦值獲取 state
和 setState
,而後咱們就能夠讀寫咱們的狀態對象了:
export default function SomeCount () {
return (
<StateConsumer> {({ state, setState }) => ( <> <p> Count: {state.count} </p> <button onClick={() => setState({ count: state.count + 1 })}> + 1 </button> <button onClick={() => setState({ count: state.count - 1 })}> - 1 </button> </> )} </StateConsumer>
);
}
複製代碼
有一點要注意的是因爲咱們傳遞了 react 內置的 this.setState
做爲咱們的 setState
方法,新增的屬性將會和已有的狀態合併。這意味着若是咱們有 count
之外的第二個屬性,它也會被自動保存。
如今咱們的做品已經能夠用在真實項目中了(儘管還不是頗有效率)。對 react 開發者來講應該會以爲 API 很熟悉,因爲使用了內置的工具所以咱們沒有引用任何新的依賴項。假如以前以爲狀態管理有點神奇,但願如今咱們多少可以瞭解它內部的結構。
熟悉 redux 的人可能會注意到咱們的解決方案缺乏一些特性:
- 沒有內置的處理反作用的方法,你須要經過 redux 中間件來作這件事
- 咱們的
setState
依賴 react 默認的this.setState
來處理咱們的狀態更新邏輯,當使用內聯方式更新複雜狀態時將可能引起混亂,同時也沒有內置的方法來複用狀態更新邏輯,也就是 redux reducer 提供的功能。- 也沒有辦法處理異步的操做,一般由 redux thunk 或者 redux saga等庫來提供解決辦法。
- 最關鍵的是,咱們沒辦法讓消費者只訂閱部分狀態,這意味着只要狀態的任何部分更新都會讓每一個消費者更新。
爲了解決這些問題,咱們模仿 redux 來應用咱們本身的 actions,reducers,和 middleware。咱們也會爲異步 actions 增長內在支持。以後咱們將會讓消費者只監聽狀態內的子狀態的改變。最後咱們來看看如何重構咱們的代碼以使用新的 hooks api。
免責聲明:接下來的內容只是爲了讓你更容易理解文章,我強烈推薦你閱讀 redux 官方完整的介紹。
若是你已經很是瞭解 redux,那你能夠跳過這部分。
如你所見,這就是單向數據流,從咱們的 reducers 接收到狀態改變以後,觸發 actions,數據不會回傳,也不會在應用的不一樣部分來回流動。
說的更詳細一點:
首先,咱們觸發一個描述改變狀態的 action,例如 dispatch({ type: INCREMENT_BY_ONE })
來加1,同咱們以前不一樣,以前咱們是經過 setState({ count: count + 1 })
來直接改變狀態。
action 隨後進入咱們的中間件,redux 中間件是可選的,用於處理 action 反作用,並將結果返回給 action,例如,假如在 action 到達 reducer 以前觸發一個 SIGN_OUT
的 action 用於從本地存儲裏刪除全部用戶數據。若是你熟悉的話,這有些相似於 express 中間件的概念。
最後,咱們的 action 到達了接收它的 reducer,伴隨而來的還有數據,而後利用它和已有的狀態合併生成一個新的狀態。讓咱們觸發一個叫作 ADD
的 action,同時把咱們想發送過去增長到狀態的值也發送過去(叫作 payload )。咱們的 reducer 會查找叫作 ADD
的 action,當它發現後就會將 payload 裏面的值和咱們現有的狀態裏的值加到一塊兒並返回新的狀態。
reducer 的函數以下所示:
(state, action) => nextState
複製代碼
reducer 應當只是處理 state 和 action ,雖然簡單卻很強大。關鍵是要知道 reducer 應當永遠是純函數,這樣它們的結果就永遠是肯定的。
如今咱們已通過了幾個 redux app 的關鍵部分,咱們須要修改 app 來模仿一些相似的行爲。首先,咱們須要一些 actions 和觸發它們的方法。
咱們的 action 會使用 action 建立器來建立,它們其實就是能生成 action 的簡單函數,action 建立器使得測試,複用,傳遞 payload 數據更加簡單,咱們也會建立一些 action type,其實就是字符串常量,爲了讓他們能夠被 reducer 複用,所以咱們把它存儲到變量裏:
// Action types
const ADD_ONE = 'ADD_ONE';
const ADD_N = 'ADD_N';
// Actions
export const addOne = () => ({ type: ADD_ONE });
export const addN = amount => ({ type: ADD_N, payload: amount });
複製代碼
如今咱們來作一個 dispatch
的佔位符函數,咱們的佔位符只是一個空函數,將會被用於替換上下文中的 setState
函數,咱們一會再回到這兒,由於咱們還沒作接收 action 的 reducer 呢。
export class Provider extends React.PureComponent {
static defaultProps = {
state: {}
};
state = this.props.state;
_dispatch = action => {};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 複製代碼
如今咱們已經有了一些 action,只須要一些 reducer 來接收就行了。回到以前的 reducer 函數標記,它只是一個關於 action 和 state 的純函數:
(state, action) => nextState
複製代碼
知道了這個,咱們只須要傳遞組件的狀態,而後在 reducer 裏觸發 action。對 reducer 來講,咱們只想要一個對應上面標記的函數數組。咱們之因此使用一個數組是由於可使用數組的 Array.reduce
方法來迭代數組,最終生成咱們的新狀態:
export class Provider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: []
};
state = this.props.state;
_dispatch = action => {
const { reducers } = this.props;
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 複製代碼
如你所見,咱們所作的就是使用 reducer 來計算並得到新狀態,而後就像以前所作的,咱們調用 this.setState
來更新 StateProvider
組件的狀態。
如今咱們只須要一個實際的 reducer:
function countReducer ({ count, ...state }, { type, payload }) {
switch (type) {
case ADD_N:
return { ...state, count: count + payload };
case ADD_ONE:
return { ...state, count: count + 1 };
}
}
複製代碼
咱們的 reducer 只是檢查傳入的 action.type
,而後假如匹配到以後將會更新相對應的狀態,不然就會在通過 switch
判斷語句以後返回函數默認的 undefined
。咱們的 reducer 和 redux 的 reducer 的一個重要的區別在當咱們不想更新狀態時,通常狀況下咱們會由於未匹配到 action type 而返回一個falsy 值,而 redux 則會返回未變化的狀態。
而後把咱們的 reducer 傳進 StateProvider
:
export default function Root () {
return (
<StateProvider state={initialState} reducers={[countReducer]}> <MyApp /> </StateProvider>
);
}
複製代碼
如今咱們終於能夠觸發一些 action,而後就會觀察到相對應的狀態更新:
export default function SomeCount () {
return (
<StateConsumer> {({ state, dispatch }) => ( <> <p> Count: {state.count} </p> <button onClick={() => dispatch(addOne())}> + 1 </button> <button onClick={() => dispatch(addN(5))}> + 5 </button> <button onClick={() => dispatch(addN(10))}> + 10 </button> </> )} </StateConsumer>
);
複製代碼
如今咱們的做品已經跟 redux 比較像了,只須要再增長一個處理反作用的方法就能夠。爲了達到這個目的,咱們須要容許用戶傳遞中間件函數,這樣當 action 被觸發時就會被調用了。
咱們也想讓中間件函數幫助咱們處理狀態更新,所以假如返回的 null
就不會被 action 傳遞給 reducer。redux 的處理稍微不一樣,在 redux 中間件你須要手動傳遞 action 到下一個緊鄰的中間件,假如沒有使用 redux 的 next
函數來傳遞,action 將不會到達 reducer,並且狀態也不會更新。
如今讓咱們寫一個簡單的中間件,咱們想經過它來尋找 ADD_N
action,若是它找到了那就應當把 payload
和當前狀態裏面的 count
加和並輸出,可是阻止實際狀態的更新。
function countMiddleware ({ type, payload }, { count }) {
if (type === ADD_N) {
console.log(`${payload} + ${count} = ${payload + count}`);
return null;
}
}
複製代碼
跟咱們的 reducer 相似,咱們會將中間件用數組傳進咱們的 StateProvider
。
export default function Root () {
return (
<StateProvider state={initialState} reducers={[countReducer]} middleware={[countMiddleware]} > <MyApp /> </StateProvider>
);
}
複製代碼
最終咱們會調用全部全部中間件,而後根據返回的結果決定是否應當阻止更新。因爲咱們傳進了一個數組,然而咱們須要的是一個單個值,所以咱們準備使用 Array.reduce
來得到咱們的值。跟 reducer 相似,咱們也會迭代數組依次調用每一個函數,而後將結果賦值給一個變量 continueUpdate
。
因爲中間件被認爲是一個高級特性,所以咱們不想它變成強制性的,所以若是沒有在StateProvider
裏面找到 middleware
屬性,咱們會將 continueUpdate
置爲默認的 undefined
。咱們也會增長一個 middleware
數組來做默認屬性,這樣的話 middleware.reduce
就不會由於沒傳東西而拋出錯誤。
export class StateProvider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: [],
middleware: []
};
state = this.props.state;
_dispatch = action => {
const { reducers, middleware } = this.props;
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, this.state) : result;
}, undefined);
if (continueUpdate !== null) {
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
}
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 複製代碼
如你所見在第13行,咱們會查看中間件函數的返回值。若是返回 null
咱們就會跳過剩下的中間件函數,continueUpdate
將爲 null
,意味着咱們會中斷更新。
由於咱們想讓咱們的狀態管理器對真實生產環境有用,因此咱們須要增長對異步 action 的支持,這意味着咱們將能夠處理像網絡請求相似案例的通用任務。咱們借鑑下 Redux Thunk ,由於它的 API 很簡單,直觀並且有效。
咱們所要作的就是檢查是否有爲被調用的函數被傳遞到 dispatch,若是找到的話咱們就會在傳遞 dispatch
和 state
時調用函數,這樣就能夠給用戶所寫的異步 action 執行的機會。拿這個受權 action 做爲例子來看下:
const logIn = (email, password) => async dispatch => {
dispatch({ type: 'LOG_IN_REQUEST' });
try {
const user = api.authenticateUser(email, password);
dispatch({ type: 'LOG_IN_SUCCESS', payload: user });
catch (error) {
dispatch({ type: 'LOG_IN_ERROR', payload: error });
}
};
複製代碼
在上面的例子中咱們寫了一個叫作 logIn
的 action 建立器,不是返回一個對象,它返回一個接收 dispatch
的函數,這可讓用戶在一個異步 API 請求的前面和後面觸發異步 action,根據 API 不一樣的返回結果觸發不一樣的 action,這裏咱們在發生錯誤時發送一個錯誤 action。
作到這一點只須要在 StateProvider
裏的 _dispatch
方法裏檢查 action
的類型是否是 function
:
export class StateProvider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: [],
middleware: []
};
state = this.props.state;
_dispatch = action => {
if (typeof action === 'function') {
return action(this._dispatch, this.state);
}
const { reducers, middleware } = this.props;
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, this.state) : result;
}, undefined);
if (continueUpdate !== null) {
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
}
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 複製代碼
這裏須要注意兩點:咱們調用 action
爲函數,傳入 this.state
,這樣用戶能夠訪問異步 action 中已有的狀態,咱們也會返回函數調用的結果,容許開發者從他們的異步 action 中得到一個返回值從而開啓更多的可能性,例如從 dispatch
觸發的 promise 鏈。
Redux 的一個常常被忽視的必要特性是它能在必須時纔會對組件從新渲染(或者更準確的說是 React-Redux — react 跟 redux 的綁定)。爲了作到這一點,它使用了 connect
高階組件,它提供了一個映射函數 — mapStateToProps
— 僅僅只在關聯的 mapStateToProps
的輸出改變時(只映射從如今開始的狀態)纔會觸發組件的從新渲染。若是不這樣的話,那麼每次狀態更新都會讓組件使用 connect 來訂閱存儲改變而後從新渲染。
想一想咱們須要作的,咱們須要一種方法來存儲 mapState
前面的輸出,這樣咱們就能夠比較二者看看有沒有差別來決定咱們是否須要繼續向前和從新渲染咱們的組件。爲了作到這一點咱們須要使用一種叫作記憶化的進程,跟咱們這行的許多事情同樣,對於一個想到簡單的進程來講,這是一個重要的詞,尤爲是咱們可使用 React.Component
來存儲咱們狀態的子狀態,而後僅在咱們檢測到 mapState
的輸出改變以後再更新。
接下來咱們須要一種可以跳過沒必要要的組件更新的方法。react 提供了一個生命週期方法 shouldComponentUpdate
可讓咱們達到目的。它將接收到的 props 和 state 當作參數來讓咱們同現有的 props 和 state 進行比較,若是咱們返回 true
那麼更新繼續,若是返回 false
那麼 react 將會跳過渲染。
class ConnectState extends React.Component {
state = this.props.mapState(this.props.state);
static getDerivedStateFromProps (nextProps, nextState) {}
shouldComponentUpdate (nextProps) {
if (!Object.keys(this.state).length) {
this.setState(this.props.mapDispatch(this.props.state));
return true;
}
console.log({
s: this.state,
nextState: nextProps.mapState(nextProps.state),
state: this.props.mapState(this.state)
});
return false;
}
render () {
return this.props.children({ state: this.props.state, dispatch: this.props.dispatch });
}
}
export function StateConsumer ({ mapState, mapDispatch, children }) {
return (
<StateContext.Consumer> {({ state, dispatch }) => ( <ConnectState state={state} dispatch={dispatch} mapState={mapState} mapDispatch={mapDispatch}> {children} </ConnectState> )} </StateContext.Consumer> ); } 複製代碼
上面只是對咱們接下來要作的事情的概述。它有了因此主要的部分:從咱們的 context 接收更新,它使用了 getDerivedStateFromProps
和 shouldComponentUpdate
,它也接收一個 render 屬性來做爲子組件,就像默認的消費者同樣。咱們也會經過使用傳遞的 mapState
函數來初始化咱們的消費者初始狀態。
如今這樣的話,shouldComponentUpdate
將只會在接收到第一次狀態更新以後渲染一次。以後它會記錄傳進的狀態和現有的狀態,而後返回 false
,阻止任何更新。
上面的解決方案中在 shouldComponentUpdate
內部也調用了 this.setState
,而咱們都知道 this.setState
老是會觸發從新渲染。因爲咱們也會從 shouldComponentUpdate
裏返回 true
,這會產生一次額外的從新渲染,因此爲了解決這個問題,咱們將使用生命週期 getDerivedStateFromProps
來獲取咱們的狀態,而後咱們再使用 shouldComponentUpdate
來決定基於咱們獲取的狀態是否繼續更新進程。
若是咱們檢查控制檯也能夠看到全局的狀態更新,同時咱們的組件阻止任何更新到 this.state
對象以致組件跳過更新:
如今咱們知道了如何阻止沒必要要的更新,咱們還須要一個能夠智能的決定咱們的消費者什麼時候應當更新的方法。若是咱們想要遞歸循環傳進來的 state 對象來查看每一個屬性來看狀態是否有改變,可是這對於幫助咱們理解有幫助卻對性能不利。咱們沒辦法知道傳進來的 state 對象層級有多深或者多複雜,若是條件永遠不知足,那麼遞歸函數將會無限期的執行下去,所以咱們準備限制咱們比較的做用域。
跟 redux 相似,咱們準備使用一個淺比較函數。淺在這裏意味着咱們在比較咱們的對象是否相等的屬性的層級,意味着咱們只會比較一層。所以咱們將會檢查咱們每一個新狀態的頂層屬性是否等於咱們現有狀態的同名屬性,若是同名屬性不存在,或者它們的值不一樣,咱們將會繼續渲染,不然咱們就認爲咱們的狀態時相同的,而後阻止渲染。
function shallowCompare (state, nextState) {
if ((typeof state !== 'object' || state === null || typeof nextState !== 'object' || nextState === null)) return false;
return Object.entries(nextState).reduce((shouldUpdate, [key, value]) => state[key] !== value ? true : shouldUpdate, false);
}
複製代碼
首先咱們從檢查是否兩個 state 都是對象開始,若是不是那麼咱們就跳過渲染。在初始檢查以後咱們把現有的狀態轉化爲一個鍵值對的數組,並經過將數組減小爲單個布爾值來檢查每一個屬性的值與傳進來的 state 對象的值。
這是困難的部分,如今咱們想用咱們的 shallowCompare
函數,實際上只是調用並檢查結果。若是它返回 true
,咱們就返回 true
來容許組件從新渲染,不然咱們就返回 false
來跳過更新(而後咱們得到的狀態被放棄掉)。咱們也想在 mapDispatch
存在的時候調用它。
class ConnectState extends React.Component {
state = {};
static getDerivedStateFromProps ({ state, mapState = s => s }) {
return mapState(state);
}
shouldComponentUpdate (nextProps, nextState) {
return shallowCompare(this.state, nextState);
}
render () {
return this.props.children({
state: this.state,
dispatch: this.props.mapDispatch ? this.props.mapDispatch(this.props.dispatch) : this.props.dispatch
});
}
}
複製代碼
最後咱們須要傳遞一個 mapState
函數讓咱們消費者以只匹配咱們的部分狀態,這樣咱們就會將它做爲一個屬性傳給咱們的 StateConsumer
:
return (
<StateConsumer mapState={state => ({ greeting: state.greeting })} mapDispatch={dispatch => dispatch}> // ... 複製代碼
如今咱們只訂閱 greeting
裏面的改變,所以假如咱們更新了組件裏的 count
將會被咱們的全局狀態改變所忽略而且避免了一次從新渲染。
若是你還沒聽過它,它正在快速變成 react 的下一個大特性。這裏有一段來自官方描述的簡單解釋:
Hooks 是一個讓你沒必要寫 class 就可使用 state 和 react 其餘特性的新功能。
hooks 提供給咱們高階組件的全部能力,以及更加清晰和直觀的 API 來渲染屬性。
咱們來看一個使用基本的 useState
鉤子的例子來看看它們是如何工做的:
import React, { useState } from 'react';
function Counter () {
const [count, setCount] = useState(0);
return (
<> <span> Count: {count} </span> <button onClick={() => setCount(count + 1)}> Increment </button> </> ); } 複製代碼
在上面的例子中,咱們經過給 useState
傳遞一個 0 來初始化新狀態,它會返回咱們的狀態:count
,以及一個更新函數 setCount
。若是你之前沒見過的話,可能會奇怪 useState
是如何不在每次渲染時初始化,這是由於 react 在內部處理了,所以咱們無需擔憂這一點。
讓咱們暫時先忘掉中間件和異步 action,用 useReducer
鉤子來從新應用咱們的 provider,就像咱們正在作的同樣,除了將 action 觸發到一個得到新狀態的 reducer,它就像 useState
同樣工做。
知道了這個,咱們只須要將 reducer 的邏輯從老的 StateProvider
拷貝到新的函數組件 StateProvider
裏就能夠了:
export function StateProvider ({ state: initialState, reducers, middleware, children }) {
const [state, dispatch] = useReducer((state, action) => {
return reducers.reduce((state, reducer) => reducer(state, action) || state, state);
}, initialState);
return (
<StateContext.Provider value={{ state, dispatch }}> {children} </StateContext.Provider> ); } 複製代碼
能夠如此的簡單,可是當咱們想要保持簡單時,咱們仍然沒有徹底掌握 hooks 的全部能力。咱們也可使用 hooks 來把咱們的 StateConsumer
換爲咱們本身的鉤子,咱們能夠經過包裹 useContext
鉤子來作到:
const StateContent = createContext();
export function useStore () {
const { state, dispatch } = useContext(StateContext);
return [state, dispatch];
}
複製代碼
儘管以前當咱們建立咱們的上下文時使用瞭解構的 Provider
和 Consumer
,可是此次咱們會將它存到咱們傳遞到 useContext
單個變量從而讓咱們可不用 Consumer
就能夠接入上下文。咱們也將它命名爲咱們本身的 useStore
鉤子,由於 useState
是一個默認的鉤子。
接下來咱們來簡單地重構下咱們消費上下文數據的方法:
export default function SomeCount () {
const [state, dispatch] = useStore();
return (
<>
<p>
Count: {state.count}
</p>
<button onClick={() => dispatch(addOne())}>
+ 1
</button>
<button onClick={() => dispatch(addN(5))}>
+ 5
</button>
<button onClick={() => dispatch(addN(10))}>
+ 10
</button>
</>
);
}
複製代碼
但願這些例子能讓你感覺到 hooks 是如何的直觀、簡單和有效。咱們減小了所需的代碼數量,而且給了咱們一個友好、簡單的 API 供使用。
咱們也想讓咱們的中間件和內置的異步 action 從新開始工做。爲了作到這一點,咱們將咱們的 useReducer
包裹進一個自定義鉤子,在咱們的 StateProvider
中被特殊的使用,而後簡單地重用咱們老的狀態組件的邏輯就行了。
export function useStateProvider ({ initialState, reducers, middleware = [] }) {
const [state, _dispatch] = useReducer((state, action) => {
return reducers.reduce((state, reducer) => reducer(state, action) || state, state);
}, initialState);
function dispatch (action) {
if (typeof action === 'function') {
return action(dispatch, state);
}
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, state) : result;
}, undefined);
if (continueUpdate !== null) {
_dispatch(action);
}
}
return { state, dispatch };
}
複製代碼
在咱們的老的解決方案中,咱們想讓中間件是可選的,因此咱們添加了一個空數組做爲默認值,一樣地此次咱們也使用一個默認的參數來替換默認屬性。相似於咱們老的 dispatch 函數,咱們調用了中間件,而後若是 continueUpdate !== null
咱們就繼續更新狀態。咱們也不會改變處理異步 action 的方式。
最終,咱們將 useStateProvider
的結果和它的參數到咱們的 provider,這咱們以前沒怎麼考慮:
export function StateProvider ({ state: initialState, reducers, middleware, children }) {
return (
<StateContext.Provider value={useStateProvider({ initialState, reducers, middleware })}> {children} </StateContext.Provider> ); } 複製代碼
完結!
在開始的時候提到過,我寫了一個庫,Use Simple State,看完這篇文章以後,你能夠在個人 github頁面看到,我已經使用 hooks 最終實現了,包括幾個新增的功能。