Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux源码分析 #33

Open
amandakelake opened this issue Mar 15, 2018 · 0 comments
Open

Redux源码分析 #33

amandakelake opened this issue Mar 15, 2018 · 0 comments

Comments

@amandakelake
Copy link
Owner

amandakelake commented Mar 15, 2018

一、compose

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

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)))
}

这是一个高阶函数,返回函数的函数被称为高阶函数
它的作用是通过传入函数引用的方式,从右到左依次调用函数,并把上一个函数的值作为下一个函数的参数
文档里面的注释写的很清楚

For example, compose(f, g, h) is identical to doing
(...args) => f(g(h(...args))).

再看被编译成ES5的写法,其中b.apply(underfined, arguments)是因为b()这里代表的是全局,所以要绑定underfined

return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });

二、bindActionCreators

主要实现的就是将ActionCreatordispatch进行绑定,官方的注释里面这么写的

Turns an object whose values are action creators, into an object with the same keys, but with every action creator wrapped into a dispatch call so they may be invoked directly.

翻译过来就是bindActionCreators将值为actionCreator的对象转化成具有相同键值的对象,但是每一个actionCreator都会被dispatch所包裹调用,因此可以直接使用

先看bindActionCreator,看清楚了,没有s的

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

返回一个新的函数,该函数调用时会将actionCreator返回的纯对象进行dispatch

接下来看bindActionCreators
代码的作用是对对象actionCreators中的所有值调用bindActionCreator,然后返回新的对象

export default function bindActionCreators(actionCreators, dispatch) {
  // 先判断是不是能返回想要的对象的函数,是就直接返回
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  // 如果不是对象,直接抛出错误
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }
  // 这段代码的作用是,遍历actionCreators,对其中的值全部调用bindActionCreator方法
  // 再由bindActionCreator对返回的纯对象进行dispatch
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

三、combineReducers

官方文档

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore

combineReducers函数总的来说是比较简单的,将大的reducer函数拆分成一个个小的reducer分别处理,

先看代码

// 传入一个object
export default function combineReducers(reducers) {
  // 获取该object的key值
  const reducerKeys = Object.keys(reducers)
  // 这里拿来放过滤后的reducers
  const finalReducers = {}
  // 这个循环的作用是过滤reducers
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 在开发环境下是否为underfined
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    // 将值类型是函数的值放进finalReducers
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // 很好,拿到了过滤后的reducers的key值
  const finalReducerKeys = Object.keys(finalReducers)

  // 在开发环境下判断,保存不期望 key 的缓存用以下面做警告,初始化为{}
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  // 如果抛出异常会将错误信息存储在shapeAssertionError,下面讲
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    // state 是否改变
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // 拿到相应的 key
      const key = finalReducerKeys[i]
      // 拿到key对应的reducer函数
      const reducer = finalReducers[key]
      // state 树下的 key 是与 finalReducers 下的 key 相同的
      // 所以在 combineReducers 中传入的参数的 key 即代表了 各个 reducer 也代表了各个 state
      const previousStateForKey = state[key]
      // 执行对应的reducer函数获得 对应的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 判断 state 的值,undefined 的话就报错
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 将state的值放进去
      nextState[key] = nextStateForKey
      // 判断state是否有改变
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 根据判断返回新的state
    return hasChanged ? nextState : state
  }
}

使用变量nextState记录本次执行reducer返回的state
hasChanged用来记录前后state是否发生改变。
循环遍历reducers,将对应的store的部分交给相关的reducer处理,当然对应各个reducer返回的新的state仍然不可以是undefined
最后根据hasChanged是否改变来决定返回nextState还是state,这样就保证了在不变的情况下仍然返回的是同一个对象。

总结来说就是接收一个对象reducers,将参数过滤后返回一个函数。该函数里有一个过滤参数后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。

再看一个使用到的辅助函数assertReducerShape

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    // 接下来要判断action为{ type: ActionTypes.INIT }时是否有初始值
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined. If you don't want to set a value for this reducer, ` +
        `you can use null instead of undefined.`
      )
    }

    // 对reduer执行一次随机的action,如果没有返回,则抛出错误,
    // 告知你不要处理redux中的私有的action,对于未知的action应当返回当前的state
    // The initial state may not be undefined, but can be null 初始值可以为null,但不能为underfined
    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

1、判断reducers中的每一个reduceraction{ type: ActionTypes.INIT }时是否有初始值,如果没有则会抛出异常。
2、对reduer执行一次随机的action,如果没有返回,则抛出错误,告知你不要处理redux中的私有的action,对于未知的action应当返回当前的state。并且初始值不能为undefined,但是可以是null

四、createStore

官方文档

function createStore(reducer, preloadedState, enhancer) {}

