Vue必须掌握的知识点你都了解了吗?

435 阅读10分钟

1.Vue——渐进式JS框架、MVC/MVP/MVVM的介绍

参考答案

库和框架的区别

  • 库(Library):本质是一些函数的集合。使用库,是由开发人员决定如何调用库中提供的方法。
  • 框架(Framework):是一套完整的解决方案,使用时把代码放到框架合适的地方,框架会在合适的时机调用代码。 学习Vue要转化思想:不要在想着怎么操作DOM,而是想着如何操作数据!!!

MVC、MVP、MVVM的介绍

M: Model 数据模型,用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法;
V:View 视图视图层,主要负责数据的展示。

MVC

Controller控制器:处理业务逻辑 MVC流程

实线代表方法调用,虚线代表事件通知。

用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。

MVP

Presenter负责业务逻辑 MVP流程

MVC里,View是可以直接访问Model的,但MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View

与MVC相比,MVP模式通过解耦ViewModel,完全分离视图和模型使职责划分更加清晰;由于View不依赖Model,可以将View抽离出来做成组件,它只需要提供一系列接口提供给上层操作。

MVVM

"Model of View":视图的模型。 MVVM MVVMViewModel的同步逻辑自动化了。以前Presenter负责的ViewModel同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。

(MVVM中的View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View)

2.Vue跟React的异同点?

参考答案

相同点:

  • 1.都使用了虚拟dom
  • 2.组件化开发
  • 3.都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
  • 4.都支持服务端渲染

不同点:

  • 1.React的JSX —— Vue的template
  • 2.数据变化,React手动(setState) —— Vue自动(初始化已响应式处理,Object.defineProperty)
  • 3.React单向绑定 —— Vue双向绑定
  • 4.React的Redux —— Vue的Vuex

尤雨溪大大的回答:

  • 1.vue使用的是web开发者更熟悉的模板与特性,vue的API跟传统web开发者熟悉的模板契合度更高,比如vue的单页面组件是以模板+JS+CSS的组合模式呈现,它跟web现有的HTML、JavaScript、CSS能够更好地配合。React 的特色在于函数式编程的理念和丰富的技术选型。Vue 比起 React 更容易被前端工程师接受。
  • 2.从使用习惯和思维模式上考虑,对于一个没有任何Vue和React基础的web开发者来说, Vue会更友好,更符合他的思维模式。Vue更加注重web开发者的习惯
  • 3.实现上,Vue跟React的最大区别在于数据的reactivity,就是反应式系统上。 Vue提供反应式的数据,当数据改动时,界面就会自动更新,而React里面需要调用方法SetState。我把两者分别称为Push-basedPull-based。所谓Push-based就是说,改动数据之后,数据本身会把这个改动推送出去,告知渲染系统自动进行渲染。在React里面,它是一个Pull的形式,用户要给系统一个明确的信号说明现在需要重新渲染了,这个系统才会重新渲染。两者并没有绝对的优劣之分,更多的也是思维模式和开发习惯的不同。

3.Vue2.x和Vue3.x的区别?

参考答案
  • 1.生命周期
    • vue2.x的beforeCreatedcreated替换成了setup
    • 生命周期钩子函数增加了on,增加了用于调试的钩子函数
  • 2.双向绑定原理
  • 3.组件通信方式
    • vue3.x使用mitt.js进行通信,mitt.js足够小,仅200kb,支持全部事件的监听和批量移除,不依赖vue实例,可以跨框架使用
  • 4.tree-shaking
    • 引入摇树优化,最小化bundle体积

详细可以看这篇:Vue3对比Vue2

4.Vue和JS的区别?

参考答案
  • 1.响应的数据绑定/响应式编程

    • Vue.js是一个构建数据驱动的 web 界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。
  • 2.组件化开发/组件复用

  • 3.虚拟DOM--Diff算法--性能

5.Vue和JQuery的区别?为什么放弃JQuery用Vue?

参考答案
  • jQuery是直接操作DOM,Vue不直接操作DOM,Vue的数据与视图是分开的,开发者只需要把大部分精力放在数据层面上

  • jQuery的操作DOM行为是频繁的,而Vue利用虚拟DOM的技术,大大提高了更新DOM时的性能

  • Vue集成的一些库,大大提高开发效率,比如Vuex,Router等

