Provider组件

Provider用于建立能够被子组件访问的全局属性,核心API有两个:

  • childContextTypes静态属性,用于指定被子组件访问的全局属性类型
  • getChildContext方法,用于指定可以被子组件访问的全局属性

Provider简版源码实现:

import React from 'react';
import PropTypes from 'prop-types';

export default class Provider extends React.Component{

    //指定子组件可以访问的属性类型
    static childContextTypes = {
        store: PropTypes.object
    }

    constructor(props){
        super(props);
    }

    //指定子组件可以访问的属性
    getChildContext(){
        return{
            store: this.props.store
        }
    }

    render() {
        //用其包含的子组件进行视图渲染
        return this.props.children;
    }
}

然后,在引用到全局属性的子组件当中指定contextTypes进行指定的全局属性获取,同时需要在子组件的构造函数中声明context,否则context是一个空对象

static contextTypes = {
    store: PropTypes.object
}
    
constructor(props,context){
    super(props,context);
}

最后,把Provider组件作为根组件,传入相应相对应的全局属性即可:

<Provider store={store}>
    <App></App>
</Provider>

connect函数

connect函数其实是一个代理模式的高阶组件,对目标组件进行增强或减弱形成一个新的组件,然后返回这个新的组件。

connect函数有两个参数,分别是mapStateToProps和mapDispatchToProps。

mapStateToProps

mapStateToProps是一个匿名函数,用于指定传递到组件当中的props,实现方式也简单,直接执行其匿名函数即可:

const stateProps = mapStateToProps(store.getState());

最后,把stateProps展开传进目标组件即可

mapDispatchToProps

mapDispatchToProps既可以是对象,也可以是函数,它主要是用于action和传进来的props进行关联,利用bindActionCreators函数对action进行了dispatch的封装,使action变成dispatch(action),形成派发指定的action对指定的props进行调用。

const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch);

//把action封装成dispatch(action)
function bindActionCreators(actionCreators,dispatch) {
    return Object.keys(actionCreators).reduce((result,item) => {
        result[item] = (...args) => dispatch(actionCreators[item](...args));
        return result;
    },{})
}

最后,把dispatchProps展开传进目标组件即可

connect函数的完整实现

import React from 'react';
import PropTypes from 'prop-types';
import {bindActionCreators} from './mini-redux';

export const connect = (mapStateToProps = state => state, mapDispatchToProps={}) => (WrapComponent) => {
    class ConnectComponent extends React.Component{

        //指定要访问的全局属性,若contextTypes没有定义,context将是一个空对象
        static contextTypes = {
            store: PropTypes.object
        }

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

        componentDidMount(){
            const {store} = this.context;
            store.subscribe(this.update);//订阅store里的状态,发生变化就调用update函数
            this.update();
        }

        componentWillUnmount(){
            const {store} = this.context;
            store.unsubscribe(this.update);//移除监听stor状态变化的函数
        }

        //获取mapStateToProps和mapDispatchToProps,放进this.state.props中更新组件
        update(){
            const {store} = this.context;
            const stateProps = mapStateToProps(store.getState());//获取传进来state
            const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch);//把action封装成dispatch(action)
            this.setState({
                props:{
                    ...this.state.props,
                    ...stateProps,
                    ...dispatchProps
                }
            })
        }

        render(){
            return(
                //把传递进来的state和封装后的dispatch(action)作为props传到目标组件
                <WrapComponent {...this.state.props}></WrapComponent>
            )
        }
    }

    return ConnectComponent;
}

中间件

中间件利用applyMiddleWare来增强createStore函数,类似于装饰器模式,先来看看createStore函数的简版实现

createStore函数

export function createStore(reducer,enhancer) {
    if(enhancer){
        //如果存在store增强器,就对createStore进行封装
        return enhancer(createStore)(reducer);
    }
    let currentState = {};//保存项目当前的state,默认为空对象
    let currentListeners = [];//用来存放监听store变化的函数

    //用来获取当前的state
    function getState() {
        return currentState;
    }

    //派发action
    function dispatch(action) {
        currentState = reducer(currentState,action);//使用reducer处理当前派发过来的action
        currentListeners.forEach(currentListener => currentListener());//处理完成后返回新的state触发store的改变,遍历监听store变化的函数并执行使组件发生变化
        return action;
    }

    //增加监听store变化的函数
    function subscribe(listener) {
        currentListeners.push(listener);
    }

    //移除取消监听store变化的函数
    function unsubscribe(listener) {
        currentListeners = currentListeners.filter(l => l !== listener)
    }

    dispatch({type:'xxxx'});//触发初次调用,type可以设任意值,但是必须要走default的case,获取默认值传给组件初始化
    return {getState,dispatch,subscribe,unsubscribe};//返回store内部的三个函数供外部调用
}

applyMiddleWare函数

上面看到存在store增强器,其实也就是applyMiddleWare函数,返回两层嵌套的函数,主要是针对store的dispatch方法进行增强,让dispatch的action不是直接到达reducer函数进行处理,而是经过层层的中间件,所以applyMiddleWare函数简版实现如下:

function applyMiddleWare(...middlewares) {
    return createStore => (...args) => {
        const store = createStore(...args);//根据reducer和初始值创建原生store
        let dispatch = store.dispatch;//获取原生store的dispatch函数
        //中间件是嵌套了两层函数的函数,接收getState和dispatch作为参数
        const middlewareApi = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        const middlewareChain = middlewares.map(middleware => middleware(middlewareApi));//获取传递进来的中间件,并且初始化中间件函数
        dispatch = compose(...middlewareChain)(store.dispatch);//组合多个中间件,并且把原生的dispatch放在作为最后一个中间件的dispatch参数
        //单个中间件的做法:dispatch = middleware(middlewareApi)(store.dispatch);
        //返回经过增强dispatch函数的store
        return{
            ...store,
            dispatch
        }
    }
}

compose函数

compose函数是把多个中间件组合在一起,把compose(fn1,fn2,fn3)转换为fn1(fn2(fn3))的形式,函数从右往左执行,其实就是下一个函数执行结果作为前一个函数的参数,对应在这里就是把下一个中间件作为前一个中间函数的next参数,compose的简版源码如下:

function compose(...fns){
    if(fns.length === 0){
        return arg => arg
    }
    else if(fns.length === 1){
        return fns[0]
    }
    else{
        //返回结果:(...args) = > fn1(fn2(fn3(...args)))
        return fns.reduce((result,item) => (...args) => result(item(...args)));
    }
}

thunk中间件简版实现

const thunk = ({dispatch,getState}) => next => action => {
    //如果action是一个函数,就执行该函数
    if(typeof action === 'function'){
        return action(dispatch,getState);
    }
    //否则,如果当前中间件不是最后一个中间件,就把action传递到下一个中间件中执行
    //如果当前中间件已经是最后一个中间件,那么next就是store上原生的dispatch,直接给到reducer处理了
    return next(action);
}

export default thunk;

定制中间件

我们尝试手动定制一个中间件arrayThunk,判断action是否为数组类型,如果为数组类型,就遍历数组,把数组中的元素作为action进行重新派发:

const arrayThunk = ({dispatch,getState}) => next => action => {
    if(Array.isArray(action)){
        return action.forEach(item => dispatch(item));
    }
    return next(action);
}

export default arrayThunk;

正常调用即可:

const store = createStore(count,applyMiddleWare(thunk,arrayThunk));

Yanglinxiao
1.2k 声望28 粉丝

learn more === know less