前言
本文采用例子 + 部分源码 的方式,一步步实现一个简单的 redux。其中还包括 combineReducers、applyMiddleware 等源码解析。
如何改变一个值
首先我们来想一个很简单的问题,我们如何改变一个值呢? 一下想到的肯定是定义变量,然后去修改这个变量的值,那如果我想控制这个变量的修改规则,只能按照我的意愿限制他怎么被修改呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const initState = { num: 1, }
function countReducer(preState, action) { const { type, data } = action; switch (type) { case "increment": return {...preState, num: preState.num + data}; case "decrement": return {...preState, num: preState.num - data}; default: return preState; } }
function changeCount(type, data) { initState = countReducer(initState, { type, data}); }
|
这里的 initState 就是我们声明的变量,countReducer 就是我们去修改它的工具,这个工具限制他只能通过 ‘increment’ 或者 ‘decrement’ 的方式来得到新的值。
到这里又产生一个问题,initState 的值确实被改变了,但我怎么知道它被改了呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const listeners = []; function subscribe(listener) { listeners.push(listener); }
subscribe(() => { console.log(`state的count值发生改变了,新的值为${initState.count}`); });
function changeCount(type, data) { initState = countReducer(initState, { type, data}); for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; listener(); } }
|
这里用了发布订阅思想,在原来代码的基础上,我们增加了 subScribe 方法来进行订阅,监听 initState 的值发生了变化,在每次改变 initState 值的时候,我们去遍历一下 listeners 进行发布,通知值被改变了
现在我们将这部分代码整合起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| const createStore = () => { let initState = { count: 1 }; let listeners = []; function subscribe(listener) { listeners.push(listener); }
function countReducer(preState, action) { const { type, data } = action; switch (type) { case "increment": return {...preState, count: preState.count + data}; case "decrement": return {...preState, count: preState.count - data}; default: return preState; } }
function changeCount(action) { const { type, data } = action; initState = countReducer(initState, { type, data }); for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; listener(); } } const getState = () => { return initState; } return { subscribe, changeCount, getState, } }
const store = createStore(); store.subscribe(() => { const state = store.getState() console.log(`state的count值发生改变了,新的值为${state.count}`); }); store.changeCount({type: 'increment', data: 10 });
|
这里我们在调用 changeCount 的时候,type需要手动写入,很容易出现单词输入错误的情况,那我们就用一个 action 来将这些类型收集起来
1 2 3 4
| export const createIncrementAction = (data) => { return { type: "increment", data }; }; store.changeCount(createIncrementAction(10));
|
至此我们可以稍微总结一下, initState 就是我们存放变量的仓库,我们通过调用 changeCount 的方法来更改 state 中的值,同时使用 subScribe 来进行订阅,监听 initState 发生的变化
combineReducers 整合 reducer
接下来我们再给他进行升级,这里的 reducer 独立存在的 我们可以将它抽离出来。那在有多个 reducer 的情况下改如何处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function combineReducers (reducers) { const keys = Object.keys(reducers); return function conbineReducer(state ={}, action) { const nextState = {}; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const reducer = reducers[key]; nextState[key] = reducer(state[key], action); } return { ...state, ...nextState, } } }
const reducers = combineReducers({ count: countReducer, info: infoReducer});
|
combineReducers 返回了一个 conbineReducer 的方法,当我们每次调用 reducers 去修改值的时候,就会执行这个方法,他首先遍历了 reducers,找到 key 值,执行 reducer 将值赋给 state 中的 同名属性,这也就是我们在执行 combineReducers 传入的对象的 key 值和 state 中的属性名需要对应的原因。
我们通过 combineReducers 方法来将多个 reducer 合并,然后将 changeCount 中的 countReducer 改成 reducers,由于不再是单独的改变 count 的方法了,这个方法还叫 changeCount 就不合适了,我们将他改为 dispatch
中间件 middleware
中间件简单来说就是对 dispatch 进行强化
比如我想在 dispatch 时,看一下 dispatch 的 type 是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function logMiddleware(next){ return (action)=>{ const { type } = action console.log(`进行了了一个 type 为 ${type} 的操作`) return next(action) } }
return { subscribe, dispatch: logMiddleware(dispatch), getState, }
|
但肯定不能每次只加强一个功能,如果我想要的更多呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function applyMiddleware(...middlewares) { return createStore => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
|
这个时候就需要用 applyMiddleware 来将我们的中间件进行整合,这个方法其实就是将我们原本 createStore 返回的 dispatch 重写了一下,再把新的 createStore 返回,
这里主要使用到 compose 对我们的 dispatch 进行强化,我们来看下compose 是怎么实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce( (a, b) => (...args) => a(b(...args)) ) }
|
compose 这个方法很简单,首先就是对传入的 middleware 进行判断,如果有多个 middleware 就用 reduce 方法依次执行。
这时候我们的 createStore 就需要重写一下了,这里需要注意下,initState 不是必传的,如果我们将 reducers 抽离出去,并且各自的 state 单独管理,这里就不需要 initState 了,所以再加一层判断第二、三个参数传的是什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducers, initState, enhancer) => { if (typeof initState === 'function' && typeof enhancer === 'undefined') { enhancer = initState; initState = undefined; } if (middleware) { const createNewStore = enhancer(createStore); return createNewStore(reducers, initState); } } const middleWare = applyMiddleware(logMiddleware)(createStore);
const store = createStore(reducers, initState, middleWare);
|
至此一个简化版的 redux 就大功告成了。
总结一下需要注意的点
- state 只读
- redux 中的数据是单向流动的
- reducer 必须是纯函数
- redux 和 react 没有关系,redux 只是一个应用状态管理库,如果你想的话,你可以在任何框架中使用它