6.Vue的SSR是什么?有什么好处?

参考答案
  • SSR就是服务端渲染
  • 基于nodejs serve服务环境开发,所有html代码在服务端渲染
  • 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
  • SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好

7.vue生命周期

7.1 生命周期

参考答案

vue 2.x的生命周期

  • beforeCreate(初始化界面前)

    数据还没有挂载,只是一个空壳,无法访问数据和dom,一般不做操作

  • created(初始化界面后)

    绑定事件,挂载数据,并且在这里更新data,不会触发updata函数

  • beforeMount(渲染dom前)

    将模板编译为虚拟dom,放到render中准备渲染,在这里更新data,不会触发updata函数

  • mounted(渲染dom后)

    渲染出真实dom,可操作真实dom

  • beforeUpdate(更新数据前)

    重新生成dom树,根据diff(本质是调用patch函数)算法,对比上一次dom树

  • updated(更新数据后)

    数据更新完成render完成

  • beforeDestroy(卸载组件前)

    一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等

  • destroyed(卸载组件后)

    组件的数据绑定、监听...去掉后只剩下dom空壳

vue生命周期官方图

7.2 created 和 mounted 的区别

参考答案

image.png

image.png

  • created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
  • mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

注:vue异步请求适合放在created里面,如涉及到需要页面加载完成之后的操作就放在mounted里面。

8.vue路由

8.1路由原理

参考答案

路由的概念来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。
在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)

可以看这篇:Vue Router 路由实现原理

可以看这篇:路由原理

8.2vue路由的模式和区别

参考答案
  • hash模式

    hash模式背后的原理是浏览器的onhashchange()事件,可在window对象上进行监听。hash就是指url#号以及后面的字符,不会包含在http请求中,hash值改变不会导致浏览器向服务端方发送请求,hash发生改变的url会被浏览器记录下来,浏览器前进后退就可以用了。这种页面状态和url一一关联起来,称为前端路由。

  • history模式

    • 切换历史状态使用back()go()forward();
    • 修改路由状态是利用HTML5history中新增的pushState()replaceState()方法。通过pushState()把页面的状态保存在state对象中,当页面的url再变回这个url时,可通过event.state获取到这个state对象,从而对页面状态进行还原。history模式下,前端的url必须和实际后端发起请求的url一致。

8.3vue路由传参的基本方式

参考答案

路由传参方式就两种queryparams,传参形式有以下三种:

  • router-link形式
  • 通过path匹配路由的编程式导航形式
  • 通过name匹配路由的编程式导航形式

这两篇讲的很清楚,可以看看:

文档2:vue路由传参到底有几种方式

文档1:vue路由传参的# 三种基本方式

8.4 query和params传参的区别

参考答案
  • query传递参数 query传递参数不会出现参数丢失的情况,不需要做其他配置,缺点就是参数会拼接到url后面:url?xx==yy这种方式来传递,会暴露参数,并且url也有字符长度的限制。

使用方式: this.$router.push({path: 'path', query: {id:1}})
获取参数: this.$route.query.id获取key为id的路由参数

  • params传递参数 params传递参数是将参数放在route对象中,没有放在url后面,但是在跳转之后的页面刷新时,会导致当前路由中保存的params的参数丢失。

使用方式:this.$router.push({name: 'name', params:{id:1}})
获取参数: this.$route.params.id 获取route对象中的Params的参数信息

解决使用params传递参数刷新页面参数丢失:

  1. 使用sessionStorage、localStorage

sessionStorage、localStorage根据具体的场景来选择,保存到里面的数据不会在刷新下的时候丢失,不过在移动端,使用微信悬浮窗的时候,部分安卓机型的sessionStorage中的数据会丢失。

  1. 使用params中的路由匹配

使用方式: 在router.js,即匹配路由规则的位置,加上占位符即可

{
  path: '/index/:num/:name',
  name: 'index',
  component: Index
}

params中的参数名称需要和占位符的名称一致即可

获取参数,还是和获取params中参数一致:this.$route.params.name
这样的话,参数就会出现在url中,格式为:url/num/name,这种方式将参数放在url上,刷新的时候才不会丢失数据。

8.5路由按需加载

参考答案

路由懒加载的方式有两种,一种是require 另一种是 import 。当路由按需加载后,那么Vue服务在第一次加载时的压力就能够相应的小一些,不会出现 超长白屏P0问题 。下面是两种路由懒加载的写法:

