redux核心源码

466 阅读5分钟

前言

image.png

关于redux, 声明以下几点,便于理解

  • redux 是一个状态管理器。
  • redux 和 react 没有关系,redux 可以用在任何框架中。
  • connect 和 不属于 redux,属于 react-redux。
  • redux的专有名词,一个个看
  1. store
  2. createStore(返回 dispatch、getState、subscribe, replaceReducer)
  3. action
  4. reducer
  5. combineReducers
  6. middleware
  7. applyMiddleware

一、createStore

写在前面

明确store概念(包括但不限于state的读取, 还包括分发actions 和 订阅state变化)

* @returns {Store} A Redux store that lets you read the state, dispatch actions

* and subscribe to changes.

1、内部总览

把源码的注释和强大异常处理都干掉, 看下 createStore的结构, 如此清晰.

image.png
所以说 createStore就是一个用于创建 store 对象的函数,store包含 getState, dispatch, subscribe, replaceReducer, observable这些方法.

2、getState

  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
  }

源码就这么点, 作用就是获取当前state

3、subscribe

 let currentListeners = []
 let nextListeners = currentListeners
 
function subscribe(listener) {
    // 异常处理 直接跳过 主要看核心思想
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
        // 异常处理 直接跳过 主要看核心思想
    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
      )
    }

    let isSubscribed = true
        // 核心
    nextListeners.push(listener)
        // 取消订阅
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

订阅的核心 其实就是 nextListeners.push(listener) 这行, 把使用state的listener, 放到一个数组里, 美其名曰:“订阅”, 订阅的主要调用位置在dispatch里.

4、dispatch

  let currentReducer = reducer
  let currentListeners = []
    function dispatch(action) {
        
    try {
      isDispatching = true
      // 通过reducer触发action, 生成新的state
      currentState = currentReducer(currentState, action)
    } finally {         
      isDispatching = false
    }
        // nextListeners 就是上面subscribe的时候存放的多个listener
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      // 挨着个调用listener, 以达到通知使用者state更新的目的
      listener()
    }
    return action
  }

       dispatch源码的核心部分是这些, 作用就是 通过当前reducer生成新的state, 然后通知各个state的使用者

5、replaceReducer

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

    currentReducer = nextReducer

    // This action has a similiar effect to ActionTypes.INIT.
    // Any reducers that existed in both the new and old rootReducer
    // will receive the previous state. This effectively populates
    // the new state tree with any relevant data from the old one.
    dispatch({ type: ActionTypes.REPLACE })
  }

redux提供了一个替换当前reducer的方法, 实际使用场景很少

6、observable

  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

参考 reactivex.io/rxjs/class/…

二、Reducer

1、概念

reducer: 是一个根据action的‘type’, 接收老的 state,返回新的 state的函数(switch case)

action: action 是一个对象,必须包含 type 字段

combineReduces: 合并多个reducer, 返回 state

2、redux-demo-async源码分析

import { combineReducers } from 'redux';
import {
    INVALIDATE_SUBREDDIT,
    RECEIVE_POSTS, REQUEST_POSTS, SELECT_SUBREDDIT
} from '../actions';

const selectedSubreddit = (state = 'reactjs', action) => {
  switch (action.type) {
    case SELECT_SUBREDDIT:
      return action.subreddit
    default:
      return state
  }
}

const posts = (state = {
  isFetching: false,
  didInvalidate: false,
  items: []
}, action) => {
  switch (action.type) {
    case INVALIDATE_SUBREDDIT:
      return {
        ...state,
        didInvalidate: true
      }
    case REQUEST_POSTS:
      return {
        ...state,
        isFetching: true,
        didInvalidate: false
      }
    case RECEIVE_POSTS:
      return {
        ...state,
        isFetching: false,
        didInvalidate: false,
        items: action.posts,
        lastUpdated: action.receivedAt
      }
    default:
      return state
  }
}

const postsBySubreddit = (state = { }, action) => {
  switch (action.type) {
    case INVALIDATE_SUBREDDIT:
    case RECEIVE_POSTS:
    case REQUEST_POSTS:
      return {
        ...state,
        [action.subreddit]: posts(state[action.subreddit], action)
      }
    default:
      return state
  }
}
/**
 * 相当于 const rootReducer = combineReducers({
            postsBySubreddit: postsBySubreddit
            selectedSubreddit: selectedSubreddit
        })
 *  里面是state: stateReduceHandle
 */
const rootReducer = combineReducers({
  postsBySubreddit,
  selectedSubreddit
})

export default rootReducer

可以清晰的看到reducer的样子, 我们直接来看核心 combineReducers

3、combineReducers

//去掉警告和异常处理之后的核心部分
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    // 健壮校验, 保证传进来的reducer都是function
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  
  const finalReducerKeys = Object.keys(finalReducers)

  // 返回的这个combination, 就是 createStore中的 currentReducer(currentState, action)
  return function combination(state = {}, action) {
    let hasChanged = false
    // 这个就是最终返回的state
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      /** 这是state本身的形状
       * state: {
       *    reducerKey1: {
       *    
       *    },
       *    reducerKey2: {
       *    
       *    },
       *    ...
       * }
       */
      // 根据key先获取到当前reducer要更改的state
      const previousStateForKey = state[key]
      // 调用当前reducer生成新的state
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    // 返回state
    return hasChanged ? nextState : state
  }
}

