真香~,咱也撸一个redux

从大学到现在用 React 开发差不多有1年多了,虽然还是一个小菜鸟,不过 react 虐我千百遍,我仍待她如初恋嘻嘻。

Redux 是咱使用的第一个状态管理器,在学习的时候总觉得它是依赖于 react 的,还和 react-redux 搞混了。了解了官网以及查阅很多关于 redux 才知道其实 reduxreact 以及 react-redux 没有什么关系,因为它是原生 js 编写的,所以任何框架都可以使用它。而react-reduxreactredux提供了更为便捷的关联方式,方便我们维护;

开始进入我们的真香环节,如果有不怎么熟悉 redux 建议先阅读中文文档

createStore

createStore 函数用于创建并且返回一个状态操作对象,以下是对 store 对象的部分类型定义;

interface action {
  type: string;
  [payload: string]: any;
}

type subscribe = (lister: Function) => Function

interface Store {
  dispatch: action;
  getState: Function;
  subscribe: subscribe;
}

store 对象主要有有getState,dispatchsubscribe这几个 API。

  • getState 用于获取最新的状态
  • dispatch 用于派发action对象进行状态更新
  • subscribe 用于监听状态变更

知道这几个 API 的功能后我们之一来实现吧~

/**
 * createStore
 * 创建 store 对象,并将对象返回
 * @param {(state:{[key:string]:any},action:{type: string,[key:string]:any}) => {[key:string]:any}} reducer
 * @param {Function} [middleware] 中间件
 * @returns {{dispatch: Function,getState: Function,subscribe: Function}}  state 操作对象
 */
function createStore(reducer) {
  // store 状态
  let state;
  //监听队列
  let listers = [];

  /**
   * 获取最新的 state
   * @returns {store} state
   */
  function getState() {
    const { parse, stringify } = JSON;
    // 为了安全起见,返回深拷贝后的 state 对象,防止组件随意增加属性造成数据污染
    return parse(stringify(state));
  }

  /**
   * 发布函数
   * 接受一个 action 对象
   * @param {{type: string,[key:string]:any}} action
   * @returns {{[key:string]: any}} action
   */
  function dispatch(action) {
    // 将 action 派发给 reducer 函数进行状态处理,并且更新最新的 state
    state = reducer(state, action);

    // 状态更新后还得执行以下我们的监听队列,告诉他们我们的 state 更新啦
    listers.forEach(observer => typeof observer === 'function' && observer());

    // 将此次分发的 action 返回出去
    return action;
  }

  /**
   * 订阅函数
   * @param {Function} lister 监听函数
   * @returns {Function} disconnect 注销监听
   */
  function subscribe(lister) {
    if (typeof lister !== 'function') {
      console.warn(
        'The Lister parameter of the subscribe function must be a function'
      );

      // 返回一个匿名函数,防止报错
      return () => {
        // 顺便在多提示几下  ̄ω ̄=
        console.warn(
          'The Lister parameter of the subscribe function must be a function'
        );
      };
    }


    return function () {
    // 将监听的数组从 listers (监听队列)移除掉
      listers = listers.filter(observer => observer !== lister);
    };
  }

  // 初始化 state ,派发一个私有的 action,避免重名影响到状态误改
  dispatch({ type: `CODE_ROOKIE_262@@${Date.now().toString(16)}` });

  return {
    dispatch,
    getState,
    subscribe
  };
}

以上基本实现了 createStore

基本用法
// 定义一个 计数器 reducer
function reducer(state,action){
    switch(action.type){
        case 'add':
            return {...state,count: state.count + 1};
        case 'minus':
            return {...state,count: state.count - 1};
        default: 
            return state;
    }
}

// 创建 Store
const store = createStore(reducer);

// 订阅状态监听
let observer = store.subscribe(function(){
    console.log('new state',store.getState())
});

// 派发 action 更改状态
store.dispatch({
    type: 'add'
})

// 注销监听
observer()

combineReducers

有的时候我们的状态是有模块区分的,就和 Vuex 一样有多个 module 进行分开管理,避免多个状态一同处于同一级造成数据会造成难以维护,所以我们也需要对不同的状态进行划分,需要将reducer函数拆分成多个单独的函数对独自的状态状态管控。但是上文中的 createStore 只接受一个 reducer,如何将多个 reducer 同时传进入呢?

