美团面试官:那你讲一讲Vuex吧

简介: 美团面试官:那你讲一讲Vuex吧

给大家推荐一个实用面试题库

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

前言

周一接到了美团的一面,面试官人很好,基本都是围绕着简历来问,下面就是我重新整理了一下怎么实现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的用法,你会不会发出以下疑问:

  1. 为什么要store.commit('add')才能触发事件执行呢? 可不可以进行直接调用mutation函数进行操作呢?
  2. 为什么不可以直接对state存储的状态进行修改,只能通过调用mutation函数的方式修改呢?
  3. 为什么存在异步调用的函数需要store.dispatch('asyncAdd')函数才能完成呢?可以直接调用store.commit('asyncAdd')嘛?如果不可以,为什么呢?
  4. 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里引入了reactiveinjectcomputed,并且对很大一部分的源码进行了省略,dispatchcommit远比这复杂多了

解答

问题一:为什么要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()函数,其构造函数就会接收一个包含 statemutationsactions getters 函数的对象 options, 然后将它们保存到实例属性中,此时state中的值都会转换为响应式对象,同时遍历所有的getters函数,将其封装成computed属性并保存到实例属性getters中,而在main.js里调用了app.use(), install方法自动执行,将将当前 store 实例注册到 Vue.js 应用程序中,只需要调用useStore()就可以拿到全局状态管理的store实例了,可以靠injectprovide实现全局共享。

总结

通过实现简单的vuex,完成基本的功能,对vuex有了不同的理解,但在vuex的源码方面,上面的demo只是简简单单做了大致的依赖关系整理,还有:不具备模块化功能、没有提供插件功能、没有提供严格模式、没有提供辅助函数和实现单例模式等等的实现。

给大家推荐一个实用面试题库

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
7月前
|
消息中间件 前端开发 Java
美团面试:如何实现线程任务编排?
线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
73 1
|
7月前
|
存储 缓存 监控
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
小伙伴们,有没有遇到过程序突然崩溃,然后抛出一个OutOfMemoryError的异常?这就是我们俗称的OOM,也就是内存溢出 本文来带大家学习Java OOM的三大经典场景以及解决方案,保证让你有所收获!
2457 0
美团面试:说说OOM三大场景和解决方案? (绝对史上最全)
|
2月前
|
存储 安全 Java
每日大厂面试题大汇总 —— 今日的是“美团-后端开发-一面”
文章汇总了美团后端开发一面的面试题目,内容涉及哈希表、HashMap、二叉树遍历、数据库索引、死锁、事务隔离级别、Java对象相等性、多态、线程池拒绝策略、CAS、设计模式、Spring事务传播机制及RPC序列化工具等。
67 0
|
1月前
|
SQL 缓存 关系型数据库
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
|
1月前
|
存储 安全 Java
美团面试:String 为什么 不可变 ?(90%答错了,尼恩来一个绝世答案)
45岁老架构师尼恩分享Java面试心得,涵盖String不可变性、字符串常量池、面试技巧等内容。尼恩强调,掌握深层技术原理,如String不可变性的真正原因,可在面试中脱颖而出,赢得高薪Offer。此外,尼恩还提供了大量技术资源和面试指导,帮助求职者提升技术水平,顺利通过大厂面试。
|
2月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
2月前
|
SQL 存储 关系型数据库
美团面试:binlog、redo log、undo log的底层原理是什么?它们分别实现ACID的哪个特性?
老架构师尼恩在其读者交流群中分享了关于 MySQL 中 redo log、undo log 和 binlog 的面试题及其答案。这些问题涵盖了事务的 ACID 特性、日志的一致性问题、SQL 语句的执行流程等。尼恩详细解释了这些日志的作用、所在架构层级、日志形式、缓存机制以及写文件方式等内容。他还提供了多个面试题的详细解答,帮助读者系统化地掌握这些知识点,提升面试表现。此外,尼恩还推荐了《尼恩Java面试宝典PDF》和其他技术圣经系列PDF,帮助读者进一步巩固知识,实现“offer自由”。
美团面试:binlog、redo log、undo log的底层原理是什么?它们分别实现ACID的哪个特性?
|
1月前
|
SQL 关系型数据库 MySQL
美团面试:Mysql如何选择最优 执行计划,为什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴面试美团时遇到了关于MySQL执行计划的面试题:“MySQL如何选择最优执行计划,为什么?”由于缺乏系统化的准备,小伙伴未能给出满意的答案,面试失败。为此,尼恩为大家系统化地梳理了MySQL执行计划的相关知识,帮助大家提升技术水平,展示“技术肌肉”,让面试官“爱到不能自已”。相关内容已收录进《尼恩Java面试宝典PDF》V175版本,供大家参考学习。
|
2月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩在读者群中分享了关于分库分表的基因算法设计,旨在帮助大家应对一线互联网企业的面试题。文章详细介绍了分库分表的背景、分片键的设计目标和建议,以及基因法的具体应用和优缺点。通过系统化的梳理,帮助读者提升架构、设计和开发水平,顺利通过面试。
美团面试:百亿级分片,如何设计基因算法?

热门文章

最新文章