整理vue3面试题

1,775 阅读5分钟

1. Vue 3 为什么使用 Proxy?

  1. 弥补 Object.defineProperty 的两个不足
  • 动态创建的 data 属性需要用 Vue.set 来赋值,Vue 3 用了 Proxy 就不需要了
  • 基于性能考虑,Vue 2 篡改了数组的 7 个 API,Vue 3 用了 Proxy 就不需要了
  1. defineProperty 需要提前递归地遍历 data 做到响应式,而 Proxy 可以在真正用到深层数据的时候再做响应式

2. Vue 3 为什么使用 Composition API?

参考尤雨溪的博客:传送门

  1. Composition API 比 mixins、高阶组件、extends、Renderless Components 等更好,原因有三:
  • 模版中的数据来源不清晰。
  • 命名空间冲突。
  • 性能。
  1. 更适合 TypeScript

3. Vue 3 对比 Vue 2 做了哪些改动?

官方文档写了(中文在这),这里列出几个容易被考的:

  1. createApp() 代替了 new Vue()
  2. v-model 代替了以前的 v-model 和 .sync
  3. 根元素可以有不止一个元素了
  4. 新增 Teleport 传送门
  5. destroyed 被改名为 unmounted 了(before 当然也改了)
  6. ref 属性支持函数了

4.Vue3 Diff算法和 Vue2 的区别

编译阶段的优化:

  • 事件缓存:将事件缓存(如: @click),可以理解为变成静态的了
  • 静态提升:第一次创建静态节点时保存,后续直接复用
  • 添加静态标记:给节点添加静态标记,以优化 Diff 过程

由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",

Diff 优化如下

  • Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff
  • 使用最长递增子序列优化了对比流程

根据尤大公布的数据就是 Vue3 update 性能提升了 1.3~2 倍

5.composition API 与 options API的区别

  • vue2 采用的就是 optionsAPI

    (1) 优点:易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)

    (2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显

    (3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护

  • vue3 新增的就是 compositionAPI

    (1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起

    (2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api

    (3) 大大的提升了 代码可读性可维护性

  • vue3 推荐使用 composition API, 也保留了options API

    即就算不用composition API, 用 vue2 的写法也完全兼容!!

6.step函数

step()函数是vue3中,专门为组件提供的新属性。为composition API新特性提供了统一的入口,step函数在beforeCreate、created之前执行,vue3取消了这两个钩子,用step代替,该函数相当于一个生命周期钩子,vue中过去的data,methods,watch等全部都用新增api写在函数当中

step接受两个参数props和context,里面不能用this,用contest对象来替代当前执行的上下文,context对象有四个属性:attrs、slots、emit、expose

通过ref和reactive代替以前的data语法,return出去的内容(变量、方法等)可以在模版直接使用

<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template>
​
<script>
export default {
  setup (props,context) {
    console.log('setup执行了')
    console.log(this)  // undefined
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
    // Attribute (非响应式对象,等同于 $attrs)
    context.attrs
    // 插槽 (非响应式对象,等同于 $slots)
    context.slots
    // 触发事件 (方法,等同于 $emit)
    context.emit
    // 暴露公共 property (函数)
    context.expose
​
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)  
  }
}
</script>

7.setup语法糖 (script setup语法)

script step是在单文件组件(SFC)中使用组合式API的语法糖。相比于普通script语法更简洁

<template>
  <div>
    <h3>根组件</h3>
    <div>点击次数:{{ count }}</div>
    <button @click="add">点击修改</button>
  </div>
</template>
​
<script setup>
import { ref } from 'vue'const count = ref(0)
const add = () => {
  count.value++
}
</script>

使用setup语法糖,不用eaport default{},,自组建不用注册,直接倒入使用,属性,方法也不用return

并且里面不需要async可以直接使用await,因为会默认变为async step 用语法糖时,props、attrs、solts、emit、expose的获取方式也不一样

3.0~3.2版本变成了通过 import 引入的 API:definePropsdefineEmituseContext(在3.2版本已废弃),useContext 的属性 { emit, attrs, slots, expose }

3.2+版本不需要引入,而直接调用:definePropsdefineEmitsdefineExposeuseSlotsuseAttrs

8.# 7个 Vue 3 中的组件通信方式

本文采用

  • props
  • emit
  • v-model
  • refs
  • provide/inject
  • eventBus
  • vuex/pinia

1、Props

<template>
  <!-- child component -->
  <child-components :list="list"></child-components>
  <!-- parent component -->
  <div class="child-wrap input-group">
    <input
      v-model="value"
      type="text"
      class="form-control"
      placeholder="Please enter"
    />
    <div class="input-group-append">
      <button @click="handleAdd" class="btn btn-primary" type="button">
        add
      </button>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
  list.value.push(value.value)
  value.value = ''
}
</script>

子组件只需要渲染父组件传递的值。


<template>
  <ul class="parent list-group">
    <li class="list-group-item" v-for="i in props.list" :key="i">{{ i }}</li>
  </ul>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
  list: {
    type: Array,
    default: () => [],
  },
})
</script>

