react-hooks 是 react 官方新的編寫推薦,咱們很容易在官方的 useReducer 鉤子上進行一層很簡單的封裝以達到和以往 react-redux \ redux-thunk \ redux-logger 相似的功能,而且大幅度簡化了聲明。html
react-hooks 的更多信息請閱讀 reactjs.org/hooks;react
這 70 行代碼是一個完整的邏輯, 客官能夠先閱讀,或許後續的說明文檔也就不須要閱讀了。git
import React from 'react';
function middlewareLog(lastState, nextState, action, isDev) {
if (isDev) {
console.log(
`%c|------- redux: ${action.type} -------|`,
`background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`,
);
console.log('|--last:', lastState);
console.log('|--next:', nextState);
}
}
function reducerInAction(state, action) {
if (typeof action.reducer === 'function') {
return action.reducer(state);
}
return state;
}
export default function createStore(params) {
const { isDev, reducer, initialState, middleware } = {
isDev: false,
reducer: reducerInAction,
initialState: {},
middleware: params.isDev ? [middlewareLog] : undefined,
...params,
};
const AppContext = React.createContext();
const store = {
isDev,
_state: initialState,
useContext: function() {
return React.useContext(AppContext);
},
dispatch: undefined,
getState: function() {
return store._state;
},
initialState,
};
let isCheckedMiddleware = false;
const middlewareReducer = function(lastState, action) {
let nextState = reducer(lastState, action);
if (!isCheckedMiddleware) {
if (Object.prototype.toString.call(middleware) !== '[object Array]') {
throw new Error("react-hooks-redux: middleware isn't Array");
}
isCheckedMiddleware = true;
}
for (let i = 0; i < middleware.length; i++) {
const newState = middleware[i](store, lastState, nextState, action);
if (newState) {
nextState = newState;
}
}
store._state = nextState;
return nextState;
};
const Provider = props => {
const [state, dispatch] = React.useReducer(middlewareReducer, initialState);
if (!store.dispatch) {
store.dispatch = async function(action) {
if (typeof action === 'function') {
await action(dispatch, store._state);
} else {
dispatch(action);
}
};
}
return <AppContext.Provider {...props} value={state} />; }; return { Provider, store }; } 複製代碼
reducer-in-action 是一個 reducer 函數,這 6 行代碼就是 reducer-in-action 的所有:github
function reducerInAction(state, action) {
if (typeof action.reducer === 'function') {
return action.reducer(state);
}
return state;
}
複製代碼
它把 reducer 給簡化了,放置到了每個 action 中進行 reducer 的處理。咱們不再須要寫一堆 switch,不再須要時刻關注 action 的 type 是否和 redcer 中的 type 一致。chrome
reducer-in-action 配合 thunk 風格,能夠很是簡單的編寫 redux,隨着項目的複雜,咱們只須要編寫 action,會使得項目結構更清晰。redux
安裝 react-hooks-redux, 須要 react 版本 >= 16.7數組
yarn add react-hooks-redux
複製代碼
咱們用了不到 35 行代碼就聲明瞭一個完整的 react-redux 的例子, 擁抱 hooks。瀏覽器
import React from 'react';
import ReactHookRedux from 'react-hooks-redux';
// 經過 ReactHookRedux 得到 Provider 組件和一個 sotre 對象
const { Provider, store } = ReactHookRedux({
isDev: true, // 打印日誌
initialState: { name: 'dog', age: 0 },
});
function actionOfAdd() {
return {
type: 'add the count',
reducer(state) {
return { ...state, age: state.age + 1 }; // 每次須要返回一個新的 state
},
};
}
function Button() {
function handleAdd() {
store.dispatch(actionOfAdd()); //dispatch
}
return <button onClick={handleAdd}>add</button>;
}
function Page() {
const state = store.useContext();
return (
<div> {state.age} <Button />{' '} </div>
);
}
export default function App() {
return (
<Provider> <Page /> </Provider>
);
}
複製代碼
總結一下:緩存
咱們閱讀這個小例子會發現,沒有對組件進行 connect, 沒有編寫 reducer 函數, 這麼簡化設計是爲了迎合 hooks, hooks 極大的簡化了咱們編寫千篇一概的類模板,可是若是咱們仍是須要對組件進行 connect, 咱們又回到了編寫模板代碼的老路。異步
絕大部分狀況,你不須要編寫 middleware, 不過它也極其簡單。middleware 是一個一維數組,數組中每一個對象都是一個函數, 傳入了參數而且若是返回的對象存在, 就會替換成 nextState 而且繼續執行下一個 middleware。
咱們可使用 middleware 進行打印日誌、編寫 chrome 插件或者二次處理 state 等操做。
咱們看看 middleware 的源碼:
let nextState = reducer(lastState, action);
for (let i = 0; i < middleware.length; i++) {
const newState = middleware[i](lastState, nextState, action, isDev);
if (newState) {
nextState = newState;
}
}
return nextState;
複製代碼
性能(和實現上)上最大的區別是,react-hooks-redux 使用 useContext 鉤子代替 connect 高階組件進行 dispatch 的派發。
在傳統的 react-redux 中,若是一個組件被 connect 高階函數進行處理,那麼當 dispatch 時,這個組件相關的 mapStateToProps 函數就會被執行,而且返回新的 props 以激活組件更新。
而在 hooks 風格中,當一個組件被聲明瞭 useContext() 時,context 相關聯的對象被變動了,這個組件會進行更新。
理論上性能和 react-redux 是一致的,因爲 hooks 相對於 class 有着更少的聲明,因此應該會更快一些。
因此,咱們有節制的使用 useContext 能夠減小一些組件被 dispatch 派發更新。
若是咱們須要手動控制減小更新 能夠參考 useMemo 鉤子的使用方式進行配合。
若是不但願組件被 store.dispatch() 派發更新,僅讀取數據可使用 store.getState(), 這樣也能夠減小一些沒必要要的組件更新。
以上都是理論分析,因爲此庫和此文檔是一個深夜的產物,並無去作性能上的基準測試,因此有人若是願意很是歡迎幫忙作一些基準測試。
隨着工做的進展,完善了一些功能, 代碼量也上升到了300行,有興趣的能夠去倉庫看看:
import React from 'react';
import ReactHookRedux, {
reducerInAction,
middlewareLog,
} from 'react-hooks-redux';
// 經過 ReactHookRedux 得到 Provider 組件和一個 sotre 對象
const { Provider, store } = ReactHookRedux({
isDev: true, // default is false
initialState: { count: 0, asyncCount: 0 }, // default is {}
reducer: reducerInAction, // default is reducerInAction 因此可省略
middleware: [middlewareLog], // default is [middlewareLog] 因此可省略
actions: {}, // default is {} 因此可省略
autoSave: {
item: 'localSaveKey',
keys: ['user'], // 須要緩存的字段
},
});
// 模擬異步操做
function timeOutAdd(a) {
return new Promise(cb => setTimeout(() => cb(a + 1), 500));
}
const actions = {
// 若是返回的是一個function,咱們會把它當成相似 react-thunk 的處理方式,而且額外增長一個ownState的對象方便獲取state
asyncAdd: () => async (dispatch, ownState) => {
const asyncCount = await timeOutAdd(ownState.asyncCount);
dispatch({
type: 'asyncAdd',
// if use reducerInAction, we can add reducer Function repeat reducer
reducer(state) {
return {
...state,
asyncCount,
};
},
});
},
};
function Item() {
const state = store.useContext();
return <div>async-count: {state.asyncCount}</div>;
}
function Button() {
async function handleAdd() {
// 使用 async dispatch
await store.dispatch(actions.asyncAdd());
}
return <button onClick={handleAdd}>add</button>;
}
export default function App() {
return (
<Provider> <Item /> <Button /> </Provider>
);
}
複製代碼
import React, { useCallback } from 'react';
import ReactHookRedux from 'react-hooks-redux';
import { Map } from 'immutable';
const { Provider, store } = ReactHookRedux({
initialState: Map({ products: ['iPhone'] }), // 請確保immutable是一個Map
isDev: true, // 當發現對象是 immutable時,middleware會遍歷屬性,使用getIn作淺比較打印 diff的對象
});
function actionAddProduct(product) {
return {
type: 'add the product',
reducer(state) {
return state.update('products', p => {
p.push(product);
return [...p];
});
},
};
}
let num = 0;
function Button() {
function handleAdd() {
num += 1;
store.dispatch(actionAddProduct('iPhone' + num)); //dispatch
}
return <button onClick={handleAdd}>add-product</button>;
}
function Page() {
const state = store.useContext();
// 從immutable獲取對象,若是products未改變,會從堆中獲取而不是從新生成新的數組
const products = state.get('products');
return useCallback(
<div> <Button /> {products.map(v => ( <div>{v}</div> ))} </div>,
[products], // 若是products未發生改變,不會進行進行重渲染
);
}
export default function App() {
return (
<Provider> <Page /> </Provider>
);
}
複製代碼
謝謝閱讀。