react-redux演化史——野史

639 阅读4分钟

需求1. 解决公共数据问题 (发布订阅模式):

1. react 需要一块存储公共数据的地方,各组件可以订阅该数据是否发生变化

function createStore(reducer, initialState) {
  let state = initialState;
  let listeners = [];

  function getState() {
    return state;
  }
  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }
  function subscribe(listener) {
    listeners.push(listener)
    return () => {
      listeners = listeners.filter(item => item !== listener)
    }
  }

  dispatch({ type: '@@REDUX/INIT' }) // 初始化 state 的值
 
  return { getState, dispatch, subscribe };
}

const redux = { createStore }
export redux

createStore: 创建一个发布订阅对象
dispatch: 改变数据
subscribe:监听获得改变的数据
getState:获取变化的数据

2. 这么让所有组件都能访问 store 这个发布订阅对象呢?

import { createStore } from 'redux';
const store = createStore(reducer)

3. 通过 <Provider store={store}> 将 store 存储到一个叫 ReactReduxContext 的对象中

import { Provider } from 'react-redux'

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)

<Provider store={store}> 在 react-redux 内部做了什么?
其实就是存储了一下 store, 见如下:

const ReactReduxContext = React.createContext();
const { Provider } = ReactReduxContext

Provider.context._currentValue = store;

createContext 其实就是返回一个对象,存储传入的值

function createContext() {
  const context = {}
  context.Provider = { context }
  return context
}

store 就这样存储到 ReactReduxContext 内部的
组件通过 connect 或 useSelector 获取 store 其实内也是直接取 ReactReduxContext 内的值

import { connect, useSelector } from 'react-redux'
// hooks组件
const state = useSelector((state) => state.counter1)
// 函数组件
connect(mapStateToProps, actionCreators)((state) => state)

需求2. store变成 多状态:

原来的:

const store = createStore(reducer)

改变前的 state 取 reducer 状态 store.getState() 现在的:

store = createStore(combineReducers({reducer1, reducer2}))

1. 改变后的 state 取某个状态 store.getState().counter1

2. combineReducers(reducer1, reducer2) 会返回一个 新reducer

const 新reducer = combineReducers({ reducer1, reducer2 })

3. 当执行 dispatch({ type: 'xxx' }) 改变数据时, 就会执行 新reducer(state, { type: 'xxx' }) ,新reducer会便利执行 reducer1, reducer2 传入 action 返回新的 state 状态

function combineReducers(reducers) {
  return function (state = {}, action) {
    const nextState = {}
    for (const key in reducers) {
      nextState[key] = reducers[key](state[key], action)
    }
    return nextState
  }
}

export default combineReducers;

需求3. 解决 { type: 'xxx' } 公用问题:

早期需要改变数据时就 dispatch({ type: 'xxx' }) 一下,使用地方多了就会出现 dispatch 参数相同的情况,改的话就要一次性改好几个地方

1. 公用就需要写成一个一个 action 函数,执行返回 { type: 'xxx', ... }

// actionCreators

function action1() {
    return { type: actionTypes.MINUS2 };
}
function action2(amount) {
    return { type: actionTypes.ADD2, payload: { amount } };
}


使用时 dispatch(action1()), dispatch(action2()) 即可,但是又发现个优化的点,每次执行都要用一个 dispatch 包裹,这个 dispatch 就显的很鸡肋了

2. 直接调用 action 函数后能不能直接触发 dispatch 呢? 能, bindActionCreators 就是用来干这个的

const actionCreators = { action1, action2 }
const boundActions = bindActionCreators(actionCreators, dispatch)
const { action1 } boundActions

// 使用
action1()
// 等价于
dispatch(action1())

bindActionCreators 通过高阶函数将 action1 做了一层包装:
action1 = () ⇒ { dispatch(action1(arguments)) }
bindActionCreators 原理如下:

function bindActionCreators(actionCreators, dispatch) {
  const boundActions = {}
  for (const key in actionCreators) {
    boundActions[key] = bindActionCreator(actionCreators[key], dispatch)
  }
  return boundActions
}

function bindActionCreator(actionCreator, dispatch) {
  return function () {
      return dispatch(actionCreator.apply(this, arguments));
  }
}

3. 后面发现 action 仅仅只返回 { type: 'xxx', ... } 这样的对象不太方便, 还需要支持返回函数,返回promise,返回其他乱七八糟的东西就好了

function action1() {
  // dispatch, getState 是哪来的? 是 redux-thunk 中间件 applyMiddleware 给的
  return function (dispatch, getState) { // 这里的 dispatch 是 重写后的store.dispatch
    setTimeout(() => {
      dispatch({ type: actionTypes.action1 });
    }, 1000);
  };
}
function action2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ type: actionTypes.action2 });
    }, 1000);
  });
}

需求4. 解决 action 支持返回各种类型:

react-redux 中间件 applyMiddleware 诞生了
原来是:

const store = createStore(combineReducers({reducer1, reducer2}))

现在是:

const store = applyMiddleware(thunk, promise, logger)(createStore)(combineReducers(reducer1, reducer2))

1. applyMiddleware 目的是改变了原 store 中的 dispatch

2. 为什么作者选择改变 dispatch,因为 dispatch 接收了 action 的返回值,dispatch 就可以对 action 返回不同内容做特定的处理

3. 作者想 action 返回什么是用户写的只有用户知道需要用什么来处理返回值,就像用户使用 sass 或 less 需选择打包他们的 webpack 插件

4. 那用户就需要自己配置插件如: { ’function‘: 插件1, ‘promise’: 插件2 } 返回函数执行插件1 返回promise执行插件2 但是这样配置会不会显得很 low

5. 作者心想我要怎么写才能既实现功能,代码又不通俗易懂让人感觉高大上呢?起码要把高级程序员的尿性体现出来,看看 express 想想洋葱模型,有了!

6. 将中间件先排好队 thunk, promise, logger, action函数的返回值依次交给他们处理

// const store = store = createStore(combineReducers(reducer1, reducer2))
原来的store.dispatch = (action返回值) => {
    // .'&^%^%~~啊啊!! action返回值不是 { type: 'xxx', ... } 的样子,我无能为力 呜呜呜~~
}

// const store = applyMiddleware(thunk, promise, logger)(createStore)(combineReducers(reducer1, reducer2))
重写后的store.dispatch = (action返回值) => {
    // 排好队的中间件
    function thunk(action返回值) {
        if (action返回值是我想要的) {
	    // thunk 自己处理
        } else {
            function promise(action返回值) {
		if (action返回值是我想要的) {
                    // promise 自己处理
		} else {
                    function logger(action返回值) {
			if (action返回值是我想要的) {
				// logger 自己处理
			} else {
                            dispatch(action返回值) // 最终执行原始的 dispatch
			}
                    }(action返回值)
                }
            }(action返回值)
        }
    }(action返回值) // 自执行
}

thunk, promise ... 这些中间件自己处理 action 返回值时,处理完成后会再次调用 重写后的store.dispatch

也就是外部调用一次 重写后的store.dispatch,则中间件里面会处理,处理后又调用 重写后的store.dispatch 一直递归下去, 直到 action 为 { type: 'xxx' } 类型才传递给原始的 dispatch 执行

后面发现支持 action 返回各种各样的类型最终还是为了执行一个 dispatch,有点太单调了,于是 redux-saga 就出世了

本系列代码链接
GitHubgithub.com/shunyue1320…
Giteegitee.com/shunyue/min…