面试题 - VUE

168 阅读10分钟

最近找工作整理了一些面试题,分享给大家一起来学习。如有问题,欢迎指正。

前端面试题系列文章:

VUE

单页面应用(SPA)和多页面应用(MPA)区别

  • 单页面应用(SPA):只有一个html页面的应用,页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次

    • SPA的优点

      1. 页面切换速度快,用户体验较好
      2. seo优化好,搜索引擎优化比较容易
    • SPA的缺点

      1. 首屏加载速度慢
      2. 不易于SEO
    • 如何给SPA做SEO

      SEO效果差,因为搜索引擎只认识html里的内容,不认识js的内容,而单页应用的内容都是靠js渲染生成出来的,搜索引擎不识别这部分内容。

      1. 使用服务端渲染SSR:将组件或页面通过服务器生成html,再返回给浏览器。
      2. 预渲染 prerender-spa-plugin:使用预渲染方式,在构建时(build time)简单地生成针对特定路由的静态HTML文件
  • 多页面应用(MPA):指一个应用中有多个html页面,页面跳转时是整页刷新,都需要重新加载htmlcssjs文件

    • MPA的优点

      1. 首屏加载速度快
      2. seo优化好,搜索引擎优化比较容易
    • MPA的缺点

      1. 页面切换加载缓慢,用户体验差
对比项SPAMPA
结构一个主页面+许多模块的组件许多个完整的页面
体验页面切换快,体验良好;但是初次加载文件过多时,需要做相关的调优网速慢的时候页面切换慢,体验不好
内容更新相关组件的切换,即局部更新整体HTML切换,重复的HTTP请求
资源文件组件公用的资源只需要加载一次每个页面都要自己加载公用的资源
适用场景对体验度和流畅度有较高的要求,但不利于SEO适用于对SEO要求较高的应用
过渡动画Vue提供了transition的封装组件,容易实现较难实现
路由模式hash模式 or history模式普通的链接跳转
数据传递使用全局变量(Vuex)cookie、localStorage等缓存方案,URL参数等
相关成本前期开发成本较高、后期维护较为容易前期开发成本低、后期维护比较麻烦(可能牵一发而动全身)

MVVM、MVC区别

  • MVVM表示的是 Model-View-ViewModel
    • Model代表数据模型,数据和业务逻辑都在Model层中定义;

    • View代表UI视图,负责数据的展示;

    • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

      Model 和 View 并无直接关联,而是通过 ViewModel 来进行交互的(即双向数据绑定)

      image.png

  • MVC表示的是 Model-View-ViewModel
    • Model(模型)存储页面的业务数据,以及对相应数据的操作;

    • View(视图)负责页面的显示逻辑。

    • Controller(控制器)负责用户与应用的响应操作

      View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改。

      image.png

VUE3 优势

  1. 性能: 比vue2.x快1.2~2倍

    • diff算法优化

      • vue2.x: 生成新的虚拟DOM树 与 之前的虚拟DOM树, 全量比较,部分更新
      • vue3.0:创建虚拟DOW的时候,根据DOM内容是否发生变化添加patchFlag标志, 虚拟DOM比较时,静态标记的节点进行比较
    • 静态提升

      • vue2: 无论是否参与更新,每次都会重新创建,然后渲染。
      • vue3: 把不需要更新的元素做静态提升,只被创建一次,在渲染时直接复用
    • 事件侦听器缓存

      • vue2:我们写的@click="onClick"也是被当作动态属性,diff的时候也要对比。但我们知道它不会变化,比如变成@click="onClick2",绑定别的值。

      • vue3中,如果事件是不会变化的,会将onClick缓存起来(跟静态提升达到的效果类似),该节点也不会被标记上PatchFlag(也就是无需更新的节点)。这样在render和diff两个阶段,事件侦听属性都节约了不必要的性能消耗。

  2. vue3重写了数据双向绑定

    使用了ES6的proxy。vue2使用的是defineProperty进行数据劫持,缺陷是对数组数据不友好,需要对数组的原生方法进行重写,并且监听不到对数组的长度。

  3. 体积小

    引入tree-shaking ,按需编译,在打包的过程中就可以将这些没有被用户使用的API移除,减少打包体积。

  4. 组合API(Composition API)逻辑更加分明

    Vue2需要使用的数据要在data、method等里面分开写,不是一个整体。现在可以将需要使用的数据和方法放在一起写,或者通过写hook函数进行区分。

  5. 更好的 ts 支持

    vue2 不适合使用 ts,在于它的 Options API 风格。options 是一个简单的对象,而 ts 是一种类型系统、面向对象的语法,两个不匹配。

    vue3 新增了 defineComponent 函数,使组件在 ts 下,更好的利用参数类型推断。如:reactive 和 ref 很具有代表性。

