foreword
On Monday, I received an interview with Meituan. The interviewer was very nice, and they basically asked questions around the resume. The following is how I rearranged how to implement the simple version of Vuex. You can see the general principle of Vuex.
Basic use of vuex
//index.js
import { createStore } from './gvuex.js'
const store = createStore({
// 定义store的状态
state() {
return {
count: 1
}
},
// 定义获取state状态数据的计算属性getters,以下的值都被认为是state的派生值
getters: {
double(state) {
return state.count * 2
}
},
// 定义修改state状态数据的方法mutations
mutations: {
add(state) {
state.count++
}
},
// 定义异步操作的方法actions
actions: {
asyncAdd({ commit }) {
setTimeout(() => {
commit('add')
}, 1000)
}
}
})
// App.vue
<script setup>
import { useStore } from '../store/gvuex'
import { computed } from 'vue'
let store = useStore();
let count = computed(()=>{ store.state.count })
let double = computed(()=>{ store.getters.double })
function add() {
store.commit('add')
}
function asyncAdd() {
store.dispatch('asyncAdd')
}
</script>
<template>
<div class="">
{
{ count }} * 2 = {
{ double }}
<button @click="add">add</button>
<button @click="asyncAdd">async add</button>
</div>
</template>
<style scoped>
</style>
Knowing the usage of vuex, will you ask the following questions:
Why is it necessary
store.commit('add')
to trigger event execution? Is it possible to directly callmutation
the function for operation?state
Why can't the stored state be modified directly , only by callingmutation
a function?Why do functions called asynchronously need
store.dispatch('asyncAdd')
functions to complete? Can it be called directlystore.commit('asyncAdd')
? If not, why?createStore()
anduseStore()
what the hell happened?
Then let's decrypt it one by one.
Register global components in vue
import { createApp } from 'vue'
import store from './store'
import App from './App.vue'
const app = createApp(App)
app
.use(store)
.mount('#app')
app.use()
It is used to install a plug-in and accepts a parameter, usually a plug-in object, which must expose a install
method, which app.use()
will be automatically executed when called install()
.
Parse the process in the Store class
import { reactive, inject } from 'vue'
// 定义了一个全局的 key,用于在 Vue 组件中通过 inject API 访问 store 对象
const STORE_KEY = '__store__'
// 用于获取当前组件的 store 对象
function useStore() {
return inject(STORE_KEY)
}
// Store 类,用于管理应用程序状态
class Store {
// 构造函数,接收一个包含 state、mutations、actions 和 getters 函数的对象 options,然后将它们保存到实例属性中
constructor(options) {
this.$options = options;
// 使用 Vue.js 的 reactive API 将 state 数据转换为响应式对象,并保存到实例属性 _state 中
this._state = reactive({
data: options.state()
})
// 将 mutations 和 actions 函数保存到实例属性中
this._mutations = options.mutations
this._actions = options.actions;
// 初始化 getters 属性为空对象
this.getters = {};
// 遍历所有的 getters 函数,将其封装成 computed 属性并保存到实例属性 getters 中
Object.keys(options.getters).forEach(name => {
const fn = options.getters(name);
this.getters[name] = computed(() => fn(this.state));
})
}
// 用于获取当前状态数据
get state() {
return this._state.data
}
// 获取mutation内定义的函数并执行
commit = (type, payload) => {
const entry = this._mutations[type]
entry && entry(this.state, payload)
}
// 获取actions内定义的函数并返回函数执行结果
// 简略版dispatch
dispatch = (type, payload) => {
const entry = this._actions[type];
return entry && entry(this, payload)
}
// 将当前 store 实例注册到 Vue.js 应用程序中
install(app) {
app.provide(STORE_KEY, this)
}
}
// 创建一个新的 Store 实例并返回
function createStore(options) {
return new Store(options);
}
// 导出 createStore 和 useStore 函数,用于在 Vue.js 应用程序中管理状态
export {
createStore,
useStore
}
reactive
Are you surprised that the underlying implementation of vuex is realized in just a few dozen lines of code? Hey, that’s because , inject
and were introduced from vue, computed
and a large part of the source code was omitted. dispatch
He commit
is far more complicated than this , If you are interested in understanding the implementation of reactive, you can read my other article Learning VUE Source Code: Handwritten Min Version Responsive Prototype \- Nuggets\(juejin.cn\) [1] Let’s answer the questions raised above .
answer
Question 1: Why is it necessary store.commit('add')
to trigger event execution? Is it possible to directly call mutation
the function for operation?
Answer: There is no mutation method in the store class at all, and the function list in the mutation can only be executed by calling the commit method.
Question 2: Why can't the stored state be modified directly state
, only by calling a function?
Answer: Vuex ensures state traceability by enforcing restrictions on how the store can be modified. The state in the store can only be modified through the mutation function, which can easily track state changes, and can also avoid unintentionally directly modifying the store from different components, which makes the code difficult to maintain and debug.
Question 3: Why do functions called asynchronously need store.dispatch('asyncAdd')
functions to complete? Can it be called directly store.commit('asyncAdd')
? If not, why?
Answer: In fact, the dispatch method and the commit method are far more than that simple. Let me post some of the source code parts of vuex about these two methods
Store.prototype.dispatch = function dispatch (_type, _payload) {
var this$1$1 = this;
// check object-style dispatch
var ref = unifyObjectStyle(_type, _payload);
var type = ref.type;
var payload = ref.payload;
var action = { type: type, payload: payload };
var entry = this._actions[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown action type: " + type));
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(function (sub) { return sub.before; })
.forEach(function (sub) { return sub.before(action, this$1$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in before action subscribers: ");
console.error(e);
}
}
var result = entry.length > 1
? Promise.all(entry.map(function (handler) { return handler(payload); }))
: entry[0](payload);
return new Promise(function (resolve, reject) {
result.then(function (res) {
try {
this$1$1._actionSubscribers
.filter(function (sub) { return sub.after; })
.forEach(function (sub) { return sub.after(action, this$1$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in after action subscribers: ");
console.error(e);
}
}
resolve(res);
}, function (error) {
try {
this$1$1._actionSubscribers
.filter(function (sub) { return sub.error; })
.forEach(function (sub) { return sub.error(action, this$1$1.state, error); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in error action subscribers: ");
console.error(e);
}
}
reject(error);
});
})
};
Store.prototype.commit = function commit (_type, _payload, _options) {
var this$1$1 = this;
// check object-style commit
var ref = unifyObjectStyle(_type, _payload, _options);
var type = ref.type;
var payload = ref.payload;
var options = ref.options;
var mutation = { type: type, payload: payload };
var entry = this._mutations[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown mutation type: " + type));
}
return
}
this._withCommit(function () {
entry.forEach(function commitIterator (handler) {
handler(payload);
});
});
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(function (sub) { return sub(mutation, this$1$1.state); });
if (
(process.env.NODE_ENV !== 'production') &&
options && options.silent
) {
console.warn(
"[vuex] mutation type: " + type + ". Silent option has been removed. " +
'Use the filter functionality in the vue-devtools'
);
}
};
In the source code, we can see that the dispatch method returns a Promise object, while the commit method does not return a value, and it completely performs the operation of synchronous code. Although the return value may not be applicable to many scenarios, the main purpose of this design is to Ensure status traceability
Question 4: What exactly happened to createStore()
and ?useStore()
When we call createStore()
a function, its constructor will receive an object containing state
, mutations
, actions
and functions , and then save them to instance properties. At this time, the values in it will be converted into responsive objects, and all functions will be traversed at the same time. Encapsulate it into a property and save it in the instance property , and call app.use() in main.js, the install method will be executed automatically, and the current store instance will be registered in the Vue.js application, and you only need to call it to get it The store instance for global state management can be relied on and shared globally.getters
options
state
getters
computed
getters
useStore()
inject
provide
Summarize
By implementing simple vuex and completing the basic functions, I have a different understanding of vuex, but in terms of the source code of vuex, the above demo is simply a rough arrangement of dependencies, and: does not have modular functions, There is no implementation that provides plug-in functionality, does not provide strict mode, does not provide helper functions, implements the singleton pattern, and so on.
References
[1]
https://juejin.cn/post/7222928881215881274
About this article
Author: Superman Can't Fly
https://juejin.cn/post/7237479703167483961