前言
不管是学习还是工作需要,我们都免不了阅读三方的源码。那么如何比较高效的阅读,笔者基于vuex分享一下自己的阅读经验。本文分为四个部分:
- vuex功能介绍
- 问题点&兴趣点
- 源码阅读对比
- 总结
一,Vuex功能介绍
vuex是专门为vue.js开发的状态管理模式,它解决的多个组件依赖同一个状态的情况。特别是子组件与子组件之间的交互,异常的曲折。所以在vue的项目实践中,一般有两种处理方式,一种是vuex,还有一种是较为轻量的event-bus,需要结合项目复杂度来判定具体使用哪种方式。
vuxe的运行架构图如下:
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的具体实现差异?
三,源码阅读对比
第一步先整体看一下目录结构:
- module:提供module对象与module对象树的创建功能;
- plugins:提供开发辅助插件
- helpers.js:提供action、mutations以及getters的查找API;
- index.js:是源码主入口文件,提供store的各module构建安装;
- store.js:提供了store的实现;
- Store-util.js:提供了工具方法
重点关注入口文件:index.cjs.js
结合实例的初始化,我们重点关注类Store,其代码全貌如下:
先了解一下大体结构,从上图我们可以找到日常使用的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
在方法中我们在红框标记处,看到了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方法如图:
红框处我们可以看到,异步是返回的Promise。整体实现上比我们想象的要简单。
除了计划的三个问题,在阅读过程中,也碰到一些有意思的点。
4,dispatch的入参从何而来?
文档:
代码:
从代码中我们并没有找到包含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的具体实现:
红框标记代码如下:
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的具体实现
我们可以找到严格模式下的一段处理,基于vue的watch来实现状态变更的监听。在这里我们看到了一个变量_committing,这个值什么时候是true呢?
在commit方法中,我们找到了_withCommit方法:
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
这样结合起来看,和我们想定的目标是吻合的。
只有通过commit上来的变更,_committing才会为true
通过vue的watch来监听state的改变,当变化的时候对_committing做判定
四,总结
vuex的代码量不多,很适合作为一个入门阅读的范例。阅读的时候我们大体分下面三个步骤:
对阅读目标的使用,要足够熟练
- 用都不会谈何理解呢
先结构,再细节 - 对于大型库,一定要先理结构,然后再入细节,不然容易懵圈
读前猜想,读中印证,读后总结 - 其实就是带着思考阅读,不然读完可能1-2个周就忘记了,没有什么收获。
以上就是个人在源码阅读方面的一些心得,通过vuex做一个示例,希望能给大家带来一定的启发。