VUE 双向绑定原理

Vue2.x是借助Object.defineProperty()实现的,而Vue3.x是借助Proxy实现的。发布订阅模式(观察者模式)

vue2 双向绑定原理:

vue初始化时会对data中的属性在Object.defineProperty()中定义get和set函数,为每个属性添加dep数组(订阅器)。 模板编译时, 遇到变量触发该属性的get方法,在get方法内创建一个watcher(订阅者),将watcher添加到dep中。 当数据发生变化,触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。实现了数据驱动视图。当输入框输入数据时,数据发生变化。实现视图驱动数据。

VUE 生命周期

  • vue 2 image.png

    生命周期描述
    beforeCreate组件实例被创建之初
    created组件实例已经完全创建
    beforeMount组件挂载之前
    mounted组件挂载到实例上去之后
    beforeUpdate组件数据发生变化,更新之前
    updated组件数据更新之后
    beforeDestroy组件实例销毁之前
    destroyed组件实例销毁之后
    activatedkeep-alive 缓存的组件激活时
    deactivatedkeep-alive 缓存的组件停用时调用
    • 当前路由不使用 <keep-alive>缓存,离开当前路由会直接调用 beforeDestroy 和 destroyed 销毁

    • 当前路由使用 <keep-alive> 缓存,离开当前路由不会直接调用 beforeDestroy 和 destroyed 销毁,需要使用路由钩子函数主动的调用

      beforeRouteLeave(to, from, next) {
       this.$destroy();
       next();
      }
      
  • vue 3 image.png

    生命周期描述
    beforeCreate组件实例被创建之初
    created组件实例已经完全创建
    beforeMount组件挂载之前
    mounted组件挂载到实例上去之后
    beforeUpdate组件数据发生变化,更新之前
    updated组件数据更新之后
    beforeUnmount组件实例卸载之前
    unmounted组件实例卸载之后
    activatedkeep-alive 缓存的组件激活时
    deactivatedkeep-alive 缓存的组件停用时调用
    errorCaptured捕获了后代组件传递的错误时调用

    setup()执行顺序在 beforeCreate 和 created这两个钩子函数之前。是最早执行的,所以不能使用this,通过ref()和reactive( )函数的使用,可以完全替换掉以前的data{}语法形式。使用组合式API没有 beforeCreate 和 created 这两个生命周期

父子组件生命周期执行顺序

  • 加载渲染过程

    正常: 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created-> 子beforeMount-> 子mounted -> 父mounted

    子组件懒加载: 父beforeCreated ->父created ->父beforeMounted -> 父mounted ->子beforeCreated ->子created ->子beforeMounted ->子mounted

  • 更新过程

    父beforeUpdate -> 子beforeUpdate-> 子updated -> 父updated

  • 销毁过程

    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

