头图

前言

探索Redux 和 Mobx 原理从我做起,从这篇文章看起!

所以

一位程序员的职业生涯大约十年,只有人寿命的十分之一。前端项目只是你生活工作的一部分,而你却是它的全部,你是他的灵魂。请放下长时间的游戏、工作时的摸鱼。多学习来以最完美的状态好好陪你项目!

正文

这篇文章将会详细分析 Redux 和 Mobx 核心 Api, 看一遍学不会就看两次、三次、手写一次!

知识点

  • Redux 基本使用(没有)
  • createStore
  • bindActionCreators
  • combineReducers
  • applyMiddleware
  • 彩蛋 thunk

Redux.png

  • Mobx 基本使用(没有)
  • observable
  • autorun
  • observer

这个 代码很多哦 , 我这菜鸟看不懂 ,我看了文章,应该能实现 这些api 和 vue的的 响应式差不多.

Redux

createStore

这个是个比较核心的函数之创建仓库,分为了3个核心函数 dispatch,getState,subscribe

这三个函数的基本使用是这样的...

// 构建仓库
const store = createStore(reducer);

const unListen = store.subscribe(() => {
    console.log("监听器1", store.getState());
})

store.dispatch(createAddUserAction({
    id: 1,
    name: "遇见同学",
    age: 21
}));

unListen(); //取消监听

本质就如思维导图思路

createStore

基本实现且看代码的详细解释


/**
 * 得到一个指定长度的随机字符串
 * @param {*} length 
 */
function getRandomString(length) {
    return Math.random().toString(36).substr(2, length).split("").join(".")
}

/**
 * 判断某个对象是否是一个plain-object
 * @param {*} obj 
 */
function isPlainObject(obj) {
    if (typeof obj !== "object") {
        return false;
    }
    return Object.getPrototypeOf(obj) === Object.prototype;
}

// 上面连个都为辅助工具函数

/**
 * 实现createStore的功能
 * @param {function} reducer reducer
 * @param {any} defaultState 默认的状态值
 */
export default function createStore(reducer, defaultState) {
    let currentReducer = reducer, //当前使用的reducer
        currentState = defaultState; //当前仓库中的状态

    const listeners = [];  //记录所有的监听器(订阅者)

    function dispatch(action) {
        //验证action
        if (!isPlainObject(action)) {
            throw new TypeError("action must be a plain object");
        }
        //验证action的type属性是否存在
        if (action.type === undefined) {
            throw new TypeError("action must has a property of type");
        }
        currentState = currentReducer(currentState, action)
        //运行所有的订阅者(监听器)
        for (const listener of listeners) {
            listener();
        }
    }

    function getState() {
        return currentState;
    }

    /**
     * 添加一个监听器(订阅器)
     */
    function subscribe(listener) {
            if (typeof listener !== 'function') {
              throw new Error(
                `Expected the listener to be a function. Instead, received: '${kindOf(
                  listener
                )}'`
              )
            }
        listeners.push(listener); //将监听器加入到数组中
        let isSubscribed = false;//是否已经移除掉了
        return function () {
            if (isSubscribed) {
                return;
            }
            //将listener从数组中移除
            const index = listeners.indexOf(listener);
            listeners.splice(index, 1);
            isSubscribed = true;
        }
    }

    //创建仓库时,需要分发一次初始的action
    dispatch({
        type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
    })

    return {
        dispatch,
        getState,
        subscribe
    }
}

实现的代码基本对标 Github Redux

由于官方使用的是Ts 写的 阅读起来可能更加困难

所以过滤许多的细节处理和TS,将本质的思维呈现出来!

从入口带你理解下一个核心函数bindActionCreators

bindActionCreators
  • 作用
    用于返回可直接调用的 dispatch 的函数方法
  • 参数

    • object | function
    • (store.dispatch)
    const actionCreators = {
      addUser: createAddUserAction,
      deleteUser: createDeleteUserAction
    }
    
    // 这可直接传入一个函数
    const actions = bindActionCreators(actionCreators, store.dispatch)
    
    actions.addUser({ id: 1, name: "遇见同学", age: 21 })

使用还是非常简单的, 本质也是一个函数, 非常的精妙,一起看看吧!

bindActionCreators

思路是这样,核心函数是另外一个getAutoDispatchActionCreator

/**
 * @param {object | function } actionCreators 
 * @param {*} dispatch 
 * @returns 
 */
