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,
}
// 根据初始数据创建 store
const 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,并组合起来生成新的 state
newState[item] = reducers[item](state[item], action)
})
// 最终返回一个新的 state
return newState
}
}
接着我们只要把合并后的 reducer 传入 createStroe 方法,就可以了。
const store = createStore(newReducer, initState)
State 的拆分与合并
上面我们将 reducer 进行了合并,可以让我们只关心当前模块的 reducer,但在创建 Store 的时候,还是需要将初始化的所有 State 传入。但我们并不关其他的模块的 State, 接下来,再继续改进。
// 在自己的模块定义自己的初始 state
const initState = {
name: 'xiaoming',
age: 12
}
// 当 state 为 undefined 的时候,取 initState
function 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 来初始化 state
dispatch({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. 复合使用
```javascript
const 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.diapatch
const {
getState,
} = store;
const chain = middlewars.forEach(middleware => middlerware(store))
dispatch = compose(...chain)(store.disptach)
store.dispath = dispatch;
return store;
}