对虚拟DOM的理解

  • 什么是虚拟 DOM

    虚拟 DOM 就是一个普通的 JavaScript 对象,用来描述真实dom结构的js对象。包含了 tag、props、children 三个属性,以这三个属性来描述一个DOM节点

    <div id="app"> <p class="text">hello world!!!</p> </div>
    

    将上面的HTML模版抽象成虚拟DOM树:

    {
      tag: 'div',
      props: {
        id: 'app'
      },
      chidren: [
        {
          tag: 'p',
          props: {
            className: 'text'
          },
          chidren: [
            'hello world!!!'
          ]
        }
      ]
    }
    
  • 为什么需要虚拟DOM

    • 操作真实DOM∶ 总损耗 = 真实DOM完全增删改+(可能较多的节点)排版与重绘
    • 操作虚拟DOM∶ 总损耗 = 虚拟DOM增删改+(与Diff算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘

    由于JavaScript需要借助浏览器提供的DOM接口才能操作真实DOM,再加上修改DOM经常导致页面重绘,所以一般来说,DOM操作越多,网页的性能就越差。虚拟DOM有效地减少对真实DOM的操作,以此来提升网页性能。

DIFF算法的原理

  • patch方法:对比当前同层的虚拟节点是否为同一种类型的标签
    • 是:继续执行patchVnode方法进行深层比对
    • 否:没必要比对了,直接整个节点替换成新虚拟节点
  • patchVnode方法,对比虚拟新旧虚拟节点内容,更新真实Dom节点
    • 找到对应的真实DOM,称为el
    • 判断newVnodeoldVnode是否指向同一个对象,如果是,那么直接return
    • 如果他们都有文本节点并且不相等,那么将el的文本节点设置为newVnode的文本节点。
    • 如果oldVnode有子节点而newVnode没有,则删除el的子节点
    • 如果oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el
    • 如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要

在新老虚拟DOM对比时:

  • 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
  • 匹配时,找到相同的子节点,递归比较子节点

$nextTick

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和Dom操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和Dom操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

vue 2 的data为什么是一个方法

因为对象是引用数据类型,如果写成对象,这个组件在多处被引用,只要修改一处的值,那么另一处的值也会变化,造成数据污染。而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

vue3的proxy与vue2的defineProperty的对比

  • Object.defineProperty只能针对定义好的属性进行监听,无法对新增属性或者删除属性监听

  • proxy是对整个对象进行监听,新增属性或者删除属性都能响应。

    vue2的Object.defineProperty没有实现对数组的监听,只提供了push,popsortreservespliceunshiftshift等七种方法能触发数组监听,所以没有数组下标响应式。 proxy是对整个数组对象进行监听,新增元素或者删除元素都能响应,数组下标也能响应。

    // Object.defineProperty
    let name = '答案cp3'
    let obj = { name }
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        Object.defineProperty(obj, key, {
          get () {
            console.log('get函数被调用了')
            return name
          },
          set (val) {
            console.log('set函数被调用了')
            name = val
          }
        })
      }
    }
    obj.name = 'cp3' // set函数被调用了
    obj.age = 18 // 没有打印set函数被调用了
    console.log(obj.name) // get函数被调用了
    console.log(obj.age) // 没有打印
    
    
    // Proxy
    let name = '答案cp3'
    let obj = new Proxy({ name }, {
      get (target, key) {
        console.log('get函数被调用了')
        return target[key]
      },
      set (target, key, val) {
        console.log('set函数被调用了')
        target[key] = val
      }
    })
    obj.name = 'cp3' // set函数被调用了
    obj.age = 18 // set函数被调用了
    console.log(obj.name) // get函数被调用了
    console.log(obj.age) // get函数被调用了
    

为什么动态给vue的data添加一个新属性为非响应式?

vue2是通过Object.defineProperty实现数据响应式,新增的属性,并没有通过Object.defineProperty设置成响应式数据。

解决方案

  • Vue.set()
  • Object.assign():应创建一个新的对象,合并原对象和混入对象的属性
  • $forcecUpdated():强制刷新

Vue 2为什么没有数组下标响应式

Vue 的双向数据绑定,使得修改数据后,视图就会跟着发生更新,然而直接通过下标修改数组内容后,视图却不发生变化。

但是Vue对数组的7个(push、pop、shift、unshift、splice、sort、reverse)实现了响应式

性能代价和用户体验收益不成正比。对于对象而言,每一次的数据变更都会对对象的属性进行一次枚举,一般对象本身的属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计,但是对于数组而言呢?数组包含的元素量是可能达到成千上万,假设对于每一次数组元素的更新都触发了枚举/遍历,其带来的性能损耗将与获得的用户体验不成正比,故vue无法检测数组的变动

解决方案

  • this.$set(array, index, data)
  • 数组的splice方法

v-model 语法糖实现

  • vue 2
    <CustomInput v-model="title" />
    // 相当于
    <CustomInput :value="title" @input="newTitle => title = newTitle"/>
    
    // 使用.sync 使得 props属性双向绑定
    <CustomInput :title.sync="title" />
    // 相当于
    <CustomInput :title="title" @update:title="newTitle => title = newTitle"/>
    
  • vue 3
    <CustomInput v-model="title" />
    // 相当于
    <CustomInput :modelValue="title" @update:modelValue="newTitle => title = newTitle" />
    
    // props属性双向绑定
     <CustomInput v-model:title="title" />
    // 相当于
    <CustomInput :title="title" @update:title="newTitle => title = newTitle" />
    
    