export default function (actionCreators,dispatch) {
    // 为函数时 直接调用自动分发的action创建函数
    if(typeof actionCreators === 'function'){
        _getAutoDispatchActionCreator(actionCreators,dispatch)
     // 为对象时 遍历对象 转化为已创建自动分发的action创建函数的对象
    }else if(typeof actionCreators === 'object'){
        const result = {}
        Object.keys(actionCreators).forEach((key)=>{
            result[key] = _getAutoDispatchActionCreator(actionCreators[key],dispatch)
        })
        return result
    }else{
        throw new TypeError("actionCreators must be an object or function which means action creator")
    }

}
/**
 * 自动分发的action创建函数
 * @param {function} actionCreator 
 * @param {any} dispatch 
 * @returns 
 */
function _getAutoDispatchActionCreator(actionCreator, dispatch){
    return function(...args){
       return dispatch(actionCreator(...args))
    }
}

是不是豁然开朗了,乘热打铁进入下一个函数吧combineReducers

combinReducers

基本思路是这样的

combinReducers

**
 * 合并reducers函数
 * @param {object} reducers 
 * @returns 
 */
export default function (reducers) {
    validateReducers(reducers);
    /**
     * 返回的是一个reducer函数
     */
    return function (state = {}, action) {
        const newState = {}; //要返回的新的状态
        for (const key in reducers) {
            if (reducers.hasOwnProperty(key)) {
                const reducer = reducers[key];
                newState[key] = reducer(state[key], action);
            }
        }
        return newState; //返回状态
    }
}

