vue+ts 基础面试题 (二)
1. v-if /
v-show
条件渲染
一
、示例:
<template><div><p v-if="isVisible">这个文本在 isVisible 为 true 时显示。</p><p v-else>否则显示这个文本。</p><button @click="toggleVisibility">切换显示</button></div>
</template><script setup>
import { ref } from 'vue';const isVisible = ref(true);function toggleVisibility() {isVisible.value = !isVisible.value;
}
</script>
<template><div><p v-show="isActive">这个文本在 isActive 为 true 时可见。</p><button @click="toggleActivity">切换活动状态</button></div>
</template><script setup>
import { ref } from 'vue';const isActive = ref(true);function toggleActivity() {isActive.value = !isActive.value;
}
</script>
二、对比
1. 性能差异:
v-if
:切换时涉及 DOM 操作 不适合频繁变化的场景。
v-show
:切换时只修改 CSS,开销小。适合高频切换的场景。
2. 初始渲染:
v-if
:条件为假时,元素不会被渲染,节省初始资源。
v-show
:元素总是被渲染,然后通过 CSS 隐藏,可能增加初始加载时间。
2.列表渲染 (v-for)
一、列表渲染基础语法
<ul><li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
items
是源数据数组,item
是当前迭代的别名。:key
是必需的,推荐使用唯一标识(如id
),帮助 Vue 高效更新 DOM。
二、遍历对象
v-for
可遍历对象的属性,支持值、键名和索引:
<div v-for="(value, key, index) in object" :key="key">{{ index }}. {{ key }}: {{ value }}
</div>
value
:属性值key
:属性名index
:遍历顺序索引
三、性能优化与注意事项
- 始终提供
:key
:避免使用索引作为key
,优先使用数据中的唯一标识。 - 避免
v-for
与v-if
共用:Vue 会优先执行v-for
,可能导致不必要的计算。推荐使用计算属性过滤数据。 - 复杂列表使用计算属性:若需过滤或排序,预先处理数据再渲染:
computed: {filteredItems() {return this.items.filter(item => item.isActive);} }
四、动态更新列表
Vue 能检测数组变更方法(如 push()
、splice()
)并自动更新视图。直接通过索引修改数组或替换整个数组时,需使用 Vue.set
或展开语法:
// 替换数组
this.items = [...this.items, newItem];
// 修改特定项
this.items.splice(index, 1, newItem);
3.事件处理修饰符的作用
一、阻止默认行为
.prevent
:阻止事件的默认行为(如表单提交后刷新页面)。.stop
:阻止事件冒泡(阻止事件向上级元素传播)。
二、键盘/鼠标事件修饰符
.enter
、.tab
:仅当按下特定键时触发(如@keyup.enter
)。.left
、.right
:限定鼠标左右键触发(如@click.right
)。
三、事件触发频率控制
.once
:事件仅触发一次(如@click.once
)。.debounce
(需手动实现):防抖,延迟触发事件。
四、框架特有修饰符
- Vue的
.native
:监听组件根元素的原生事件。 - React的
passive
:优化滚动性能(如onScroll={handleScroll}
配合{passive: true}
)。
代码示例
// Vue模板中使用修饰符
<template><form @submit.prevent="handleSubmit"> <button @click.stop="handleClick">点击</button></form>
</template>
注意事项
- 修饰符可链式使用(如
@click.stop.prevent
)。 - 部分修饰符需结合特定事件(如
.enter
仅对@keyup
有效)。 - 不同框架的修饰符语法可能不同,需查阅对应文档。
4.插槽的基本概念
一、默认插槽的使用
子组件 ChildComponent.vue
:
<template><div><h2>子组件标题</h2><slot></slot></div>
</template>
父组件使用:
<template><ChildComponent><p>这是父组件传递的内容</p></ChildComponent>
</template>
二、命名插槽的使用
子组件 LayoutComponent.vue
:
<template><div><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer></div>
</template>
父组件使用:
<template><LayoutComponent><template v-slot:header><h1>这是头部内容</h1></template><p>这是默认插槽的内容</p><template v-slot:footer><p>这是页脚内容</p></template></LayoutComponent>
</template>
三、作用域插槽的使用
子组件 TodoList.vue
:
<template><ul><li v-for="item in items" :key="item.id"><slot :item="item"></slot></li></ul>
</template><script>
export default {data() {return {items: [{ id: 1, text: '项目1' },{ id: 2, text: '项目2' }]}}
}
</script>
父组件使用:
<template><TodoList><template v-slot:default="slotProps"><span>{{ slotProps.item.text }}</span></template></TodoList>
</template>
四、插槽的简写语法
Vue3 提供了插槽的简写语法,v-slot:header
可以简写为 #header
。
<template><LayoutComponent><template #header><h1>简写头部内容</h1></template></LayoutComponent>
</template>
五、动态插槽名
<template><LayoutComponent><template #[dynamicSlotName]><p>动态插槽内容</p></template></LayoutComponent>
</template><script>
export default {data() {return {dynamicSlotName: 'header'}}
}
</script>
六、插槽的默认内容
子组件可以在 <slot>
标签内提供默认内容,当父组件没有提供插槽内容时会显示。
<template><button type="submit"><slot>提交</slot></button>
</template>
注意事项
- Vue3 中废弃了
slot
和slot-scope
特性,统一使用v-slot
语法 - 默认插槽可以使用
v-slot:default
或直接省略 - 作用域插槽的数据通过对象形式传递,可以使用解构语法
- 在单个默认插槽的情况下,
v-slot
可以直接用在组件标签上
5. 表单双向绑定方法
一、文本输入框(input/textarea)
<template><input v-model="message" placeholder="请输入内容"><p>输入的内容: {{ message }}</p>
</template><script setup>
import { ref } from 'vue'
const message = ref('')
</script>
二、复选框(checkbox)(布尔类型)
1. 单个复选框
<input type="checkbox" v-model="checked">
2. 多个复选框绑定数组:
<input type="checkbox" value="A" v-model="checkedNames">
<input type="checkbox" value="B" v-model="checkedNames">
三、单选按钮(radio)
<input type="radio" value="男" v-model="gender">
<input type="radio" value="女" v-model="gender">
四、下拉选择框(select)
1. 单选下拉框:
<select v-model="selected"><option disabled value="">请选择</option><option value="A">选项A</option>
</select>
2.多选下拉框(绑定数组):
<select v-model="selected" multiple><option value="A">选项A</option>
</select>
五、自定义组件 v-model
在 Vue3 中,自定义组件的 v-model 默认使用 modelValue
作为 prop,update:modelValue
作为事件:
子组件:
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script><template><input:value="modelValue"@input="$emit('update:modelValue', $event.target.value)">
</template>
父组件使用:
<CustomInput v-model="message" />
六、v-model 修饰符
Vue3 提供了一些有用的修饰符:
.lazy
- 在 change 事件后同步(而非 input 事件).number
- 将输入转为数字.trim
- 自动去除首尾空白字符
示例:
<input v-model.lazy="msg">
6.组件注册方式
一、全局注册
全局注册的组件可以在应用中的任何地方使用,无需再次导入。通常在应用的入口文件(如 main.js
或 main.ts
)中进行注册。
import { createApp } from 'vue';
import App from './App.vue';
import MyComponent from './components/MyComponent.vue';const app = createApp(App);
app.component('MyComponent', MyComponent);
app.mount('#app');
注册后,可以在任何组件的模板中直接使用 <MyComponent />
。
二、局部注册
局部注册的组件仅在当前组件中可用,需要在组件的 components
选项中声明。
<template><MyComponent />
</template><script>
import MyComponent from './components/MyComponent.vue';export default {components: {MyComponent}
};
</script>
三、自动全局注册
通过 require.context
或 import.meta.glob
可以批量注册组件,适用于大量基础组件的自动化注册。
import { createApp } from 'vue';
import App from './App.vue';const app = createApp(App);const modules = import.meta.glob('./components/*.vue');
for (const path in modules) {const componentName = path.split('/').pop().replace(/\.\w+$/, '');app.component(componentName, defineAsyncComponent(() => modules[path]()));
}app.mount('#app');
四、动态组件注册
动态组件通过 <component :is="...">
实现,配合 defineAsyncComponent
可以实现按需加载。
<template><component :is="currentComponent" />
</template><script>
import { defineAsyncComponent } from 'vue';export default {data() {return {currentComponent: null};},methods: {async loadComponent() {this.currentComponent = defineAsyncComponent(() =>import('./components/MyComponent.vue'));}}
};
</script>
五、插件注册
通过插件方式批量注册组件,适合组件库或大型项目。
// my-plugin.js
import MyComponent from './components/MyComponent.vue';export default {install(app) {app.component('MyComponent', MyComponent);}
};
在入口文件中使用插件:
import { createApp } from 'vue';
import App from './App.vue';
import MyPlugin from './my-plugin';const app = createApp(App);
app.use(MyPlugin);
app.mount('#app');
7.动态组件(component
)的基本用法
一、基础用法
<template><component :is="currentComponent"></component>
</template><script setup>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
import { ref } from 'vue'const currentComponent = ref('ComponentA')
</script>
二、组件切换与状态保持
动态组件在切换时会销毁旧组件实例,默认不保留状态。使用 <KeepAlive>
包裹可以缓存组件状态。
<template><KeepAlive><component :is="currentComponent"></component></KeepAlive>
</template>
三、传递 Props 与事件
动态组件可以像普通组件一样传递 props 和监听事件。
<template><component :is="currentComponent":someProp="value"@customEvent="handler"></component>
</template>
四、动态导入组件
结合 defineAsyncComponent
实现按需加载组件,优化性能。
import { defineAsyncComponent } from 'vue'const AsyncComponent = defineAsyncComponent(() =>import('./AsyncComponent.vue')
)
五、动态组件与 v-for
动态组件可与 v-for
结合渲染多个不同组件。
<template><componentv-for="(item, index) in items":key="index":is="item.component"></component>
</template>
六、注意事项
动态组件的 is
属性可以是注册的组件名、导入的组件对象或 Vue 组件选项对象。当使用字符串时需确保组件已正确注册。
组件切换时若需执行特定逻辑,可利用生命周期钩子或 onActivated
/onDeactivated
(与 <KeepAlive>
配合使用)。
8.异步组件加载指南
一、基础异步加载
使用动态导入语法实现按需加载:
import { defineAsyncComponent } from 'vue'const AsyncComponent = defineAsyncComponent(() =>import('./components/MyComponent.vue')
)
在模板中直接使用:
<template><AsyncComponent />
</template>
二、高级配置选项
支持加载状态处理和错误捕获:
const AsyncComponent = defineAsyncComponent({loader: () => import('./components/HeavyComponent.vue'),loadingComponent: LoadingSpinner, // 加载中显示的组件errorComponent: ErrorDisplay, // 错误时显示的组件delay: 200, // 延迟显示加载状态(ms)timeout: 3000, // 超时时间(ms)suspensible: false // 是否使用Suspense控制
})
三、Suspense 集成(推荐)
使用 Vue 3 内置的 <Suspense>
组件管理加载状态:
<template><Suspense><template #default><AsyncComponent /></template><template #fallback><div class="loading">加载中...</div></template></Suspense>
</template>
四、 路由级异步加载
结合 Vue Router 实现路由懒加载:
const routes = [{path: '/dashboard',component: () => import('./views/Dashboard.vue')}
]
五、性能优化技巧
- 预加载策略:在空闲时预加载关键组件
// 组件定义后预加载 AsyncComponent.preload()
- 代码分割:配合 Webpack 魔法注释
defineAsyncComponent(() => import(/* webpackChunkName: "admin" */ './AdminPanel.vue') )
六、错误处理
全局捕获异步组件错误:
app.config.errorHandler = (err, instance, info) => {if (info === 'async component') {console.error('异步组件加载失败:', err)// 执行错误恢复逻辑}
}
七、使用场景建议
- 大型组件($>100\text{KB}$)
- 非首屏关键组件
- 条件渲染组件($v-if$ 控制的组件)
- 路由级组件
9.依赖注入 (provide/inject)
依赖注入是 Vue3 中实现跨层级组件通信的核心机制,通过 provide
和 inject
实现祖先组件向后代组件直接传递数据/方法,避免逐层传递 props 的繁琐流程。
一、核心概念
1.provide
在祖先组件中声明需共享的数据或方法,支持响应式数据
// 祖先组件
import { provide, ref } from 'vue'setup() {const count = ref(0)const updateCount = () => count.value++// 提供响应式数据和方法provide('countKey', count)provide('updateKey', updateCount)
}
2.inject
在后代组件中注入祖先提供的数据
// 后代组件
import { inject } from 'vue'setup() {const injectedCount = inject('countKey')const injectedUpdate = inject('updateKey')return { injectedCount, injectedUpdate }
}
二、特性详解
1.响应式传递
使用 ref
/reactive
提供的数据会自动保持响应性:
// 后代组件可直接使用
<button @click="injectedUpdate">{{ injectedCount }}</button>
2.注入默认
防止未提供数据时的错误:
const data = inject('key', '默认值')
3.工厂函
动态生成默认值:
const config = inject('config', () => ({ color: 'red' }))
4.Symbol 防冲
推荐用 Symbol 作为 key:
// keys.js
export const COUNT_KEY = Symbol()// 祖先组件
provide(COUNT_KEY, ref(0))// 后代组件
inject(COUNT_KEY)
三、最佳实践
应用场景:全局配置(主题/语言),复杂表单状态管理,插件开发(如 UI 库)
// 主题提供者组件
provide('theme', { primaryColor: '#1890ff' })
注意事项
interface ThemeConfig {primaryColor: string
}
const theme = inject<ThemeConfig>('theme')
provide('data', readonly(sensitiveData))
四、对比 Vue2
特性 | Vue2 | Vue3 |
---|---|---|
响应性 | 需手动处理 | 自动追踪 |
组合式 API | 不支持 | 原生支持 |
TypeScript | 弱类型支持 | 完整类型推断 |
默认值处理 | 无 | 支持函数式默认值 |
Provide/Inject 跨层级传值
在 Vue3 中,provide
和 inject
是实现跨层级组件通信的核心 API,适用于祖先组件向任意后代组件传递数据的场景,无需逐层传递 props。
核心机制
provide
在祖先组件中使用,提供可被注入的数据或方法:<script setup> import { ref, provide } from 'vue'const count = ref(0) const increment = () => count.value++// 提供数据和方法 provide('counter', {count,increment }) </script>
inject
在任意后代组件中注入数据:<script setup> import { inject } from 'vue'// 注入祖先提供的数据 const { count, increment } = inject('counter') </script>
关键特性
响应式数据
通过ref
或reactive
提供响应式数据,注入后自动保持同步:provide('user', reactive({ name: 'Alice', age: 30 }))
默认值设置
防止未提供数据时的错误:const data = inject('key', 'defaultValue')
工厂函数
动态生成默认值:const config = inject('config', () => ({ timeout: 1000 }))
最佳实践
Symbol 避免命名冲突
const counterKey = Symbol() provide(counterKey, { count, increment }) inject(counterKey)
组合式函数封装
创建可复用的逻辑:// useCounter.js export function useCounter() {const count = ref(0)provide('counter', count) // 提供数据return { count } }export function useInjectCounter() {return inject('counter') }
类型安全 (TypeScript)
使用泛型确保类型:interface Counter {count: Ref<number>increment: () => void } inject<Counter>('counter')!
对比 Props
特性 | Provide/Inject | Props |
---|---|---|
传递方向 | 祖先 → 任意后代 | 父 → 子 |
层级深度 | 任意深度 | 仅直接子组件 |
维护成本 | 低(无需中间组件传递) | 高(需逐层传递) |
适用场景 | 全局配置、主题、用户信息 | 紧密耦合的父子通信 |
注意事项
- 避免滥用:优先考虑 props/emits,仅在跨多层级时使用
- 响应式解构:使用
toRefs
保持响应性const { count } = toRefs(inject('counter'))
- 只读控制:通过
readonly
防止意外修改provide('data', readonly(reactive({ /*...*/ })))
通过合理使用 Provide/Inject,可显著简化深层嵌套组件的通信逻辑,同时保持代码的可维护性和响应式特性。
10.Provide/Inject 跨层级传值
在 Vue3 中,provide
和 inject
是实现跨层级组件通信的核心 API,适用于祖先组件向任意后代组件传递数据的场景,无需逐层传递 props。
一、核心机制
1.provid
在祖先组件中使用,提供可被注入的数据或方法:
<script setup>
import { ref, provide } from 'vue'const count = ref(0)
const increment = () => count.value++// 提供数据和方法
provide('counter', {count,increment
})
</script>
2.inject
在任意后代组件中注入数据:
<script setup>
import { inject } from 'vue'// 注入祖先提供的数据
const { count, increment } = inject('counter')
</script>
二、关键特性
1.响应式数
通过 ref
或 reactive
提供响应式数据,注入后自动保持同步:
provide('user', reactive({ name: 'Alice', age: 30 }))
2.默认值设
防止未提供数据时的错误:
const data = inject('key', 'defaultValue')
3.工厂函数
动态生成默认值:
const config = inject('config', () => ({ timeout: 1000 }))
三、最佳实践
1.Symbol 避免命名冲突
const counterKey = Symbol()
provide(counterKey, { count, increment })
inject(counterKey)
2.组合式函数封装
创建可复用的逻辑:
// useCounter.js
export function useCounter() {const count = ref(0)provide('counter', count) // 提供数据return { count }
}export function useInjectCounter() {return inject('counter')
}
3.类型安全 (TypeScript)
使用泛型确保类型:
interface Counter {count: Ref<number>increment: () => void
}
inject<Counter>('counter')!
四、对比 Props
特性 | Provide/Inject | Props |
---|---|---|
传递方向 | 祖先 → 任意后代 | 父 → 子 |
层级深度 | 任意深度 | 仅直接子组件 |
维护成本 | 低(无需中间组件传递) | 高(需逐层传递) |
适用场景 | 全局配置、主题、用户信息 | 紧密耦合的父子通信 |
五、注意事项
避免滥用:优先考虑 props/emits,仅在跨多层级时使用
响应式解构:使用 toRefs
保持响应性
const { count } = toRefs(inject('counter'))
只读控制:通过 readonly
防止意外修改
provide('data', readonly(reactive({ /*...*/ })))