vue组件间通信

  • 组件通信方案:

    1. 通过 props 传递 和 $emit
    2. 使用$refs$parent
    3. EventBus
    4. attrs 与 listeners
    5. Provide 与 Inject
    6. Vuex
  • 父子组件通信

    • props 和 $emit
    • $refs$parent
  • 兄弟组件

    • EventBus
  • 祖孙组件

    • provide 与 inject
    • $attrs和 $listeners
  • 非关系组件

    • vuex
  • EventBus实现

    // 创建一个中央时间总线类
    class Bus {
      constructor() {
        this.callbacks = {}; // 存放事件的名字
      }
      $on(name, fn) {
        this.callbacks[name] = this.callbacks[name] || [];
        this.callbacks[name].push(fn);
      }
      $emit(name, args) {
        if (this.callbacks[name]) {
          this.callbacks[name].forEach((cb) => cb(args));
        }
      }
    }
    
    // main.js
    Vue.prototype.$bus = new Bus(); // 将$bus挂载到vue实例的原型上
    // 另一种方式
    Vue.prototype.$bus = new Vue(); // Vue已经实现了Bus的功能
    
    
    // children1.vue
    this.$bus.$emit('foo')
    // children2.vue
    this.$bus.$on('foo', this.handle)
    

vue slot插槽

  • slot插槽分类:
    • 默认插槽
    • 具名插槽
    • 作用域插槽
 // 子组件 child.vue 
<template> 
  <!-- 默认插槽 -->  
  <slot></slot>
  
  <!-- 具名插槽 -->  
  <slot name="conent"></slot>
  
   <!-- 作用域插槽 -->  
  <slot name="footer" testProps="子组件的值"></slot>
</template>

// 父组件
<child>     
    <div>默认插槽</div>

    <template v-slot:content>
      具名插槽
    </template>
    
    <template v-slot:footer="slotProps">
      作用域插槽:来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
</child>

v-show与v-if的区别

  • v-show隐藏则是为该元素添加css--display:nonedom元素依旧还在。v-if显示隐藏是将dom元素整个添加或删除
  • v-iffalse变为true的时候,触发组件的beforeCreatecreatebeforeMountmounted钩子,由true变为false的时候触发组件的beforeDestorydestoryed方法

v-if和v-for的优先级是什么

v-for优先级比v-if高。

v-if 和 v-for 同时用在同一个元素上,会带来性能方面的浪费(每次渲染都会先循环再进行条件判断)

vue中key的原理吗

  • 使用v-for时,需要给每项加上key

    Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed

  • 组件更新key,手动强制触发重新渲染

    旧key的组件被移除,新key的组件被加载

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点

Vue常用的修饰符

  • 表单的修饰符

    • lazy: 光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步

      <input type="text" v-model.lazy="value">
      <p>{{value}}</p>
      
    • trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤

      <input type="text" v-model.trim="value">
      
    • number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值

      <input type="number" v-model.number="value">
      
  • 事件修饰符

    • stop:阻止了事件冒泡,相当于调用了event.stopPropagation方法
    • prevent:阻止了事件的默认行为,相当于调用了event.preventDefault方法
    • self:只当在 event.target 是当前元素自身时触发处理函数
      • v-on:click.prevent.self 会阻止所有的点击
      • v-on:click.self.prevent 只会阻止对元素自身的点击
    • once:绑定了事件以后只能触发一次,第二次就不会触发
    • capture:使事件触发从包含这个元素的顶层开始往下触发
    • passive:在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符。.passive 和 .prevent不一起使用,passive 会告诉浏览器你不想阻止事件的默认行为
    • native:让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件。 使用.native修饰符来操作普通HTML标签是会令事件失效的
  • .sync:能对props进行一个双向绑定

    • 使用sync的时候,子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致
    • 注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用,例如v-bind.sync=”{ title: doc.title }”,是无法正常工作的

template与render函数对比

  1. render渲染方式可以让我们将js发挥到极致,因为render的方式其实是通过createElement()进行虚拟DOM的创建。逻辑性比较强,适合复杂的组件封装。
  2. template是类似于html一样的模板来进行组件的封装。
  3. render的性能比template的性能好很多
  4. render函数优先级大于template

