前端面试第65期 - Vue 专题 - 2024.09.17 更新前端面试问题总结(20道题)前端面试第65期 - Vu
2024.09.17 更新前端面试问题总结(20道题) 获取更多面试相关问题可以访问 github 地址: github.com/pro-collect… gitee 地址: gitee.com/yanleweb/in…
目录:
-
初级开发者相关问题【共计 1 道题】
- 896. [Vue] 为何会被称为渐进式框架, 哪儿体现了渐进式【热度: 390】【web框架】
-
中级开发者相关问题【共计 8 道题】
- 897. [Vue] 选项式 API 和组合式 API,两者有何区别,该如何取舍【热度: 322】【web框架】【出题公司: 美团】
-
- [Vue] 解释一下 @submit.prevent="onSubmit" 这个指令【热度: 590】【web框架】
-
- [Vue] 响应式里面 ref 和 reactive 有啥区别【热度: 414】【web框架】
-
- [Vue] computed 和 普通函数有何区别【热度: 332】【web框架】
-
- [Vue] 提供了哪些默认事件修饰符【热度: 366】【web框架】
-
- [Vue] 介绍一下 3.x 侦听器 watch【热度: 671】【web框架】
-
- [Vue] 介绍一下 3.x 侦听器 watchEffect【热度: 323】【web框架】
-
- [Vue] 多个 slot 插槽场景,该如何申明与使用【热度: 380】【web框架】
-
高级开发者相关问题【共计 11 道题】
- 898. [Vue] 3.x 中 app.config 有哪些应用配置?【热度: 200】【web框架】【出题公司: 美团】
-
- [Vue] 如何处理异步加载组件【热度: 254】【web框架】
-
- [Vue] 深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map 等, 在响应式使用方面, ref 和 reactive 有何区别吗【热度: 415】【web框架】
-
- [Vue] vue 中 reactive() 返回的为何是一个原始对象的 Proxy,有和好处【热度: 416】【web框架】
-
- [Vue] reactive() 的局限性有哪些【热度: 410】【web框架】
-
- [Vue] 触发事件修饰符 .exact 的作用是啥【热度: 454】【web框架】
-
- [Vue] 输入绑定修饰符 .lazy 作用是啥【热度: 95】【web框架】
-
- [Vue] 生命周期【热度: 848】【web框架】
-
- [Vue] watch 和 watchEffect 场景上有何区别, 该如何选择【热度: 174】【web框架】
-
- [Vue] 侦听器在什么情况下是需要清理副作用的【热度: 148】【web框架】
-
- [Vue] useTemplateRef 作用是啥, 哪些情况下要要使用这个 api【热度: 405】【web框架】
初级开发者相关问题【共计 1 道题】
896. [Vue] 为何会被称为渐进式框架, 哪儿体现了渐进式【热度: 390】【web框架】
关键词:如何体现渐进式
作者备注 这个概念直接在官网起步就可以看到 cn.vuejs.org/guide/intro… 所以作为基础知识即可, 定义为 「简单」级别
Vue 是一个框架,也是一个生态。其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的,不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用 Vue:
- 无需构建步骤,渐进式增强静态的 HTML
- 在任何页面中作为 Web Components 嵌入
- 单页应用 (SPA)
- 全栈 / 服务端渲染 (SSR)
- Jamstack / 静态站点生成 (SSG)
- 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面
如果你是初学者,可能会觉得这些概念有些复杂。别担心!理解教程和指南的内容只需要具备基础的 HTML 和 JavaScript 知识。即使你不是这些方面的专家,也能够跟得上。
如果你是有经验的开发者,希望了解如何以最合适的方式在项目中引入 Vue,或者是对上述的这些概念感到好奇,我们在使用 Vue 的多种方式中讨论了有关它们的更多细节。
无论再怎么灵活,Vue 的核心知识在所有这些用例中都是通用的。即使你现在只是一个初学者,随着你的不断成长,到未来有能力实现更复杂的项目时,这一路上获得的知识依然会适用。如果你已经是一个老手,你可以根据实际场景来选择使用 Vue 的最佳方式,在各种场景下都可以保持同样的开发效率。这就是为什么我们将 Vue 称为“渐进式框架”:它是一个可以与你共同成长、适应你不同需求的框架。
中级开发者相关问题【共计 8 道题】
897. [Vue] 选项式 API 和组合式 API,两者有何区别,该如何取舍【热度: 322】【web框架】【出题公司: 美团】
关键词:选项式 API 和组合式 API 区别
选项式 API (Options API)
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0,
};
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++;
},
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`);
},
};
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式 API (Composition API)
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
下面是使用了组合式 API 与 <script setup>
改造后和上面的模板完全一样的组件:
<script setup>
import { ref, onMounted } from "vue";
// 响应式状态
const count = ref(0);
// 用来修改状态、触发更新的函数
function increment() {
count.value++;
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`);
});
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
如何取舍
在 Vue.js 开发中,选择使用选项式 API 或组合式 API 取决于多个因素,包括项目的规模、团队的熟悉度、组件复杂性,以及对 TypeScript 的需求。下面是一些建议,帮助你决定在特定情况下应该采用哪种 API 风格:
考虑项目规模和复杂性
-
对于简单或中等复杂度的项目,特别是如果你已经习惯于 Vue 2 的开发模式,选项式 API 可能更加简单直接。它提供了一个清晰的结构,将组件的不同方面(如数据、方法、计算属性等)分隔开,易于理解和上手。
-
对于大型项目或具有复杂组件逻辑的项目,组合式 API 更能展现其优势。它能够更好地组织和复用逻辑,尤其是当你需要处理跨组件的共享逻辑时。通过使用组合式 API,可以将相关的逻辑紧密地放在一起,而不是分散在选项式 API 的各个区域。这降低了大型项目的维护难度。
考虑团队熟悉度
-
如果你的团队已经对选项式 API 比较熟悉,且没有遇到因结构导致的维护问题,那么可能没有必要强制迁移到组合式 API。但是,鼓励团队了解和探索组合式 API,以便于未来可能的迁移或混合使用。
-
对于新项目或新团队,考虑从一开始就采用组合式 API,尤其是在团队成员对其感兴趣或组件逻辑预期较为复杂的情况下。这样可以从项目初期就充分利用组合式 API 的优点。
考虑对 TypeScript 的支持
- 组合式 API 对 TypeScript 的支持更友好,如果你的项目或团队打算使用 TypeScript,那么组合式 API 是更好的选择。它提供了类型推导和更清晰的类型定义,使得 TypeScript 代码更加健壮和易于维护。
考虑代码复用和逻辑抽象
- 当有大量需要跨组件复用的逻辑时,组合式 API 提供了更灵活和强大的方式来组织这些逻辑。通过自定义组合函数,可以更容易地在组件之间共享逻辑,减少代码重复。
900. [Vue] 解释一下 @submit.prevent="onSubmit" 这个指令【热度: 590】【web框架】
关键词:指令的含义
直接上图:
在 Vue 中,@submit.prevent="onSubmit"
是一个指令修饰符的示例,它结合了事件侦听和事件修饰符的概念来提供一种声明式的方式处理表单提交事件,并自动阻止其默认行为。
这个指令可以分为几个部分来解释:
@submit
@
是一个简写符号,用于标识事件侦听器。它是v-on:
的简写,因此@submit
等同于v-on:submit
。submit
是要侦听的事件名称。在这里,它指的是 HTML 表单的提交事件。
.prevent
.prevent
是一个事件修饰符。事件修饰符用于指示 Vue 对触发的事件进行特定的处理。- 在这种情况下,
.prevent
修饰符告诉 Vue 阻止事件的默认行为。对于submit
事件,其默认行为通常是将表单数据发送到服务器(根据action
属性的值)并重新加载页面。使用.prevent
可以防止这种默认行为,允许你通过 JavaScript 手动处理表单提交。
"onSubmit"
- 这部分是对应的方法名称,当事件被触发时应该调用。在这个例子中,当表单提交事件被触发(同时默认行为被
.prevent
阻止)时,Vue 会调用组件中名为onSubmit
的方法。
901. [Vue] 响应式里面 ref 和 reactive 有啥区别【热度: 414】【web框架】
关键词:响应式 api
在 Vue 3 中,ref
和 reactive
是创建响应式数据的两种不同方法,它们都是 Vue 的响应式系统的一部分,但在使用方式和适用场景上有一些区别。下面是 ref
和 reactive
的主要区别:
ref
- 用法:
ref
用于创建一个响应式的引用类型数据。当你需要使基本数据类型(例如:string, number, boolean)变得响应式时,ref
是一个很好的选择。 - 返回值:
ref
返回一个包含value
属性的对象。你需要通过.value
属性来访问或修改其内部值。 - 适用场景:适用于基本数据类型,也可以用于对象和数组,但主要是为了基本数据类型设计的。
import { ref } from "vue";
const count = ref(0);
console.log(count.value); // 访问值
count.value++; // 修改值
reactive
- 用法:
reactive
用于创建一个响应式的复杂类型数据,如对象或数组。 - 返回值:直接返回原始对象的响应式代理,不需要通过
.value
属性来访问或修改。 - 适用场景:是为了使对象或数组这样的引用数据类型变得响应式而设计的。
import { reactive } from "vue";
const state = reactive({ count: 0 });
console.log(state.count); // 访问值
state.count++; // 修改值
主要区别
- 数据类型:
ref
主要用于基本数据类型,但也可以用于对象和数组;reactive
适用于对象或数组等引用数据类型。 - 返回值:
ref
返回一个对象,这个对象包含一个value
属性,这意味着你需要通过.value
来获取或设置值;而reactive
返回的是对象或数组的响应式代理,可以直接操作。 - 模板中使用:在模板中使用时,
ref
创建的响应式数据访问时不需要.value
,Vue 模板会自动解包;reactive
对象在模板中的行为与普通对象相同。
使用建议
-
当你处理基本数据类型时,使用
ref
; -
当你需要管理一个复杂的数据结构(如对象或数组),使用
reactive
以保持代码的简洁和直观。
905. [Vue] computed 和 普通函数有何区别【热度: 332】【web框架】
关键词:计算属性 computed 作用
作者总结 其实没有啥好说的, 就一个核心:缓存!computed 是支持缓存的, 避免了不必要的重复计算, 有较高性能表现
下面是一个表格,对比了 Vue 3 中的 computed
计算属性和普通函数方法的主要差异:
特性 | 计算属性 (computed ) | 普通函数方法 (methods ) |
---|---|---|
缓存 | 是。只有当依赖数据变化时,才会重新计算。 | 否。每次调用都会执行函数逻辑。 |
性能 | 高。避免了不必要的计算,只在依赖变化时重新计算。 | 较低。不区分是否有数据更新,都会执行。 |
触发更新 | 依赖数据变化时自动更新。 | 需要手动触发或组件重新渲染时才更新。 |
使用场景 | 当数据变化需要进行复杂运算时,且结果要被多次引用。 | 每次都需执行新逻辑或操作不依赖响应式数据时。 |
访问方式 | 如访问属性一样,不需要加括号。 | 在模板中调用时需要加括号。 |
返回值 | 调用时返回计算后的结果,不需要执行任何函数。 | 必须执行函数以获取结果。 |
更新性能 | 只在依赖变化时重新求值,如果依赖未变化则返回上次的结果。 | 在每次访问或调用时无条件执行,不考虑依赖数据是否变化。 |
通过对比,我们可以看到两者在 Vue 应用中各自的优势和应用场景。computed
适合用于基于数据变化需要重新计算的场景,特别是当这些计算比较昂贵,或者计算结果会被多处使用时。而普通函数方法更适合用于执行不依赖响应式数据的操作,或者当操作每次都需要产生不同结果时。正确地选择使用计算属性还是普通方法,可以优化你的 Vue 应用的性能和可维护性。
906. [Vue] 提供了哪些默认事件修饰符【热度: 366】【web框架】
关键词:事件修饰符
在 Vue 中,事件修饰符是一些由点 (.) 开头的特殊后缀,用于指示 Vue 对 DOM 事件进行某种特殊处理。Vue 提供了一系列的默认事件修饰符来帮助开发者更方便地处理一些常见的 DOM 事件行为。
下面是 Vue 3 中提供的一些默认事件修饰符:
事件修饰符 | 描述 |
---|---|
.stop | 调用 event.stopPropagation() 阻止事件冒泡。 |
.prevent | 调用 event.preventDefault() 阻止默认事件行为。 |
.capture | 使用事件捕获模式添加事件监听器,而不是冒泡模式。 |
.self | 仅当事件是从事件绑定的元素本身触发时才触发回调。 |
.once | 事件只触发一次,之后移除事件监听器。 |
.passive | 以 { passive: true } 模式添加监听器,表示不会调用 preventDefault() 。 |
这些修饰符可以单独使用,也可以组合使用。以下是一些示例:
<!-- 阻止点击事件冒泡 -->
<button @click.stop="doThis">Stop Propagation</button>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit">Prevent Default</form>
<!-- 修饰符链 -->
<a @click.stop.prevent="doThat">Stop Propagation and Prevent Default</a>
<!-- 只在 @click.self 表达式中的元素本身(而非子元素)触发时调用 doThat -->
<div @click.self="doThat">Only Trigger on Self</div>
<!-- 点击事件将只触发一次 -->
<button @click.once="doOnce">Trigger Once</button>
使用这些事件修饰符可以使你的事件处理逻辑更简洁和直观,同时也能够实现一些复杂的事件处理方式。
910. [Vue] 介绍一下 3.x 侦听器 watch【热度: 671】【web框架】
关键词:侦听器 watch
Vue 3 提供了一个灵活的响应式系统,其中 watch
函数是实现细粒度数据观察和响应的重要工具。watch
能够侦听 Vue 应用中的响应式数据的变化,并在数据变化时执行相应的回调函数。这个功能在 Vue 2 中以 watch
选项的形式存在,而在 Vue 3 的组合式 API 中,则是作为 watch
函数来使用。
使用 watch
侦听 Refs
watch
函数可以用来侦听一个引用类型(ref)的变化:
import { ref, watch } from "vue";
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`新值:${newValue},旧值:${oldValue}`);
});
在上面的例子中,当 count
的值变化时,回调函数就会被调用,并打印新旧值。
使用 watch
侦听响应式对象
除了侦听引用类型,watch
还可以侦听响应式对象的属性变化:
import { reactive, watch } from "vue";
const state = reactive({ count: 0 });
watch(
() => state.count,
(newValue, oldValue) => {
console.log(`新值:${newValue},旧值:${oldValue}`);
}
);
记住,当侦听响应式对象的某个属性时,你需要使用一个函数来返回这个属性的当前值。
侦听多个源
watch
还可以同时侦听多个数据源:
watch([ref1, ref2], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
// 处理逻辑
});
深度侦听
通过设置 watch
的选项 { deep: true }
,可以进行深度侦听,即侦听对象内嵌属性的变化:
watch(obj, callback, { deep: true });
清理和停止监听
watch
函数返回一个停止监听的函数,可以用来在合适的时机停止侦听:
const stopWatching = watch(dataSource, callback);
// 停止侦听
stopWatching();
watchEffect
除了 watch
,Vue 3 也引入了 watchEffect
函数。watchEffect
自动跟踪其回调函数中使用的响应式引用和响应式对象的属性,并在它们变化时运行回调函数。它不需要显式声明侦听的数据源,这让它更简单易用,但在某些情况下,它可能不如 watch
那么精确控制。
911. [Vue] 介绍一下 3.x 侦听器 watchEffect【热度: 323】【web框架】
关键词:侦听器 watchEffect
Vue 3 引入了 Composition API,其中包括一个强大的函数 watchEffect
,用于侦听响应式状态的变化,并当响应式状态变化时自动执行。
基本用法
watchEffect
接收一个函数作为参数,Vue 将会自动跟踪这个函数内部使用的所有响应式状态(响应式引用、响应式对象等)。当这些状态变化时,watchEffect
将重新执行这个函数。
import { ref, watchEffect } from "vue";
const count = ref(0);
watchEffect(() => {
console.log(count.value);
});
在上面的示例中,每当 count
的值发生变化时,watchEffect
回调函数都会被执行,并打印 count
的新值。
立即执行
与 watch
API 不同,watchEffect
在初次调用时会立即执行一次回调函数。这对于根据响应式状态进行初始化设置非常有用。
清理副作用
watchEffect
的回调函数可以返回一个清理函数,用来在回调函数重新执行之前进行清理。这就像组件的 beforeDestroy
钩子函数,用来防止内存泄露等问题。
watchEffect((onInvalidate) => {
const timer = setInterval(() => {
/* ... */
}, 1000);
// 当侦听器重新执行或组件卸载时,清理定时器
onInvalidate(() => {
clearInterval(timer);
});
});
控制侦听
watchEffect
函数还可以接收一个第二个参数——一个选项对象,用来控制侦听器的行为。例如,通过设置 flush
选项,你可以控制执行时机是在组件更新之前、之后,还是同步执行。
watchEffect(
() => {
/* ... */
},
{
flush: "post", // 'pre', 'post', 或 'sync'
}
);
使用场景
watchEffect
适用于以下场景:
- 自动收集依赖:不需要像
watch
那样明确指定侦听的源。 - 初始化时的副作用:例如,根据响应式状态的初始值进行 DOM 操作、发送请求等。
- 定期自动清理:比如,自动清理定时器、取消订阅等。
watchEffect
提供了一种更为简洁和自动化的方式来响应状态变更,使得管理副作用(side effects)的逻辑更加直观和易于维护。
914. [Vue] 多个 slot 插槽场景,该如何申明与使用【热度: 380】【web框架】
关键词:多 slot 场景
Vue 支持多个插槽(slot
),使得组件的内容分发更加灵活。在 Vue 3 中,你可以通过具名插槽来实现这一点,这允许你定义多个插槽,并在父组件中指定对应的内容填充到子组件的不同部位。
定义具名插槽
在子组件中,你可以使用 <slot>
元素来定义一个或多个插槽,并通过 name
属性为每个插槽命名。未命名的插槽被视为默认插槽,它会接收所有未匹配到具名插槽的内容。
<!-- 子组件 -->
<template>
<div>
<header>
<!-- 定义一个名为 "header" 的插槽 -->
<slot name="header"></slot>
</header>
<main>
<!-- 默认插槽 -->
<slot></slot>
</main>
<footer>
<!-- 定义一个名为 "footer" 的插槽 -->
<slot name="footer"></slot>
</footer>
</div>
</template>
使用具名插槽
在父组件中,你可以通过 <template>
元素结合 v-slot
指令(或缩写 #
)来指定填充到具名插槽的内容。对于默认插槽,直接放在子组件标签内部的内容即可。
<!-- 父组件 -->
<template>
<div id="app">
<MyComponent>
<!-- 使用 v-slot 指定插槽内容 -->
<template v-slot:header>
<h1>这里是头部内容</h1>
</template>
<!-- 默认插槽内容 -->
<p>这里是主体内容</p>
<!-- 使用缩写 # 指定插槽内容 -->
<template #footer>
<footer>这里是尾部内容</footer>
</template>
</MyComponent>
</div>
</template>
作用域插槽
除了普通的具名插槽之外,Vue 还支持作用域插槽(scoped slots)。这是一种特殊类型的插槽,它允许子组件传递数据回到插槽内容中,这样,父组件就可以访问子组件的数据了。
<!-- 子组件 -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<!-- 使用作用域插槽传递数据 -->
<slot name="item" :item-data="item">{{ item.text }}</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, text: "Item 1" },
{ id: 2, text: "Item 2" },
],
};
},
};
</script>
父组件可以这样使用作用域插槽:
<!-- 父组件 -->
<MyComponent>
<template #item="{ itemData }">
<span>{{ itemData.text }}</span>
</template>
</MyComponent>
在这里,itemData
是子组件传递给插槽的数据对象,父组件通过解构它来访问插槽数据。
高级开发者相关问题【共计 11 道题】
898. [Vue] 3.x 中 app.config 有哪些应用配置?【热度: 200】【web框架】【出题公司: 美团】
关键词:app.config 配置
确实,在 Vue 3 中,app.config
提供了一系列的应用级别的配置选项,用于自定义或调整 Vue 应用的行为。你提到的这些配置项都是 app.config
的一部分,下面是关于它们的详细介绍:
app.config.errorHandler
- 作用:为未捕获的异常定义一个全局的处理函数。这在集中处理组件渲染或观察者(watchers)中的异常时非常有用。
- 示例:
app.config.errorHandler = (err, instance, info) => {
// 处理错误
};
app.config.warnHandler
- 作用:为 Vue 运行时警告定义一个全局的处理函数,允许你在开发过程中自定义处理警告的方式。
- 示例:
app.config.warnHandler = (msg, instance, trace) => {
// 处理警告
};
app.config.performance
- 作用:开启性能追踪。在开发模式下启用,能够测量和追踪组件的初始化、编译时间等性能指标。
- 示例:
app.config.performance = true;
app.config.compilerOptions
- 作用:允许自定义编译器选项,如模板中的自定义指令等。这对于更细致地控制模板的编译过程很有帮助。
- 示例:
app.config.compilerOptions = {
// 编译器配置
};
app.config.globalProperties
- 作用:定义全局可用的属性。这在 Vue 2 中通过
Vue.prototype
实现,Vue 3 中通过app.config.globalProperties
实现。 - 示例:
app.config.globalProperties.$utils = {
// 一些全局方法或属性
};
app.config.optionMergeStrategies
- 作用:自定义选项的合并策略。允许你为自定义选项指定如何合并父子选项。
- 示例:
app.config.optionMergeStrategies.myOption = (parent, child) => { // 合并策略 };
app.config.idPrefix
- 作用:配置此应用中通过 useId() 生成的所有 ID 的前缀。由 3.5+ 版本引入。
- 示例:
app.config.idPrefix = "custom-";
// 在组件中:
const id1 = useId(); // 'my-app:0'
const id2 = useId(); // 'my-app:1'
app.config.throwUnhandledErrorInProduction
- 作用:强制在生产模式下抛出未处理的错误。 由 3.5+ 版本引入。
默认情况下,在 Vue 应用中抛出但未显式处理的错误在开发和生产模式下有不同的行为:
在开发模式下,错误会被抛出并可能导致应用崩溃。这是为了使错误更加突出,以便在开发过程中被注意到并修复。
在生产模式下,错误只会被记录到控制台以尽量减少对最终用户的影响。然而,这可能会导致只在生产中发生的错误无法被错误监控服务捕获。
通过将 app.config.throwUnhandledErrorInProduction 设置为 true,即使在生产模式下也会抛出未处理的错误。
这些应用级配置选项提供了对 Vue 应用的高度控制,允许开发者根据实际需要调整 Vue 的默认行为。在使用时,建议根据项目实际情况和需求进行选择性地配置。
899. [Vue] 如何处理异步加载组件【热度: 254】【web框架】
关键词:异步加载组件
在 Vue 应用中,异步组件是指那些在声明时不会立即加载,而是在需要的时候才加载的组件。使用异步组件能够帮助你提高应用的加载速度和性能,特别是在处理大型应用和路由懒加载时。Vue 提供了几种处理异步加载组件的方法。
Vue 3 中处理异步组件的方法
使用 defineAsyncComponent
方法
Vue 3 提供了 defineAsyncComponent
方法,使得定义和使用异步组件变得简单。你可以通过传递一个函数,该函数返回一个 import()
调用(返回 Promise),来动态加载组件。
import { defineAsyncComponent } from "vue";
const AsyncComponent = defineAsyncComponent(() => import("./components/AsyncComponent.vue"));
// 在组件中使用
export default {
components: {
AsyncComponent,
},
};
加载状态处理
你还可以使用 defineAsyncComponent
的高级用法,提供一个对象来处理加载状态,如显示加载中的提示、错误处理和超时处理。
const AsyncComponent = defineAsyncComponent({
// 加载异步组件的工厂函数
loader: () => import("./components/AsyncComponent.vue"),
// 加载中时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的延迟 | 默认值:200(毫秒)
delay: 200,
// 如果提供了超时时间(毫秒),超时后将显示错误组件 | 默认值:Infinity
timeout: 3000,
});
Vue 2 中处理异步组件的方法
在 Vue 2 中,异步组件的定义略有不同,你可以直接在组件注册时提供一个返回 Promise 的工厂函数。
Vue.component("async-component", () => import("./components/AsyncComponent.vue"));
或者为了处理加载状态,可以提供一个高级的对象形式:
Vue.component("async-component", (resolve, reject) => ({
// 需要加载的组件 (应该是一个 Promise)
component: import("./components/AsyncComponent.vue"),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载中组件前的等待时间。默认:200ms。
delay: 200,
// 如果提供了超时时间 (毫秒),超时后会显示错误组件。默认:Infinity
timeout: 3000,
}));
902. [Vue] 深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map 等, 在响应式使用方面, ref 和 reactive 有何区别吗【热度: 415】【web框架】
关键词:响应式 api
在 Vue 3 的响应式系统中,处理深层嵌套的数据时,ref
和 reactive
在行为上有一些细微但重要的区别,特别是当涉及到对象、数组以及 JavaScript 内置的数据结构(如 Map 和 Set)时。这些区别主要体现在如何使嵌套的数据成为响应式的,以及如何维护这些响应性。
处理深层嵌套的数据
无论是使用 ref
还是 reactive
,Vue 会尝试使给定的数据结构及其嵌套的所有子结构变成响应式的。但是,具体的实现机制有所不同。
reactive
reactive
对象默认深度响应式。当你使用reactive
使一个对象变成响应式时,这个对象的所有嵌套对象和数组也会自动变成响应式的。这意味着你可以在任意深度的嵌套数据上进行修改,并且这些修改将会触发视图更新。- 对于 JavaScript 的内置数据结构,如 Map 和 Set,Vue 3 也提供了响应式支持,但它们必须通过
reactive
方法来创建或转换为响应式的。
const state = reactive({
nested: {
count: 0,
},
numbers: [1, 2, 3],
map: new Map(),
});
state.nested.count++; // 触发视图更新
state.numbers.push(4); // 触发视图更新
state.map.set("key", "value"); // 触发视图更新
ref
- 使用
ref
创建响应式数据时,如果ref
被赋值为一个对象或数组,Vue 会将该对象或数组内部转换为深度响应式。然而,这种转换仅发生在赋值操作时,如果后续对该对象或数组进行再嵌套,新增的嵌套不会自动转换为响应式。 - 对于内置数据结构如 Map 和 Set,
ref
可以存储它们,但不会使它们或其内容变成响应式的。如果你需要在模板中直接绑定这些数据结构的响应式变化,使用reactive
会是更好的选择。
const nestedObj = ref({
nested: {
count: 0,
},
});
nestedObj.value.nested.count++; // 触发视图更新
const map = ref(new Map());
map.value.set("key", "value"); // 不会触发视图更新,除非重新赋值给 map.value
总结
当处理深层嵌套的对象、数组或内置数据结构时:
reactive
默认提供深度响应式,并且可以使 Map、Set 等内置数据结构变为响应式。ref
在赋值对象或数组时自动将其转换为响应式,但不适用于 Map 或 Set 等内置数据结构的深度响应。
一般情况下,对于复杂或深层嵌套的数据结构,reactive
更加适合。对于基本数据类型或不太复杂的嵌套数据,ref
可以提供方便的响应式转换。
903. [Vue] vue 中 reactive() 返回的为何是一个原始对象的 Proxy,有和好处【热度: 416】【web框架】
关键词:响应式 api
在 Vue 3 中,reactive
函数通过返回原始对象的 Proxy
来实现响应式系统具有几个好处:
1. 智能依赖跟踪
Proxy
允许 Vue 精确地控制对象属性的读取和修改。这意味着 Vue 可以智能地追踪哪些组件依赖于哪些数据,只有当相关数据发生改变时,依赖那些数据的组件才会重新渲染。这种细粒度的依赖跟踪可以极大地提高应用的性能。
2. 深层嵌套的响应式转换
通过使用 Proxy
,Vue 能够在访问任意层级的属性时,将该属性转换为响应式的(如果还不是的话)。这样,你不需要手动确保对象的每一层都被转换成响应式的,Vue 会自动处理,这使得处理深层嵌套的对象变得简单且高效。
3. 动态属性的响应式支持
与 Vue 2 的 Object.defineProperty
方法相比,Proxy
可以让新增的属性也成为响应式的,而无需额外的步骤。这意味着你可以随时向响应式对象中添加新的属性,并且这些新属性会自动地被跟踪和响应。
4. 更好的性能
由于 Proxy
提供了更精细的拦截能力,Vue 可以减少不必要的检查和依赖收集工作,从而提高了响应系统的整体性能。此外,Vue 3 的响应式系统基于 Proxy
,使得底层实现在处理复杂场景时更加高效。
5. 支持更多数据类型
通过 Proxy
,Vue 3 的响应式系统不仅支持对象和数组,还能支持 Map、Set 等更多的 JavaScript 数据类型。这为开发者处理不同类型的响应式数据提供了更大的灵活性。
904. [Vue] reactive() 的局限性有哪些【热度: 410】【web框架】
关键词:响应式 api
作者备注 前三条是官网自己总结的限制, 属于核心限制 后面提交是作者自行补充
1. 基本数据类型不是响应式的
reactive
只能将对象或数组转换为响应式对象。对于基本数据类型(如字符串、数字、布尔值等),reactive
无法将其转换为响应式的。如果需要使基本类型数据响应式,应使用 ref
。
2. 不能替换整个对象
当你使用 reactive
创建响应式对象后,如果尝试直接替换掉整个响应式对象,新对象不会自动成为响应式的。这是因为 reactive
返回的是原始对象的代理(Proxy),直接替换原始对象并不会改变已经建立的代理关系。解决办法是通过修改对象的属性来实现,或者使用 ref
并对其 .value
进行替换,后者可以保持响应性。
举个例子:
const state = reactive({ count: 0 });
// 这样做是错误的,不会保持响应性
state = { count: 1 };
// 正确做法:修改现有对象的属性值
state.count = 1;
3. 对解构操作不友好
Vue 3 reactive
对象在进行解构操作时会失去其响应性,这是因为解构操作本质上是对对象属性的值进行了一次拷贝。如果对一个响应式对象进行解构赋值,那么得到的新变量不会是响应式的。为了在解构后保持响应性,Vue 3 提供了 toRefs
和 toRef
函数。
toRefs
可以将reactive
对象中的每个属性转换为一个ref
对象,从而保留其响应性。toRef
可以为reactive
对象的某个属性创建一个ref
引用,同样可以保持其响应性。
举个例子:
const state = reactive({
count: 0,
title: "Hello",
});
// 直接解构将会失去响应性
const { count, title } = state;
// 使用 toRefs 解构,保持响应性
const { count, title } = toRefs(state);
这些限制说明了 Vue 3 的响应式系统虽然提供了强大的功能,但在使用过程中需要更加注意一些特定的使用场景和解决方案,以确保数据的响应性不被意外破坏。
4. 返回 Proxy 对象
reactive
返回的是原始对象的 Proxy。这意味着,比较原始对象和通过 reactive
处理后的对象时,必须注意它们并不严格相等(===
)。
5. 不支持 IE11 及更早 IE 版本
由于 reactive
基于 Proxy
实现,而 Proxy
是 ES2015 的一个特性,它不被 IE11 或更早版本的 IE 浏览器支持。因此,如果你的应用需要兼容这些浏览器,使用 reactive
可能会有兼容性问题。
6. 响应式转换不可逆
一旦使用 reactive
将一个对象转换为响应式对象,就不能将其转换回原始的非响应式对象。虽然这在大多数情况下不是问题,但在某些特定场景下可能需要注意。
7. 内置类实例的限制
对于某些内置类(例如 Map
、Set
、Date
等)的实例进行 reactive
转换时,虽然 Vue 3 提供了对这些类型的响应式支持,但在转换后的响应式版本上使用某些原型链上的方法时,可能表现不一致或不符合预期。
907. [Vue] 触发事件修饰符 .exact 的作用是啥【热度: 454】【web框架】
关键词:事件修饰符、.exact 作用
.exact
修饰符在 Vue 事件处理中起着非常特定的作用。它允许控制触发事件处理器的确切方式,确保只有在指定的系统修饰键(如 ctrl
、alt
、shift
、meta
)组合完全匹配时,事件处理函数才会被触发。这意味着,如果你绑定了 .exact
修饰符到一个事件上,只有在没有其他未指定的修饰键被按下的情况下,该事件才会被触发。
使用场景
.exact
修饰符非常有用,尤其是在你想要精确控制事件触发条件的时候。例如,你可能有以下场景:
- 当用户严格只按下
ctrl
键时触发一个动作,如果用户同时按下了ctrl
和shift
,则不触发。
示例
<!-- 只有当没有任何其他键被同时按下时,点击才会调用 doThis -->
<button @click.exact="doThis">No Modifier Key</button>
<!-- 只有当仅按下 ctrl 键时点击才会调用 doThat -->
<button @click.ctrl.exact="doThat">Ctrl + Click Only</button>
在第一个例子中,点击按钮将只在没有按下 ctrl
、alt
、shift
或 meta
键的情况下触发 doThis
方法。在第二个例子中,doThat
方法只会在严格按下 ctrl
键时触发点击事件。
908. [Vue] 输入绑定修饰符 .lazy 作用是啥【热度: 95】【web框架】
关键词:输入绑定修饰符
在 Vue 中,.lazy
是一个输入绑定修饰符,用于 v-model
指令。它的主要作用是改变数据同步的时机:默认情况下,使用 v-model
绑定的输入字段会在每次 input
事件触发时同步数据(即用户输入时实时同步),而通过添加 .lazy
修饰符后,数据同步会改为在 change
事件发生时才进行,通常这意味着在输入字段失去焦点或按下回车键后。
使用 .lazy
修饰符的好处
- 性能优化:对于一些性能敏感的应用,或者当输入操作导致重度计算时,减少数据同步的频率可以提升性能。
- 用户体验:在一些场景下,可能希望用户完成输入后(例如填写完整的表单字段后)再收集数据,使用
.lazy
可以提升这类体验。 - 减少数据校验:如果你在输入数据时进行校验或处理,使用
.lazy
可以减少这种校验的频率,仅在用户完成输入时执行。
示例
<!-- 在输入框失去焦点或用户按下回车后,才更新 data 的 message 属性 -->
<input v-model.lazy="message" />
在这个例子中,不会在每次用户输入时同步 message
的值,而是在输入框失去焦点,或用户按下回车键时同步,这可以减少数据同步的次数,适用于不需要实时更新数据,或更新操作比较昂贵的场景。
总之,.lazy
修饰符提供了一种简便的方式来优化数据绑定的行为,尤其是在你希望控制数据更新频率,或者当实时更新不是必要时非常有用。
909. [Vue] 生命周期【热度: 848】【web框架】
关键词:生命周期
最基础生命周期直接可以看下面这个图, 如果只能回答下面这个图里面的生命周期, 那么该问题只能是「中等」级别; 之所以是 「高等」级别的问题, 因为还有很多别的生命周期, 大家不一定知道, 但是也很重要;
Vue3 新增的生命周期
onErrorCaptured()
onRenderTracked()
onRenderTriggered()
onActivated()
onDeactivated()
onServerPrefetch()
Vue 3 引入了组合式 API,随之而来的是一系列新的生命周期钩子,这些钩子提供了更细粒度的控制方式,尤其是在使用 setup()
函数时非常有用。下面简单解释一下你提到的这几个新的生命周期钩子:
onErrorCaptured()
- 作用:捕获组件及其子组件树中发生的错误。它提供了一个句柄来处理错误,并防止错误继续冒泡。
- 使用场景:当你需要在组件树中某个层级捕获并处理错误时使用,特别适用于构建错误边界。
onRenderTracked()
- 作用:每当一个响应式依赖被访问时调用,允许开发者跟踪渲染过程中依赖的访问。
- 使用场景:用于调试目的,帮助开发者理解组件如何响应数据变化,以及哪些依赖触发了组件的重新渲染。
onRenderTriggered()
- 作用:每当响应式依赖的变化导致组件重新渲染时调用。
- 使用场景:同样用于调试目的,让开发者知道是哪个具体的依赖变化导致了组件的更新。
onActivated() 和 onDeactivated()
- 作用:这两个钩子分别在
<keep-alive>
缓存的组件激活和停用时被调用。 - 使用场景:在使用
<keep-alive>
时非常有用,可以用来执行如清理或设置相关资源的操作。
onServerPrefetch()
- 作用:允许组件在服务器端渲染(SSR)期间进行数据预取。
- 使用场景:用于服务器端渲染的 Vue 应用中,可以在组件级别添加数据预取逻辑,提高首屏加载性能和 SEO 优化。
这些新的生命周期钩子为 Vue 应用提供了更多的灵活性和控制力,允许开发者编写更高效、更可靠的代码。特别是在构建大型应用或需要精细管理资源和错误处理的情况下非常有用。
912. [Vue] watch 和 watchEffect 场景上有何区别, 该如何选择【热度: 174】【web框架】
关键词:侦听器选择
watch
和 watchEffect
在 Vue 3 中都是强大的响应式特性,用于侦听响应式状态的变化并执行一些副作用(如调用函数)。虽然它们很相似,但在使用场景和行为上有一些关键的区别,了解这些区别可以帮助你选择最合适的工具来实现你的需求。
watch
- 精确性:
watch
允许你明确指定要侦听的数据源,并且可以分别访问其新值和旧值。这让watch
在需要对特定数据变化做出响应时非常精确和灵活。 - 惰性执行:
watch
默认情况下是惰性执行的,即它需要数据发生变化后才执行回调。这意味着在初始化时,watch
的回调不会执行,除非你通过配置使其立即执行。 - 使用场景:当你需要明确知道数据何时改变以及如何改变时(例如对比新旧值),或者需要侦听一个或多个特定的响应式引用时,
watch
是更好的选择。
watchEffect
- 自动侦测:
watchEffect
会自动侦测其回调函数中用到的响应式状态,并在这些状态改变时重新执行。这意味着你不需要明确指定侦听的状态,让侦听副作用的编写更简单直接。 - 立即执行:
watchEffect
回调会在初始时立即执行一次,然后再每次依赖的响应式状态变化时再次执行。这适合于不需要初始条件判断且希望立即根据响应式状态渲染或执行逻辑的场景。 - 使用场景:当你需要自动追踪并响应所有使用到的响应式状态变化时,
watchEffect
是更便捷的选项。它适用于依赖项不明确或者希望自动追踪依赖并执行副作用的场合。
如何选择
- 如果你的副作用逻辑需要明确侦听特定的数据源,并且需要区分初始执行和依赖更新时的逻辑,那么使用
watch
更合适。watch
提供了对侦听数据和执行逻辑的细粒度控制。 - 如果你的逻辑只是单纯地需要对使用到的任何响应式状态的改变做出响应,且希望简化依赖跟踪,
watchEffect
更简单、更易于使用。它自动收集依赖项,简化了代码,使你的副作用逻辑更容易编写和维护。
通常,选择依赖于你想要的控制级别和特定的使用情况。watch
提供了更高的灵活性和控制力,watchEffect
则为常见的自动响应逻辑提供了便利。了解这些区别和使用场景可以帮助你更合理地使用 Vue 3 的响应式系统。
913. [Vue] 侦听器在什么情况下是需要清理副作用的【热度: 148】【web框架】
关键词:侦听器副作用
在 Vue 3 中,清理副作用主要指的是在一个响应式侦听器(例如:watch
或 watchEffect
)中,当侦听的响应式状态(或侦听的回调函数)重新执行之前或组件销毁时,移除或停止之前创建的资源,以避免内存泄漏、性能问题或意外的行为。下面列举了一些需要清理副作用的典型情况:
1. 使用定时器时
当你在侦听器的回调函数中设置了定时器(如使用setInterval
或setTimeout
),并且不希望这个定时器在回调函数下次执行时仍然活动,你就需要在回调函数下一次执行之前清理这个定时器。
watchEffect((onInvalidate) => {
const timer = setInterval(() => {
// 执行一些逻辑
}, 1000);
// 清理函数
onInvalidate(() => {
clearInterval(timer);
});
});
2. 订阅外部或异步资源时
当你订阅了一些外部资源,如 WebSocket 连接、外部 API 的实时数据流、或是自定义事件监听器,如果这些资源在组件卸载后继续活动,可能会导致内存泄漏或其他意外行为。
watchEffect((onInvalidate) => {
const ws = new WebSocket("ws://example.com/feed");
ws.onmessage = (message) => {
// 处理消息
};
// 侦听器清理函数
onInvalidate(() => {
ws.close();
});
});
3. 响应式引用发生变化时
当你侦听的响应式引用(如 ref
或 reactive
对象)在回调函数生命周期中发生变化时,如果回调生产了外部副作用(比如修改了外部状态、操作了 DOM、设置了全局事件监听器等),你可能需要清理这些副作用,避免它们在重新计算或组件卸载时造成问题。
const user = ref(null);
watchEffect((onInvalidate) => {
// 假设fetchUser返回一个取消订阅或清理资源的函数
const unsubscribe = fetchUser(user.value, (newUser) => {
user.value = newUser;
});
onInvalidate(() => {
unsubscribe();
});
});
为什么要清理副作用
清理副作用是为了防止不必要的资源占用和潜在的内存泄漏,尤其是在使用外部资源、设置定时器、或订阅数据时。Vue 提供的onInvalidate
回调允许在侦听器重新运行之前或组件销毁时执行清理逻辑,确保应用资源被适当管理和释放。
915. [Vue] useTemplateRef 作用是啥, 哪些情况下要要使用这个 api【热度: 405】【web框架】
关键词:useTemplateRef api
转载自:https://juejin.cn/post/7414856908223610895