手写实现 createStore、bindActionCreators、combineReducers

696 阅读1分钟

createStore

这个 createStore 函数需要返回一个对象,包含以下属性:

  • dispatch:分发 action 去改变数据
  • getState:获取当前仓库中的状态
  • subscribe:注册一个监听器,分发 action 后触发;并返回一个取消监听的函数
  • replaceReducer:替换仓库中当前的 reducer
  • Symbol("observable"):内置函数,实现观察者模式 (暂不实现)

代码实现:

/**
 * 创建仓库
 * @param {function} reducer 
 * @param {any} defaultState 默认状态值
 */
export default function createStore(reducer, defaultState) {
    // 简单验证 reducer 是不是一个函数
    if (typeof reducer !== 'function') {
        throw new TypeError(`The first parameter reducer must be a function`)
    }

    let currentReducer = reducer // 当前使用的 reducer
    let currentState = defaultState // 当前状态值
    const subscribers = [] // 监听器数组

    function dispatch(action) {
        // 验证 action 类型
        if (!_isPlainObject(action)) {
            throw new TypeError(`The required parameter action must be a "Plain Object"`)
        }
        if (action.type === undefined) {
            throw new TypeError(`The required parameter action must have a property of "type"`)
        }
        currentState = currentReducer(currentState, action)
        // 执行所有的监听器
        subscribers.forEach(subscriber => {
            subscriber()
        })
    }

    function getState() {
        return currentState
    }

    // 添加监听器
    function subscribe(subscriber) {
        subscribers.push(subscriber)
        let isRemoved = false // 判定是否已移除
        return () => {
            if (isRemoved) return
            const index = subscribers.indexOf(subscriber)
            subscribers.splice(index, 1)
            isRemoved = true
        }
    }

    function replaceReducer(newReducer) {
        // 简单验证 reducer 是不是一个函数
        if (typeof newReducer !== 'function') {
            throw new TypeError(`The first parameter reducer must be a function`)
        }
        currentReducer = newReducer
    }

    // 创建仓库时,需要调用一次 dispatch 分发 action,完成状态初始化
    dispatch({
        type: `@@redux/INIT${_getRandomStr(7)}`
    })

    return {
        dispatch,
        getState,
        subscribe,
        replaceReducer
    }
}

涉及的辅助函数:

/**
 * 判断此对象是否是平面对象
 * @param {Object} o 目标对象
 * @returns boolean
 */
function _isPlainObject(o) {
    if (typeof o !== 'object') {
        return false
    }
    return Object.getPrototypeOf(o) === Object.prototype
}

/**
 * 得到一个指定长度的字符串,例如:'f.a.4.d.7'
 * @param {number} len 字串长度
 */
function _getRandomStr(len = 7) {
    // 转 36 进制,即:26 字母 + 10 数字
    return Math.random().toString(36).substr(2, len).split('').join('.')
}

bindActionCreators

通过传递两个参数:(1) action 创建函数action 创建函数的对象;(2) dispatch 函数

从而获得增强功能的 action 创建函数action 创建函数的对象

/**
 * 增强 action 创建函数
 * @param {*} actionCreators 
 * @param {*} dispatch 
 */
export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        return _getAutoDispatchActionCreator(actionCreators, dispatch)
    } else if (
        actionCreators !== null &&
        typeof actionCreators === 'object' &&
        !Array.isArray(actionCreators)
    ) {
        const res = {}
        for (const key in actionCreators) {
            if (Object.hasOwnProperty.call(actionCreators, key)) {
                const actionCreator = actionCreators[key]
                if (typeof actionCreator === 'function') {
                    res[key] = _getAutoDispatchActionCreator(actionCreator, dispatch)
                }
            }
        }
        return res
    } else {
        throw new TypeError(`The first parameter of actionCreators must be an "object" or "function" which means action creator`)
    }
}

/**
 * 得到自动分发 action 的创建函数
 * @param {*} actionCreators 
 * @param {*} dispatch 
 */
function _getAutoDispatchActionCreator(actionCreators, dispatch) {
    return (...args) => {
        const action = actionCreators(...args)
        dispatch(action)
    }
}

combineReducers

功能:组装 reducers,返回一个 reducer,数据使用一个对象表示,对象的属性名与传递的参数对象保持一致

调用它合并 reducers 时,每个 reducer 会分发两个特殊类型的 action,用于确保返回的 state 不是 undefined

实现如下:

/**
 * 合并 reducers
 * @param {Object}} reducers
 * @return {Funtion} reducer
 */
export default function combineReducers(reducers) {
  
  _validateReducers(reducers)
  
  return function (state = {}, action) {
    const newState = {}
    for (const key in reducers) {
      if (Object.hasOwnProperty.call(reducers, key)) {
        const reducer = reducers[key]
        newState[key] = reducer(state[key], action)
      }
    }
    return newState
  }
}

/**
 * 验证 reducers
 * @param {*} reducers
 */
function _validateReducers(reducers) {
  if (
    typeof reducers !== 'object' ||
    reducers === null ||
    Array.isArray(reducers)
  ) {
    throw new TypeError(`reducers must be an "object"`)
  }
  if (!isPlainObject(reducers)) {
    throw new TypeError(`reducers must be a "plain object"`)
  }
  // 验证每个 reducer 的返回结果是不是 undefined
  for (const key in reducers) {
    if (Object.hasOwnProperty.call(reducers, key)) {
      const reducer = reducers[key]
      let state = reducer(undefined, {
        type: `@@redux/INIT${_getRandomStr(7)}`
      })
      if (state === undefined) {
        throw new TypeError(`reducer returns state cannot be undefined`)
      }
      state = reducer(undefined, {
        type: `@@redux/PROBE_UNKNOWN_ACTION${_getRandomStr(7)}`
      })
      if (state === undefined) {
        throw new TypeError(`reducer returns state cannot be undefined`)
      }
    }
  }
}