Vue Router

history 模式和 hash 模式区别

  • hash 模式

    在 url 中的 # 之后对应的是 hash 值。无需发起 http 请求,window 通过hashChange() 事件监听hash值的变化,根据路由表对应的hash值来判断加载对应的路由加载对应的组件。

    优点

    • 只需要前端配置路由表, 不需要后端的参与
    • 兼容性好, 浏览器都能支持
    • hash值改变不会向后端发送请求, 完全属于前端路由

    缺点

    • hash值前面需要加#, 不符合url规范,也不美观
  • history 模式

    利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。pushState()方法可以改变URL地址且不发送请求,replaceState()方法可以读取历史记录栈,还可以对浏览器记录进行修改。 这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

    页面跳转时,使用 pushState() 和 replaceState() 方法对历史记录栈修改,不发送请求。

    页面刷新时,会请求服务器。后端需要配置路由指向index.html,否则会出现404

    优点

    • 符合url地址规范, 不需要#, 使用起来比较美观

    缺点

    • 在用户手动输入地址或刷新页面时会发起url请求, 后端需要配置index.html
    • 兼容性比较差

Vue-Router 的懒加载如何实现

  1. 使用箭头函数+import动态加载
// 复制代码
const List = () => import('@/components/list.vue')
const router = new VueRouter({
  routes: [
    { path: '/list', component: List }
  ]
})
  1. 使用箭头函数+require动态加载
const router = new Router({
  routes: [
   {
     path: '/list',
     component: resolve => require(['@/components/list'], resolve)
   }
  ]
})
  1. 使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
const List = resolve => require.ensure([], () => resolve(require('@/components/list')), 'list');
// 路由也是正常的写法  这种是官方推荐的写的 按模块划分懒加载 
const router = new Router({
  routes: [
  {
    path: '/list',
    component: List,
    name: 'list'
  }
 ]
}))

Vue-router 路由钩子在生命周期的体现

  1. 全局路由钩子
    • router.beforeEach 全局前置守卫 进入路由之前
    • router.beforeResolve 全局解析守卫(2.5.0+)
    • router.afterEach 全局后置钩子 进入路由之后
  2. 单个路由独享钩子
    • beforeEnter 为某些路由单独配置守卫,只在进入路由时触发
  3. 组件内钩子
    • beforeRouteEnter 在渲染该组件的对应路由被验证前调用
    • beforeRouteUpdate 在当前路由改变,但是该组件被复用时调用
    • beforeRouteLeave 在导航离开渲染该组件的对应路由时调用
  • 完整的导航解析流程 导航被触发。 在失活的组件里调用 beforeRouteLeave 守卫。 调用全局的 beforeEach 守卫。 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。 在路由配置里调用 beforeEnter。 解析异步路由组件。 在被激活的组件里调用 beforeRouteEnter。 调用全局的 beforeResolve 守卫(2.5+)。 导航被确认。 调用全局的 afterEach 钩子。 触发 DOM 更新。 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

params和query的区别

用法this.$route.query.namethis.$route.params.name

区别

  • query在浏览器地址栏中显示参数,params后者则不显示
  • 刷新页面query里面的数据不会丢失 params会丢失

VUEX

Vuex 核心概念和原理介绍

  • 特点:

    • Vuex 的状态存储是响应式的
    • 能直接改变 store 中的状态。更改store 中的状态的唯一方法是提交 mutation
  • state:存放公共数据的地方;

    使用方式

    • {{this.$store.state.值名称}}
    • ...mapState(["值名称"])
  • getters:获取根据业务场景处理返回的数据;

    使用方式

    • {{this.$store.getters.值名称}}
    • ...mapGetter(["值名称"])
  • mutations:更改state数据的函数,必须是同步函数

    使用方式

    • store.commit('函数名称')
    • ...mapMutations(["函数名称"])
  • actions:提交的是 mutation

    使用方式

    • store.dispatch('函数名称')
    • ...mapActions(["函数名称"])
  • module:Vuex 允许将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

Vuex中action和mutation的区别

  • mutation中的操作是一系列的同步函数,用于修改state中的变量的的状态
  • action 可以包含任意异步操作。 action 提交的是 mutation,而不是直接修改state中的变量的的状态。