Redux
对于 Redux 结构与代码实现的剖析,以及项目中的高级用法,不进行对于API的介绍
createStore
- createStore是一个函数,接收三个参数
recdcer,initState,enhancer
enhancer
是一个高阶函数,用于增强create出来的store,他的参数是createStore
,返回一个更强大的store生成函数。(功能类似于middleware)。- 我们mobile仓库中的
storeCreator
其实就可以看成是一个enhancer,在createStore的时候将saga揉入了进去只不过不是作为createStore的第三个参数完成,而是使用middleware
完成。
function createStore(reducer, preloadedState, enhancer) { if (typeof enhancer !== 'undefined') { // createStore 作为enhancer的参数,返回一个被加强的createStore,然后再将reducer, preloadedState传进去生成store return enhancer(createStore)(reducer, preloadedState); } // ...... return { dispatch: dispatch, subscribe: subscribe, getState: getState, replaceReducer: replaceReducer }; }
- applyMiddleware 与 enhancer的关系。
- 首先他们两个的功能一样,都是为了增强store
- applyMiddleware的结果,其实就是一个enhancer
function applyMiddleware() {
// 将传入的中间件放入middlewares
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
// return了一个 enhancer函数,参数为createStore,内部对store进行了增强
return function (createStore) {
return function () {
// 将createStore的参数传入createStore,并生成store
var store = createStore.apply(undefined, args);
// 增强 dispatch
var _dispatch = compose.apply(undefined, chain)(store.dispatch);
// return 一个被增强了dispatch的store
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
store
store有四个基础方法: dispatch、subscribe、getState、replaceReducer
- store.dispatch (发起action)
function dispatch(action) {
// 校验 action 格式是否合法
if (typeof action.type === 'undefined') {
throw new Error('action 必须有type属性');
}
// 不可以在 reducer 进行中发起 dispatch
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
// 标记 dispatch 状态
isDispatching = true;
// 执行相应的 reducer 并获取新更新的 state
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
// 把上次subscribe时得到的新的监听函数列表,赋值成为当前的监听函数列表
var listeners = currentListeners = nextListeners;
// dispatch 的时候会依次执行 nextListeners 的监听函数
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
- store.subscribe (用于监听 store 的变化)
function subscribe(listener) {
// 如果是在 dispatch时注册subscribe,抛出警告
if (isDispatching) {
throw new Error('......');
}
// 将监听函数放入一个队列
nextListeners.push(listener);
// return 一个函数,用于注销监听事件
return function unsubscribe() {
// 同样的,不能再 dispatch 时进行注销操作
if (isDispatching) {
throw new Error('......');
}
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
- store.getState (获取当前的state)
function getState() {
if (isDispatching) {
throw new Error('不允许在reducer执行中获取state');
}
// retuen 上次 dispatch 时所更新的 currentState
return currentState;
}
- store.replaceReducer (提换当前的reducer)
function replaceReducer(nextReducer) {
// 检验新的 reducer 是否是一个函数
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
// 替换掉当前的 reducer
currentReducer = nextReducer;
// 发起一次新的 action, 这样可以使 sisteners 函数列表执行一遍,也可以更新一遍 currentState
dispatch({ type: ActionTypes.REPLACE });
}
combineReducers(组合reducer)
用于将多个reducer组合成一个reducer,接受一个对象,对象的每个属性即是单个reducer,各个reducer的key需要和传入该reducer的state参数同名。
function combineReducers(reducers) {
// 所有传入 reducers 的 key
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
// 遍历reducerKeys,将合法的 reducers 放入 finalReducers
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
// 可用的 reducers的 key
var finalReducerKeys = Object.keys(finalReducers);
var unexpectedKeyCache = void 0;
{
unexpectedKeyCache = {};
}
var shapeAssertionError = void 0;
// 将每个 reducer 都执行一遍,检验返回的 state 是否有为undefined的情况
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
// return 一个组合过的 reducer 函数,返回值为 state 是否有变化
return function combination() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];
// 如果有返回的state不合法的reducer,抛出错误
if (shapeAssertionError) {
throw shapeAssertionError;
}
{
// 校验 state 与 finalReducers 的合法性
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
// 遍历所有可用的reducer,将reducer的key所对应的state,代入到reducer中调用
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
// reducer key 所对应的 state,这也是为什么 reducer 名字要与 state 名字相对应的原因
var previousStateForKey = state[_key];
// 调用 reducer
var nextStateForKey = reducer(previousStateForKey, action);
// reducer 返回了新的 state,调用store.getState时返回的就是他
nextState[_key] = nextStateForKey;
// 新旧 state 是否有变化 ?
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
bindActionCreators
其实就是改变action发起的方式,之前是dispatch的方式,用bindActionCreators将actionCreator包装后,生成一个key为actionType,value为接受 payload 的函数的对象,发起action的时候直接调用这里面名为跟action的type同名的函数
- 它的核心其实就是将actionCreator传入然后返回一个可以发起dispatch的函数,函数中的dispatch接受一个已经生成的action,和在使用它的时候传入的playload
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments));
};
}
- 将多个 actionCreators 进行包装,最终返回一个被包装过的actionCreators
function bindActionCreators(actionCreators, dispatch) {
// 如果传入一个函数,说明只有一个,actionCreator,返回一个可以进行 dispatch 的函数
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if ((typeof actionCreators === 'undefined' ? 'undefined' : _typeof(actionCreators)) !== 'object' || actionCreators === null) {
throw new Error('校验actionCreators是否是对象');
}
// 检索出 actionCreators 的 key
var keys = Object.keys(actionCreators);
var boundActionCreators = {};
// 循环将 actionCreators 中的项用 bindActionCreator 包装一遍,放入 boundActionCreators 对象中并return
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
compose
将多个函数组合成一个,从右往左依次执行
function compose() {
// 获取传入参数的映射
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
// 如果参数为0,return 一个 所传即所得的函数
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
// 如果只有一个,返回此参数
if (funcs.length === 1) {
return funcs[0];
}
// 使用 reduce 将所有传入的函数组合为一个函数,每一次执行reduce,a作为前一个函数都会被这个return的函数重新赋值
return funcs.reduce(function (a, b) {
// 每次执行 reduce 都会返回这个函数,这个函数里返回的前一个函数接受下一个函数的返回值作为参数
return function () {
return a(b.apply(undefined, arguments));
};
});
}
applyMiddleware (增强dispatch)
其实applyMiddleware就是将传入的中间件进行组合,生成了一个接受 createStore为参数的函数(enhancer)。
// applyMiddleware将传入的中间件组合成一个enhancer
// 然后再传入createStore改造成一个增强版的createStore
// 最后传入reducer 和 initialState 生成 store。
const store = applyMiddleware(...middlewares)(createStore)(reducer, initialState);
// 其实跟这样写没什么区别
const store = createStore(reducer, initialState, applyMiddleware(...middlewares));
- 代码分析
function applyMiddleware() {
// 将传入的中间件组合成一个数组
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
// 返回一个接受 createStore 为参数的函数,也就是 enhancer
return function (createStore) {
// 其实这就是最终返回的被增强的 createStore
return function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
// 生成一个 store
var store = createStore.apply(undefined, args);
// 声明一个_dispatch,用于替换 store 的 dispatch
var _dispatch = function dispatch() {
throw new Error('不允许在构建中间件时进行调度');
};
// 返回一个middlewareAPI,下一步将会被带入中间件,使得每一个中间件中都会有 getState 与 dispatch (例如redux-thunk)
// 这里面的 dispatch中,将会执行_dispatch(被增强的dispatch)
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
// 每一个中间件都执行一遍 middlewareAPI
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
// 将 chain 用compose进行组合,所以传入的中间件依赖必须是倒序的
// 并传入 store.dispatch,生成一个被增强的 dispatch
_dispatch = compose.apply(undefined, chain)(store.dispatch);
// 生成 store, 使用 _dispatch 替换 store 原始的 dispatch
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
结合中间件redux-thunk
感受一下applyMiddleware
redux-thunk 可以使dispatch接受一个函数,以便于进行异步操作
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
{},
applyMiddleware(reduxThunk),
);
- 源码
function createThunkMiddleware(extraArgument) {
// reuturn 一个接受dispatch, getState的函数,
// 这个函数返回的函数又接受上一个中间件的返回值,也就是被上一个中间件包装过的dispatch
// 如果接受的action是个函数,那么就将dispatch, getState传进去
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
export default thunk;
- 如果把thunk跟applyMiddleware组装起来,就是这样的
function applyMiddleware() {
...
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
var chain = middlewares.map(function () {
// 这是middleware将middlewareAPI传进去后return的函数
return function(next) {
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
}
});
// 将store.dispatch,也就是next传进去
_dispatch = compose.apply(undefined, chain)(store.dispatch);
}
react-redux
用于绑定react 与 redux,其主要提供了两个功能
Provider
用于包装组件树,将store传入context中,使其子节点都可以拿到store,不需要一级一级的往下传。
class Provider extends Component {
// 将 store 放入 context 中
getChildContext() {
return { store: this.store}
}
constructor(props, context) {
super(props, context)
this.store = props.store;
}
render() {
return Children.only(this.props.children)
}
}
connect
connect 用于state与容器组件之间的绑定。
connect 接受三个参数mapStateToProps, mapDispatchToProps, mergeProps
用于定义需要传入容器组件的state与dispatch,然后return一个接受容器组件的函数(高阶组件),这个高阶函数会对将组合好的props混入进容器组件。
var containerComponent = connect(mapStateToProps,mapDispatchToProps)(someComponent);
ReactDOM.render(
<Provider store={store}>
<HashRouter>
<div>
<Route exact path="/" component={containerComponent} />
</div>
</HashRouter>
</Provider>,
document.querySelector('.doc')
);
- connect 接收的参数
- mapStateToProps 返回需要传入容器组件的 state 的函数
const mapStateToProps = (state) => { return { stateName: state[stateName], }; }
- mapDispatchToProps 返回需要传入容器组件dispatch的函数 or 对象(如果是对象的话传入的需要是个actionCreator,因为connect 内会用 bindActionCreators 将这个对象包装)
// mapDispatchToProps 是个函数 const mapDispatchToProps = (dispatch) => { return { dispatchName: (action) => { dispatch(action); }, }; } // or 当 mapDispatchToProps 是个对象时源码中的处理 export function whenMapDispatchToPropsIsObject(mapDispatchToProps) { return (mapDispatchToProps && typeof mapDispatchToProps === 'object') ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch)) : undefined }
- mergeProps 规定容器组件props合并方式的函数
// 默认是将 mapStateToProps, mapDispatchToProps 与组件自身的props进行 merge const mergeProps = (stateProps, dispatchProps, ownProps) => { return { ...ownProps, ...stateProps, ...dispatchProps }; }
- connect 内的核心函数
-
finalPropsSelectorFactory(dispatch, {...options})
return 一个
pureFinalPropsSelector
函数,这个函数接受两个参数,(state, props)并返回一个 mergeProps, 他将会在高阶组件wrapWithConnect
中使用并传入store.getState()和props,并以此对比当前的 props 以决定在shouldComponentUpdate
时是否需要更新 -
connectAdvanced(finalPropsSelectorFactory)
return 一个接受 容器组件为参数的高阶组件(wrapWithConnect)。 wrapWithConnect需要的变量与属性,这也就是
connect
最终 return 的结果。 -
wrapWithConnect(WrappedComponent)
这个就是被
connectAdvanced
返回的高阶组件,其接受一个容器组件作为参数,在内部创建一个Connect
组件并在 render 的时候将整合好的 props 传入 容器组件。 -
hoistStatics(a, b) 将 b 的属性复制到 a
用于在包装组件的时候,将传入的容器组件内的属性都复制到 Connect 组件
function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) { // 如果传入的 b 是字符串,直接return a if (typeof sourceComponent !== 'string') { // 层层递归,直到拿到 sourceComponent 的构造函数 var inheritedComponent = Object.getPrototypeOf(sourceComponent); if (inheritedComponent && inheritedComponent !== Object.getPrototypeOf(Object)) { hoistNonReactStatics(targetComponent, inheritedComponent, blacklist); } // b 的所有自有属性的 key ,包括 Symbols 属性 var keys = Object.getOwnPropertyNames(sourceComponent); keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent)); // 过滤掉某些属性,并将 b 的属性复制给 a for (var i = 0; i < keys.length; ++i) { var key = keys[i]; if (!REACT_STATICS[key] && !KNOWN_STATICS[key] && (!blacklist || !blacklist[key])) { var descriptor = Object.getOwnPropertyDescriptor(sourceComponent, key); Object.defineProperty(targetComponent, key, descriptor); } } // return 一个被添加了 b 的属性的 a return targetComponent; } return targetComponent; }
-
- connect 源码分析
function connect( mapStateToProps, mapDispatchToProps, mergeProps){
// 对传入的参数进行类型校验 与 封装
const initMapStateToProps = match(mapStateToProps, defaultMapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, defaultMapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, defaultMergePropsFactories, 'mergeProps')
// return 一个接受 容器组件 为参数的高阶组件(wrapWithConnect)
return connectAdvanced(finalPropsSelectorFactory)
}
// 接受的其实是 `finalPropsSelectorFactory`
function connectAdvanced(selectorFactory) {
const storeKey = 'store';
// 用于说明订阅对象
const subscriptionKey = storeKey + 'Subscription';
// 定义 contextTypes 与 childContextTypes 用于返回的高阶函数里的包装组件 Connect
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
}
// 返回一个高阶组件
return function wrapWithConnect(WrappedComponent) {
// 这是一个接受真假 与 提示语 并抛出错误的方法,这里用来校验传入的是否是个函数
invariant(typeof WrappedComponent == 'function', `You must pass a component to the function`)
// 将要传入 finalPropsSelectorFactory 的 option
const selectorFactoryOptions = {
getDisplayName: name => `ConnectAdvanced(${name})`,
methodName: 'connectAdvanced',
renderCountProp: undefined,
shouldHandleStateChanges: true,
storeKey: 'store',
withRef: false,
displayName: getDisplayName(WrappedComponent.name),
wrappedComponentName: WrappedComponent.displayName || WrappedComponent.name,
WrappedComponent
}
// 用于生成一个 selector,用于Connect组件内部的更新控制
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
// 比较 state 与 当前的selector的props,并更新selector
// selector 有三个属性:
// shouldComponentUpdate: 是否允许组件更新更新
// props: 将要更新的props
// error: catch 中的错误
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}
// 最终 return 的组件,用于包装传入的WrappedComponent
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.store = props['store'] || context['store']
this.propsMode = Boolean(props['store'])
// 校验是否传入了 store
invariant(this.store, `Could not find store in either the context')
this.initSelector()
this.initSubscription()
}
componentDidMount() {
// 会把 onStateChange 挂载到对store的订阅里
// 内部调用了 store.subscribe(this.onStateChange)
this.subscription.trySubscribe()
// 更新一遍 props
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
// 每次更新 props 都去对比一遍 props
componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}
// 根据 selector 来进行组件更新的控制
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}
// 初始化 selector,用于组件props更新的控制
initSelector() {
// 用于比较state与props的函数。并返回 merge 后的props
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}
// 初始化订阅模型: this.subscription
initSubscription() {
// 定义需要订阅的数据源,并将其传入 Subscription 生成一个 subscription 对象
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
// 数据的监听函数
onStateChange() {
this.selector.run(this.props)
}
// 将 selector.props 传入到传入的组件
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
return createElement(WrappedComponent, selector.props)
}
}
// 上面定义的 type 将作为 Connect 组件的属性
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes
// 将传入的组件的属性复制进父组件
return hoistStatics(Connect, WrappedComponent)
}
}
实用进阶
动态加载 reducer
场景:
- 随着项目的增大与业务逻辑越来越复杂,数据状态与业务组件(WrappedComponent)也会越来越多,在初始化 store 的时候将所有reducer注入的话会使得资源很大
- 在入口将所有 reducer 注入的话,由于业务比较多,所以不一定都能用到,造成资源与性能浪费
方案:
利用
redux.combineReducers
与store.replaceReducer
组合与更新reducer
// 初始化 store 的时候,将 reducer 记录下来
// initReducer: 初始化时的 reducer 对象
var reducers = combineReducers(initReducer);
const store = createStore(
reducers,
initState
);
store.reducers = initReducer;
// 加载子组件的时候,动态将新的 reducer 注入
function assignReducer(reducer) {
// 合并新老 reducer
const newReducer = Object.assign(store.reducers, reducer);
// 经 combineReducers 组合后进行替换
store.replaceReducer(combineReducers(newReducer));
}
时间旅行
场景:
- redux 是一个状态管理器,我们的对 action 以及 reducer 的操作,其实最终的目的就是为了更新状态,而时间旅行就是记录我们的状态更新的轨迹,并能回到某一个轨迹的节点。
- 比较容易理解的一个场景比如说翻页,记录每页的数据,页码变化的时候直接恢复数据不用再次请求接口(并不适合实际业务场景)
- 时间旅行更大的作用其实在于我们可以监控状态的变化,更方便的调试代码
方案:
利用 store.subscribe, 监听 dispatch 时记录下此时的 状态
const stateTimeline = [ initState ]; // 记录状态的时间线
let stateIndex = 0; // 当前所处状态的索引
// 当时间节点发生改变的时候,更替 state
const reducer = (state, action) => {
switch (action.type) {
case 'CHANGE_STATE_INDEX':
const currentState = action.playload.currentState;
return currentState;
default:
return state;
}
};
const saveState = () => {
// 将当前状态push进时间线
stateTimeline.push(store.getState);
stateIndex++;
};
// 注册监听事件
store.subscribe(saveState);
// 获取某个时间节点的 state
const getSomeNodeState = () => {
return stateTimeline[stateIndex];
};
// 时间线控制器
const timeNodeChangeHandle = (someIndex) => {
stateIndex = someIndex;
store.dispatch({
type: 'CHANGE_STATE_INDEX',
playload: {
currentState: getSomeNodeState();
}
});
};