解析: 让你弄懂redux原理

做者: HerryLohtml

本文永久有效连接: https://github.com/AttemptWeb......前端

Redux是JavaScript状态容器,提供可预测化的状态管理。react

在实际开发中,常搭配React + React-redux使用。这表明了目前前端开发的一个基本理念,数据和视图的分离git

redux应运而生,固然还有其余的一些状态管理库,如Flux、Elm等,固然,咱们这里只对redux进行解析。github

#redux建立Store

建立redux的store对象,须要调用combineReducers和createStore函数,下面解释不包含中间件。编程

const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})
const store = createStore(reducer)
// 暂时挂载在window下,下面会使用到
window.$reduxStore = store
复制代码

#combineReducers函数

首先调用combineReducers函数,将多个reducer函数做为参数传入,源码以下:redux

// reducers便是传入的参数对象
function combineReducers(reducers) {
    // ......省略
    return function combination(state = {}, action) {
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // finalReducerKeys 是传入reducers对象的key值
            const key = finalReducerKeys[i]
            // finalReducers 等价于 reducers
            const reducer = finalReducers[key]
            const previousStateForKey = state[key]
            // 运行reducer函数,返回一个state
            // 核心:调用combination函数,实际就是循环调用传入的reducer函数
            const nextStateForKey = reducer(previousStateForKey, action)

            nextState[key] = nextStateForKey
            // hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        // hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
        // 返回state对象
        return nextState
    }
}
// 源码地址:https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts#L139
复制代码

上面的代码其实很是简单,combineReducers函数运行,返回一个新的combination函数。combination函数的主要做用是返回一个挂载所有state的对象当combination函数被调用时,实际就是循环调用传入的reducer函数,返回state对象。将combination函数做为参数传入到createStore函数中。数组

#createStore函数

function createStore(reducer, preloadedState, enhancer) {
    // reducer --> combination函数
    let currentReducer = reducer
    // 所有的state属性,挂载在currentState上
    let currentState = preloadedState

    // 下面的中间件会用到
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        // 第二个参数是一个函数,没有第三个参数的状况
        enhancer = preloadedState
        // 将preloadedState重置
        preloadedState = undefined
    }
    if (typeof enhancer !== 'undefined') {
        // 存在中间件时,将createStore传入中间件函数,调用enhancer函数,return结束。
        return enhancer(createStore)(reducer, preloadedState)
    }

    function dispatch(action) {
        // currentReducer --> combination函数
        currentState = currentReducer(currentState, action)
    }

    // 初始化调用dispatch,建立初始state
    dispatch({ type: ActionTypes.INIT })

    const store = ({
        dispatch: dispatch,
        subscribe,s
        getState,
        replaceReducer,
        [$$observable]: observable
    }
    return store
}
// 源码地址:https://github.com/reduxjs/redux/blob/master/src/createStore.ts#L60
复制代码

reducer就是传入的combination函数,preloadedState是初始化的state(没有太大的做用),enhancer是中间件,没有第三个参数enhancer的状况下,同时第二个参数preloadedState是一个函数,preloadedState被赋值给enhancer。bash

调用dispatch函数初始化,currentReducer便是传入combination函数,就向上文提到的,调用combination函数实际就是循环调用reducer函数。全部的state对象,被挂载在内部变量currentState上。存在中间件enhancer时,将createStore传入中间件函数,调用enhancer函数,return结束,这个下文会继续讲到。前端框架

建立的store对象,暴露出的方法以下:

const store = ({
    // 分发 action,这是触发 state 变化的唯一途径。
    dispatch: dispatch as Dispatch<A>,
    // 变化监听器
    subscribe,
    // 获取store下的 所有state
    getState,
    // 替换 store 当前用来计算 state 的 reducer
    replaceReducer
}
return store
复制代码

dispatch函数触发action,调用reducer函数,修改state。subscribe函数能够监听变化state的变化。getState函数获取所有state。replaceReducer函数替换用来计算state的reducer函数。

经过combineReducers函数合并reducer函数,返回一个新的函数combination(这个函数负责循环遍历运行reducer函数,返回所有state)。将这个新函数做为参数传入createStore函数,函数内部经过dispatch,初始化运行传入的combination,state生成,返回store对象

#redux中间件

最好把上面看懂以后,再看中间件部分!!下面对中间件进行分析:

redux-thunk只是redux中间件的一种,也是比较常见的中间件。redux-thunk库容许你编写与store交互的异步逻辑。

import thunkMiddleware from 'redux-thunk'
const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})

const store = createStore(
    reducer,
    applyMiddleware(
        thunkMiddleware, // 异步支持
    )
)
复制代码

createStore函数支持三个参数,若是第二个参数preloadedState是一个函数,而没有第三个参数enhancer的话,preloadedState会被赋值给enhancer。

下面会以redux-thunk中间件做为例子,下面就是thunkMiddleware函数的代码:

