Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何从零实现一个 Redux? #9

Open
CodeRookie262 opened this issue Dec 25, 2020 · 1 comment
Open

如何从零实现一个 Redux? #9

CodeRookie262 opened this issue Dec 25, 2020 · 1 comment

Comments

@CodeRookie262
Copy link
Owner

Redux 是目前 React 框架最火的状态管理工具,但是它与 React 没有任何关系,我们可以通过阅读源码并且模仿实现它才可以更为深刻的了解 Redux 的迷人之处,并且从源码中找出它的不足之处并尝试优化,为此你会如何实现它呢?

@CodeRookie262
Copy link
Owner Author

CodeRookie262 commented Dec 28, 2020

Redux 是目前 React 框架最火的状态管理工具,但是它与 React 没有任何关系,我们可以通过阅读源码并且模仿实现它才可以更为深刻的了解 Redux 的迷人之处,并且从源码中找出它的不足之处并尝试优化,为此你会如何实现它呢?

reducex的缺点

状态一改动就会触发订阅事件池的所有回调函数;
getState返回的是state的引用,用户可以直接通过引用修改状态,最好应该深克隆;
订阅函数中没有回调函数去重,导致可能相同的方法会多次push事件池;
状态更新触发订阅事件池,但是没有判断属性是否发生改变,因为是新的引用,所以应该遍历(虽然比较消耗性能)

如果有不怎么熟悉 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 middle = middleware(store);
+            let middles = middleware.map(middle => middle(store));
            // 将 dispatch 传入中间件返回的函数进行修饰,并返回
-             let middleDispatch = middle(store.dispatch);
+            let middleDispatch = compose(...middles)(store.dispatch);
            return {
                ...store,
                dispatch: middleDispatch
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant