本次学习redux
源码主要是通过示例图的形式,以便于读者更好的理解。
图上有,张三、李四、王五 三个人,张三想要修改仓库的状态进行
颜色
的的更改,李四和王五,监听仓库中的状态是否发生变化,如果状态发生了改变我要就要通知到这两个人(发布订阅)。
redux 的工作流
张三通过
dispatch({type:'changeColor,payload:'red'})
派发一个action(纯对象)
给我们的reducer(纯函数)
reducer 根据 老状态 返回 新状态,之后去通知李四
和王五
两个人(因为他们订阅了subscribe了状态的变化,所以会通知它们,至于他们进行什么操作,就不用管了)
纯函数 和 纯对象 的解释
纯函数:
- 没有副作用(一个函数在执行的过程中产生了外部可以观察到的变化)
1.基于 `DOM API`修改页面
2.修改全局变量、基于`AJAX`发送接口
...
- 引用透明(函数的返回值依赖于函数的入参)
纯对象
- 通过
new Object()
或者字面量
形式创建的对象,那源码里是如何验证的呢,其实就是基于 原型链的查找机制
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
redux 源码的目录结构
src
- utils (tools 工具方法库)
- applyMiddleware.js (中间件)
- bindActionCreators.js(绑定多个
action
的创建者) - combineReducers.js (合并多个reducer)
- compose.js (组合函数)
- createStore.js (创建的仓库方法)
正在更新中 ...
createStore
/**
* 创建仓库的方法,返回一个仓库,仓库就是一个JS对象
* @param {*} reducer 根据老状态,和动作计算下一个新状态
*/
const createStore = (reducer, preloadedState, enhancer) => {
if (typeof enhancer !== 'undefined') {
applyMiddleware(thunk, promise, logger)(createStore)
(combinedReducer,initialState);
return enhancer(createStore)(reducer, preloadedState);
}
let state = preloadedState;//可以存放任意的内容
let listeners = [];
function getState() {
return state;
}
// dispatch 派发把订阅好的东西,全都执行,进而更新store 重新
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(l => l());
return action;
}
// 发布订阅模式
function subscribe(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
}
// 默认走一次派发流程
dispatch({ type: '@@REDUX/INIT' });
return {
getState,
dispatch,
subscribe
}
}
export default createStore;
bindActionCreators
函数 bindActionCreator 的作用在于绑定 action 与 dispatch 这样就无须单独引入 dispatch 并手动调用 dispatch({type:'ADD'}),其在项目开发中的好处不言而喻:例如对于 react 展示型组件,内部则无须引入 dispatch ,取而代之的是由父组件传递而来的经过 bindActionCreators 包装过后的函数,使其充分解耦。
/*
actionCreators = {
add(){
return {type:'ADD'}
},
minus(){
return {type:'MINUS'}
}
}
or
actionCreatorFirst = add(){
return {type:'ADD'}
}
actionCreatorSecond = add(){
return {type:'ADD'}
}
正因为可以写成这两种方式,所以才会有以下判断 `actionCreators` 是否是一个函数。
*/
function bindActionCreator(actionCreator, dispatch) {
return function (...args) {
return dispatch(actionCreator.apply(this, args));
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
const boundActionCreators = {};
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
combineReducers
`redux` 规定只能有一个 reducer,只有一个仓库
在真实项目,我们不会把所有组件的业务逻辑都写到一个reducer里面,那怎么办呢
redux 开发者,早就给我们想到这一步了,以对象键值对的形式,对 reducer 进行一个合并
function combineReducers(reducers) {
return function (state = {}, action) {
let nextState = {};
for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
}
}
export default combineReducers;
##### compose
```js
/**
* 示例
* compose的作用就是组合函数,将函数串联起来执行,前一个函数的输出值是后一个函数的输入值
* function A(str) {
* return str + '界'
* }
* function B(str) {
* return str + '世'
* }
* function C() {
* return '你好'
* }
* // 示例 1
* let c = C();
* console.log('c',c)
* let b = B(c)
* console.log('b',b)
* let a = A(b)
* console.log('a',a)
* // 示例 2
* let res = compose(A, B, C); // => A(B(C))
* console.log(res(),'compose')
* ------------------------------------------ 打印结果
* c 你好
* b 你好世
* a 你好世界
* 你好世界 compose
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
redux-thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
可以像我们的 redux 派发方法,就加了这一行来判断是不是个 function
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
//=> 在这的时候,其实我就产生疑问了,你自己执行一下干啥,我们想一下,不自己执行一下行不行
我觉得,当然可以,但是它为啥自己还执行啊,其实就是=> 柯理化的编程思想,多个参数的传入,转化为
N个函数。
export default thunk;
redux-logger
日志中间件
function logger({ getState, dispatch }) {
return function (next) {
return function (action) {
console.log(store.getState())
next(action)
console.log(store.getState())
}
}
}
applyMiddleware
import compose from './compose'
//applyMiddleware(thunk, promise, logger)(createStore)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
// 这个错误的dispatch方法,其实就是为了,拿到改造后的 store.dispatch
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
//=> chain 和 dispatch 这两行代码尤为重要,深切阐释了,洋葱模型的实现原理
以此我们可以得出结论:next=>store.dispatch,他返回的那个函数就是我们改造后
的store.dispatch,=>中间件是什么=> console.log(中间件)=>是一个函数,其实
就是用来处理副作用的,因为reducer规定是纯函数,我们只能用 redux 中间件来处理
副作用
总结: 中间件是一个函数,在用户dispatch派发一个action到我们reducer纯函数之前
加上了一些自己的业务逻辑,实现原理=>AOP面向切片编程
return {
...store,
dispatch,
}
}