前言
昨天准备今天看看那道 vuex 面试题 - 为什么要在 actions 中使用异步,在 mutations 中可以使用异步吗? 本来想看看 vuex源码 中关于这方面的代码,发现这个 vuex 整体代码量并不多,于是冒出了一个根据源码「实现自己的 vuex 的想法」
由于是第一篇,所以只实现基本功能 - state / commit
vuex
先简单介绍一下今天要实现 vuex
的用法,更多细节查看 -> vuex
js
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
现在,你可以访问 store.state
,也可以通过触发store.commit
方法来改变状态
今天我们实现 上述代码所要完成的效果
准备
首先创建一个 vuex
模块,因为我用的 vite + ts
,需要 vite
识别路径并且让 ts
识别
vite.config.ts
ts
export default defineConfig({
resolve:{
vuex: resolve(__dirname, "src/store/")
}
})
vite-env.d.ts
ts
declare module "vuex" {
const commit: (type:string,payload:any)=>void;
const state:any
export {
commit,
state
}
}
通过 vite
配置别名和 ts
配置全局模块,我们的准备工作就已经结束了
类型声明文件 - types/index.d.ts
配置别名文件 - examples/webpack.config.js
开始
根据刚刚创建的 vite.config
别名查找规则,需要创建一个 store
文件夹
store/index.ts
ts
type StoreOtpions<T> = {
state?: T
mutations?: Record<string, (s: T, payload: any) => void>
}
export function createStore<T>(options: StoreOtpions<T> = {}) {
return new Store(options)
}
使用工厂方法,在类 Store
中进行管理
state
因为 state
的变化可以引起 视图的 变化,所以我们可以使用 reactive
包裹
ts
import { reactive } from "vue";
class Store<T> {
_state: {
data: any
} = {
data: {}
};
constructor(options) {
this._state.data = reactive(options.state || {})
}
get state() {
return this._state.data
}
}
由于 state 必须通过 commit / dispatch
来修改,所以使用了属性访问器 get
store-util.js
store.js
commit
通过 boundCommit
的方法把 this
指向了 当前实例,这样不论在哪调用 commit,this 永远不会变, 是一个非常巧妙的办法
ts
class Store {
constructor(){
// bind commit and dispatch to self
const store = this;
const { commit } = this;
this.commit = function boundCommit(type, payload) {
return commit.call(store, type, payload)
}
}
commit(type, payload) {}
}
在 commit 方法中,只需要执行对应的 方法即可,在源码中他使用了发布订阅模式
ts
class Store<T> {
_mutations: StoreOtpions<T>["mutations"][] | Record<string, []> = [];
constructor(options){
// 收集 mutations
this._mutations = Object.create(null)
// 订阅 所有的 mutations
Object.entries(options.mutations).forEach(([fnName, fnValue]) => {
this.registerMutation(store, fnName, fnValue)
})
}
// 注册 mutations
registerMutation(store: Store<T>, type, handler) {
const entry = this._mutations[type] || (this._mutations[type] = [])
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, store.state, payload)
})
}
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
return
}
// 发布事件
entry.forEach(function commitIterator(handler) {
handler(payload)
})
}
}
使用一个 this._mutations
注册所有 mutations
,数据结构形如 {a:[()=>{},()=>{}]}
因为当你 使用 commit
方法触发事件 a
时,可以快速的触发在 this._mutations
对应的注册事件
store.js
store-util.js
测试
html
<div>
{{ count }}
<el-button @click="increment">
+
</el-button>
<el-button @click="decrement">
-
</el-button>
</div>
ts
import { createStore } from "@/store"
const { commit, state } = createStore({
state: {
count: 0,
age: "111"
},
mutations: {
increment(state, payload) {
state.count += payload
},
decrement(state, payload) {
state.count -= payload
}
}
})
const count = computed(() => {
return state.count
})
const increment = () => {
commit('increment', 1)
commit('increment', 1)
commit('increment', 1)
}
const decrement = () => {
commit('decrement', 1)
}
也有对应的类型提示,说明ts的类型推导功能也正常
结尾
最近看源码也有一些心得,在此记录一下
- 不要全篇通读,要挑重点
- 前期先走通流程,不要管某个类或者某个方法具体做了什么,只需要知道他可以干什么,最后再深入研究
- 如果遇到很难的代码,可以先看看网上找别人分析过的
- 如果想要真的理解源码,只看没用,还要自己简单模仿写出来,我发现自己写出来的东西理解起来更深刻,但是看别人的代码很快就会忘记
以上只是我个人总结,每个人有不同的学习方法
附上源码
ts
import { reactive } from "vue";
type StoreOtpions<T> = {
state?: T
mutations?: Record<string, (s: T, payload: any) => void>
}
export function createStore<T>(options: StoreOtpions<T> = {}) {
return new Store<T>(options)
}
class Store<T> {
_state: {
data: any
} = {
data: {}
};
_mutations: StoreOtpions<T>["mutations"][] | Record<string, []> = [];
constructor(options) {
this._mutations = Object.create(null)
const store = this
const { commit } = this;
this.commit = function boundCommit(type, payload) {
return commit.call(store, type, payload)
}
this._state.data = reactive(options.state || {})
Object.entries(options.mutations).forEach(([fnName, fnValue]) => {
this.registerMutation(store, fnName, fnValue)
})
}
registerMutation(store: Store<T>, type, handler) {
const entry = this._mutations[type] || (this._mutations[type] = [])
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, store.state, payload)
})
}
get state() {
return this._state.data
}
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
return
}
entry.forEach(function commitIterator(handler) {
handler(payload)
})
}
}