Vue3 面试题

作者:Administrator 发布时间: 2026-01-27 阅读量:4 评论数:0

Vue3面试真题 (6题)

  1. Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

image

1.1. 开始之前

Composition API 可以说是 Vue3 的最大特点,那么为什么要推出 Composition Api,解决了什么问题?

通常使用Vue2开发的项目,普遍会存在以下问题:

  • 代码的可读性随着组件变大而变差

  • 每一种代码复用的方式,都存在缺点

  • TypeScript支持有限

以上通过使用Composition Api都能迎刃而解

1.2. 正文

1.2.1. Options Api

Options API,即大家常说的选项API,即以 vue 为后缀的文件,通过定义 methods,computed,watch,data 等属性与方法,共同处理页面逻辑

如下图:

Options API export default { data(){ return { 功能A 功能B },   
},   
methods:{ 功能A 功能B },   
computed:{ 功能A   
},   
watch:{ 功能B   
}

可以看到 Options 代码编写方式,如果是组件状态,则写在 data 属性上,如果是方法,则写在 methods 属性上...

用组件的选项(data、computed、methods、watch)组织逻辑在大多数情况下都有效然而,当组件变得复杂,导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解

1.2.2. Composition Api

在Vue3Composition API中,组件根据逻辑功能来组织的,一个功能所定义的所有API会放在一起(更加的高内聚,低耦合)

即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API

image

1.2.3.对比

下面对 Composition Api 与 Options Api 进行两大方面的比较

逻辑组织

逻辑复用

1.2.3.1. 逻辑组织

1.2.3.1.1. Options API

假设一个组件是一个大型组件,其内部有很多处理逻辑关注点(对应下图不用颜色)

image

可以看到,这种碎片化使得理解和维护复杂组件变得困难

选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块

1.2.3.1.2. Composition API

而 Compositon API 正是解决上述问题,将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

下面举个简单例子,将处理 count 属性相关的代码放在同一个函数了

1 function useCount() {  
2 let count = ref(10);  
3 let double = computed() => {  
4 return count.value * 2;  
5 };  
6 const handleConut = () => {  
7 count.value = count.value * 2;  
8 };  
9 console.log(count);  
10 return {  
11 count,  
12 double,  
13 handleConut,  
14 };  
15 }

组件上中使用 count

1 export default defineComponent({  
2 setup() {  
3 const { count, double, handleConut } = useCount();  
4 return {  
5 count,  
6 double,  
7 handleConut  
8 }  
9 },  
10 });

再来一张图进行对比,可以很直观地感受到Composition API在逻辑组织方面的优势,以后修改一个属性功能的时候,只需要跳到控制该属性的方法中即可

Options API

Composition API

image

image

1.2.3.2. 逻辑复用

在Vue2中,我们是用过mixin去复用相同的逻辑

下面举个例子,我们会另起一个 mixin.js 文件

1 export const MoveMixin = {
2 data() {
3 return {
4 x: 0,
5 y: 0,
6 };
10 console.log(e.code);   
11 //上下左右xy   
12 switch (e.code){   
13 case "ArrowUp":   
14 this.y--;   
15 break;   
16 case "ArrowDown":   
17 this.y++;   
18 break;   
19 case "ArrowLeft":   
20 this.x--;   
21 break;   
22 case "ArrowRight":   
23 this.x++;   
24 break;   
25 }   
26 },   
27 },   
28 mounted() {   
29 window.addEventListener("keyup", this.handleKeyup);   
30 },   
31 unmounted() {   
32 window.removeEventListener("keyup", this.handleKeyup);   
33 },   
34 };

然后在组件中使用

1 <template>   
2 <div>   
3 Mouse position: x {x} / y {y}   
4 </div>   
5 </template>   
6 <script>   
7 import mousePositionMixin from ./mouse'   
8 export default {   
9 mixins: [mousePositionMixin]   
10 }   
11 </script>

使用单个 mixin 似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时候

1 mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]

会存在两个非常明显的问题:

  • 命名冲突

数据来源不清晰

现在通过 Compositon API 这种方式改写上面的代码

1 import { onMounted, onUnmounted, reactive } from "vue";  
2 export function useMove() {  
3 const position = reactive({  
4 x: 0,  
5 y: 0,  
6 });  
7 const handleKeyup = (e) => {  
8 console.log(e.code);  
9 //上下左右xy  
10 switch (e.code) {  
11 case "ArrowUp":  
12 //y.value--;  
13 position.y--;  
14 break;  
15 case "ArrowDown":  
16 //y.value++;  
17 position.y++;  
18 break;  
19 case "ArrowLeft":  
20 //x.value--;  
21 position.x--;  
22 break;  
23 case "ArrowRight":  
24 //x.value++;  
25 position.x++;  
26 break;  
27 }  
28 };  
29 onMounted() => {  
30 window.addEventListener("keyup", handledKeyup);  
31 });  
32 onUnmounted() => {  
33 window.removeEventListener("keyup", handledKeyup);  
34 });  
35 return { position };  
36 }
1 <template>   
2 <div>   
3 Mouse position: x {x} / y {y}   
4 </div>   
5 </template>   
6 <script>   
7 import {useMove} from "./useMove";   
8 import {toRefs} from "vue";   
9 export default {   
10 setup() {   
11 const {position} = useMove();   
12 const {x, y} = toRefs(position);   
13 return {   
14 x,   
15 y,   
16 };   
17 },   
18 };   
19 </script>

