关于前端:人人能读懂redux原理剖析

62次阅读

共计 7858 个字符,预计需要花费 20 分钟才能阅读完成。

一、Redux 是什么?

家喻户晓,Redux最早使用于 React 框架中,是一个全局状态管理器。Redux解决了在开发过程中数据有限层层传递而引发的一系列问题,因而咱们有必要来理解一下 Redux 到底是如何实现的?

二、Redux 的核心思想?

Redux次要分为几个局部:dispatchreducerstate
咱们着重看下 dispatch,该办法是Redux 流程的第一步,在用户界面中通过执行 dispatch,传入绝对应的action 对象参数,action是一个形容类型的对象,紧接着执行 reducer,最初整体返回一个store 对象,咱们来看下这部分的源码:

// 主函数 createStore
// 返回一个 store 对象
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.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

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

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 获取最终的 state
  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
  }

  // dispatch
  // 参数 action
  function dispatch(action) {
      // 校验传入的 action
    // action 必须是个对象,否则抛出错误信息
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects.' +
          'Use custom middleware for async actions.'
      )
    }

    // 测验 action 对象的必要属性
    // type 属性是 action 对象必要的属性
    // 如果传入的 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 函数
      // 返回 state,给 currentState 赋值
      currentState = currentReducer(currentState, action)
    } finally {
        // 一个 dispatch 执行完,还原状态
      isDispatching = false
    }

    // 执行订阅函数队列
    // dispatch 执行的同时会一并执行订阅队列
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]
      listener()}

    // 返回 action
    return action
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.

  // 默认执行一次 dispatch,做初始化
  dispatch({type: ActionTypes.INIT})

  // 返回一个 store 对象
  return {
    dispatch,
    subscribe,
    getState,
    ...
  }
}

通过源码咱们能够根本分明,通过执行 createStore 办法,最终会返回一个 store 对象,该对象次要裸露几个属性,咱们次要关注比拟罕用的:dispatchgetStategetState,看下理论用例:

import createStore from 'redux'

// 创立一个 reducer
function reducer(state={}, action) {switch(action.type) {
        case 'TEST':
        return {
            ...state,
            test: 'test success'
        }
    }
}

// 返回 store
const store = createStore(reducer, initState={})

// 执行 dispatch
store.dispatch({type: 'TEST'})

const state = store.getState() // 返回 {test: 'TEST'}

三、Redux 中间件原理

接下来咱们来探讨 Redux 的另一个重要组成部分 — 中间件。什么是 Redux 的中间件?Redux中间件其实是通过重写 createStore 来加强和扩大原来的 dispatch 办法,使其可能在执行 dispatch 的同时能够同步执行其它办法,比方 redux-thunk 就是一个解决异步的中间件:

function createThunkMiddleware(extraArgument) {
    // 中间件规定格局
    // 闭包返回三层嵌套
  return ({dispatch, getState}) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

下载了中间件,那么咱们来看下如何应用中间件:

import createStore, {applyMiddleWare} from 'reduximport reduxThunk from'redux-thunk'// 创立一个 reducerfunction reducer(state={}, action) {switch(action.type) {case'TEST':        return {            ...state,            test:'test success'}    }}// 返回 store// 中间件作为 applyMiddleWare 的参数传入 createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))

咱们会发现,中间件的应用形式是用 applyMiddleWare 把中间件作为参数传入 createStore 中,那么 applyMiddleWare 是如何实现的?在这之前咱们先看下 createStore 办法的第三个参数是什么,咱们回看下 createStore 源码:

export default function createStore(reducer, preloadedState, enhancer) {
  ...
  // 增强器
  // 第三个参数是 enhancer,也就是咱们传入的 applyMiddleWare
  if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')
    }

    // 在这里 return 了 enhancer 后果
    // 传入了 createStore,reducer,preloadedState
    // 实际上是重写了 createStore
    return enhancer(createStore)(reducer, preloadedState)
  }

  ...
}

看完了 enhancer 的理论作用,咱们能够弄清楚 applyMiddleWare 的实现原理,请看源码:

import compose from './compose'

// 传入 middlewares 中间件
export default function applyMiddleware(...middlewares) {
  // 闭包嵌套返回 2 个办法
  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.'
      )
    }

    // 返回一个对象
    // 蕴含 getState 办法和 dispatch 办法
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) // 返回一个全新的 dispatch 办法,不净化原来的 dispatch
    }

    // 执行中间件第一层办法
    // 回顾下两头的格局:({getState, dispatch}) => next => action => next(action)
    // 这里会比拟绕
    const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一个中间件的函数汇合[next => action => next(action), next => action => next(action)]

    // 应用 compose 聚合 chain 函数汇合
    // 返回新的 dispatch
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