这个时候就是我们大哥 combineReducers 的工作了,他接受一个对象,通过{reducer: reducerFun}形式的参数传递给 combineReducers,combineReducers接收到参数后会返回一个reducer函数,将这个函数传递给 createStore 即可。

/**
 * 合并多个 reducer 函数
 * @param   reducers {reducer1: reducer1,reducer2: reducer2,...}
 * @returns reducer
 */
function combineReducers(reducers) {
  return function reducer(state = {}, action) {
    let newState = {};
    // 更新每个模块的 state,并且将其最新状态返回
    for (var key in reducers) {
      newState[key] = reducers[key](state[key], action);
    }

    return newState;
  };
}
使用方法
const count = function countReducer(state,action){...};
const list = function countReducer(state,action){...};

const reducer = combineReducers({
    count: count,
    list: list
})

const store = createStore(reducer);

applyMiddleware

使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。

以上是引用 redux 中文网

可以看出 applyMiddleware 主要是通过 一个或者多个 middleware 包装 store.dispatch

中间件的用法
// redux-logger 是打印状态变更的一个中间件
import logger from 'redux-logger';
import {createStore,applyMiddleware} from 'redux'

function reducer(state,action){}

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

这个时候我们得给我的上面的 createStore 函数进行一些参数的调整。

function createStore(reducer,middleware){
    //...other code
    // 安装 middleware
    if(typeof middleware === 'function'){
        return middleware(createStore)(reducer)
    }
    
    return {
        getState: getState,
        dispatch: dispatch,
        subscribe: subscribe
    }
}

我们先从单个中间件middleware的思路入手更容易理解吧~

function applyMiddleware(middleware){
    return function(createStore){
        return function(reducer){
            // 创建 store
            let store = createStore(reducer);
            // 将 store 传入中间件
            let middle = middleware(store);
            // 将 dispatch 传入中间件返回的函数进行修饰,并返回
            let middleDispatch = middle(store.dispatch);
            return {
                ...store,
                dispatch: middleDispatch
            }
        }
    }
}

基本的中间件注入是解决了,但是applyMiddleware是支持多个中间件的,也就是说最后返回的dispatch只有一个,并且是经过了多个中间件修饰过的了。那么就有一个难点,就是如何获取最终的的dispatch呢?

例如有 middleware1middleware2,每个函数都接受原有的dispatch后返回新的dispatch
可以分解为:

const dispatch1 = middleware1(dispatch);
const dispatch2 = middleware(dispatch11);

所以我们是不是可以这样写呢 => middleware2(middleware2(dispatch));

那我们可以封装一函数 compose 将多个 middleware 组合成一个函数,这个函数接受一个的 dispatch并返回最终的 dispatch;

在此之前我们可以看下 redux 源码中的处理,我们可以在 compose.js 中看到这样一句代码,非常巧妙。

funcs.reduce((a, b) => (...args) => a(b(...args)));

可以看看简化后的代码

function compose(...funs) {
  return arg => {
    let res = arg;
    for (var i = 0, fl = funs.length; i < fl; i++) {
      // 接受返回值,并且将上一个函数的返回值传递给当前函数
      res = funs[i](res);
    }
    return res;
  };
}

看完后是不是焕然大悟了哈哈哈,当然用 数组的 reduce 还有另外的写法哟´( ̄▽ ̄)~*

function compose(...funs) {
  return dispatch => funs.reduce((res, fun) => fun(res), dispatch);
}

关于插件合并的问题我们基本解决了,接下来我们再次修改一下我们的 applyMiddleware 函数。

function applyMiddleware(...middleware){
    return function(createStore){
        return function(reducer){
            // 创建 store
            let store = createStore(reducer);
            // 将 store 传入中间件
+            let middles = middleware.map(middle => middle(store));
            // 将 dispatch 传入中间件返回的函数进行修饰,并返回
+            let middleDispatch = compose(...middles)(store.dispatch);
            return {
                ...store,
                dispatch: middleDispatch
            }
        }
    }
}

这样就实现多个插件同时注入的问题了,也基本实现了redux的功能,在实现的过程中虽然会遇到很多挫折,不过最终实现完成更让人充满动力哈哈哈Y(^o^)Y

你可能感兴趣的:(react.js,redux,javascript)