可以看到,整个数据来源清晰了,即使去编写更多的 hook 函数,也不会出现命名冲突的问题

1.3. 小结

  • 在逻辑组织和逻辑复用方面,Composition API是优于Options API

  • 因为 Composition API 几乎是函数,会有更好的类型推断。

  • Composition API 对 tree-shaking 友好,代码也更容易压缩

  • Composition API 中见不到 this 的使用,减少了 this 指向不明的情况

  • 如果是小型组件,可以继续使用 Options API,也是十分友好的

2. Vue3.0的设计目标是什么?做了哪些优化

image

2.1. 设计目标

不以解决实际业务痛点的更新都是耍流氓,下面我们来列举一下Vue3之前我们或许会面临的问题

随着功能的增长,复杂组件的代码变得越来越难以维护

  • 缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制

  • 类型推断不够友好

  • bundle 的时间太久了

而Vue3经过长达两三年时间的筹备,做了哪些事情?

我们从结果反推

  • 更小

  • 更快

  • TypeScript支持

  • API设计一致性

提高自身可维护性

  • 开放更多底层功能

一句话概述,就是更小更快更友好了

2.1.1. 更小

Vue3 移除一些不常用的 API

引入 tree-shaking,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了

2.1.2. 更快

主要体现在编译方面:

diff算法优化

静态提升

事件监听缓存

  • SSR优化

2.1.3. 更友好

vue3 在兼顾 vue2 的 options API的同时还推出了 composition API,大大增加了代码的逻辑组织和代码复用能力

这里代码简单演示下:

存在一个获取鼠标位置的函数

1 import { toRefs, reactive } from 'vue';  
2 function useMouse(){  
3 const state = reactive({x:0,y:0});  
4 const update = e=>{  
5 state.x = e.pageX;  
6 state.y = e.pageY;  
7 }  
8 onMounted() =>{  
9 window.addEventListener('mousemove',update);  
10 })  
11 onUnmounted() =>{  
12 window.removeEventListener('mousemove',update);  
13 })  
14 return toRefs(state);  
15 }

我们只需要调用这个函数,即可获取 $x$ 、 $y$ 的坐标,完全不用关注实现过程

试想一下,如果很多类似的第三方库,我们只需要调用即可,不必关注实现过程,开发效率大大提高同时,VUE3 是基于 typescript 编写的,可以享受到自动的类型定义提示

2.2.优化方案

vue3 从很多层面都做了优化,可以分成三个方面:

源码

性能

  • 语法 API

2.2.1. 源码

源码可以从两个层面展开:

源码管理

  • TypeScript

2.2.1.1. 源码管理

vue3 整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中

image

这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性

另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue 使用的,这样用户如果只想使用 Vue3 的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue

2.2.1.2.TypeScript

Vue3 是基于 typeScript 编写的,提供了更好的类型检查,能支持复杂的类型推导

2.2.2.性能

vue3是从什么哪些方面对性能进行进一步优化呢?

  • 体积优化

编译优化

数据劫持优化

这里讲述数据劫持:

在 vue2 中,数据劫持是通过 Object.defineProperty,这个 API 有一些缺陷,并不能检测对象属性的添加和删除

1 Object.defineProperty(data, 'a', {  
2 get() {  
3 // track  
4 },  
5 set() {  
6 // trigger  
7 }  
8 })

尽管Vue为了解决这个问题提供了set和delete实例方法,但是对于用户来说,还是增加了一定的心智负担

同时在面对嵌套层级比较深的情况下,就存在性能问题

1 default {
2 data:
3 a:
4 b:
5 c:
6 d: 1
7 }
8 }
9 }   
10 }   
11}

相比之下,vue3 是通过 proxy 监听整个对象,那么对于删除还是监听当然也能监听到

同时 Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归

2.2.3. 语法API

这里当然说的就是 composition API,其两大显著的优化:

优化逻辑组织

  • 优化逻辑复用

2.2.3.1. 逻辑组织

一张图,我们可以很直观地感受到 Composition API 在逻辑组织方面的优势

Options API

Composition API

image

image

