JerrysBlog 2019-06-21
以react
来说,state
可以包含内部的状态以及业务数据,对于一个复杂的应用来说,state
非常难以管理,一个model
的变化可能引起另一个model
的变化...依次下去,造成了难以维护的情况,别人很难一下子摸透你的state
到底是怎么变得?所以需要引入一个东西来做数据管理,使数据变化变得清晰,redux
做的就是这件事情。假如说react
是士兵的话那么redux
就是将军,来管理士兵的状态。
Flux是facebook提出的一种架构思想,与react
一样,强调的是单向数据流,由三大部分组成:
store
来保存数据,一个数据对应一个store
,每一个store
有一个监听器
dispatcher
,含有两个方法:
register
,注册事件
dispatch
,分发一个action
使store
变化
view
,与flux
搭配不仅仅是react
还可以是vue
等,为当前页面数据的一个展现,与数据保持一致
flux
并不是一个mvc
框架,flux
没有明确的contoller
,但是却有着一个controller-view
,类似于mvvm
中的vm
(view=vm(model)
)。对于react
来说,也就是一个最外层的容器,store
全部作为该容器组件的state
(类似于<Provider>
),这样一来state
发生变化,就会重新渲染整个页面,从而达到更新view
的效果。得益于virtual dom
,每次并不用更新整个dom
树。(每次触发重绘会先在内存中对新的dom
树和老的dom
树进行diff
,把变化放入到patches
中,最后统一更改)。
flux
规定store
,store
对外不暴露任何修改数据的接口。
facebook提供的flux
包基本只有一个dispatcher
的实现,意味着我们需要为对每一个store
进行一次定义并且创建一个dispatcher
实例,需要register
一次,dispatch
时也需要区分不同的store
(听着就麻烦)。基于flux
思想的redux
为我们做了简化。
redux
提倡的是单一数据源,也就是一个应用就有一个store
,包含着许多的reducer
,reducer
根据传入的action
来决定怎么改变当前状态。关于redux
,大家可以直接去看文档,说的很清楚详细,下面来看一下redux
的源码:
index.js
为redux
的入口文件,暴露出来了redux
所提供的API
:
export { createStore, // 创建store combineReducers, // 合并reducer bindActionCreators, applyMiddleware, compose }
其中还有一个isCrushed
函数,其作用就是做环境的检测,假如在非生产环境中使用了压缩的redux
,则提出警告,判断方式也很简单:
isCrushed.name !== 'isCrushed' // 压缩后函数名字会变短
下面来一个一个的看redux
暴露出API
的实现:
compose
源自于函数式编程,redux
将其用于用于store
的增强,将传入的函数从右到左的执行,避免了层层嵌套,举个例子:
const x = 10, add = x => x + 10, sub = x => x - 20, sup = x => x * 10; // 原生方式嵌套实现 add( sup( sub( add(x) ) ) ); // 利用compose改进 const fn = compose(add, sup, sub, add); fn(x);
对比上面代码,利用compose
会使代码变得清晰,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
强调的是单一数据源,把所有的state
放入到一棵object tree
中,这棵树只能唯一的存在于一个store
中,也就是说redux
强调整个应用只有一个store
。store
可以包含
该模块依赖了lodash
的isPlainObject
,该函数用来判断对象是否继承了自定义类,实现起来非常简单:
function isPlainObject(val) { // 非对象的情况直接返回false if (!isObjectLike(value) || baseGetTag(value) != '[object Object]') { return false } const proto = Object.getPrototypeOf(value) // 针对Object.create(null)创建出来的对象 if (proto === null) { return true } const Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor // prototype.constructor === Object return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString }
// 内部的action,用于reset export const ActionTypes = { INIT: '@@redux/INIT' }; /* * 创建store * reducer reducer函数 * preloadedState 初始状态 * enhancer 增强函数,对createStoren能力进行增强,如devtools */ export default function createStore(reducer, preloadedState, enhancer) { // 参数修正 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState; preloadedState = undefined; } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.'); } // 返回已经增强后的store return enhancer(createStore)(reducer, preloadedState); } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.'); } // 记录当前值 let currentReducer = reducer; let currentState = preloadedState; // 监听store变化的监听器(一些回调函数) let currentListeners = []; let nextListeners = currentListeners; // 是否处于dispatch的过程中 let isDispatching = false; /**** 看下面文字解释 ****/ function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice(); } } // 给store新增一个监听器 function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.'); } let isSubscribed = true; ensureCanMutateNextListeners(); nextListeners.push(listener); // 返回一个卸载方法 return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false; ensureCanMutateNextListeners(); const index = nextListeners.index(listener); nextListeners.splice(index, 1); }; } // 返回当前的状态 function getState() { return currentState; } function dispatch(action) { // 一些类型检测 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true; // 根据recuder来更新当前状态 currentState = currentReducer(currentState, action); } finally { isDispatching = false; } const listeners = currentListeners = nextListeners; // 发布事件 for (let i = 0; i < listeners.length; i++) { const listener = listeners; listener(); } return action; } // 更新当前reducer,重置store function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.'); } currentReducer = nextReducer; dispatch({ type: ActionTypes.INIT }); } // 初始化store dispatch({ type: ActionTypes.INIT }); // 与Rxjs这种交互,根本看不懂-- function observable() { ... } return { getState, subscribe, dispatch, replaceReducer, [$$observable]: observable }; };
需要注意的有以下几点:
观察源码可以发现,currentListeners
与nextListeners
存储的都是监听函数,这样做的目的是保证dispatch
的过程不发生错误,加入使用一个队列的话,当执行过程中有监听函数被移除时,则会发生错误,如:当前监听函数队列为:[a, b, c]
,当执行回调函数a
后将其移除,则队列发生改变[b, c]
,而利用for
循环来执行回调,执行到i = 2
时会抛出错误。
forEach
forEach
比for
差在:
ie8
不兼容forEach
(ie8 is out)
forEach
不能提前终止,但是在dispatch
中无此问题
性能,这是forEach
最大的问题,for
要比forEach
快的多的多(差不多一倍左右),查看v8代码也可以感觉出,forEach
本质上做的还是for
循环,每次loop进行一次判断和函数调用,自然速度会慢。
中间件,express
与koa
也就中间件,express
中的中间件处理的请求到响应的这一过程,redux
中的中间件处理的是从action
发出到reducer
接收到action
这一过程,在这个过程中可以利用中间件进行写日志,处理异步操作等过程,作用就是来增强createStore
,给其添加更多的功能。先来看下下面这个简化的例子:
const calc = (obj) => obj.value + 20; // 简单的模拟下stroe const obj = { calc }; // 加强fn函数 function applyMiddleware(fn, middlewares) { middlewares = middlewares.slice(); middlewares.reverse(); // 每次改变fn,一次扩展一个功能 let { calc } = obj; middlewares.forEach( middleware => calc = middleware(obj)(calc) ); return calc; } // arrow function is cool!!! const logger = obj => next => num => { console.log(`num is ${num.value}`); let result = next(num); console.log(`finish calc, result is ${result}`); return result; }; const check = obj => next => num => { console.log(`typeof num.value is ${typeof num.value}`); let result = next(num); return result; }; const fn = applyMiddleware(obj, [check, logger]); fn({ value: 50 });
在上面简单的例子中为calc
做了两个功能扩展,执行起来就是一个高阶函数check->logger->calc
,理解了这个再来看看redux
的applyMiddleware
的实现:
export default function applyMiddleware(...middlewares) { // 利用闭包保存下来了middlewares return (createStore) => (reducer, preloadadState, enhancer) => { const store = createStore(reducer, preloadadState, enhancer); let dispatch = store.dispatch; let chain = []; // middleware不会改变store,利用闭包保存 const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; // chain中的元素仍未函数 // 接受的参数为`next` => 下一个中间件 chain = middlewares.map(middleware => middleware(middlewareAPI)); // 本身的dispatch放在最后执行 // dispatch仍未函数,接受的参数为`action` // 返回的是一个高阶函数,需要注意的是中间件并不会改变原本的action // dispatch变成了一个升级版 dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch }; }; };
附一张图:
applyMiddleware代码虽少,但是却是最难理解的部分
redux-thunk
是一个十分剪短有用的中间件,尤其是在异步应用中,利用redux-thunk
可以使action
变为一个函数,而不仅仅是一个对象,并且在该函数中仍然可以触发dispatch
:
// 让action可以成为函数 function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // action为函数类型,执行action,dispatch结束 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }
combineReducers
用来合并多个reducer
:
利用createStore
文件中定义的ActionTypes
来进行初始化state
,也就是定义的reducer
会在一开始执行一遍,而后对得到state
进行检测,为undefined
抛出异常,同时利用随机字符串测试,防止其处理type
为@@redux/INIT
而未处理default
的情况。
将我们所写的reducer
进行合并,返回一个函数,每次dispatch
时,执行函数,遍历所有的reducer
,计算出最终的state
:
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} // 有效reducer集合 // 验证reducer,必须是纯函数才有效 for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (NODE_ENV !== 'production') { unexpectedKeyCache = {} } let sanityError try { // 检测reducer assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } // getUnexpectedStateShapeWarningMessage // 检测state类型是否为0继承对象 // 用于找出多余的redcuer并给出警告 if (NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} // 每次发出一个action会遍历所有的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] // 保留之前的state与新生成的state做对比 const reducer = finalReducers[key] const previousStateForKey = state[key] const next StateForKey = reducer(previousStateForKey, action) // state为undefined抛出异常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 比较是否数据发生变化 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 未发生变化返回之前的数据 return hasChanged ? nextState : state } }
这个基本上的作用不大,唯一运用的情况就是将其作为props
传入子组件,对于子组件来说可以全然不知redux
的存在。如果利用UI
库的组件来操作页面状态,bindActionCreators
是一个很好的选择,实现很简单:
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) }
bindActionCreators
仅仅是遍历所有的actions
返回一个键值对。