这里可能会让人很纳闷,不大清楚的童鞋能够先看下中间件的标准写法,这里还有一个重要的函数 compose,咱们来看下compose 怎么解决 chain 函数汇合的,请看源码:

参考 前端进阶面试题具体解答

/** * 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))). */

// 传入聚合函数汇合
// 汇合为:[next => action => next(action), next => action => next(action)]
// 返回一个新的函数: (arg) => arg  
export default function compose(...funcs) {
  // 判断如果没有则返回一个新函数
  // 能够联想一下 dispatch 的定义
  // function dispatch(action) {
      ...
      return action
  }
  if (funcs.length === 0) {return arg => arg}

  // 判断如果只有一个中间件,则间接返回第一个
  if (funcs.length === 1) {return funcs[0]
  }

  // 这里用了 reduce 函数
  // 把后一个的中间件的后果当成参数传递给下一个中间件
  // 函数列表的每个函数执行后返回的还是一个函数:action => next(action)
  // 这个函数就是新的 dispatch
  // 最初返回函数:(...args) => action => args(action)
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose的源码及其简洁,然而很精华,简直是整个中间件最出彩的中央。通过 reduce 把每个中间件都执行一遍,并且是通过管道式的传输,把每个中间件的返回后果当成参数传递给下一个中间件,实现了剥洋葱式的中间件模式。这里比拟难了解,老手能够先写几个简略的中间件,而后再去缓缓了解为什么要这么解决,了解后就会晓得这段代码有多简洁了。

四、手写一个 Redux

源码解析完了,咱们来简略实现一个redux

createStore

// 判断值是否是对象类型
function isPlainObject(obj) {if(!obj) {reutrn false}

    return Object.prototype.toString.call(obj) === '[object, Object]'
}

export default createStore(reducer, enhancer) {
    // 先判断有没有传入中间件
    // 有则之间返回
    if(typeof enhancer !== 'undefined') {
        // 必须是个函数
        // 因为须要传参
        if(typeof enhancer !== 'function') {return}

        return enhancer(createStore)(reducer)
    }

    let state = {} // 初始化 state
    let listeners = [] // 公布订阅函数队列

    // 定义 getState 函数
    function getState() {
        // 间接返回 state
        return state
    }

    // 定义 dispatch 函数
    function dispatch(action) {
        try{
            // 执行 reducer, 返回 state
            state = reducer(state, action)
        }catch(e) {console.log('dispatch error:'e)
        } 

        // 订阅
        listeners.forEach(listener => listener())

        // 返回 action
        return action
    }

    // 定义 subscribe 函数
    function subscribe(listener) {if(!listener) {return}

        // 必须是回掉函数
        // 因为须要在 dispatch 里执行
        if(typeof listener !== 'function') {return}

        Listeners.push(listener)
    }

    // 返回对象:蕴含 getState, dispatch, subscribe 三个办法
    return {
        getState,
        dispatch,
        subscribe
    }
}

compose

    function compose(...funs) {if(!funs) {return arg => arg}

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

        // 遍历传入函数,返回一个新函数
        return funs.reduce((a,b) => (...args) => a(b(...args)))
    }

applyMiddleWare

import compose from './compose'

function applyMiddleWare(...middlewares) {
    return createStore => reducer => {
        // 先返回一个 store
        const store = createStore(reducer)

        // 创立 middleApi
        const middleApi = {
            getState: store.getState,
            dispatch: (...args) => store.dispatch(...args) // 返回一个新的 dispatch
        }

        // 注入 middleApi
        // 并返回函数汇合
        const chain = middlewares.map(middleWare => middleWare(middleApi))

        // 通过 compose 函数,执行所有中间件,并返回一个新的 dispatch
        const dispatch = compose(...chain)(store.dispatch)

        // 返回 store 对象
        return {
            getState: store.getState,
            dispatch
        }
    }
}

logger 中间件

    function logger({getState, dispatch}) {return function(next) {return function(action) {console.log('prev')
                next(action)
                console.log('done')
            }
        }
    }

测试

    import createStore from './myCreateStore'
    import applyMiddleWare from './myApplyMiddleWare'
    import logger from './logger'

    // 创立 reducer
    function reducer(state={}, action) {switch(action.type) {
            case 'TEST':
            return {
                ...state,
                test: 'test success'
            }
        }
    }

    // 引入中间件
    const middleware = applyMiddleWare(logger)

    const store = createStore(reducer, middleware) // 返回{getState, dispatch}

总结

至此一个残缺的 redux 咱们就曾经剖析完了,集体认为中间件的 compose 这里是比拟不好了解的点,然而只有明确中间件次要要解决的是加强 dispatch 函数,就能够顺着这个思路去了解。接着再试着写几个中间件,进一步了解为什么中间件的格局须要返回嵌套的三层函数,明确了这两个点,redux的原理也就根本可能明确了,有问题欢送在评论中指出。

正文完
 0