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>
);
}
复制代码
谢谢阅读。