2、Emit

Emit也是Vue中最常见的组件通信方式,用于子组件向父组件传递消息。

我们在父组件中定义列表,子组件只需要传递添加的值。

子组件代码如下:


<template>
  <div class="child-wrap input-group">
    <input
      v-model="value"
      type="text"
      class="form-control"
      placeholder="Please enter"
    />
    <div class="input-group-append">
      <button @click="handleSubmit" class="btn btn-primary" type="button">
        add
      </button>
    </div>
  </div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const value = ref('')
const emits = defineEmits(['add'])
const handleSubmit = () => {
  emits('add', value.value)
  value.value = ''
}
</script>

点击子组件中的【添加】按钮后,我们会发出一个自定义事件,并将添加的值作为参数传递给父组件。


<template>
  <!-- parent component -->
  <ul class="parent list-group">
    <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
  </ul>
  <!-- child component -->
  <child-components @add="handleAdd"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
// event handling function triggered by add
const handleAdd = value => {
  list.value.push(value)
}
</script>

在父组件中,只需要监听子组件的自定义事件,然后执行相应的添加逻辑即可。

3、v-model

v-model 是 Vue 中一个优秀的语法糖,比如下面的代码。

<ChildComponent v-model:title="pageTitle" />

这是以下代码的简写形式

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

这确实容易了很多。现在我们将使用 v-model 来实现上面的示例。

子组件


<template>
  <div class="child-wrap input-group">
    <input
      v-model="value"
      type="text"
      class="form-control"
      placeholder="Please enter"
    />
    <div class="input-group-append">
      <button @click="handleAdd" class="btn btn-primary" type="button">
        add
      </button>
    </div>
  </div>
</template>
<script setup>
import { ref, defineEmits, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
  list: {
    type: Array,
    default: () => [],
  },
})
const emits = defineEmits(['update:list'])
// Add action
const handleAdd = () => {
  const arr = props.list
  arr.push(value.value)
  emits('update:list', arr)
  value.value = ''
}
</script>

在子组件中,我们先定义props和emits,添加完成后再发出指定的事件。

注意:update:*是Vue中固定的写法,*代表props中的一个属性名。

在父组件中使用比较简单,代码如下:

<template>
  <!-- parent component -->
  <ul class="parent list-group">
    <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
  </ul>
  <!-- child component -->
  <child-components v-model:list="list"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
</script>

4、Refs

使用API选项时,我们可以通过this.$refs.name获取指定的元素或组件,但在组合API中不行。如果我们想通过ref获取,需要定义一个同名的Ref对象,在组件挂载后可以访问。

示例代码如下:


<template>
  <ul class="parent list-group">
    <li class="list-group-item" v-for="i in childRefs?.list" :key="i">
      {{ i }}
    </li>
  </ul>
  <!-- The value of the child component ref is the same as that in the <script> -->
  <child-components ref="childRefs"></child-components>
  <!-- parent component -->
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const childRefs = ref(null)
</script>

