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原理解析 #15

Open
HerryLo opened this issue Nov 4, 2019 · 0 comments
Open

redux原理解析 #15

HerryLo opened this issue Nov 4, 2019 · 0 comments
Assignees
Labels

Comments

@HerryLo
Copy link
Member

HerryLo commented Nov 4, 2019

redux解析

作者: HerryLo

本文永久有效链接: https://didiheng.com/front......

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

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

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

redux创建Store

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

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

combineReducers函数

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

// 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。

调用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,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

@HerryLo HerryLo self-assigned this Nov 4, 2019
@HerryLo HerryLo added Blog 文章 Javascript JS labels Nov 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant