react-redux主要提供的功能是將redux和react連接起來。 使用提供的connect方法可使得任意一個react組件獲取到全局的store上的狀態。 實現方法是將store存放於由provider提供的context上,在調用connect時, 就可將組件的props替換, 讓其能夠訪問到定製化的數據或者方法。react
本文將嘗試使用最近很火爆的react-hook來替代react-redux的基礎功能。git
咱們先將理想的特徵列舉出來,完成這些特性纔算是替代了react-redux:github
先看一下內置useRudecer的官方實例能給咱們帶來一些什麼啓示:redux
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'reset':
return initialState;
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
// A reducer must always return a valid state.
// Alternatively you can throw an error if an invalid action is dispatched.
return state;
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, {count: initialCount});
return (
<div> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <div/> ); } 複製代碼
乍一看好像react利用hook已經可使用redux的機制了, 狀態由派發的action改變,單向數據流。可是hook不會讓狀態共享,也就是每次useReducer保持的數據都是獨立的。好比下面這個例子:app
function CountWrapper() {
return (
<section>
<Counter initialCount={1}/>
<Counter initialCount={1}/>
</setion>
)
}
複製代碼
兩個Count組件內部的數據是獨立的,沒法互相影響,狀態管理也就無從提及。 究其緣由,useReducer內部也是用useState實現的ide
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
複製代碼
useReducer看來並不能幫上忙。解決全局狀態的問題能夠參照react-redux的作法,提供一個Provider,使用context的方式來作。 這裏可使用useContext,這個內置的hook。this
Accepts a context object (the value returned from React.createContext) and returns the current context value, as given by the nearest context provider for the given context. When the provider updates, this Hook will trigger a rerender with the latest context value.spa
它接受一個由React.createContext返回的上下文對象, 當provider更新時,本文中這裏理解爲傳入的store更新時,useContext就能夠返回最新的值。那麼咱們就有了下面的代碼rest
import {createContext, useContext} from 'react';
const context = createContext(null);
export const StoreProvider = context.provider;
const store = useContext(context);
// do something about store.
複製代碼
到這裏咱們提供了一個根組件來接受store。當store有更新時,咱們也能夠利用useContext也能夠拿到最新的值。 這個時候暴露出一個hook來返回store上的dispatch便可派發action,來更改statecode
export function useDispatch() {
const store = useContext(Context);
return store.dispatch;
}
複製代碼
接下來着眼於組件拿到store上數據的問題。這個其實也很簡單,咱們都把store拿到了,編寫一個自定義的hook調用store.getStore()便可拿到全局的狀態,
export function useStoreState(mapState){
const store = useContext(context);
return mapState(store.getStore());
}
複製代碼
這裏雖然是把狀態拿到了,但忽略了一個很是重要的問題, 當store上的數據變化時,如何通知組件再次獲取新的數據。當store變化事後,並無和視圖關聯起來。另外一個問題是沒有關注mapState變化的狀況。 針對第一個問題,咱們能夠利用useEffect這個內置hook,在組件mount時完成在store上的訂閱,並在unmont的時候取消訂閱。 mapState的變動可使用useState來監聽, 每次有變動時就執行向對應的setter方法。代碼以下
export function useStoreState(mapState) {
const store = useContext(context);
const mapStateFn = () => mapState(store.getState());
const [mappedState, setMappedState] = useState(() => mapStateFn());
// If the store or mapState change, rerun mapState
const [prevStore, setPrevStore] = useState(store);
const [prevMapState, setPrevMapState] = useState(() => mapState);
if (prevStore !== store || prevMapState !== mapState) {
setPrevStore(store);
setPrevMapState(() => mapState);
setMappedState(mapStateFn());
}
const lastRenderedMappedState = useRef();
// Set the last mapped state after rendering.
useEffect(() => {
lastRenderedMappedState.current = mappedState;
});
useEffect(
() => {
// Run the mapState callback and if the result has changed, make the
// component re-render with the new state.
const checkForUpdates = () => {
const newMappedState = mapStateFn();
if (!shallowEqual(newMappedState, lastRenderedMappedState.current)) {
setMappedState(newMappedState);
}
};
// Pull data from the store on first render.
checkForUpdates();
// Subscribe to the store to be notified of subsequent changes.
const unsubscribe = store.subscribe(checkForUpdates);
// The return value of useEffect will be called when unmounting, so
// we use it to unsubscribe from the store.
return unsubscribe;
},
[store, mapState],
);
return mappedState
}
複製代碼
如上就完成了hook對react-redux的功能重寫,從代碼量來講是簡潔量很多,而且實現方式也更貼合react將來的發展方向。 可見大機率上react-redux會被hook的方式逐漸替代。本文是對redux-react-hook實現的原理講解,想要在線嘗試本文所訴內容點擊這個codesandbox
關注【IVWEB社區】公衆號,獲取最新技術週刊