给大家推荐一个实用面试题库
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
前言
周一接到了美团的一面,面试官人很好,基本都是围绕着简历来问,下面就是我重新整理了一下怎么实现Vuex的min简单版本,可以看到Vuex的大致原理。
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>
知道了vuex的用法,你会不会发出以下疑问:
- 为什么要
store.commit('add')
才能触发事件执行呢? 可不可以进行直接调用mutation
函数进行操作呢? - 为什么不可以直接对
state
存储的状态进行修改,只能通过调用mutation
函数的方式修改呢? - 为什么存在异步调用的函数需要
store.dispatch('asyncAdd')
函数才能完成呢?可以直接调用store.commit('asyncAdd')
嘛?如果不可以,为什么呢? createStore()
和useStore()
到底发生了什么?
那么下面就来一一解密吧。
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()
用来安装插件,接受一个参数,通常是插件对象,该对象必须暴露一个install
方法,调用app.use()
时,会自动执行install()
方法。
解析Store类里的流程
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 }
是不是很惊讶于vuex的底层实现就短短几十行代码就实现了,嘿嘿那是因为从vue里引入了reactive
、inject
和computed
,并且对很大一部分的源码进行了省略,dispatch
和commit
远比这复杂多了
解答
问题一:为什么要store.commit('add')
才能触发事件执行呢? 可不可以进行直接调用mutation
函数进行操作呢?
解答:store类里根本没有mutation方法,只能通过调用commit方法来执行mutation里的函数列表。
问题二:为什么不可以直接对state
存储的状态进行修改,只能通过调用函数的方式修改呢?
解答:Vuex 通过强制限制对 store 的修改方式来确保状态的可追踪性。只有通过 mutation 函数才能修改 store 中的状态,这样可以轻松地跟踪状态的变化,也可以避免无意中从不同的组件中直接修改 store 导致的代码难以维护和调试的问题。
问题三:为什么存在异步调用的函数需要store.dispatch('asyncAdd')
函数才能完成呢?可以直接调用store.commit('asyncAdd')
嘛?如果不可以,为什么呢?
解答:实际上dispatch方法和commit方法远不止这么简单,下面先贴出部分vuex的关于这两个方法的源码部分
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' ); } };
源码里我们能看到,dispatch方法返回的是一个Promise对象,而commit方法没有返回值,完全进行的是同步代码的操作,虽然返回值可能适用的场景不多,但是这样设计的主要目的还是为了确保状态的可追踪性
问题四: createStore()
和useStore()
到底发生了什么?
当我们去调用 createStore()
函数,其构造函数就会接收一个包含 state
、mutations
、actions
和 getters
函数的对象 options
, 然后将它们保存到实例属性中,此时state
中的值都会转换为响应式对象,同时遍历所有的getters
函数,将其封装成computed
属性并保存到实例属性getters
中,而在main.js里调用了app.use(), install方法自动执行,将将当前 store 实例注册到 Vue.js 应用程序中,只需要调用useStore()
就可以拿到全局状态管理的store实例了,可以靠inject
和provide
实现全局共享。
总结
通过实现简单的vuex,完成基本的功能,对vuex有了不同的理解,但在vuex的源码方面,上面的demo只是简简单单做了大致的依赖关系整理,还有:不具备模块化功能、没有提供插件功能、没有提供严格模式、没有提供辅助函数和实现单例模式等等的实现。
给大家推荐一个实用面试题库
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库