Redux / React Redux 的模拟实现

204 阅读3分钟

本文主要参考该博文:8k字 | Redux/react-redux/redux中间件设计实现剖析

为了便于本人阅读,概况总结了上述文章,并基于最新 React 修改了部分接口的实现;有兴趣的同学,可以直接阅读上文

为什么要有 redux

管理公共状态,便于组件间的通信

redux 的设计与实现

设计

要实现状态的管理,主要要有以下几步

  • 状态变量 currentState
  • 状态的存、取,即 settergetter 方法
  • 当状态发生改变的时候,还要对数据进行广播,就需要有 subscribe 方法

所以 store 的大致形状如下

const createStore = () => {
    let currentState = {};     // 公共状态

    function getState() {}     // getter
    function dispatch() {}     // setter    
    function subscribe() {}    // 发布订阅

    return { getState, dispatch, subscribe };
}

getter 方法的实现

返回当前状态

const createStore = () => {
    // ...
    
    function getState () {
        return currentState;
    }	
}

dispatch 方法的实现

传入一个 action 对象,有条件、具名的修改 store 的数据

// 仿照 reducer 的写法
const initialState = {
    count: 0
};

function Reducer(state = initialState, action) {
    switch(action.type) {
        case 'plus':
            return {
                ...state, 
                count: state.count + 1
            }
        case 'substract':
            return {
                ...state,
                count: state.count - 1
            }
        default:
            return initialState;
    }
}

const createStore = () => {
    // ...
    
    function dispatch(action) {
        currentState = reducer(currentState, action)
    }	
}

subscribe 方法的实现

参考 观察者-订阅者 模式,如下

// 观察者
class Observer {
    constructor(fn) {
        this.update = fn;
    }
}

// 被观察者
class Subject {
    constructor() {
        this.observers = [];
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    notify() {
        this.observers.forEach(observer => {
            observer.update();
        })
    }
}

var subject = new Subject();
var observer1 = new Observer(() => { console.log(1); });
var observer2 = new Observer(() => { console.log(2); });
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify();

// 1
// 2

基于上述模式,我们就可以实现当数据更新的时候,对数据进行广播更新


const createStore = () => {
    // ...

    
    let observers = [];    // 观察者队列,用于广播更新
    
    function dispatch(action) {
        currentState = reducer(currentState, action);
        observers.forEach(fn => fn());    // 依次执行观察者队列中的方法
    }

    function subscribe(fn) {
        observers.push(fn);
    }	
}

最终代码

// 仿照 reducer 的写法
const initialState = {
    count: 0
};

function reducer(state = initialState, action) {
    switch (action.type) {
        case 'plus':
            return {
                ...state,
                count: state.count + 1
            }
        case 'substract':
            return {
                ...state,
                count: state.count - 1
            }
        default:
            return initialState
    }
}

const createStore = (reducer) => {
    // 公共状态
    let currentState = {};  
    
    // 观察者
    let observers = [];   

    // getter
    function getState() { 
        return currentState;
    }     

    // setter   
    function dispatch(action) {   
        currentState = reducer(currentState, action);
        observers.forEach(fn => fn());    // 当数据发生变化时,触发对应的方法
    }

    // 发布订阅
    function subscribe(fn) {
        observers.push(fn);
    }

    // 初始化参数
    dispatch({type: ''})

    return { getState, dispatch, subscribe };
}


// 使用
const store = createStore(reducer);
store.subscribe(() => { console.log(1); });
store.subscribe(() => { console.log(2); });
store.dispatch({type: 'plus'})


// 1
// 2

react-redux 的设计与实现

提供了 Providerconnect 这两个 API

  • Provider:将 store 借助 React Context 进行数据传递
  • connect:将 getStatedispatch 方法合并进了 props,传递给组件,并实现了自动订阅更新

Provider 方法的实现

const ReduxContext = React.createContext(null);

class Provider extends React.Component {
    render() {
        return (
            <ReduxContext.Provider value={this.props.store}>
                {this.props.children}
            </ReduxContext.Provider>
        )
    }
}

connect 方法的实现

connect(mapStateToProps, mapDispatchToProps)(App)   // 高阶组件

connect 接收两个参数

  • mapStateToProps
    • store 中的变量转换为 UI 组件中的同名变量
  • mapDispatchToProps
    • 建立 UI 组件与 store.dispatch 方法的映射

通过 React 的高阶组件实现

function connect(mapStateToProps, mapDispatchToProps) {
    return function (Component) {
        class Connect extends React.Component {
            componentDidMount() {
                this.context.subscribe(this.handleStoreChange.bind(this));
            }

            handleStoreChange() {
                // 触发组件更新,也可以通过 setState 等方式触发组件更新
                this.forceUpdate();
            }

            render() {
                return (
                    <Component
                        {...this.props}
                        {...mapStateToProps(this.context.getState())}     // 转变 stroe 中的同名变量
                        {...mapDispatchToProps(this.context.dispatch)}    // 建立与 store.dispatch 方法的映射
                    />
                )
            }
        }

        Connect.contextType = ReduxContext;

        return Connect;
    }
}

简易使用

沿用 redux 的例子,使用上述接口

function mapStateToProps(state) {
    return {
        count: state.count
    };
}

// 定义对应的 action
const addCountAction = {
    type: 'plus'
}

function mapDispatchToProps(dispatch) {
    return {
        addCount: () => {
            dispatch(addCountAction);
        }
    }
}

class Test extends React.Component {
    render() {
      // console.log(this.props);
        return (
            <div>
                {this.props.count}
                <button onClick={() => this.props.addCount()}>Add</button>
            </div>
        )
    }
}

const App = connect(mapStateToProps, mapDispatchToProps)(Test);

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

redux Middleware 实现

使用中间件拦截 dispatchreducer 的这个过程,从而达到增强 dispatch 功能

实现思路

改装 dispatch,从而强化 createStore 功能

改装 createStore

heightener 存在的时候,则直接通过 heightener 接收强化 createStore

const createStore = (reducer, heightener) => {
    if(heightener) {
        heightener(createStore)(reducer);
    }

    // ...
}

定义 applyMiddleware 中间件函数

heightener 入参函数

const applyMiddleware = (...middlewares) => createStore => reducer => {
    const store = createStore(reducer);
    let { getState, dispatch } = store;   // 获取 store 中的 getState,dispatch 方法

    const params = {
        getState,
        dispatch: (action) => dispatch(action)
    };

    const middlewareArr = middlewares.map(middleware => middleware(params));

    dispatch = compose(...middlewareArr)(dispatch);
    return { ...store, dispatch };
}

function compose(...fns) {
    if (fns.length === 0) return arg => arg;
    if (fns.length === 1) return fns[0];
    return fns.reduce((res, cur) => (...args) => res(cur(...args)));
}

简单使用

const logger = store => next => action => {    
    console.log('log1'); 
    let result = next(action);
    return result;
}

const thunk = store => next => action => {
    console.log('thunk');
    return typeof action === 'function' ?  action(store.dispatch) : next(action);
}

const logger2 = store => next => action => {    
    console.log('log2'); 
    let result = next(action); 
    return result;
}

let store = createStore(reducer, applyMiddleware(logger, thunk, logger2))

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

代码仓库

gitee.com/hyy215/reac…