创建一个 Redux store 来以存放应用中所有的 state。
应用中应有且仅有一个 store。

有三个参数,reducer是处理后的reducer纯函数,preloadedState是初始状态,而enhancer使用相对较少,enhancer是一个高阶函数,用来对原始的createStore的功能进行增强。

先看它的核心代码

export default function createStore(reducer, preloadedState, enhancer) {
  // 如果没有传入参数enhancer,并且preloadedState的值又是一个函数的话
  // createStore会认为你省略了preloadedState,因此第二个参数就是enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    // 传入了enhancer但是却又不是函数类型。会抛出错误
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 一切都符合预期的话,那就先执行enhancer,再执行createStore
    return enhancer(createStore)(reducer, preloadedState)
  }
  // 如果传入的reducer也不是函数,抛出相关错误
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 当前reducer
  let currentReducer = reducer
  // 当前state
  // 在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它
  let currentState = preloadedState
  // 当前监听函数数组
  let currentListeners = []
  // 这里按照某大神的说法
  // 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证 currentListeners 数组不变
  // 可以考虑下只存在 currentListeners 的情况,如果我在某个 subscribe 中再次执行 subscribe
  // 或者 unsubscribe,这样会导致当前的 currentListeners 数组大小发生改变,从而可能导致
  // 索引出错
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    // 如果 currentListeners 和 nextListeners 相同,就赋值回去
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

	...其他代码省略了

  // 返回的就是一整个store,store里面包含了下面几个方法
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

接下来再看看它里面封装的几个方法

1、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?'
      )
    }

    // 这里主要是防止循环调用
    // 如果在reduder中做了dispatch,而dispatch又必然会导致reducer的调用,就会造成死循环
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 将isDispatching置为true,调用当前的reducer函数,并且返回新的state存入currentState,并将isDispatching置回去
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 依次调用监听者,但并不需要把新的state传给监听者,因为这里还有一个store.getState()方法可以获取最新的store
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

2、subscribe

subscribe用来订阅store的变化

  function subscribe(listener) {
    // 首先判断传入的listener是否是函数
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

	  // 该函数在下面
    // 用来判断nextListeners和currentListeners是否是完全相同
    // 如果相同(===),将nextListeners赋值为currentListeners的拷贝(值相同,但不是同一个数组)
    // 然后将当前的监听函数传入nextListeners
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // isSubscribed是以闭包的形式判断当前监听者函数是否在监听,从而保证只有第一次调用unsubscribe才是有效的
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }
  function ensureCanMutateNextListeners() {
    // 如果 currentListeners 和 nextListeners 相同,就赋值(快照)
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

为什么会存在nextListeners呢?
 
首先可以在任何时间点添加listener。无论是dispatch action时,还是state值正在发生改变的时候。但是需要注意的,在每一次调用dispatch之前,订阅者仅仅只是一份快照(snapshot),如果是在listeners被调用期间发生订阅(subscribe)或者解除订阅(unsubscribe),在本次通知中并不会立即生效,而是在下次中生效。因此添加的过程是在nextListeners中添加的订阅者,而不是直接添加到currentListeners,然后在每一次调用dispatch的时候都会做下面的赋值行为来同步currentListenersnextListeners

const listeners = currentListeners = nextListeners

3、getState

这个……emm👶

  function getState() {
    return currentState
  }

4、replaceReducer

热更新reducer用的,用的比较少

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

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

五、applyMiddleware

看它的代码之前,先加一点预备知识:柯里化函数,通常也称部分求值

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

举个例子

function add(a, b) {
    return a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2) // 3

还不知道的同学可以先去稍微补一下,大概知道柯里化函数干嘛的就行了

由于采用了ES6的结构赋值和箭头函数,applyMiddleware代码很短

// 整个applyMiddleware就是一个柯里化函数
// 所以正确的用法是applyMiddleware(...middlewares)(createStore)(...args)
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []
    // 每个中间件都有这两个函数
    // 还记得上面的createStore吗,它生成的store有五个函数,其中就包括这两
    const middlewareAPI = {
      getState: store.getState,
      // 注意,这个dispatch并不是那个传进来的dispatch,而是重新定义了一个匿名函数
      // (action) => dispatch(action),其实就是原来的dispatch的镜像
      // 如果所有的Middleware引用的都是原来的同一个dispatch(闭包),万一有一个不知名的中间件改写了dispatch呢?boom,链式爆炸……
      dispatch: (action) => dispatch(action)
    }
    // 每个中间件都应该传入middlewareAPI
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 从右到左依次调用每个中间件,传入store.dispatch
    dispatch = compose(...chain)(store.dispatch)
    // 返回对应的store和dispatch
    return {
      ...store,
      dispatch
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant