Vue Q&A

2022/05/04 Vue 共 3631 字,约 11 分钟

Vue Q&A

MVC和MVVM的区别

MVC MVC

  • V即View即DOM;M即Model即数据模型;C即Controller即逻辑控制器
  • MVC所有的数据通信都是单向的
  • 数据流动的方向为V->C->M->V,即
    • 我们通过DOM绑定的事件触发逻辑控制器Controller
    • 通过Controller变更数据模型Model,同时将Model通过DOM API映射变化到View
  • MVC一般有两种形式:
    • View绑定DOM事件触发Controller;Controller完成业务逻辑更新触发Model变更状态;Model将变更状态反映到View
    • 用户通过自定义事件或者hashchange等直接触发Controller;Controller完成业务逻辑触发Model变更状态;Model将变更同步到View

MVVM

  • MVVM即在View和Model中间存在ViewModel转换层,可以将View的变化通过中间层转化到Model,也可以将Model的变化通过中间层转化到View
  • MVVM通过中间层VM实现了双向绑定
  • VM层是可以将View和Model做自动映射的一层,即用户可以专注Model修改操作View变化

Vue的双向绑定原理

所谓的双向绑定是建立在MVVM的模型基础上的:

  • 数据层Model:应用的数据以及业务逻辑
  • 视图层View:应用的展示效果,各类的UI组件等
  • 业务逻辑层ViewModel:负责将视图和数据关联起来

MVVM或者双向绑定的的本质:

  1. 数据变化后更新视图
  2. 视图变化后更新数据

这个双向绑定主要依赖两部分的功能:

  • 监听器Observer:响应式属性监听
  • 解析器Compiler:对每个元素节点的指令进行扫描和解析,根据指令替换数据,绑定对应的更新函数

具体实现原理?参考Vue2的双向绑定和Vue3的顶层代理原理

虚拟DOM的优缺点

优点:

  • DOM解耦。首先,最重要的一点是,使用VDom使得框架从繁重的真实DOM操作中解耦出来,因为我们知道频繁操作真实DOM是很耗费性能的。
  • 同构、平台无关。其次,这使得框架同构有了可能,我们可以在任何js可运行的环境中,重用框架的运行时。
    • 在此基础上,我们完全可以通过创建自定义渲染解决方案的方式,跳出浏览器运行时约束,这里尤指SSR
  • 算法优化。数据结构量化,可借助算法优化,减少真实DOM操作开销

缺点:

  • 首次渲染大量DOM时,计算开销较大,慢于直接innerHTML

关于@click.native修饰符

官方文档

  • Vue3已经废弃,因为已经改进了事件绑定机制,所以不再需要native修饰符

Vue2中存在的作用:

  • 当自定义组件足够细腻,且自定义组件template root元素需要绑定某个事件时:
    • 传统的思路是我们通过props绑定一个function,然后在子组件进行接收,然后手动绑定这个事件到子组件的根元素。
      • 但是这样会侵入子组件逻辑,假如存在一种场景,我们需要更改element-ui中某一个组件的根节点绑定的事件,或者说这个根节点本身没有暴露出我们实际需要的事件接口时,我们可以通过native修饰符,在不侵入第三方组件代码时进行事件注入(其实我们也可以通过外层捕获事件冒泡的形式进行事件拦截)
      • 需要注意这个事件注入的是浏览器原生DOM事件

Vue3使用指南:

  • Vue3中已经没有native修饰符了,取而代之的是,我们在子组件内部接收一个emits选项,所有通过emits选项定义的事件,才会被子组件认定为组件内触发事件监听器
    • 没有在emits声明的事件,会被当作浏览器原生DOM事件绑定到子组件根DOM节点

v-model原理

在Vue2中使用v-model="modelProps"进行input等非受控组件双向绑定时:

  • 相当于在非受控组件自身同时绑定了
    • :value="modelProps"
    • @input="modelProps = $event.target.value"
  • 相当于通过一个v-model简单语法糖使非受控组件瞬间受控

在Vue2中使用v-model="inputValue"进行自定义组件双向绑定时:

  • 相当于在组件自身同时绑定了:value="inputValue"@input="inputValue = $event"事件

在Vue2中使用:title.sync="pageTitle"进行双向绑定时:

  • 相当于同时绑定了:title="pageTitle"@update:title="pageTitle = $event"事件

在Vue3中,相当于将上述混乱的用法进行了整合:

  • 在组件身上使用v-model:title="pageTitle",相当于同时在组件身上绑定了
    • :title="pageTitle"
    • @update:title="pageTitle = $event"

Vue生命周期

same-site

基本生命周期:

  • beforeCreate: 初始化实例
  • created:创建实例,此时可以访问Vue实例
    • 包含以下配置完成:reactivity、computed、methods、events
    • 检查是否有template,调用compiler生成render function
  • beforeMount:调用render function生成VNode
  • mounted:挂载VNode,此时可以访问DOM
  • beforeUpdate:当reactivity data change时调用
  • updated:当完成VNode re-render和patch以后调用
  • beforeUnmount:卸载组件实例之前调用,此时尚可访问实例
  • unmounted组件实例卸载完成后调用
    • 此时所有指令、事件均解除/移除

有两个特殊的钩子,配合keep-alive使用:

  • activated:被keep-alive缓存的组件激活时调用
  • deactivated:被keep-alive缓存的组件失活时调用

Vue keep-alive原理

keep-alive

Vue watch支持的额外参数

API

  • immediate:是否立即触发一次watch callback
  • deep:是否深度递归,发现对象内部的变化
  • flush:控制回调函数执行的时机。命名类似nodejs钩子
    • pre:默认值。在重新渲染之前调用,性能优化
    • post:在重新渲染之后调用,适用于需要通过watch的callback访问更新以后的DOM的场景
    • sync:watch监听值一旦发生变化即调用callback

key的作用

主要的区别在于diff计算VDom的算法逻辑应对上:

  • 当使用key时,Vue会基于key的变化重新排列元素顺序,移除key不存在的元素
  • 当不使用key时,因为完全对比两份VDom的异同复杂度是O(n3),其中n为元素节点数量,开销是比较大的
    • 未了降低算法复杂度,在diff时会进行抉择,如:发现元素节点类型不一致且不存在key时,会直接销毁重建

所以有以下结论:

  • 也就是说,在使用key的时候,能够更加高效精准的更新VDom
  • Vue Transition过渡时,必须使用key,否则一旦发生元素的重建,就可能会不触发过渡效果

双向绑定和Vuex是否冲突

在严格模式下会有冲突,但是一般情况下,即使发布生产,也不建议使用严格模式

开启严格模式:

const store = new Vuex.Store({
  // ...
  strict: true
})

严格模式:

  • 在严格模式下,任何不由mutation引发的状态变更,都会导致错误。这将保证所有的状态变更都能被调试工具跟踪到
  • 严格模式会深度监测状态树,进而监测不合规的状态变更,耗费性能

Vue-router三种模式

  • hash模式
    • vue默认模式
  • history模式
  • abstract模式
    • 抽象模式,在检测不到处于浏览器环境(判断window是否存在)时,默认进入抽象模式
let mode = options.mode || 'hash'
this.fallback =
  mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
  mode = 'hash'
}
if (!inBrowser) {
  mode = 'abstract'
}
this.mode = mode

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}

Search

    Table of Contents