// 部分转为ES5代码,运行middleware函数会返回一个新的函数,以下:
return ({ dispatch, getState }) => {
    // next实际就是传入的dispatch
    return function (next) {
        return function (action) {
            // redux-thunk核心
            if (typeof action === 'function') { 
                return action(dispatch, getState, extraArgument);
            }
            return next(action);
        };
    };
}
// 源码地址:https://github.com/reduxjs/redux-thunk/blob/master/src/index.js
复制代码

redux-thunk库内部源码很是的简单,github: redux-thunk 源码

,容许action是一个函数,同时支持参数传递,不然调用方法不变。

#applyMiddleware函数

// 中间件调用
return enhancer(createStore)(reducer, preloadedState)

等价于

return applyMiddleware(
    thunkMiddleware,
)(createStore)(reducer, preloadedState)
复制代码

redux的中间件,从applyMiddleware函数开始,它主要的目的就是为了处理store的dispatch函数

// 支持多个中间件传入
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, ...args) => {
    // 建立 store
    const store = createStore(reducer, ...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 遍历运行中间件函数,将middlewareAPI做为参数传入
    // middleware对应上面的redux-thunk库核心代码,固然也支持多个中间件
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 核心:将全部中间件传入到compose中,返回一个新的dispatch
    dispatch = compose(...chain)(store.dispatch)
    // 照常返回一个store对象,dispatch已经被处理过了
    return {
      ...store,
      dispatch
    }
  }
}
// 源码地址:https://github.com/reduxjs/redux/blob/master/src/applyMiddleware.ts#L55
复制代码

applyMiddleware函数接收多个middlewares参数,返回一个store对象。经过createStore建立store对象,middlewareAPI对象挂载getState和dispatch,循环middlewares中间件,将middlewareAPI做为参数传入每一个中间件。遍历结束之后,拿到了一个包含全部中间件新返回函数的一个数组,将其赋值给变量chain。

// 遍历以后chain的值,这里只是拿redux-thunk库做为例子
// next 就是 dispatch
chain = [function (next) {
    return function (action) {
        if (typeof action === 'function') { // redux-thunk核心
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    };
}, ...更多中间件]
复制代码

#compose函数

// 数组chain 保存全部中间件新返回函数
dispatch = compose(...chain)(store.dispatch)
复制代码

compose的主要做用就是运行全部中间件函数后,返回一个通过处理的dispatch函数。

// compose函数
return chain.reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}
复制代码

chain是保存中间件函数的数组,具体的内部结构参见上面👆,下面来分析一下compose函数的调用逻辑。

// chain 类比为 [fn1, fn2, fn3, fn4]
[fn1, fn2, fn3, fn4].reduce((a, b) =>{
    return (...args)=> {
        return a(b(...args))
    }
}
复制代码

调用过程以下:

循环 a值 b值 返回的值
第一轮循环 fn1 fn2 (...args)=> fn1(fn2(...args))
第二轮循环 (...args)=> fn1(fn2(...args)) fn3 (...args)=> fn1(fn2(fn3(...args)))
第三轮循环 (...args)=> fn1(fn2(fn3(...args))) fn4 (...args)=> fn1(fn2(fn3(fn4(...args))))

通过 compose 处理过以后, 最后的返回值就是 (...args) => fn1(fn2(fn3(fn4(...args)))),这个的arg就是 store.dispatch函数。最后将返回函数赋值给dispatch,就是咱们须要的dispatch函数了。而若是只有一个中间件的话,就会直接返回了。

applyMiddleware函数中间件的主要目的就是修改dispatch函数,返回通过中间件处理的新的dispatch函数

#redux使用

这里的使用是不配合react-redux+react的;

window.$reduxStore = store

store.dispatch(action);

let { aState } = store.getState()
复制代码

上面是直接将其挂载在window对象之下,这样能够配合任何前端框架使用,固然这样确定是不优雅的,后面我再会专门讲一篇配合react-redux使用的;

在这里调用store.dispatch函数,实际就是再次调用循环遍历调用reducer函数,更新以后被保存在createStore函数的内部变量currentState上。经过store.getState函数,返回currentState变量,便可获得全部state。

#结束:

内容有点多,须要总结一下

1.redux建立Store经过combineReducers函数合并reducer函数,返回一个新的函数combination(这个函数负责循环遍历运行reducer函数,返回所有state)。将这个新函数做为参数传入createStore函数,函数内部经过dispatch,初始化运行传入的combination,state生成,返回store对象

2.redux中间件applyMiddleware函数中间件的主要目的就是修改dispatch函数,返回通过中间件处理的新的dispatch函数

3.redux使用实际就是再次调用循环遍历调用reducer函数,更新state

解析到这里就结束了,redux真的很是函数化,满满的函数式编程的思想,很是的模块化,具备很强的通用性,以为很是赞👍👍

ps: 微信公众号:Yopai,有兴趣的能够关注,每周不按期更新,分享能够增长世界的快乐

相关文章
相关标签/搜索