Redux 是什么?是一个状态管理器。它实现了一套机制,用来管理应用中的状态,也就是数据。

首先,将整个应用的数据放入一个对象中,我们称这个对象为 Store。

假如我们有这样一个数据。

  1. let data = {
  2. name: 'xiaoMing'
  3. }

现在,我们想要改变这个数据,可以怎么做?

  1. data.name = 'xiaoHua';

这样可以,但我们想监听这个数据的改变,然后去做点其他事情,怎么做?不防试一试订阅-发布模式。

  1. let listeners = [];
  2. // 订阅
  3. function subscribe(listener) {
  4. listeners.push(listener)
  5. }
  6. // 改变数据
  7. changeData = (data, name) => {
  8. data.name = name;
  9. listeners.map(fun => fun(name));
  10. }

上面,我们定义了一个 subscribe 函数,用来添加订阅。每当我们使用 changeData 函数改变数据时,监听器函数就会分别执行。

  1. subscribe((name) => console.log(`看,它把 name 的值改成 ${name} 了。`))
  2. changeData(data, '小黑'); // 看,它把name 的值改成 小黑 了。

这样,我们就做到了监听数据变化的目的。

目前我们只是针对 data 这个数据,想要复用这套逻辑,需要进一步封装。

思考:复用逻辑是什么意思? 答:实现一个函数,可以对给定的任意数据,实现相同的逻辑。到我们这个例子中,就是对传入的一个数据,实现监听它变化的逻辑。

  1. // 封装一个 createStore 方法,来实现上面的更改数据监听机制
  2. function createStore(initState) {
  3. let state = { ...initState }
  4. let listeners = [];
  5. // 订阅
  6. function subscribe(listener) {
  7. listeners.push(listener)
  8. }
  9. function getState() {
  10. return state;
  11. }
  12. // 改变数据
  13. function changeState(name) {
  14. state.name = name;
  15. listeners.map(fun => fun(name));
  16. }
  17. return {
  18. getState,
  19. subscribe,
  20. changeState,
  21. }
  22. }

封装完成,下面,我们开始使用。

  1. const initState = {
  2. name: 'xiaoming',
  3. age: 13,
  4. }
  5. // 根据初始数据创建 store
  6. const Store = createStore(initState)
  7. // 读取值
  8. Store.getState();
  9. // 订阅值的修改
  10. Store.subscribe(() => console.log('看,store 的值改变了'))
  11. // 更改值
  12. Store.changeState('xioaLiu'); // 看,store 的值改变了

不难发现,我们现在只能通过 changState 方法来更改数据,并且只能更改 name 的值, 这显然不能满足我们的需求,我们尝试以一种新的方式来更改数据。

  1. // 改变数据
  2. function changeState(newState = {}) {
  3. state = {
  4. ...state,
  5. ...newState,
  6. }
  7. listeners.map(item => item(newState));
  8. }
  9. changState({name: 'xiaoHua', age: 34})

通过更改 changeState 方法,我们可以做到自由更改想要更改的数据啦,但这样一来,在任何地方都可以更改任意的数据, 这无疑是很危险的事情。

这个时候我们做到了监听数据的每次变化,但每次的变化都可以是任意的,怎么解决?

制定一个规则,只能按照这个规则修改数据。

所以,我们决定制定一个规则,更改 name 或者 age ,都要遵循这个规则来改。

假如我们想要更改 name ,就这样调用:

  1. changeState({
  2. type: 'CHANGE_NAME',
  3. name: 'xioaLi'
  4. })

传入一个对象,我们称之为 action,这个对象有一个 type 属性,表明我想更改 name。

当然,也可以有其他类型的 aciton。接下来,我们要实现更改数据的逻辑 —— 根据当前的 state 和传入的 action,返回一个新的 state。

  1. function plan(state, action) {
  2. switch(action.type) {
  3. case 'CHANGE_NAME':
  4. return {
  5. ...state,
  6. name: action.name
  7. }
  8. case 'CHANGE_AGE':
  9. return {
  10. ...state,
  11. age: action.age
  12. }
  13. default:
  14. return state;
  15. }
  16. }