相同功能的代码编写在一块,而不像 options API 那样,各个功能的代码混成一块

2.2.3.2. 逻辑复用

在 vue2 中,我们是通过 mixin 实现功能混合,如果多个 mixin 混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰

而通过 composition 这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可

同样是上文的获取鼠标位置的例子

1 import { toRefs, reactive, onUnmounted, onMounted } from 'vue';  
2 function useMouse(){  
3 const state = reactive({x:0,y:0});  
4 const update = e=>{  
5 state.x = e.pageX;
state.y = e.pageY;
7 }  
8 onMounted() => {  
9 window.addEventListener('mousemove', update);  
10 }  
11 onUnmounted() => {  
12 window.removeEventListener('mousemove', update);  
13 }  
14 return toRefs(state);  
15 }

组件使用

1 import useMousePosition from './mouse'  
2 export default {  
3 setup() {  
4 const { x, y } = useMousePosition()  
5 return { x, y }  
6 }  
7 }

可以看到,整个数据来源清晰了,即使去编写更多的 hook 函数,也不会出现命名冲突的问题

3. 用Vue3.0 写过组件吗?如果想实现一个 Modal你会怎么设计?

image

3.1. 一、组件设计

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式

现在有一个场景,点击新增与编辑都弹框出来进行填写,功能上大同小异,可能只是标题内容或者是显示的主体内容稍微不同

这时候就没必要写两个组件,只需要根据传入的参数不同,组件显示不同内容即可

这样,下次开发相同界面程序时就可以写更少的代码,意义着更高的开发效率,更少的 Bug 和更少的程序体积

3.2.需求分析

实现一个 Modal 组件,首先确定需要完成的内容:

  • 遮罩层

  • 标题内容

  • 主体内容

确定和取消按钮

主体内容需要灵活,所以可以是字符串,也可以是一段html代码

特点是它们在当前 vue 实例之外独立存在,通常挂载于 body 之上

除了通过引入 import 的形式,我们还可通过 API 的形式进行组件的调用

还可以包括配置全局样式、国际化、与 typeScript 结合

3.3. 实现流程

首先看看大致流程:

目录结构

  • 组件内容

实现API形式

事件处理

其他完善

3.3.1. 目录结构

Modal 组件相关的目录结构

1 |---plugins  
2 |--- modal  
3 |--- Content.tsx // 维护 Modal 的内容,用于 h 函数和 json 语法  
4 |--- Modal.vue // 基础组件
5 config.ts // 全局默认配置  
6 index.ts // 入口  
7 locale // 国际化相关  
8 index.ts  
9 lang  
10 en-US.ts  
11 | | zh-CN.ts  
12 | | zh-TW.ts  
13 modal.type.ts // ts类型声明相关

因为Modal会被app.use(Modal)调用作为一个插件,所以都放在plugins目录下

3.3.2. 组件内容

首先实现 modal.vue 的主体显示内容大致如下

1 <Teleport to="body" :disabled想不到Teleport>
2 <div v-if="modelValue" class="modal">
3 <div>
4 class="mask"
5 :style="style"
6 @click="maskClose && !loading && handleCancel())
7 >
8 <div class="modal_main">
9 <div class="modal_title line line--b">
10 <span>{ title || t("r.title") }></span>
11 <span>
12 v-if="close"
13 :title="t('r.close')" class="close"
14 @click="_loading && handleCancel"/>
15 > </span>
16 > </div>
17 </div>
18 </div>
19 <div class="modal_content">
20 <Content v-if="typeof content === 'function'" :render="content">
3 }
4 <slot v-else>
5 { content }
6 </slot>
7 </div>
8 <div class="modal_bsline line--t">
9 <button :disabled="loading" @click="handleConfirm">
10 <span class="loading" v-if="loading" ></span>
11 <div class="modal_content">
22 <span class="loading" v-if="loading">
3 t("r.confirm") }}
28 </button>   
29 <button @click  $\equiv$  !loading && handleCancel())">   
30 {{ t("r.cancel") }}   
31 </button>   
32 </div>   
33 </div>   
34 </div>   
35 </Teleport>

最外层上通过Vue3 Teleport 内置组件进行包裹,其相当于传送门,将里面的内容传送至 body 之上

并且从DOM结构上来看,把modal该有的内容(遮罩层、标题、内容、底部按钮)都实现了关于主体内容

1 <div class  $=$  "modal__content">   
2 <Content v-if  $\equiv$  "typeof content  $\equiv = =$  'function'   
3 :render  $\equiv$  "content" />   
4 <slot v-else>   
5 {{content}}   
6 </slot>   
7 </div>

可以看到根据传入 content 的类型不同,对应显示不同得到内容

最常见的则是通过调用字符串和默认插槽的形式