// require法
component: resolve=>(require(['@/components/HelloWorld'],resolve))

// import
component: () => import('@/components/HelloWorld')

8.6路由守卫

参考答案
  • 全局守卫 router.beforeEach对所有的路由都起作用,全局守卫有三个参数:
to:即将要进入的目标 路由对象
from:当前导航正要离开的路由对象
next:参数不同,做的事也不同
    * next() 直接进入下一个钩子
    * next(false) 停止当前导航
    * next('/path') 跳转到path路由地址,也可写成对象形式next({path:'/path'})
    * next(error) 如果传入参数是一个error实例,则导航会终止且该错误会被传递给router.onError()
router.beforeEach((to, from, next) => { 
     next();//使用时,千万不能漏写next!!!
   }).catch(()=>{
     //跳转失败页面
     next({ path: '/error', replace: true, query: { back: false }}
   )
})
  • 路由独享的守卫 beforeEnter路由对象独享的守卫写在routes里面
const router = new VueRouter({
  routes: [
    {
      path: '/goods',
      component: goods,
      beforeEnter: (to, from, next) => {
        // 和全局守卫一样的用法
      }
    }
  ]
})
  • 组件内的守卫
  • beforeRouteEnter 进入路由前,组件还没有被实例化所以这里无法获取到this

  • beforeRouteUpdate(2.2) 这个阶段可以获取this,在路由复用同一个组件时触发

  • beforeRouteLeave 这个阶段可以获取this,当离开组件对应的路由时,此时可以用来保存数据,或数据初始化,或关闭定时器等等

参考文献:从使用到自己实现简单Vue Router看这个就行了

9.keep-alive介绍与应用

参考答案

keep-alive是一个抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们。

应用场景

用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(选中)状态。keep-alive就是用来解决这种场景问题。当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。 总的来说,keep-alive用于保存组件的渲染状态

keep-alive原理

可以看这篇:keep-alive实现原理

keep-alive用法

  • 在动态组件中
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
  <component :is="currentComponent"></component>
</keep-alive>
  • 在vue-router中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
  <router-view></router-view>
</keep-alive>

include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。

keep-alive不会生成真正的DOM节点,这是怎么做到的?

Vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在keep-alive中,设置了abstract: true,那Vue就会跳过该组件实例。最后构建的组件树中就不会包含keep-alive组件,由组件树渲染成的DOM树也不会有keep-alive相关的节点。

  • keep-alive的生命周期
    • 初次进入时:
      1. created > mounted > activated
      2. 退出后触发 deactivated
    • 再次进入:
      1. 只会触发 activated
    • 事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated

源码及更多可看参考文档:彻底揭秘keep-alive原理

10.vue组件中的data为什么必须是一个函数

参考答案

组件是可以被复用的,如果data是一个对象,那么注册一个组件本质是创建一个组件构造器的引用,当我们真正使用组件时才会将组件实例化。实例化对象component1,component2共享同样的对象data,当你修改实例属性时,data也会发生改变。当data是一个函数时,每一个实例的data属性都是独立的,不相互影响。

11.v-if和v-show的区别

参考答案
  • 手段
    • v-if是动态向DOM树中增删元素;v-show是通过DOM元素的display样式属性控制显隐
  • 编译过程
    • v-if有一个局部编译/卸载的过程,切换过程会销毁和重建;v-show只是基于CSS的切换
  • 编译条件
    • v-if只有条件为真才会编译;v-show在任何条件下都被编译,只是显示还是隐藏罢了
  • 性能消耗
    • v-if切换消耗高;v-show初次渲染消耗高
  • 使用场景
    • v-if适用于不太可能改变的情况;v-show适用于频繁切换的情况

12.watch和computed的区别、computed和methods区别

