阅读 280

redux 源码解析与实际应用

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

  1. 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;
}
复制代码
  1. 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);
    };
}
复制代码
  1. store.getState (获取当前的state)
function getState() {
    if (isDispatching) {
      throw new Error('不允许在reducer执行中获取state');
    }
    // retuen 上次 dispatch 时所更新的 currentState
    return currentState;
}
复制代码
  1. 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

场景:

  1. 随着项目的增大与业务逻辑越来越复杂,数据状态与业务组件(WrappedComponent)也会越来越多,在初始化 store 的时候将所有reducer注入的话会使得资源很大
  2. 在入口将所有 reducer 注入的话,由于业务比较多,所以不一定都能用到,造成资源与性能浪费

方案:

利用 redux.combineReducersstore.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));
}
复制代码

时间旅行

场景:

  1. redux 是一个状态管理器,我们的对 action 以及 reducer 的操作,其实最终的目的就是为了更新状态,而时间旅行就是记录我们的状态更新的轨迹,并能回到某一个轨迹的节点。
  2. 比较容易理解的一个场景比如说翻页,记录每页的数据,页码变化的时候直接恢复数据不用再次请求接口(并不适合实际业务场景)
  3. 时间旅行更大的作用其实在于我们可以监控状态的变化,更方便的调试代码

方案:

利用 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();
        }
    });
};

复制代码
关注下面的标签,发现更多相似文章
评论