3

实现一个redux

先不考虑中间件,实现一个简洁的redux

实现createStore

createStore是redux最主要的一个API了,通过createStore可以创建一个store用来存放应用中所有的state,一个应用只能有一个store。

// 先创建一个mini-redux.js文件:
export function createStore(reducer) {
    // 初始化store
    let currentStore = {};
    // 初始化事件列表
    let currentListeners = [];
    
    // 获取state
    function getState() {
        return currentStore;
    }
    // 订阅事件
    function subscribe(listener) {
        currentListeners.push(listener);
    }
    // 定义dispatch方法
    function dispatch(action) {
        currentStore = reducer(currentStore, action);
        currentListeners.forEach(v => v());
        // return dispatch;
    }
    // 默认执行reducer type类型不要命中reducer中自定义的case
    dispatch({type: '@ZQT-REDUX'});
    return {getState, subscribe, dispatch}
}

上面创建了一个redux.js文件,并暴露了一个createStore方法,接受reducer作为参数

// 创建mini-react-redux.js
import React from 'react';
import PropTypes from 'prop-types';

export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => (WrapComponent) => {
    return class connentComponent extends React.Component{
        static contextTypes = {
            store: PropTypes.object
        }
        constructor(props, context) {
            super(props, context);
            this.state = {
                props: {}
            }
        }
        componentDidMount() {
            const {store} = this.context;
            // 为什么非要订阅  因为没一个connect实际上就是一个订阅  每当dispatch执行的时候  就要重新执行以下update方法
            store.subscribe(() => this.update());
            this.update();
        }
        update = () => {
            const {store} = this.context;
            const stateProps = mapStateToProps(store.getState());

            // 每一个action需要用dispatch包裹一下
            const stateDispatch = bindActionCreators(mapDispatchToProps, store.dispatch);
            this.setState({
                props: {
                    ...this.props,
                    ...stateProps,
                    ...stateDispatch
                }
            })
        }
        render() {
            return <WrapComponent {...this.state.props}/>
        }
    }
}

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
    }
}

function bindActionCreators(creators, dispatch) {
    const bound = {};
    Object.keys(creators).forEach(v => {
        bound[v] = bindActionCreator(creators[v], dispatch);
    })
    return bound;
}
function bindActionCreator(creator, dispatch) {
    return (...args) => dispatch(creator(...args))
}

上面创建了mini-react-redux.js文件,主要暴露了connect方法和Provider组件。

先看Provider组件。Provider利用的react的context属性,把store注入到Provider组件,并返回this.props.children(也就是Provider组件里面嵌入的组件,一般是页面的跟组件App组件),这样所有的组件都可以共享store。

然后再看connect方法。connect方法是一个双重嵌套的方法(专业名词叫函数柯里化)里面的方法接受一个组件并且返回一个组件,正式高阶组件的用法,外面的函数接受mapStateToProps和mapDispatchToProps两个参数,mapStateToProps是用来把store里面的数据映射到组件的props属性中,mapDispatchToProps是把用户自己定义的action映射到组件的props属性中。

在componentDidMount方法里面执行了store.subscribe(() => this.update())这句代码,是因为每次使用dispatch触发一个action的时候都要执行一下update方法,即重新获取store数据并映射到组件中去,这样才能保证store数据发生变化,组件props能同时跟着变化。

bindActionCreators方法是用来把每一个action用dispatch方法包裹一下,因为action可能只是返回一个具有type属性的对象,只有用dispatch执行action才有意义。

到此为止,一个没有中间件的不支持异步dispatch的简洁版的redux已经实现了,创建一个demo,就可以看到效果了
// 创建index.js 作为项目入口文件,大家可以自己添加action和reducer,就可以查看效果
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from './mini-redux';
import { counter } from './index.redux'
import { Provider } from './mini-react-redux';
import App from './App'

const store = createStore(counter);
ReactDOM.render(
  (
    <Provider store={store}>
      <App />
    </Provider>
  ),
  document.getElementById('root'))

支持中间件和异步action的redux实现

上面实现了简洁版的redux,再此基础上添加支持中间件的代码

// 修改mini-redux.js为
export function createStore(reducer, enhancer) {
    if(enhancer) {
        return enhancer(createStore)(reducer)
    }
    let currentStore = {};
    let currentListeners = [];

    function getState() {
        return currentStore;
    }

    function subscribe(listener) {
        currentListeners.push(listener);
    }

    function dispatch(action) {
        currentStore = reducer(currentStore, action);
        currentListeners.forEach(v => v());
        // return dispatch;
    }

    dispatch({type: '@ZQT-REDUX'});
    return {getState, subscribe, dispatch}
}

export function applyMiddleware(...middlewares) {
    return createStore=>(...args)=> {
        // 这里args 就是上面createStore 传过来的reducers
        const store = createStore(...args)
        let dispatch = store.dispatch
        // 暴漏 getState 和 dispatch 给 第三方中间价使用
        const midApi = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        // 创造第三方中间件使用 middlewareAPI 后返回的函数组成的数组
        const middlewareChain = middlewares.map(middleware => middleware(midApi))
        // 结合这一组函数 和 dispatch 组成的新的 dispatch,然后这个暴漏给用户使用,而原有的 store.dispatch 是不变的,但是不暴漏
        dispatch = compose(...middlewareChain)(store.dispatch);
        return{
            ...store,
            dispatch
        }
    }
}

export function compose(...funcs) {
    if(funcs.length === 0){
        return arg => arg
    }
    if(funcs.length === 1) {
        return funcs[0]
    }
    return funcs.reduce((ret, item) => (...args) => item(ret(...args)));
}

createStore方法修改了一下,多接受了一个enhancer方法,enhancer就是在index.js创建store的时候传过来的applyMiddleware方法。判断是否传了enhancer参数,如果有就return enhancer(createStore)(reducer)

applyMiddleware方法接受多个中间件作为参数,这个方法的最终目的就是创建一个新的dispatch属性,新的dispatch属性是经过中间件修饰过的,并且暴露这个新的dispatch属性,原来的dispatch属性不变。

compose方法是一个可以吧compose(fn1,fn2,fn3)(arg)转为fn3(fn2(fn1(arg)))的方法,也就是fn1的执行结果作为fn2的参数,fn2的执行结果作为fn1的参数,依次类推。正好可以利用reduce的特性实现这个效果。

const thunk = ({getState, dispatch}) => next => action => {
    // 如果是函数  就执行action
    if(typeof action === 'function') {
        return action(dispatch, getState)
    }
    return next(action)
}
export default thunk

异步action在定义的时候返回的就是一个接受一个dispatch的方法,所以如果action是一个函数,就吧dispatch和getState方法传给该action,并且执行该action。如果不是一个函数,就直接返回action。

到此为止一个支持中间件的redux就实现了,该demo只是为了学习redux的思想,不能作为真正的redux来使用,有很多类型检查代码都省略了

从实现迷你版的redux可以体会到redux精巧的设计和函数式编程的魅力,有队函数式编程感兴趣的可以看一下这篇文章https://llh911001.gitbooks.io...

github源码地址:https://github.com/zhuqitao/z...


zhuqitao
498 声望80 粉丝

心有猛虎,细嗅蔷薇