参考答案
  • computed --get/set方式

    • 支持缓存,只有一栏数据发生改变,才会重新计算
    • 不支持异步,异步操作无效
    • 计算属性是基于响应式依赖进行缓存的,也就是基于data中声明过或父组件传递props中的数据通过计算得到的值
    • 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是多对一或一对一,一般用computed
  • watch--类似于监听机制+事件机制

    • 不支持缓存,数据改变会直接触发响应操作
    • 支持异步
    • 监听的函数接收两个参数(oldVal, newVal)
    • 当一个属性发生改变时,需要执行对应的操作,一对多
    • 监听数据必须是data中声明过或父组件传递props中的数据通过计算得到的值
    • watch函数的属性:
      • immediate:组件加载立即触发回调函数执行
      • deep:true 深度监听,递归对对象内部进行监听
      • sync:不将watch加入到nextTick队列而同步的更新
  • methods

    • 挂载在对象上的函数,通常是 Vue实例本身 或 Vue组件 ,methods里面是用来定义函数的,很显然,它需要主动调用才能执行(像"fuc()"这样去调用它),而不像watch和computed那样,“自动执行”预先定义的函数。
  • methods与computed区别

    • 调用方式不同: computed直接以对象属性方式调用,不需要加括号,而methods必须要函数执行才可以得到结果
    • 绑定方式不同: methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定。 是否存在缓存。methods没有缓存,调用相同的值计算还是会重新计算。competed有缓存,在值不变的情况下不会再次计算,而是直接使用缓存中的值。

可以看这篇:vue中 methods watch和computed的区别

13.vue组件中的slot作用

参考答案

插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽具名插槽以及作用域插槽

14.Vue.set 和 Vue.delete( 对象新属性/删除属性无法更新视图 )

参考答案

vue声明在data中的属性都是响应式的,也就是说,我们修改data中的属性时,一般页面都能实时更新。但是由于ES5的限制,vue不能检测数组和对象的变化。比如我们对data中的对象属性和数组属性进行一些修改时,无法响应式更新渲染到页面,因此vue提供了$set$delete的API来解决这个限制。

简单来说:set和delete的源码的话是对 数组对象 分别进行了判定。底层源码本质:如果是数组,是利用splice方法进行增删修改,对象的话会判断key值是否有效,来进行设置和修改,然后通知dom更新。

Vue.set == $set ,Vue.delete == $delete

大致流程

  • 1.判断目标值是否为有效值,不是有效值直接停止
  • 2.判断是否为数组,并且key值是否为有效的key值
    • 对比数组的key值和数组长度,取较大值设置为数组的长度
    • 用数组的splice方法替换目标值
  • 3.判断目标值是否为响应式的__ob__
    • 如果是vue实例,直接警告
    • 如果不是响应式的数据,就是普通的修改对象操作
    • 如果是响应式数据,那就通过Object.defineProperty进行数据劫持
  • 4.通知dom更新 下面来看vue.set代码:
export function set (target: Array<any> | Object, key: any, val: any): any {
// 先判断目标值是否是有效值,不是直接停止,警告
if (process.env.NODE_ENV !== 'production' &&    (isUndef(target) || isPrimitive(target))  ) {
    warn(`Cannot set reactive property on undefined,
    null, or primitive value: ${(target: any)}`)
  }
  // 判断目标值是否为数组,并且key值是否为有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
//对比数组的key值和数组长度,取较大值设置为数组的长度
    target.length = Math.max(target.length, key)
    // 目标值替换
    target.splice(key, 1, val)    return val  
}  
// 如果目标值是对象,并且key值是目标指存在的有效key值,且不是原型上的key值
if (key in target && !(key in Object.prototype)) {
    target[key] = val    return val  // 目标值更改
}
  const ob = (target: any).__ob__  // 判断目标值是否为响应式的,该属性为true标志着target是响应式对象
if (target._isVue || (ob && ob.vmCount)) { //如果是vue根实例,就警告
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'    )
    return val  }
if (!ob) {  // 如果目标值不是响应式的,那么值需要给对应的key赋值
    target[key] = val
    return val  
}  
// 其他情况,目标值是响应式的,就通过Object.defineProperty进行数据监听
defineReactive(ob.value, key, val)  
// 通知更新dom操作
ob.dep.notify()  
return val
}

set 函数接收三个参数:第一个参数 target 是将要被添加属性的对象,第二个参数 key 以及第三个参数 val分别是要添加属性的键名和值。