function validateReducers(reducers) {
    if (typeof reducers !== "object") {
        throw new TypeError("reducers must be an object");
    }
    if (!isPlainObject(reducers)) {
        throw new TypeError("reducers must be a plain object");
    }
    //验证reducer的返回结果是不是undefined
    for (const key in reducers) {
        if (reducers.hasOwnProperty(key)) {
            const reducer = reducers[key];//拿到reducer
            //传递一个特殊的type值
            let state = reducer(undefined, {
                type:`@@redux/INIT${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined");
            }
            state = reducer(undefined, {
                type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined");
            }
        }
    }
}

来对比一下这个Redux 的 combinReducers官方代码

总得来说这几个都属于辅助函数,而createStore传入的reducer 才是调动整个 状态数据的核心, 不过Redux 提供了 一个核心机制 中间件 也是Redux 的灵魂之一。

接下来看下 applyMiddleware 是如何实现的

applyMiddleware

KoaRedux 等优秀库都提供了中间思想, 中间件一出, 必有 组合函数

我们看下 Redux 的组合函数

function compose(...funcs: Function[]) {
  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args))
  )
}

省略一些边界判断 , 实现就一个 reducer

就这一行

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

函数由内向外,返回作为 reducer 的新的累加值 依次执行 b、a、...

再看看applyMiddleware函数

import compose from "./compose"
/**
 * 注册中间件
 * @param  {...any} middlewares 所有的中间件 本质是一个函数
 */
export default function (...middlewares) {
    return function (createStore) { //给我创建仓库的函数
        //下面的函数用于创建仓库
        return function (reducer, defaultState) {
            //创建仓库
            const store = createStore(reducer, defaultState);
            let dispatch = () => { throw new Error("目前还不能使用dispatch") };
            const simpleStore = {
                getState: store.getState,
                dispatch: (...args) => dispatch(...args)
            }
            //给dispatch赋值
            //根据中间件数组,得到一个dispatch创建函数的数组
            const dispatchProducers = middlewares.map(mid => mid(simpleStore));
            dispatch = compose(...dispatchProducers)(store.dispatch);
            return {
                ...store,
                dispatch
            }
        }
    }
}

以我理解干了这几件事

applyMiddleware

调用返回了 一个具备以上能力的函数 抛出给 createStore 的第二个参数, 我们看看 createStore 有何改动

export default function createStore(reducer, defaultState, enhanced) {
    //enhanced表示applymiddleware返回的函数
    if (typeof defaultState === "function") {
        //第二个参数是应用中间件的函数返回值
        enhanced = defaultState;
        defaultState = undefined;
    }
    if (typeof enhanced === "function") {
        //进入applyMiddleWare的处理逻辑
        return enhanced(createStore)(reducer, defaultState);
    }

   // ....同上面代码

    return {
        dispatch,
        getState,
        subscribe
    }
}
  • 执行applyMiddleware 返回一个需要仓库函数 的函数

     applyMiddleware(
          thunk, // 返回中间件函数
          logger
    )
  • 接着 传入createStore 仓库函数函数本身 很巧妙
  • 得到一个新的仓库构造函数 传入 (reducer, defaultState)
  • 用第一次传入的 createStore(reducer, defaultState) 执行构造出没使用中间件的仓库
  • 挂载 storedispatch 新对象传给 每一个中间件函数,返回新的dispatch数组
  • 合并所有新的dispatch,执行第一次
  • 返回 store 和 新的 dispatch

这便是 中间件的基本 运行流程 有些绕 好好学习,天天向上

送你一个彩蛋 thunk 实现

代码是个只有几行的函数


function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => (next) => (action) => {
      if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
      }
      return next(action);
    };
  }
  // 创建 thunk 中间件
  const thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;
  
  export default thunk;

看图 我带你 穿起来 代码 一起解读

thunk

红色部分是 中间件函数dispatch, getState 丢给 thunk , 接着返回函数

usethunk

图中红丝部分代表着 thunk 处理部分 , 判断是否 为 函数 不是就对给下步去执行

我们再看下 next 是什么。

thunk

清晰的看一下 next 就是 dispatch 函数

后面两个 action 就是 一个普通 平面对象 类型异步函数 thunk 处理类型

Mobx

Mobx 也是一款状态管理工具 ,但是思维模式不同

Mobx

类似 Vue 响应式原理 , 配合 观察者模式 收集依赖 实现

Api 有点多 我没看懂 , 下面仅供参考

observable

这个方法显而易见 构建出 一个 可观察的对象

import reaction from './reaction'
export default function observable(target,key,descritor){
    // 当使用装饰器 修饰属性时
    if( typeof key === 'string'){
     let Reaction = new reaction
     let v =  descritor.initailizer()
     v = createObservable(v)
     return {
       enumerable:true,
       configurable:true,
       get(){
        Reaction.collect()
        return v
       },
       set(value){
        v = value
        Reaction.run()
       },
     }
    }
      return createObservable(target)
  } 
  
  function createObservable(val){
      let handle = () =>{
      let Reaction = new reaction
       return {
         get(target,key){
          Reaction.collect()
          return Reflect.get(target,key)
         },
         set(target,key,value){
          let v = Reflect.set(target,key,value)
          Reaction.run()
          return v
         },
       }
      }   
      return deepProxy(val,handle)
  }
  
  function deepProxy(val,handle){
    if(typeof val !== 'object') return val
  
    // 从后往前依此实现代理
    for(let key in val){
      val[key] = deepProxy(val[key],handle)
    }
  
    return createObservable(val, handle())
  }

可以很清楚的看到 这个函数实现了以下

  • 判断是否处于装饰器修饰使用状态
  • Proxy观察一个深度对象
  • 在get时触发 依赖收集
reaction
let nowFn = null
let counter = 0
class Reaction {
  constructor(){
    this.id = ++counter
    this.store = {}

  }
  run(){
    if(this.store[this.id]){
      this.store[this.id].forEach(w=>w())
    }
  }
  collect(){
    if(nowFn){
      this.store[this.id] = this.store[this.id] || []
      this.store[this.id].push(nowFn)
    }
  }
  static start(handle){
    nowFn = handle
  }
  static end (){
    nowFn = null
  }
}    
autorun
  import reaction from './reaction'
  export default function autorun(handle){
    reaction.start()
    handle()
    reaction.end()
  }

再 看看 autorunreaction

基本就是一个简单的依赖收集 和 触发

相信 各位 知道 观察者模式 和 了解 Vue 依赖收集的对这样的写法并不陌生

而 最后的一个 observer

observer
/**
 * 装饰器 mobx-react 修饰类组件
 * @param {*} target 
 */
export default function observer(target){
    let cwm = target.prototype.componentWillMount;
    target.prototype.componentWillMount = function(){
      cwm && cwm.call(this);
      autorun(()=>{
        this.render();
        this.forceUpdate();
      })
    }
  }

这个仅只是 修饰 react 类组件的 状态

对比

Redux
  • 单一数据源
  • 状态数据只读
  • 使用纯函数修改状态
Mobx
  • 多数据源
  • 被观察对象
  • 观察依赖
  • 触发动作
两者的区别:
比较reduxmobx
核心模块Action,Reducer,Store,没有调度器的概念observerState、Derivations、Actions...
Store只有一个 Store, Store 和更改逻辑是分开的,带有分层 reducer的单一 Stor多个 store
状态状态是不可改变的(建议不可变值)通常将状态包装成可观察对象,观察者依赖收集模式
编程思想遵循函数式编程思想函数响应式编程编程
对象JavaScript对象可观察对象

总结

  • Redux 的 源码实现 了解中间件的核心思想
  • 实现Mobx 的 核心 (其他的慢慢摸索是吧)
  • 对比两中状态管理的 差异 优劣
本文首发 GitHub |
源码 Analysis

遇见同学
21 声望0 粉丝

避免