redux 原理浅析

在对 redux 的使用过程中,了解到 redux 中的一些核心概念和方法,为了达到 “知其然,也知其所以然” 的学习目标,尝试从应用层面出发,剖析原理,手撸 redux 和 react-redux 中的核心方法。

一. redux 中的基本概念

整个工作流如图所示,涉及到以下核心概念:

  • Store:状态树,存储对象状态的地一个容器

  • Action :操作 store 的行为载荷,通过 store.dispatch 传递到 store

  • Reducers:真正操作 store 的方法,可以查看之前的状态,也可以响应接收到的 action 并返回一个新的状态

以下概念为 React-redux 中才具备的:

  • Provider:一个外层容器,配合 connect 实现父子层级组件的通信

  • Connect:连接 React 组件与 redux store 的一个方法,接收 Provider 组件提供的 store,返回一个高阶组件,将响应的 state 和 dispatch 作为属性参数传给内部组件

redux 原理浅析_第1张图片
redux 工作流

二. redux 核心方法实现

在手撸核心方法之前,先回忆一下使用 redux 的流程:

  1. 首先需要声明一个 reducer,然后调用 createStore 来实例化一个 store

  2. 紧接着如果要获取对象的状态,就需要调用 store.getState 方法来获取

  3. 如果要修改对象的状态,就需要调用 store.dispatch 方法来修改

  4. 最后不要忘了使用 store.subscribe 方法来添加监听函数

根据上述回顾,可以知道实例化的 store 包含三个方法 getStatedispatchsubscribe,其次为了实现对对象的存储与事件的监听,还需要两个变量 currentStatecurrentListeners 分别用于存储当前对象状态和监听队列。于是可以 createStore 函数的基本结构就清楚了

export function createStore(reducer, enhancer) {
    // 先忽略 enhancer 的操作
    // ...
    let currentState, currentListeners = []
    function getState() {
        // todo
    }
    function dispatch(action) {
        // todo
    }
    function subscribe(listener) {
        // todo
    }
    return {getState, dispatch, subscribe}
}

有了基础框架之后,再来仔细思考一下三个方法都具备了哪些功能:

  1. getState 方法:没有参数,能够返回当前的对象状态,故而函数内部很简单,就是直接返回 currentState

  2. dispatch 方法:传入一个 action,将其代理到 reducer 中,由 reducer 去执行真正的状态更改并得到新的状态,同时触发状态变更事件,也即需要执行所有的监听函数;

  3. subscribe 方法:传入一个 listener,将其添加到监听队列中,一旦 store 中的状态发生变更,listener 将被执行。

createStore 实现如下:
export function createStore(reducer, enhancer) {
    if(enhancer) {
        // 如果存在 enhancer,则对本函数进行转换,再将 reducer 传入
        return enhancer(createStore)(reducer)
    }
    let currentState, currentListeners = []
    
    function getState() {
        return currentState
    }

    function subscribe(listener) {
        currentListeners.push(listener)
        // 触发一个不可能存在的 action,使得 currentState 具备一个初始值
        dispatch('@roadlin/myRedux')
    }

    function dispatch(action) {
        currentState = reducer(currentState, action)
        // 遍历监听队列,依次执行监听事件
        currentListeners.forEach(v => v())
        return action
    }

    return {
        getState,
        subscribe,
        dispatch
    }
}

除了上述的 createStore 方法,redux 还提供了其它 API,比如当存在多个 reducer 时,需要调用 combineReducers 进行合并;当要引入中间件时,需要使用 applyMiddleware 进行注入。同样对这两个 API 的功能进行拆分分析,有助于理解其内部实现:

  1. combineReducers :传入的参数是一个对象,包含多个 reducers,最终经处理合并之后,返回一个合并之后的 reducer,代码描述如下:
combineReducers({
   count: countReducer,
   cart: cartReducer
})

// 上述代码合并之后得到一个整体的 reducer
function reducer(state = {}, action) {
   return {
       count: countReducer(state.count, action),
       cart: cartReducer(state.cart, action)
   }
}

合并之后,在调用 dispatch 的时候,会将 action 传递给合并后的 reducer, 合并后的reducer 中会遍历获取每个 key 值对应的状态,传递给相应的原始 reducer 执行,也即无论 action 是操作哪一个状态,所有的 reducer 都会执行一次

  1. applyMiddleware: 传入的参数是 n 个中间件(n ≥ 1),其主要作用是包装 store 原始的 dispatch 方法,使其支持中间件的功能,比如 applyMiddleware(thunk) 之后 dispatch 中支持异步操作。当 n > 1时,中间件是从右往左进行链式调用,也即最后的中间件处理之后的结果交给前一个中间件处理