所以, 其实 combineReducers 所做的就是把多个reducer合并成一个总的combination reducer, 而这个 combination reducer 会在 createStore里作为currentReducer调用 以生成最新的state ,然后在通知到各个使用state的地方,吧啦吧啦....

三、Middleware

1、概念

        官方这个注释还是很清楚的, enhancer 是用来增强store的, 可选的方式有 中间件、时间旅行、持久性等. redux提供的唯一store增强函数就是 applyMiddleware.

image.png

中间件: 也就是用来增强store功能的, 具体的说,是扩展dispatch的函数

2、Applymiddleware

先上源码

// =====index.js===========
import { createLogger } from 'redux-logger'
const middleware = [ thunk ]
if (process.env.NODE_ENV !== 'production') {
  // middleware 是个数组, 
  middleware.push(createLogger())
}

// 调用applyMiddleware (函数调用的'...‘: 扩展运算符, 把数组转为用逗号分隔的参数序列)
const store = createStore(
  reducer,
  applyMiddleware(...middleware) 
);

// =====createStore.js===========
// export default function createStore(reducer, preloadedState, enhancer) {
//   ...省略,
//   if (typeof enhancer !== 'undefined') {
//     if (typeof enhancer !== 'function') {
//       throw new Error('Expected the enhancer to be a function.')
//     }
//       enhancer 就相当于applyMiddleware, 可以看到接受createStore作为第一个参数
//     return enhancer(createStore)(reducer, preloadedState)
//   }
//   ...省略,
// }

// =====applyMiddleware.js===========
// 函数入参的‘...’: 剩余参数运算符, 将一个不定数量的参数表示为一个数组
export default function applyMiddleware(...middlewares) {
  // 返回一个接收createStore作为参数的函数, args实际上是createStore中enhancer的第二个参数(reducer, preloadedState) 
  return createStore => (...args) => {
    // 获取到当前store
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // middlewares 外边调用的时候展开,里面使用的时候又合成数组, 好处就是写法简单
    // 给每个middleware传入middlewareAPI, 原因得看middleware的长相(写在第三点)
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 那上面得到的就是多个符合redux要求的 middleware chain
    // 接下来看compose 这个函数 (写在第四点)
    // 把store.dispatch透传给多个middleware
    dispatch = compose(...chain)(store.dispatch)
    // 更新store里的dispatch, 至此完成原有dipatch的增强
    return {
      ...store,
      dispatch
    }
  }
}

3、middleware

image.png

image.png

//这个是 Redux.Middleware 的长相

 /** @template DispatchExt  Extra Dispatch signature added by this middleware.
 * @template S  The type of the state supported by this middleware.
 * @template D  The type of Dispatch of the store where this middleware is installed.
 */
// 可以看到 Redux的对middleware的入参要求 MiddlewareAPI<D, S>
export interface Middleware<
  DispatchExt = {},
  S = any,
  D extends Dispatch = Dispatch
> {
  (api: MiddlewareAPI<D, S>): (
    next: Dispatch<AnyAction>
  ) => (action: any) => any
}

4、compose

源码非常精炼

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
    // 如果只有一个中间件,就直接返回它
  if (funcs.length === 1) {
    return funcs[0]
  }
    // 这个写法太骚了 reduce里面curry(先看第五点)
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

5、Curry

A curried function is a function that takes multiple arguments

one at a time.

Given a function with 3 parameters, the curried version will take one argument and return a function that takes the next argument, which returns a function that takes the third argument. The last function returns the result of applying the function to all of its arguments.

柯里化函数是一次接受多个参数的函数。给定一个具有3个参数的函数,当前版本将使用一个参数,并返回一个使用下一个参数的函数,该函数返回一个使用第三个参数的函数。最后一个函数返回将函数应用于所有参数的结果。

// =======demo1
const add = a => b => a + b;
const result = add(2)(3); // => 5

// =======demo2
const g = n => n + 1;
const f = n => n * 2;
const h = x => f(g(x));
h(20); //=> 42
据此 可以写出这么个函数 
const compose = (g,f) => x => f(g(x));
如果参数很多怎么办? 肯定不能像这么一直写...
const compose1 = (g,f,h,j) => x => j(h(f(g(x))));

// compose2 容易理解, 第一次调用,返回一个接收x作为初始值的reduce函数,x作为reduce函数的init值
const compose2 = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
// compose3 相当于把 compose1 的写法放进 reduce
const compose3 = (...funcs)=> funcs.reduce((a,b) => (...args) => a(b(...args)))
//使用: 
var add1 = x => x+1
var add6 = x => x+6
var mul =   o => o *100
var test2 = compose2(add1,add6,mul);
var test3= compose3(add1,add6,mul);
test2(10) // => 1007
test3(10) // => 1007

// =======demo3
const map = fn => mappable => mappable.map(fn);
可以给每个数组element依次调用fn
使用1:
var add6 = x => x+6
var testMap = map(add6)
var arr = [1,2,4,5]
testMap(arr) // => [7,8,10,11]
使用2:
const arr = [1, 2, 3, 4];
const isEven = n => n % 2 === 0;
const stripe = n => isEven(n) ? 'dark' : 'light';
const stripeAll = map(stripe);
const striped = stripeAll(arr); 
log(striped);
// => ["light", "dark", "light", "dark"]

总结:

还是这张图

image.png