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

前言

不管是学习还是工作需要,我们都免不了阅读三方的源码。那么如何比较高效的阅读,笔者基于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篇)