继续,我们需要改造 changeState 方法。

  1. function createStore(plan, initState) {
  2. // ....
  3. // 改变数据
  4. function changeState(action) {
  5. state = plan(state,action)
  6. listeners.map(item => item());
  7. }
  8. return {
  9. getState,
  10. subscribe,
  11. changeState,
  12. }
  13. }

将 plan 作为参数传入 createStore 方法,在执行 changeState 的时候,通过 plan 来更改 state。

现在,我们将这个 plan 叫 reducer, changeState 叫 dispatch 。

合并多个 reducer

对于一个应用来说,如果把所有的 reducer 都定义在一起的话,无论是开发还是使用,都不太方便。通常我们会每个模块都会创建一个 reducer ,来负责当前模块的数据。

举一个简单的例子。

  1. const initState = {
  2. one: {
  3. name: 'xiaoming',
  4. age: 13,
  5. },
  6. two: {
  7. name: 'xiaoHong',
  8. age: 12,
  9. }
  10. }

对于 one 、two 两个模块,分别对应着不同的 reduce。

  1. const initStateone = {
  2. name: 'xiaoming',
  3. age: 13,
  4. }
  5. function reducerOne(state, action) {
  6. switch(action.type) {
  7. case 'a_CHANGE_NAME':
  8. return {
  9. ...state,
  10. name: action.name
  11. }
  12. case 'CHANGE_AGE':
  13. return {
  14. ...state,
  15. age: action.age
  16. }
  17. default:
  18. return state;
  19. }
  20. }
  21. function reducerTwo(state, action) {
  22. switch(action.type) {
  23. case 'CHANGE_NAME':
  24. return {
  25. ...state,
  26. name: action.name
  27. }
  28. case 'CHANGE_AGE_2':
  29. return {
  30. ...state,
  31. age: action.age
  32. }
  33. default:
  34. return state;
  35. }
  36. }

现在,我们想将这两个 reduce 合并起来使用。当我们 dispatch 一个 action 的时候,可以正确的修改各自模块下的 state。

我们假定有一个函数通过下面的方式调用可以实现。

  1. const newReducer = combineReducer({
  2. one: oneReducer,
  3. two: twoReducer,
  4. })

试着实现下 combineReducer 函数。

  1. function combineReducer(reducers) {
  2. // 合并之后还是需要返回一新的 reduce 函数
  3. return function(state, action) {
  4. // ['one', 'two']
  5. const keys = Object.keys(reducers);
  6. const newState = {}
  7. keys.forEach(item => {
  8. // 遍历执行每个 reduce,生成各自的 state,并组合起来生成新的 state
  9. newState[item] = reducers[item](state[item], action)
  10. })
  11. // 最终返回一个新的 state
  12. return newState
  13. }
  14. }

接着我们只要把合并后的 reducer 传入 createStroe 方法,就可以了。

  1. const store = createStore(newReducer, initState)

State 的拆分与合并

上面我们将 reducer 进行了合并,可以让我们只关心当前模块的 reducer,但在创建 Store 的时候,还是需要将初始化的所有 State 传入。但我们并不关其他的模块的 State, 接下来,再继续改进。

  1. // 在自己的模块定义自己的初始 state
  2. const initState = {
  3. name: 'xiaoming',
  4. age: 12
  5. }
  6. // 当 state 为 undefined 的时候,取 initState
  7. function reducerOne(state = initState, action) {
  8. switch(action.type) {
  9. case 'CHANGE_NAME':
  10. return {
  11. ...state,
  12. name: action.name
  13. }
  14. case 'CHANGE_AGE':
  15. return {
  16. ...state,
  17. age: action.age
  18. }
  19. default:
  20. return state;
  21. }
  22. }
  23. function createStore(reducer) {
  24. let state = {}
  25. // 改变数据
  26. function dispatch(action) {
  27. state = reducer(state,action)
  28. listeners.map(item => item());
  29. }
  30. // 在内部进行一次 dispatch 来初始化 state
  31. dispatch({type: Symbol()})
  32. return {
  33. getState,
  34. subscribe,
  35. dispatch,
  36. }
  37. }

这样,我们在创建 store 的时候不需要将整体的 initState 传入,而是在内部调用 dispatch 来生成一个初始的 state。

middleware

middleware 是干什么的?

是用来增强 dispatch 的功能的。既然是增强,说明 middleware 会在原来的 dispatch 的基础上,添加新的功能。我们试着自己增强一下。

1. 尝试打印记录 action

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. store.dispatch = function(action) {
  4. console.log(action)
  5. next(action)
  6. }

以前的 dispatch只能派发 action , 而我们增强后的 dispatch 还可以打印当次的 action

2. 打印 state

  1. const store = createStore(reducer);
  2. const next = store.dispatch;
  3. store.dispatch = function(action) {
  4. console.log('preState:',store.getState())
  5. console.log('action:',action)
  6. next(action)'
  7. console.log('nextState: ',store.getState())
  8. }

我们这个时候dispatch 已经有两个新增的功能了,他们分别是打印action 和打印 state。

现在,如果我们想添加第三个功能,第四个功能,第10 个功能时,怎么办?

我们需要封装一种逻辑,实现无论你想如何扩展 dispatch , 都可以随意扩展,而每一种扩展方式都是独立的。

3. 抽离上面两个方法

现在我们试着抽离上面两个方法。

  1. 打印 action

    1. const actionLog = function(next) {
    2. return function(action) {
    3. console.log('action: ',action);
    4. next(action);
    5. }
    6. }
  2. 打印 state

    1. const stateLog = funciton(next) {
    2. return function(action){
    3. console.log('preState:',store.getState())
    4. next(action);
    5. console.log('nextState: ',store.getState())
    6. }
    7. }

4. 使用抽离的方法

  1. 单独使用 ```javascript const store = createStore(reducer) const next = store.dispatch;

const actionLoger = actionLog(next);

store.dispatch = actionLoger;

  1. 2. 复合使用
  2. ```javascript
  3. const store = createStore(reducer)
  4. const next = store.dispatch;
  5. const actionLogger = actionLog(next);
  6. const stateActionLogger = stateLog(actionLogger)
  7. store.dispatch = stateActionLogger;
  8. // 如果换一种写法,就是我们比较熟悉的洋葱圈(俄罗斯套娃)了
  9. store.dispatch = stateLog(actionLog(store.dispatch));

5. 还有一个问题

我们的 stateAction 其实是需要用到 store 的,前面由于我们写在同一个页面,所以使用的时候是通过作用域链找到的,但我们写中间件的时候是独立的,所以需要传入进来,那我们写中间件就可以写成这样。

  1. const stateLog = (store) => (next) => (action) => {
  2. console.log('preState:',store.getState())
  3. next(action);
  4. console.log('nextState: ',store.getState())
  5. }

使用的时候就要这样

  1. ...
  2. const actionLogger = actionLog(store)
  3. const stateActionLogger = stateLog(store)
  4. store.dispatch = stateActionLogger(actionLogger(next))

6. 又来了几个中间件

如果后面还有其他的中间件,使用起来就成了这样。

  1. const mid1 = mid1(store);
  2. const mid2 = mid2(store);
  3. const mid3 = mid3(store);
  4. const mid4 = mid4(store);
  5. store.dispatch = mid1(mid2(mid3(mid4(next))))

7. 干脆在创建 store 的时候就完成 dispatch 的增强

既然中间件是对 dispath 方法的增强,如果在一开始创建 store 的时候就完成最好了,这样一来,我们也不用再对 middleware 手动传入 store

假定我们有一个 applyMiddleware 方法,可以传入多个 middleware 和原始的 createStore 方法,然后返回一个新的 newCreateStore 方法。

  1. function applyMiddleware = (middlwares) => (createStore) => (reducer, initState) => {
  2. const store = createStore(reducer);
  3. let dispatch = store.diapatch
  4. const {
  5. getState,
  6. } = store;
  7. const chain = middlewars.forEach(middleware => middlerware(store))
  8. dispatch = compose(...chain)(store.disptach)
  9. store.dispath = dispatch;
  10. return store;
  11. }