Redux 是什么?是一个状态管理器。它实现了一套机制,用来管理应用中的状态,也就是数据。
首先,将整个应用的数据放入一个对象中,我们称这个对象为 Store。
假如我们有这样一个数据。
let data = {name: 'xiaoMing'}
现在,我们想要改变这个数据,可以怎么做?
data.name = 'xiaoHua';
这样可以,但我们想监听这个数据的改变,然后去做点其他事情,怎么做?不防试一试订阅-发布模式。
let listeners = [];// 订阅function subscribe(listener) {listeners.push(listener)}// 改变数据changeData = (data, name) => {data.name = name;listeners.map(fun => fun(name));}
上面,我们定义了一个 subscribe 函数,用来添加订阅。每当我们使用 changeData 函数改变数据时,监听器函数就会分别执行。
subscribe((name) => console.log(`看,它把 name 的值改成 ${name} 了。`))changeData(data, '小黑'); // 看,它把name 的值改成 小黑 了。
这样,我们就做到了监听数据变化的目的。
目前我们只是针对 data 这个数据,想要复用这套逻辑,需要进一步封装。
思考:复用逻辑是什么意思? 答:实现一个函数,可以对给定的任意数据,实现相同的逻辑。到我们这个例子中,就是对传入的一个数据,实现监听它变化的逻辑。
// 封装一个 createStore 方法,来实现上面的更改数据监听机制function createStore(initState) {let state = { ...initState }let listeners = [];// 订阅function subscribe(listener) {listeners.push(listener)}function getState() {return state;}// 改变数据function changeState(name) {state.name = name;listeners.map(fun => fun(name));}return {getState,subscribe,changeState,}}
封装完成,下面,我们开始使用。
const initState = {name: 'xiaoming',age: 13,}// 根据初始数据创建 storeconst Store = createStore(initState)// 读取值Store.getState();// 订阅值的修改Store.subscribe(() => console.log('看,store 的值改变了'))// 更改值Store.changeState('xioaLiu'); // 看,store 的值改变了
不难发现,我们现在只能通过 changState 方法来更改数据,并且只能更改 name 的值, 这显然不能满足我们的需求,我们尝试以一种新的方式来更改数据。
// 改变数据function changeState(newState = {}) {state = {...state,...newState,}listeners.map(item => item(newState));}changState({name: 'xiaoHua', age: 34})
通过更改 changeState 方法,我们可以做到自由更改想要更改的数据啦,但这样一来,在任何地方都可以更改任意的数据, 这无疑是很危险的事情。
这个时候我们做到了监听数据的每次变化,但每次的变化都可以是任意的,怎么解决?
制定一个规则,只能按照这个规则修改数据。
所以,我们决定制定一个规则,更改 name 或者 age ,都要遵循这个规则来改。
假如我们想要更改 name ,就这样调用:
changeState({type: 'CHANGE_NAME',name: 'xioaLi'})
传入一个对象,我们称之为 action,这个对象有一个 type 属性,表明我想更改 name。
当然,也可以有其他类型的 aciton。接下来,我们要实现更改数据的逻辑 —— 根据当前的 state 和传入的 action,返回一个新的 state。
function plan(state, action) {switch(action.type) {case 'CHANGE_NAME':return {...state,name: action.name}case 'CHANGE_AGE':return {...state,age: action.age}default:return state;}}
继续,我们需要改造 changeState 方法。
function createStore(plan, initState) {// ....// 改变数据function changeState(action) {state = plan(state,action)listeners.map(item => item());}return {getState,subscribe,changeState,}}
将 plan 作为参数传入 createStore 方法,在执行 changeState 的时候,通过 plan 来更改 state。
现在,我们将这个 plan 叫 reducer, changeState 叫 dispatch 。
合并多个 reducer
对于一个应用来说,如果把所有的 reducer 都定义在一起的话,无论是开发还是使用,都不太方便。通常我们会每个模块都会创建一个 reducer ,来负责当前模块的数据。
举一个简单的例子。
const initState = {one: {name: 'xiaoming',age: 13,},two: {name: 'xiaoHong',age: 12,}}
对于 one 、two 两个模块,分别对应着不同的 reduce。
const initStateone = {name: 'xiaoming',age: 13,}function reducerOne(state, action) {switch(action.type) {case 'a_CHANGE_NAME':return {...state,name: action.name}case 'CHANGE_AGE':return {...state,age: action.age}default:return state;}}function reducerTwo(state, action) {switch(action.type) {case 'CHANGE_NAME':return {...state,name: action.name}case 'CHANGE_AGE_2':return {...state,age: action.age}default:return state;}}
现在,我们想将这两个 reduce 合并起来使用。当我们 dispatch 一个 action 的时候,可以正确的修改各自模块下的 state。
我们假定有一个函数通过下面的方式调用可以实现。
const newReducer = combineReducer({one: oneReducer,two: twoReducer,})
试着实现下 combineReducer 函数。
function combineReducer(reducers) {// 合并之后还是需要返回一新的 reduce 函数return function(state, action) {// ['one', 'two']const keys = Object.keys(reducers);const newState = {}keys.forEach(item => {// 遍历执行每个 reduce,生成各自的 state,并组合起来生成新的 statenewState[item] = reducers[item](state[item], action)})// 最终返回一个新的 statereturn newState}}
接着我们只要把合并后的 reducer 传入 createStroe 方法,就可以了。
const store = createStore(newReducer, initState)
State 的拆分与合并
上面我们将 reducer 进行了合并,可以让我们只关心当前模块的 reducer,但在创建 Store 的时候,还是需要将初始化的所有 State 传入。但我们并不关其他的模块的 State, 接下来,再继续改进。
// 在自己的模块定义自己的初始 stateconst initState = {name: 'xiaoming',age: 12}// 当 state 为 undefined 的时候,取 initStatefunction reducerOne(state = initState, action) {switch(action.type) {case 'CHANGE_NAME':return {...state,name: action.name}case 'CHANGE_AGE':return {...state,age: action.age}default:return state;}}function createStore(reducer) {let state = {}// 改变数据function dispatch(action) {state = reducer(state,action)listeners.map(item => item());}// 在内部进行一次 dispatch 来初始化 statedispatch({type: Symbol()})return {getState,subscribe,dispatch,}}
这样,我们在创建 store 的时候不需要将整体的 initState 传入,而是在内部调用 dispatch 来生成一个初始的 state。
middleware
middleware 是干什么的?
是用来增强 dispatch 的功能的。既然是增强,说明 middleware 会在原来的 dispatch 的基础上,添加新的功能。我们试着自己增强一下。
1. 尝试打印记录 action
const store = createStore(reducer);const next = store.dispatch;store.dispatch = function(action) {console.log(action)next(action)}
以前的 dispatch只能派发 action , 而我们增强后的 dispatch 还可以打印当次的 action 。
2. 打印 state
const store = createStore(reducer);const next = store.dispatch;store.dispatch = function(action) {console.log('preState:',store.getState())console.log('action:',action)next(action)'console.log('nextState: ',store.getState())}
我们这个时候dispatch 已经有两个新增的功能了,他们分别是打印action 和打印 state。
现在,如果我们想添加第三个功能,第四个功能,第10 个功能时,怎么办?
我们需要封装一种逻辑,实现无论你想如何扩展 dispatch , 都可以随意扩展,而每一种扩展方式都是独立的。
3. 抽离上面两个方法
现在我们试着抽离上面两个方法。
打印 action
const actionLog = function(next) {return function(action) {console.log('action: ',action);next(action);}}
打印 state
const stateLog = funciton(next) {return function(action){console.log('preState:',store.getState())next(action);console.log('nextState: ',store.getState())}}
4. 使用抽离的方法
- 单独使用 ```javascript const store = createStore(reducer) const next = store.dispatch;
const actionLoger = actionLog(next);
store.dispatch = actionLoger;
2. 复合使用```javascriptconst store = createStore(reducer)const next = store.dispatch;const actionLogger = actionLog(next);const stateActionLogger = stateLog(actionLogger)store.dispatch = stateActionLogger;// 如果换一种写法,就是我们比较熟悉的洋葱圈(俄罗斯套娃)了store.dispatch = stateLog(actionLog(store.dispatch));
5. 还有一个问题
我们的 stateAction 其实是需要用到 store 的,前面由于我们写在同一个页面,所以使用的时候是通过作用域链找到的,但我们写中间件的时候是独立的,所以需要传入进来,那我们写中间件就可以写成这样。
const stateLog = (store) => (next) => (action) => {console.log('preState:',store.getState())next(action);console.log('nextState: ',store.getState())}
使用的时候就要这样
...const actionLogger = actionLog(store)const stateActionLogger = stateLog(store)store.dispatch = stateActionLogger(actionLogger(next))
6. 又来了几个中间件
如果后面还有其他的中间件,使用起来就成了这样。
const mid1 = mid1(store);const mid2 = mid2(store);const mid3 = mid3(store);const mid4 = mid4(store);store.dispatch = mid1(mid2(mid3(mid4(next))))
7. 干脆在创建 store 的时候就完成 dispatch 的增强
既然中间件是对 dispath 方法的增强,如果在一开始创建 store 的时候就完成最好了,这样一来,我们也不用再对 middleware 手动传入 store 。
假定我们有一个 applyMiddleware 方法,可以传入多个 middleware 和原始的 createStore 方法,然后返回一个新的 newCreateStore 方法。
function applyMiddleware = (middlwares) => (createStore) => (reducer, initState) => {const store = createStore(reducer);let dispatch = store.diapatchconst {getState,} = store;const chain = middlewars.forEach(middleware => middlerware(store))dispatch = compose(...chain)(store.disptach)store.dispath = dispatch;return store;}
