您好, 如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想
我们还是先回顾一下Redux的基本使用,看一下下面这个简单的例子,包含了Redux中的store、action和reducer三大概念:
typescript
import { createStore } from 'redux';
const initState = {
milk: 0,
};
function reducer(state = initState, action) {
switch (action.type) {
case 'PUT_MILK':
return { ...state, milk: state.milk + action.count };
case 'TAKE_MILK':
return { ...state, milk: state.milk - action.count };
default:
return state;
}
}
let store = createStore(reducer);
// subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用
// 如果是结合页面更新,更新的操作就是在这里执行
store.subscribe(() => console.log(store.getState()));
// 将action发出去要用dispatch
store.dispatch({ type: 'PUT_MILK' }); // milk: 1
store.dispatch({ type: 'PUT_MILK' }); // milk: 2
store.dispatch({ type: 'TAKE_MILK' }); // milk: 1
例子虽然短小,但是包含了Redux的全部核心功能,我们的手写目标就是替换引入createStore的redux库,要替换它,我们需要知道它都有些什么东西。目前看只有一个createStore API,接收reducer函数作为参数,返回一个store,而所有的功能都在store上。
store中包含了什么能力呢?
-
subscribe:订阅state变化,用于触发页面更新;
-
dispatch:发出action的方法,每次dispatch action都会执行reducer生成新的state,然后去执行subscribe;
-
getState:简单方法,返回当前的state;
接下来我们开始手写一个基本雏形:
typescript
const createStore = (reducer: any) => {
let state: any;
let listeners: Array<() => void> = [];
/**
* @description: 订阅,添加订阅器
* @param {function} fn
*/
const subscribe = (fn: () => void) => {
listeners.push(fn);
};
/**
* @description: 执行变更,触发所有回调
* @param {*} action
*/
const dispatch = (action: any) => {
state = reducer(state, action);
listeners.forEach((fn) => fn());
};
/**
* @description: 获取当前state
* @return {*}
*/
const getState = () => {
return state;
};
return {
subscribe,
dispatch,
getState,
};
};
export createStore;
代替一下redux来源库:
typescript
import { createStore } from './redux';
跑一下结果:
符合预期。
初步实现了,和redux的预期效果一致。可喜可贺,接下里我们继续增强Redux。当store庞大的时候,有多个模块化reducer的时候,我们需要使用combineReducers API来合并reducer再消费,就像这样:
typescript
const reducer = combineReducers({milk: milkReducer, rice: riceReducer});
我们接下来实现这个API,首先分析入参,就是输入多个reducer,输出一个reducer。
typescript
const combineReducers = (reducerMap: { [key in string]: any }) => {
const reducerKeys = Object.keys(reducerMap); // 拿到所有reducer map
const reducer = (state = {}, action) => {
const newState = {};
for (let i = 0; i < reducerKeys.length; i++) {
// reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值
// 然后将所有reducer返回的state按照参数里面的key组装好
// 最后再返回组装好的newState就行
const key = reducerKeys[i];
const currentReducer = reducerMap[key];
const prevState = state[key];
newState[key] = currentReducer(prevState, action);
}
return newState;
};
return reducer;
};
我们改一下测试用例:
typescript
// 使用combineReducers组合两个reducer
const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});
let store = createStore(reducer);
store.subscribe(() => console.log(store.getState()));
// 操作
store.dispatch({ type: 'PUT_MILK', count: 1 }); // milk: 1
store.dispatch({ type: 'PUT_MILK', count: 1 }); // milk: 2
store.dispatch({ type: 'TAKE_MILK', count: 1 }); // milk: 1
// 操作大米的action
store.dispatch({ type: 'PUT_RICE', count: 1 }); // rice: 1
store.dispatch({ type: 'PUT_RICE', count: 1 }); // rice: 2
store.dispatch({ type: 'TAKE_RICE', count: 1 }); // rice: 1
跑一下结果,如图:
结果没问题,再次可喜可贺,而官方的实现方式也是这样的,只是在中间加入了更多的异常处理。最后我们再实现一下applyMiddleware,它是Redux中很重要的概念,就是redux中间件。我们可以在状态变更中插入变更日志,就像这样:
typescript
function logger(store) {
return function (next) {
return function (action) {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result;
};
};
}
// 在createStore的时候将applyMiddleware作为第二个参数传进去
const store = createStore(reducer, applyMiddleware(logger));
可以看到,中间件在createStore中作为第二个参数传入,官方称为enhancer,顾名思义是一个增强器,用于增强辅助store能力。因此我们在手写就需要改造一开始的createStore方法了,就像这样:
typescript
const createStore = (reducer, enhancer) = () => {};
并且加入判断:
typescript
const createStore = ((reducer, enhancer) = () => {
if (enhancer && typeof enhancer === 'function') {
const newCreateStore = enhancer(createStore);
const newStore = newCreateStore(reducer);
return newStore;
}
// 执行之前的逻辑
});
如果传入了enhancer并且是个函数,我们就走新的逻辑,否则就走之前实现的逻辑。
结合之前logger中间件的结构可以知道applyMiddleware包了两层函数,因此我们也把applyMiddleware的结构先设计出来。
typescript
function applyMiddleware(middleware) {
function enhancer(createStore) {
function newCreateStore(reducer) {
// ...
}
}
}
那这里面做了什么呢?中间件本质是加强dispatch,我们拿到加强后的dispatch,最后再把dispatch和原始store返回即可。
typescript
function applyMiddleware(middleware) {
function enhancer(createStore) {
function newCreateStore(reducer) {
const store = createStore(reducer);
// dispatch加强函数
const func = middleware(store);
// 解构出原始的dispatch
const { dispatch } = store;
// 得到增强版的dispatch
const newDispatch = func(dispatch);
return { ...store, dispatch: newDispatch };
}
return newCreateStore;
}
return enhancer;
}
三行代码就实现了~再跑一下测试用例,拿到了对应logger中间件的结构,如图:
那如何实现多个中间件呢?入参的数量就是无限的了,因此我们需要一个组合函数,把所有函数串联起来:
typescript
const compose = (f1, f2, f3, f4) => {
return (args) => f1(f2(f3(f4(args)))
}
在middleware中,将多个中间件不断地升级dispatch,就像这样:
typescript
function applyMiddleware(...middlewares) {
function enhancer(createStore) {
function newCreateStore(reducer) {
const store = createStore(reducer);
// dispatch加强函数
const chain = middlewares.map((middleware) => middleware(store));
// 解构出原始的dispatch
const { dispatch } = store;
// 用compose得到一个组合了所有newDispatch的函数
const newDispatchGen = compose(...chain);
// 获得加强版dispatch
const newDispatch = newDispatchGen(dispatch);
return { ...store, dispatch: newDispatch };
}
return newCreateStore;
}
return enhancer;
}
最后再加个logger来测试下:
typescript
function logger2(store) {
return function (next) {
return function (action) {
let result = next(action);
console.log('logger2');
return result;
};
};
}
let store = createStore(reducer, applyMiddleware(logger, logger2));
新的logger也打印出来了,可喜可贺。因此我们也知道Redux中间件包裹了几层函数了:
-
第一层,store参数;
-
第二层,dispatch参数,可以是多个;
-
第三层,最终返回值,增强过的dispatch,最终逻辑;
如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。