子组件代码如下:

<template>
  <div class="child-wrap input-group">
    <input
      v-model="value"
      type="text"
      class="form-control"
      placeholder="Please enter"
    />
    <div class="input-group-append">
      <button @click="handleAdd" class="btn btn-primary" type="button">
        add
      </button>
    </div>
  </div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
  list.value.push(value.value)
  value.value = ''
}
defineExpose({ list })
</script>

注意:默认情况下,setup 组件是关闭的,通过模板 ref 获取组件的公共实例。如果需要公开,需要通过defineExpose API 公开。

5、provide/inject

provide/inject是 Vue 中提供的一对 API。无论层级多深,API 都可以实现父组件到子组件的数据传递。

示例代码如下所示:

父组件

<template>
  <!-- child component -->
  <child-components></child-components>
  <!-- parent component -->
  <div class="child-wrap input-group">
    <input
      v-model="value"
      type="text"
      class="form-control"
      placeholder="Please enter"
    />
    <div class="input-group-append">
      <button @click="handleAdd" class="btn btn-primary" type="button">
        add
      </button>
    </div>
  </div>
</template>
<script setup>
import { ref, provide ,readonly} from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// Provide data to child components.
provide('list', readonly(list.value))
// event handling function triggered by add
const handleAdd = () => {
  list.value.push(value.value)
  value.value = ''
}
</script>

子组件

<template>
  <ul class="parent list-group">
    <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
  </ul>
</template>
<script setup>
import { inject } from 'vue'
// Accept data provided by parent component
const list = inject('list')
</script>

注意:使用provide进行数据传输时,尽量使用readonly封装数据;避免子修改父数据

6、eventBus

Vue 3 中移除了 eventBus,但可以借助第三方工具来完成。Vue 官方推荐使用 mitt 或 tiny-emitter。在大多数情况下,不建议使用全局事件总线来实现组件通信。虽然比较简单粗暴,但是维护事件总线从长远来看是个大问题,这里就不解释了。有关详细信息,您可以阅读特定工具的文档。

7、vuex/pinia

Vuex 和 Pinia 是 Vue 3 中的状态管理工具,使用这两个工具可以轻松实现组件通信。由于这两个工具都比较强大,这里就不一一展示了。有关详细信息,请参阅文档。

9. vue3:兄弟组件,跨组件传值,事件总线的通信方式(mitt / tiny-emitter)

在vue2中的跨组件通信中,我们如果不用状态管理vuex的话,我们就会采用事件总线的通信的方式,通常做法就是新建一个js文件,例如bus.js,在里面new Vue(),然后export default导出,但是在vue3中移除了事件总线,我们不可以再这么用了,,,但是官方给我们推荐了外部第三方的库来帮我们完成事件总线,官方推荐了两个: mitt 或者 tiny-emitter

npm地址:mitt - npm 或者 tiny-emitter - npm

用法也很简单:我这里以mitt举例

1.找到vue项目中的utils文件夹,新建一个bus.js

bus.js

import mitt from "mitt";
 
const emitter = mitt()
 
export default emitter

2.使用,我现在需要使用mitt进行兄弟组件之间的通信实现

父组件

<template>
  <child1></child1>
  <child2></child2>
</template>
 
<script setup>
import Child1 from "./components/Child1";
import Child2 from "./components/Child2";
</script>

子组件-child1

<template>
  <div>child1
    <button @click="click">给child2 传值</button>
  </div>
 
</template>
 
<script setup>
import emitter from "@/utils/bus"
 
function click() {
  emitter.emit('child2Data', {name: '小米'})
}
</script>

子组件-child2

<template>
  <div>child1
    <div>{{str}}</div>
  </div>
 
</template>
<script setup>
import emitter from "@/utils/bus"
import ref onbeforeUnmount from "vue"
let str = ref()
emitter.on("child2data",data=>{
  str.value = data.name
})
onbeforeUnmount(()=>{
    emitter.off('child2data')
})
</script>