1 //默认插槽  
2 <Modal v-model="show" title="演示 slot">  
3 title="演示 slot">  
4 <div>hello world~</div>  
5 </Modal>  
6 //字符串  
7 <Modal v-model="show" title="演示 content" content="hello world~"/>

通过 API 形式调用 Modal 组件的时候,content 可以使用下面两种

$\bullet$ h函数

1 $$modal.show({
2 title:'演示h函数'3 content(h){  
4 return h(  
5 'div'6 {  
7 style:'color:red;',  
8 onClick:(\ ${event:Event) \(\Rightarrow$  console.log('clickd',\\)event.target)  
9 },  
10 'hello world ~'  
11 );  
12 }  
13});

- JSX

1 $$modal.show({
2 title: '演示 json 语法',
3 content() {
4 return (
5 <div
6 onClick={{$$event: Event}} => console.log('clickd', $event.target)}
7 >
8 hello world ~
9 </div>
10 );
11 }
12 });

3.3.3. 实现API形式

那么组件如何实现 API 形式调用 Modal 组件呢?

在Vue2中,我们可以借助Vue实例以及Vue extend的方式获得组件实例,然后挂载到body上

1 import Modal from '.Modal.vue';  
2 const ComponentClass = Vue extend(Modal);  
3 const instance = new ComponentClass({ el: document.createElement("div") });  
4 document.body.appendChild.instance.$el);

虽然Vue3移除了Vue extend方法,但可以通过createVNode实现

1 import Modal from '.Modal.vue';  
2 const container = document.createElement('div');  
3 const vnode = createVNode(Modal);  
4 render(vnode, container);  
5 const instance = vnode.element;  
6 document.body.appendChild/container);

在Vue2中,可以通过this的形式调用全局API

1 export default {
2 install(vue) {
3 vue.prototype.\(create = create
4 }
5 }

而在Vue3的setup中已经没有this概念了,需要调用app.config.globalProperties挂载到全局

1 export default {
2 install(app) {
3 app.config.globalProperties.\(create = create
4 }
5 }

3.3.4. 事件处理

下面再看看看Modal组件内部是如何处理「确定」「取消」事件的,既然是Vue3,当然采用Composition API形式

1 // Modal.vue  
2 setup(props, ctx) {  
3 let instance = getCurrentInstance(); // 获得当前组件实例  
4 onBeforeMount() => {  
5 instance._hub = {  
6 'on-cancel': () => {},  
7 'on confirm': () => {}  
8 };  
9 });  
10 const handleConfirm = () => {  
11 ctx emit('on confirm');  
12 instance._hub['on confirm'].);
13 };   
14 const handleCancel  $=$  ()  $\Rightarrow$  {   
15 ctx emitted('on-cancel');   
16 ctx.emit('update:modelValue',false);   
17 instance._hub['on-cancel']();   
18 };   
19 return {   
20 handleConfirm,   
21 handleCancel   
22 };   
23 }

在上面代码中,可以看得到除了使用传统emit的形式使父组件监听,还可通过hub属性中添加on-cancel,on confirm方法实现在API中进行监听

1 app.config.globalProperties.\(modal = \{  
2 show({}) {  
3 /*监听确定、取消事件*/  
4 }  
5 \}

下面再来目睹下 _hub 是如何实现

1 // index.ts
2 app.config.globalProperties.\(modal = \{ show({ /* 其他选项
5 / onConfirm,
7 onCancel
8 }}
9 * /* 
10 ... */
11 const { props, _hub } = instance;
12 const _closeModal = () => {
13 props.modelValue = false;
14 container.parentNode!.removeChild/container);
15 };
16 // 往 _hub 新增事件的具体实现
17 Object.assign(_hub, {
18 async 'on confirm')(   ) {
19 if (onConfirm) {
20 const fn = onConfirm(   );
21 //当方法返回为 Promise  
22 if (fn && fn.then) {  
23 try {  
24 propsloading = true;  
25 await fn;  
26 propsloading = false;  
27 closeModal();  
28 } catch (err) {  
29 //发生错误时,不关闭弹框  
30 console.error(err);  
31 propsloading = false;  
32 }  
33 } else {  
34 closeModal();  
35 }  
36 } else {  
37 closeModal();  
38 }  
39 },  
40 'on-cancel'( ) {  
41 onCancel && onCancel();  
42 closeModal();  
43 }  
44 });  
45 }  
46 };

3.3.5. 其他完善

关于组件实现国际化、与 typesScript 结合,大家可以根据自身情况在此基础上进行更改

4. Vue3.0性能提升主要是通过哪几方面体现的?

image

4.1.编译阶段

回顾Vue2,我们知道每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把用到的数据property记录为依赖,当依赖发生改变,触发setter,则会通知watcher,从而使关联的组件重新渲染

image

试想一下,一个组件结构如下图

1

评论