redux 原理浅析

作者: 林木木road | 来源:发表于2019-12-02 20:11 被阅读0次

    在对 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 工作流

    二. 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 时,主要用到了 <Provider></Provider> 组件以及 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 <WrapComponent {...this.state.props}></WrapComponent>
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:redux 原理浅析

        本文链接:https://www.haomeiwen.com/subject/imofgctx.html