redux阅读

createStore

function (
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
){
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }
  function subscribe ...
  function dispatch...
  function replaceReducer...
  // 初始化state
  dispatch({ type: ActionTypes.REPLACE } as A)
  const store = ({
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
  return store
 }

做一些不符合规则的参数,如果初始化state是函数,并且enhancer没有定义,那就认为初始化State是undefined,enchance是第二个参数。如果enhancer是个函数那不是立马调用createStore,转交给enhancer去createStore enhancer(createStore)(reducer,preloadedState as PreloadedState<S>)

初始化state = currentState,如果调用getState()其实返回的是currentState。初始化会dispatch({ type:xx })也就是说,你可以不必传initialState这个额外的参数,只需要在reducers里面指定。type不中标的return值。

dispatch

function dispatch(action) {
      // 判断action对象
      if (!isPlainObject(action)) {
        throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
      }
      // 判断action type
      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;
        // 执行reducer
        currentState = currentReducer(currentState, action);
      } finally {
        isDispatching = false;
      }
      // 执行subscribe监听器
      // store.subscribe(() => store.getState() // state是最新的state)
      var listeners = currentListeners = nextListeners;
      for (var i = 0; i < listeners.length; i++) {
        var listener = listeners[i];
        listener();
      }
  
      return action;
    }

dispatch 触发一个action,首先,判断action是否为对象,判断action的type,其次判断isDispatching(这是一个flag,防止dispatch的reducer的时候,触发另外一个reducers,redux限制reducers同时只能触发一个)。

接下来,执行reducer函数。最后去执行subscribe监听器,注意这时候subscribe监听器可以拿到最新的state,也就是说store.subscribe(() => console.log(store.getState()) ),当前这个console是reducers改变state之后最新的值。

combineReducers

假设我们目前有combineReducers(reducer1,reducer2),如果我们要dispatch({ type: xxx }),那么reducer1,和reducer2哪个会执行?答案是:都会执行

function combineReducers(reducers) {
    var reducerKeys = Object.keys(reducers);
    var finalReducers = {};
  
    for (var i = 0; i < reducerKeys.length; i++) {
      var key = reducerKeys[i];
  
      {
        if (typeof reducers[key] === 'undefined') {
          warning("No reducer provided for key \"" + key + "\"");
        }
      }
  
      if (typeof reducers[key] === 'function') {
        finalReducers[key] = reducers[key];
      }
    }
  
    var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
    // keys multiple times.
  
    var unexpectedKeyCache;
  
    {
      unexpectedKeyCache = {};
    }
  
    var shapeAssertionError;
  
    try {
      assertReducerShape(finalReducers);
    } catch (e) {
      shapeAssertionError = e;
    }
  
    return function combination....

assertReducerShape,dispatch 一个action,type为INIT和UNLNOW_ACTION,获取初始值,判断初始值不为空

image.png

getUnexpectedStateShapeWarningMessage,1 判断reducers是否存在一个及以上。 2 判断state为一个对象。 3 判断state对应的key有响应的reducers处理

function combination(state, action) {
      if (state === void 0) {
        state = {};
      }
  
      if (shapeAssertionError) {
        throw shapeAssertionError;
      }
  
      {
        var warningMessage = getUnexpectedStateShapeWarningMessage(state, 
              finalReducers, action, unexpectedKeyCache);
  
        if (warningMessage) {
          warning(warningMessage);
        }
      }
  
      var hasChanged = false;
      var nextState = {};
  
      for (var _i = 0; _i < finalReducerKeys.length; _i++) {
        var _key = finalReducerKeys[_i];
        var reducer = finalReducers[_key];
        var previousStateForKey = state[_key];
        var nextStateForKey = reducer(previousStateForKey, action);
  
        if (typeof nextStateForKey === 'undefined') {
          var errorMessage = getUndefinedStateErrorMessage(_key, action);
          throw new Error(errorMessage);
        }
  
        nextState[_key] = nextStateForKey;
        hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
      }
  
      hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
      return hasChanged ? nextState : state;
 };

combinationcombineReducers最核心部分,combineReducers的返回值,首先从参数上去看就combination就是一个reducers。

当你执行一个dispatch(action)时,这个action会在每个模块reducers里面执行一次,2个重要的变量nextStateForKey(执行reducers后的state),previousStateForKey当前模块的state。
这里有一个非常重要的flag,hasChanged,hasChange初始值是false,当执行完reducers之后,会去判断对象的浅比较,如果nextStateForKeypreviousStateForKey不是同一个,则hasChange为true,返回值为更新的state即currentState = nextStateForKey。

Enhancer & Middleware

compose

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
// 看起来很绕,翻译成人话就是
function compose(...params) {
   let result = params.shift()
   params.forEach((current) => {
      let prev = result
      result = (...args) => {
        return current(prev(...args))
      }
   })
   return result
}

相当于compose(funcA, funcB, funcC) 形象为 compose(funcA(funcB(funcC())),这一层看起来还好理解,但是在Redux里面这个是非常绕的,虽然compose成了一个新的洋葱形函数,但是如果compose(funcA(funcB(funcC()))(1)(2),这样子的话是特别绕的因为新的函数compose(funcA(funcB(funcC()))执行后,返回值又是一个函数newCompose,那么这个函数再执行轨迹就是完全倒过来的。

applyMiddleware

如果我们正常写一个middleware是怎么样的:

function log({ getState, dispatch }) {
  console.log(1)
  return (next) => {
    console.log(2)
    return action => {
       console.log(action.type)
       next(action)
    }
  }
}

getState,和dispatch自然不必多说,next就是调用下一个函数。

初始化:第一次执行:初始化getState dispatch,第二次执行,初始化next的指向。等到调用action函数的时候才是dispatch真正的执行路径。

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    ...args: any[]
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch: Dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

这里分2个逻辑,一个createStore,一个是compose。createStore上述createStore函数有提到,enhancer为函数时,不直接初始化,而是丢给下面的enhancer函数。

接下来初始化middlewareAPI。把传入的middlewares合成一个chain,并且保存middlewareAPI。接着,改变dispatch的指向,dispatch变成middleware compose,真正的dispatch作为最后middleware的next传入进去。

这部分比较绕,可以看一下流程图:


readux-compose.png

顺便可以参考一下这里伪代码

以上,redux核心代码都分析完。百行代码千行文档,redux绕在他定的一些概念性的东西,只要理解了,也不难。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容