combineReducersapplyMiddleware 实现如下:
function combineReducers(reducersObj) {
    // 1. 首先做了去重和判断,去除重复的 key 值,保证传入的每一个属性值都是 reducer 函数,保证一个 key 只对应一个 reducer
    let finnalReducers = {}
    for(let key in reducersObj) {
        if(typeof reducersObj[key] === 'function') {
            finnalReducers[key] = reducersObj[key]
        }
    }
    // 2. 中间还做了下判断,保证传入的 reducers 中不包括复合型的 reducer,也就是 combineReudcers 处理后的结果不能用于再一次的 combineReducers
    // ... 该部分省略
    // 3. 返回一个合并后的 reducer 函数 combination(state ={}, action)
    //  3.1 内部遍历了所有的 reducers,获取上一次 key 值对应的 state,调用对应的 reducer 函数,执行 action
    //  3.2 循环过程中判断是否更新了 state 值,如果更新了,则返回新的 state,否则依旧返回旧的 state
    return function combination(state = {}, action) {
        let hasChange = false, nextState = {}
        for(let key in finnalReducers) {
            let previousStateForKey = state[key]
            let nextStateForKey = reducersObj[key](previousStateForKey, action)
            nextState[key] = nextStateForKey
            hasChange = hasChange || nextStateForKey !== previousStateForKey
        }
        return hasChange ? nextState : state
    }
}

export function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        const store = createStore(...args)
        let dispatch = store.dispatch
        let midApi = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        let middleChain = middlewares.map(middleware => middleware(midApi))
        dispatch = compose(middleChain)(store.dispatch)
        return {
            ...store,
            dispatch
        }
    }
}

// 遵循从右到左的链式调用,所以 compose(f, h, g) 等价于 (...args) => f(h(g(...args)))
export function compose(...funcs) {
    if(funcs.length === 0) {
        return arg => arg
    }
    if(funcs.length === 1) {
        return funcs[0]
    }
    return funcs.reduce((ret, item) => (...args) => ret(item(...args)))
}


/*******************华丽分割线*************************/
// 除此之外,redux 中还有别的函数,比如:
/* 
    调用 connect 时,如果 mapDispatchToProps 是对象时执行 bindActionCreators 如

    @connect(
        state => ({goods: state.goods}), 
        {addGood, deleteGood, asyncAdd}
    )
*/
function bindActionCreator(creator, dispatch) {
    return (...args) => dispatch(creator(...args))
}
export function bindActionCreators(creators, dispatch) {
    return Object.keys(creators).reduce((ret, item) => {
        ret[item] = bindActionCreator(creators[item], dispatch)
        return ret
    }, {})
}

三. react-redux 核心方法实现

在应用 react-redux 时,主要用到了 组件以及 connect 函数,同样的,先分析它们的主要功能:

  1. Provider 组件:通过属性传参的方式传入 store 参数,并将其传给子组件,同时渲染内部组件。为了实现子组件中可以读取到 store 参数,需要借助 context 上下文来传递,作为父组件,其需要设置 childContextTypes

  2. connect 函数:传入的是两个 map,返回一个高阶组件,将 redux 中的 statedispatch 变成组件的 props

    • 为了从 Provider 中获取到 store,需要设置 contextTypes

    • 高阶组件最后返回的新组件中,也即经 connect 装饰之后的组件可以直接通过 this.props[key] 读取到 redux 中的数据和方法,故而需要执行两个 map,获取到 redux 数据和方法,最后以 props 的方式传给新组件

    • 此外,还需要设置监听事件,当 redux 中的数据发生更改时,更新当前组件的 props

import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './myRedux'

export class Provider extends React.Component{
    static childContextTypes = {
        store: PropTypes.object
    }

    getChildContext() {
        return {store: this.store}
    }

    constructor(props, context) {
        super(props, context)
        this.store = props.store
    }

    render() {
        return this.props.children
    }
}


export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => WrapComponent =>{

    return class NewComponent extends React.Component {
        static contextTypes = {
            store: PropTypes.object
        }

        constructor(props, context) {
            super(props, context)
            this.state = {
                props: {}
            }
        }

        // 在组件渲染前更新,否则因为读不到对应的 props 属性值而报错
        componentWillMount() {
            const {store} = this.context
            // 注册监听事件
            store.subscribe(() => this.update())
            this.update()
        }

        update() {
            const {store} = this.context
            const stateProps = mapStateToProps(store.getState())
            const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
            // 整合所有的参数,包括组件自身的参数、redux 中的 state 及 dispatch
            this.setState({
                props: {
                    ...this.state.props,
                    ...stateProps,
                    ...dispatchProps
                }
            })
        }

        render() {
            return 
        }
    }
}

你可能感兴趣的:(redux 原理浅析)