共计 10946 个字符,预计需要花费 28 分钟才能阅读完成。
Redux 可是一个赫赫有名的库,很多中央都在用,我也用了几年了,明天这篇文章就是本人来实现一个 Redux,以便于深刻了解他的原理。咱们还是老套路,从根本的用法动手,而后本人实现一个 Redux 来代替源码的 NPM 包,然而性能放弃不变。本文只会实现 Redux 的外围库,跟其余库的配合应用,比方 React-Redux 筹备前面独自写一篇文章来讲。有时候咱们过于关注应用,只记住了各种应用形式,反而疏忽了他们的外围原理,然而如果咱们想真正的进步技术,最好还是一个一个搞清楚,比方 Redux 和 React-Redux 看起来很像,然而他们的核心理念和关注点是不同的,Redux 其实只是一个单纯状态治理库,没有任何界面相干的货色,React-Redux 关注的是怎么将 Redux 跟 React 联合起来,用到了一些 React 的 API。
基本概念
Redux 的概念有很多文章都讲过,想必大家都看过很多了,我这里不再开展,只是简略提一下。Redux 基本概念次要有以下几个:
Store
人如其名,Store 就是一个仓库,它存储了所有的状态 (State),还提供了一些操作他的 API,咱们后续的操作其实都是在操作这个仓库。如果咱们的仓库是用来放牛奶的,初始状况下,咱们的仓库外面一箱牛奶都没有,那 Store 的状态(State) 就是:
{milk: 0}
Actions
一个 Action 就是一个动作,这个动作的目标是更改 Store 中的某个状态,Store 还是下面的那个仓库,当初我想往仓库放一箱牛奶,那 ” 我想往仓库放一箱牛奶 ” 就是一个 Action,代码就是这样:
{
type: "PUT_MILK",
count: 1
}
Reducers
后面 ” 我想往仓库放一箱牛奶 ” 只是想了,还没操作,具体操作要靠 Reducer,Reducer 就是依据接管的 Action 来扭转 Store 中的状态,比方我接管了一个 PUT_MILK
,同时数量count
是 1,那放进去的后果就是 milk
减少了 1,从 0 变成了 1,代码就是这样:
const initState = {milk: 0}
function reducer(state = initState, action) {switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count}
default:
return state
}
}
能够看到 Redux 自身就是一个单纯的状态机,Store 寄存了所有的状态,Action 是一个扭转状态的告诉,Reducer 接管到告诉就更改 Store 中对应的状态。
简略例子
上面咱们来看一个简略的例子,蕴含了后面提到的 Store,Action 和 Reducer 这几个概念:
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
参考 前端 react 面试题具体解答
本人实现
后面咱们那个例子尽管短小,然而曾经蕴含了 Redux 的外围性能了,所以咱们手写的第一个指标就是替换这个例子中的 Redux。要替换这个 Redux,咱们得先晓得他外面都有什么货色,认真一看,咱们如同只用到了他的一个 API:
createStore
:这个 API 承受reducer
办法作为参数,返回一个store
,次要性能都在这个store
上。
看看 store
上咱们都用到了啥:
store.subscribe
: 订阅state
的变动,当state
变动的时候执行回调,能够有多个subscribe
,外面的回调会顺次执行。
store.dispatch
: 收回action
的办法,每次dispatch
action
都会执行reducer
生成新的state
,而后执行subscribe
注册的回调。
store.getState
: 一个简略的办法,返回以后的state
。
看到 subscribe
注册回调,dispatch
触发回调,想到了什么,这不就是公布订阅模式吗?
function createStore() {
let state; // state 记录所有状态
let listeners = []; // 保留所有注册的回调
function subscribe(callback) {listeners.push(callback); // subscribe 就是将回调保留下来
}
// dispatch 就是将所有的回调拿进去顺次执行就行
function dispatch() {for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];
listener();}
}
// getState 间接返回 state
function getState() {return state;}
// store 包装一下后面的办法间接返回
const store = {
subscribe,
dispatch,
getState
}
return store;
}
上述代码是不是很简略嘛,Redux 外围也是一个公布订阅模式,就是这么简略!等等,如同漏了啥,reducer
呢?reducer
的作用是在公布事件的时候扭转 state
,所以咱们的dispatch
在执行回调前应该先执行 reducer
, 用reducer
的返回值从新给 state
赋值,dispatch
改写如下:
function dispatch(action) {state = reducer(state, action);
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];
listener();}
}
到这里,后面例子用到的所有 API 咱们都本人实现了,咱们用本人的 Redux 来替换下官网的 Redux 试试:
// import {createStore} from 'redux';
import {createStore} from './myRedux';
能够看到输入后果是一样的,阐明咱们本人写的 Redux 没有问题:
理解了 Redux 的外围原理,咱们再去看他的源码应该就没有问题了
最初咱们再来梳理下 Redux 的外围流程,留神单纯的 Redux 只是个状态机,是没有 View
层的哦。
除了这个外围逻辑外,Redux 外面还有些 API 也很有意思,咱们也来手写下。
手写combineReducers
combineReducers
也是应用十分宽泛的 API,当咱们利用越来越简单,如果将所有逻辑都写在一个 reducer
外面,最终这个文件可能会有成千上万行,所以 Redux 提供了combineReducers
,能够让咱们为不同的模块写本人的reducer
,最终将他们组合起来。比方咱们最开始那个牛奶仓库,因为咱们的业务倒退很好,咱们又减少了一个放大米的仓库,咱们能够为这两个仓库创立本人的reducer
,而后将他们组合起来,应用办法如下:
import {createStore, combineReducers} from 'redux';
const initMilkState = {milk: 0};
function milkReducer(state = initMilkState, 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;
}
}
const initRiceState = {rice: 0};
function riceReducer(state = initRiceState, action) {switch (action.type) {
case 'PUT_RICE':
return {...state, rice: state.rice + action.count};
case 'TAKE_RICE':
return {...state, rice: state.rice - action.count};
default:
return state;
}
}
// 应用 combineReducers 组合两个 reducer
const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});
let store = createStore(reducer);
store.subscribe(() => console.log(store.getState()));
// 操作🥛的 action
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
下面代码咱们将大的 state
分成了两个小的 milkState
和riceState
,最终运行后果如下:
晓得了用法,咱们尝试本人来写下呢!要手写 combineReducers
,咱们先来剖析下他干了啥,首先它的返回值是一个reducer
,这个reducer
同样会作为 createStore
的参数传进去,阐明这个返回值是一个跟咱们之前一般 reducer
构造一样的函数。这个函数同样接管 state
和action
而后返回新的 state
,只是这个新的state
要合乎 combineReducers
参数的数据结构。咱们尝试来写下:
function combineReducers(reducerMap) {const reducerKeys = Object.keys(reducerMap); // 先把参数外面所有的键值拿进去
// 返回值是一个一般构造的 reducer 函数
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;
}
手写applyMiddleware
middleware
是 Redux 外面很重要的一个概念,Redux 的生态次要靠这个 API 接入,比方咱们想写一个 logger
的中间件能够这样写(这个中间件来自于官网文档):
// logger 是一个中间件,留神返回值嵌了好几层函数
// 咱们前面来看看为什么这么设计
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
的能力的。官网对于 enhancer
的定义如下:
type StoreEnhancer = (next: StoreCreator) => StoreCreator
下面的构造的意思是说 enhancer
作为一个函数,他接管 StoreCreator
函数作为参数,同时返回的也必须是一个 StoreCreator
函数。留神他的返回值也是一个 StoreCreator
函数,也就是咱们把他的返回值拿进去继续执行应该失去跟之前的 createStore
一样的返回构造,也就是说咱们之前的 createStore
返回啥构造,他也必须返回构造,也就是这个store
:
{
subscribe,
dispatch,
getState
}
createStore
反对enhancer
依据他对于 enhancer
的定义,咱们来改写下本人的createStore
,让他反对enhancer
:
function createStore(reducer, enhancer) { // 接管第二个参数 enhancer
// 先解决 enhancer
// 如果 enhancer 存在并且是函数
// 咱们将 createStore 作为参数传给他
// 他应该返回一个新的 createStore 给我
// 我再拿这个新的 createStore 执行,应该失去一个 store
// 间接返回这个 store 就行
if(enhancer && typeof enhancer === 'function'){const newCreateStore = enhancer(createStore);
const newStore = newCreateStore(reducer);
return newStore;
}
// 如果没有 enhancer 或者 enhancer 不是函数,间接执行之前的逻辑
// 上面这些代码都是之前那版
// 省略 n 行代码
// .......
const store = {
subscribe,
dispatch,
getState
}
return store;
}
applyMiddleware
返回值是一个enhancer
后面咱们曾经有了 enhancer
的根本构造,applyMiddleware
是作为第二个参数传给 createStore
的,也就是说他是一个 enhancer
,精确的说是applyMiddleware
的返回值是一个 enhancer
,因为咱们传给createStore
的是他的执行后果applyMiddleware()
:
function applyMiddleware(middleware) {
// applyMiddleware 的返回值应该是一个 enhancer
// 依照咱们后面说的 enhancer 的参数是 createStore
function enhancer(createStore) {
// enhancer 应该返回一个新的 createStore
function newCreateStore(reducer) {
// 咱们先写个空的 newCreateStore,间接返回 createStore 的后果
const store = createStore(reducer);
return store
}
return newCreateStore;
}
return enhancer;
}
实现applyMiddleware
下面咱们曾经有了 applyMiddleware
的根本构造了,然而性能还没实现,要实现他的性能,咱们必须先搞清楚一个中间件到底有什么性能,还是以后面的 logger
中间件为例:
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
}
}
}
这个中间件运行成果如下:
能够看到咱们 let result = next(action);
这行执行之后 state
扭转了,后面咱们说了要扭转 state
只能 dispatch(action)
,所以这里的next(action)
就是 dispatch(action)
,只是换了一个名字而已。而且留神最初一层返回值return function(action)
的构造,他的参数是 action
,是不是很像dispatch(action)
,其实他就是一个新的dispatch(action)
,这个新的dispatch(action)
会调用原始的dispatch
,并且在调用的前后加上本人的逻辑。所以到这里一个中间件的构造也分明了:
- 一个中间件接管
store
作为参数,会返回一个函数- 返回的这个函数接管老的
dispatch
函数作为参数,会返回一个新的函数- 返回的新函数就是新的
dispatch
函数,这个函数外面能够拿到里面两层传进来的store
和老dispatch
函数
所以说白了,中间件就是增强 dispatch
的性能,用新的 dispatch
替换老的 dispatch
,这不就是个装璜者模式吗?其实后面enhancer
也是一个装璜者模式,传入一个 createStore
,在createStore
执行前后加上些代码,最初又返回一个增强版的createStore
。
遵循这个思路,咱们的 applyMiddleware
就能够写进去了:
// 间接把后面的构造拿过去
function applyMiddleware(middleware) {function enhancer(createStore) {function newCreateStore(reducer) {const store = createStore(reducer);
// 将 middleware 拿过去执行下,传入 store
// 失去第一层函数
const func = middleware(store);
// 解构出原始的 dispatch
const {dispatch} = store;
// 将原始的 dispatch 函数传给 func 执行
// 失去增强版的 dispatch
const newDispatch = func(dispatch);
// 返回的时候用增强版的 newDispatch 替换原始的 dispatch
return {...store, dispatch: newDispatch}
}
return newCreateStore;
}
return enhancer;
}
照例用咱们本人的 applyMiddleware
替换老的,跑起来是一样的成果,阐明咱们写的没问题,哈哈~
反对多个middleware
咱们的 applyMiddleware
还差一个性能,就是反对多个middleware
,比方像这样:
applyMiddleware(
rafScheduler,
timeoutScheduler,
thunk,
vanillaPromise,
readyStatePromise,
logger,
crashReporter
)
其实要反对这个也简略,咱们返回的 newDispatch
外面顺次的将传入的 middleware
拿进去执行就行,多个函数的串行执行能够应用辅助函数 compose
,这个函数定义如下。只是须要留神的是咱们这里的compose
不能把办法拿来执行就完了,应该返回一个包裹了所有办法的办法。
function compose(...func){return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
这个 compose
可能比拟让人困惑,我这里还是解说下,比方咱们有三个函数,这三个函数都是咱们后面接管 dispatch
返回新 dispatch
的办法:
const fun1 = dispatch => newDispatch1;
const fun2 = dispatch => newDispatch2;
const fun3 = dispatch => newDispatch3;
当咱们应用了 compose(fun1, fun2, fun3)
后执行程序是什么样的呢?
// 第一次其实执行的是
(func1, func2) => (...args) => func1(fun2(...args))
// 这次执行完的返回值是上面这个,用个变量存起来吧
const temp = (...args) => func1(fun2(...args))
// 咱们下次再循环的时候其实执行的是
(temp, func3) => (...args) => temp(func3(...args));
// 这个返回值是上面这个,也就是最终的返回值,其实就是从 func3 开始从右往左执行完了所有函数
// 后面的返回值会作为前面参数
(...args) => temp(func3(...args));
// 再看看下面这个办法,如果把 dispatch 作为参数传进去会是什么成果
(dispatch) => temp(func3(dispatch));
// 而后 func3(dispatch)返回的是 newDispatch3,这个又传给了 temp(newDispatch3),也就是上面这个会执行
(newDispatch3) => func1(fun2(newDispatch3))
// 下面这个外面用 newDispatch3 执行 fun2(newDispatch3)会失去 newDispatch2
// 而后 func1(newDispatch2)会失去 newDispatch1
// 留神这时候的 newDispatch1 其实曾经蕴含了 newDispatch3 和 newDispatch2 的逻辑了,将它拿进去执行这三个办法就都执行了
所以咱们反对多个 middleware
的代码就是这样:
// 参数反对多个中间件
function applyMiddleware(...middlewares) {function enhancer(createStore) {function newCreateStore(reducer) {const store = createStore(reducer);
// 多个 middleware,先解构出 dispatch => newDispatch 的构造
const chain = middlewares.map(middleware => middleware(store));
const {dispatch} = store;
// 用 compose 失去一个组合了所有 newDispatch 的函数
const newDispatchGen = compose(...chain);
// 执行这个函数失去 newDispatch
const newDispatch = newDispatchGen(dispatch);
return {...store, dispatch: newDispatch}
}
return newCreateStore;
}
return enhancer;
}
最初咱们再加一个 logger2
中间件实现成果:
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));
能够看到 logger2
也曾经打印进去了,功败垂成。
当初咱们也能够晓得他的中间件为什么要包裹几层函数了:
第一层:目标是传入
store
参数第二层:第二层的构造是
dispatch => newDispatch
,多个中间件的这层函数能够compose
起来,造成一个大的dispatch => newDispatch
第三层:这层就是最终的返回值了,其实就是
newDispatch
,是加强过的dispatch
,是中间件的真正逻辑所在。
到这里咱们的 applyMiddleware
就写完了
总结
- 单纯的 Redux 只是一个状态机,
store
外面存了所有的状态state
,要扭转外面的状态state
,只能dispatch action
。 - 对于收回来的
action
须要用reducer
来解决,reducer
会计算新的state
来代替老的state
。 subscribe
办法能够注册回调办法,当dispatch action
的时候会执行外面的回调。- Redux 其实就是一个公布订阅模式!
- Redux 还反对
enhancer
,enhancer
其实就是一个装璜者模式,传入以后的createStore
,返回一个加强的createStore
。 - Redux 应用
applyMiddleware
反对中间件,applyMiddleware
的返回值其实就是一个enhancer
。 - Redux 的中间件也是一个装璜者模式,传入以后的
dispatch
,返回一个加强了的dispatch
。 - 单纯的 Redux 是没有 View 层的,所以他能够跟各种 UI 库联合应用,比方
react-redux
,打算下一篇文章就是手写react-redux
。