错误写法:this.$set(key,value)(ps: 可能是vue1.0的写法)
mounted () {
  this.$set(this.student.age, 24)
}
正确写法:this.$set(this.data,”key”,value')  ---三个参数
mounted () {
  this.$set(this.student,"age", 24)
}

下面来看vue.delete代码:

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))  ) {
    warn(`Cannot delete reactive property on undefined, 
      null, or primitive value: ${(target: any)}`)  
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)    return  // 数组的处理
  }  
   const ob = (target: any).__ob__  
   if (target._isVue || (ob && ob.vmCount)) {
      process.env.NODE_ENV !== 'production' && warn( 
     'Avoid deleting properties on a Vue instance or its root $data ' +   
    '- just set it to null.'    )
     return  
    }  
   if (!hasOwn(target, key)) {  // 对象的处理
    return  
   }
   delete target[key]  
  if (!ob) {
    return  
  }
  ob.dep.notify()
}

del 函数接收两个参数,分别是将要被删除属性的目标对象 target 以及要删除属性的键名 key.与set源码原理大致相同。

15.vue组件通信方式

参考答案:vue中8种组件通信方式

16.vue的Diff算法、key的作用

16.1.当数据发生改变时,vue是如何更新节点的?

参考答案

首先,根据真实DOM生成一颗虚拟DOM,当虚拟DOM的节点数据改变后会生成一个新的Vnode,然后VnodeoldNode进行对比,发现不一样的地方直接修改在真实DOM上,然后使oldVNode的值为Vnode。节点比较的过程就是用的Diff算法。(注:VNodeoldVNode都是对象,是以对象的形式模拟树形结构)

16.2.Diff的比较方法

参考答案

Diff算法比较新旧节点的时候,比较只会在同层级进行,不会跨级比较。如下图: Diff比较方法

16.3.Diff流程图

Diff流程图

当数据发生改变时,set方法会调用Dep.notify通知所有订阅者,订阅者会调用patch函数比较新旧节点,给真实DOM打补丁,更新相应的视图。 Diff算法

16.4.源码分析

看这篇很详细:详解vue的Diff算法

16.5.Vue中key的作用

参考答案

key的作用主要是为了更高效地更新虚拟DOM,给每个节点唯一的标识。在Diff算法中,key是为了在diff算法执行时更快的找到对应节点,提高diff速度。当页面发生变化时,diff算法只会比较同一层级的节点。

  • 节点类型不同,直接干掉前面的节点,再创建插入新节点,不再比较之后的节点
  • 节点类型相同,则会设置该节点的属性,从而实现节点的更新

16.5 Virtual Dom的效率一定比直接操作Dom高吗

可以看这篇:尤雨溪的回答

17.vue.nextTick作用和应用场景

参考答案:Vue nextTick实现原理、源码解析

18.Vue模板编译原理

参考答案

image.png

关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)
  • 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器),静态节点指的是 DOM 不需要发生变化的节点。
    • 标记静态节点有两个好处:1.每次重新渲染的时候不需要为静态节点创建新节点; 2.在 Virtual DOM 中 patching 的过程可以被跳过
    • 优化器的实现原理主要分两步:1.用递归的方式将所有节点添加 static 属性,标识是不是静态节点; 2.标记所有静态根节点
  • 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)

更多可以看这篇: Vue源码之模板编译原理

19.VueX

参考答案

1.什么是Vuex?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理插件。它采用集中式存储管理应用的所有组件的状态,而更改状态的唯一方法是提交mutation。

2.解决了什么问题?应用场景?

  • 多个组件依赖同一状态时,多层嵌套的组件传参很繁琐,用vuex能很好地管理状态。
  • 来自不同组件的行为需要变更同一状态时,集中存储管理。

3.Vuex的五个核心属性

const store = new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
  },
  mutations: {
    increment (state) {
      state.count++;  // 变更状态
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
     }
  },
})
  • state:存放数据
  • getters:获取state数据的状态,可以对数据进行简单操作
  • mutations: 更改store中修改状态,必须是同步函数,通过this.$store.commit(increment', 10)来提交
  • actions: 提交的是 mutation,而不是直接变更状态。可以包含任意异步操作,在组件中使用 this.$store.dispatch('xxx',data) 分发action
  • module:将store分割成模块(module),每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块。
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

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

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

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

4.在v-model上使用Vuex中state的值

需要通过computed计算属性来转换。

<input v-model="message">
// ...
computed: {
    message: {
        get () {
            return this.$store.state.message
        },
        set (value) {
            this.$store.commit('updateMessage', value)
        }
    }
}

20.vue2.x和vue3.x双向数据绑定原理区别

参考答案:vue2.x双向绑定原理