Simply talk about how to understand the Vuex interview question

9e5b1a36e151de36afe7e19ee32ac568.jpeg

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:

  1. Why is it necessary store.commit('add')to trigger event execution? Is it possible to directly call mutationthe function for operation?

  2. stateWhy can't the stored state be modified directly , only by calling mutationa function?

  3. Why do functions called asynchronously need store.dispatch('asyncAdd')functions to complete? Can it be called directly store.commit('asyncAdd')? If not, why?

  4. createStore()and useStore()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 installmethod, 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
}

reactiveAre you surprised that the underlying implementation of vuex is realized in just a few dozen lines of code? Hey, that’s because , injectand were introduced from vue, computedand a large part of the source code was omitted. dispatchHe commitis 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 mutationthe 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.gettersoptionsstategetterscomputedgettersuseStore()injectprovide

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

Guess you like

Origin blog.csdn.net/Ed7zgeE9X/article/details/131525967