需求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 就出世了
| 本系列代码 | 链接 |
|---|---|
| GitHub | github.com/shunyue1320… |
| Gitee | gitee.com/shunyue/min… |