美文网首页
快速理解前端开源代码-vuex篇

快速理解前端开源代码-vuex篇

作者: cd2001cjm | 来源:发表于2022-08-29 18:04 被阅读0次
    前言

    不管是学习还是工作需要,我们都免不了阅读三方的源码。那么如何比较高效的阅读,笔者基于vuex分享一下自己的阅读经验。本文分为四个部分:

    • vuex功能介绍
    • 问题点&兴趣点
    • 源码阅读对比
    • 总结
    一,Vuex功能介绍

    vuex是专门为vue.js开发的状态管理模式,它解决的多个组件依赖同一个状态的情况。特别是子组件与子组件之间的交互,异常的曲折。所以在vue的项目实践中,一般有两种处理方式,一种是vuex,还有一种是较为轻量的event-bus,需要结合项目复杂度来判定具体使用哪种方式。
    vuxe的运行架构图如下:


    image.png

    vuex的基本API:

    定义状态:

    • State:存储在store里的数据
      读取状态:
    • 直接读取:this.$store.state.XXX
    • Getter:属性读取和方法读取(类似计算属性)
      修改状态:
    • Mutation:同步
    • Action:异步

    vuex的实例创建:

    import { createApp } from 'vue'
    import { createStore } from 'vuex'
    
    // Create a new store instance.
    const store = createStore({
      state () {
        return {
          count: 0
        }
      },
      mutations: {
        increment (state) {
          state.count++
        }
      }
    })
    
    const app = createApp({ /* your root component */ })
    
    // Install the store instance as a plugin
    app.use(store)
    

    简单总结就是:在全局放了一个数据块,所有组件都可以进行修改和读取。不用进行父子组件的层层传递。

    二,问题点&兴趣点

    在我们阅读源码之前,对目标一定要熟悉。如何初始化,一些常用方法要了然于心。其次呢,要想明白为什么要读源码?

    • 是对它哪一部分有疑问?
    • 是想知道它哪一部分的实现方式?
    • 是想做个思路实现方式对比?

    笔者在读vuex源码之前,有下面三个问题:

    • 状态存在哪里?内存?Localstorage?
    • 如何控制state.XXX不直接被修改?
    • Mutation与Action的具体实现差异?
    三,源码阅读对比
    image.png

    第一步先整体看一下目录结构:

    • module:提供module对象与module对象树的创建功能;
    • plugins:提供开发辅助插件
    • helpers.js:提供action、mutations以及getters的查找API;
    • index.js:是源码主入口文件,提供store的各module构建安装;
    • store.js:提供了store的实现;
    • Store-util.js:提供了工具方法

    重点关注入口文件:index.cjs.js


    image.png

    结合实例的初始化,我们重点关注类Store,其代码全貌如下:

    image.png

    先了解一下大体结构,从上图我们可以找到日常使用的API方法。然后我们结合前面提出的问题去具体分析。

    1,状态存在哪里?内存?Localstorage?

    在初始化方法中,我们找到下述语句:

    // initialize the store state, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreState(this, state)
    

    看注释是初始化store的state,然后再找到方法resetStoreState

    image.png

    在方法中我们在红框标记处,看到了vue的reactive方法。所以我们可以知道,state是存在于内存中。也就意味着刷新页面会丢失数据,vuex会重新初始化。

    2,如何控制state.XXX不直接被修改?

    文档中说不允许直接修改state的内容,可我要是硬改呢?vuex内部是如何应对的?

     get state () {
        return this._state.data
      }
    
      set state (v) {
        if (__DEV__) {
          assert(false, `use store.replaceState() to explicit replace store state.`)
        }
      }
    

    通过该部分的代码可以看到,set state的时候:
    开发模式会提示
    运行时会直接忽略

    所以,我们没法对其通过state.XXX=YYY的方式,进行强制修改。

    3,Mutation与Action的具体实现差异?

    从api功能上我们知道一个是同步一个是异步,哪代码具体有何不同呢?
    commit方法如图:


    image.png
    image.png

    红框处我们可以看到,异步是返回的Promise。整体实现上比我们想象的要简单。
    除了计划的三个问题,在阅读过程中,也碰到一些有意思的点。

    4,dispatch的入参从何而来?

    文档:


    image.png

    代码:


    image.png

    从代码中我们并没有找到包含commit属性的对象。payload在实际执行中是event对象。那么commit执行的时候,从哪来的呢?

    回顾Vuex的使用的地方:

    • 初始化
    • 调用

    既然调用没有,那么我们就去找找初始化。
    在初始化中,我们看到一行代码:

       // init root module.
        // this also recursively registers all sub-modules
        // and collects all module getters inside this._wrappedGetters
        installModule(this, state, [], this._modules.root)
    

    然后看一下installModule的具体实现:


    image.png

    红框标记代码如下:

    function registerAction (store, type, handler, local) {
      const entry = store._actions[type] || (store._actions[type] = [])
      entry.push(function wrappedActionHandler (payload) {
    
        let res = handler.call(store, {
          dispatch: local.dispatch,
          commit: local.commit,
          getters: local.getters,
          state: local.state,
          rootGetters: store.getters,
          rootState: store.state
        }, payload)
        if (!isPromise(res)) {
          res = Promise.resolve(res)
        }
        if (store._devtoolHook) {
          return res.catch(err => {
            store._devtoolHook.emit('vuex:error', err)
            throw err
          })
        } else {
          return res
        }
      })
    }
    

    在handler.call这个位置我们可以看到,在action注册的时候,就赋值了:

    {
          dispatch: local.dispatch,
          commit: local.commit,
          getters: local.getters,
          state: local.state,
          rootGetters: store.getters,
          rootState: store.state
        }
    

    所以,当我们阅读源码中断的时候,就需要考虑其他关联的地方,然后进行寻找。

    5,vue注册的时候都做了些什么?

    对于vue的插件我们重点看install方法:

    install (app, injectKey) {
        app.provide(injectKey || storeKey, this)
        app.config.globalProperties.$store = this
    
        const useDevtools = this._devtools !== undefined
          ? this._devtools
          : __DEV__ || __VUE_PROD_DEVTOOLS__
    
        if (useDevtools) {
          addDevtools(app, this)
        }
      }
    

    内容比较简单,就是把当前实例赋值给vue实例上的$store

    6,在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误

    这是一个很有意思的部分,可以先想想如果我们自己实现的话,如何处理?

    其实题目拆分后可以分为两个部分:

    状态是不是变更了(监听)
    是不是mutation 函数引起的

    下面我们看看vuex的具体实现


    image.png

    我们可以找到严格模式下的一段处理,基于vue的watch来实现状态变更的监听。在这里我们看到了一个变量_committing,这个值什么时候是true呢?
    在commit方法中,我们找到了_withCommit方法:

    image.png
    _withCommit (fn) {
        const committing = this._committing
        this._committing = true
        fn()
        this._committing = committing
      }
    

    这样结合起来看,和我们想定的目标是吻合的。

    只有通过commit上来的变更,_committing才会为true
    通过vue的watch来监听state的改变,当变化的时候对_committing做判定

    四,总结

    vuex的代码量不多,很适合作为一个入门阅读的范例。阅读的时候我们大体分下面三个步骤:
    对阅读目标的使用,要足够熟练

    • 用都不会谈何理解呢
      先结构,再细节
    • 对于大型库,一定要先理结构,然后再入细节,不然容易懵圈
      读前猜想,读中印证,读后总结
    • 其实就是带着思考阅读,不然读完可能1-2个周就忘记了,没有什么收获。

    以上就是个人在源码阅读方面的一些心得,通过vuex做一个示例,希望能给大家带来一定的启发。

    相关文章

      网友评论

          本文标题:快速理解前端开源代码-vuex篇

          本文链接:https://www.haomeiwen.com/subject/zmhknrtx.html