1、组件间通信
- 父子组件通信
props(父 → 子),单向数据流(子组件不应直接修改)
<!-- 父组件 --> <Child :msg="message" /> <!-- 子组件 --> <script> defineProps(['msg']); props: ['msg']; </script>emit(子 → 父)
<!-- 子组件 --> <script> emit('update', newValue); // Vue 3 this.$emit('update', newValue); // Vue 2 </script> <!-- 父组件 --> <Child @update="handleUpdate" />v-model(双向绑定),props + emit的组合,Vue3 支持多个v-model:propName
<Child v-model:title="title" /> <script> // 子组件 const modelValue = defineModel('title'); // Vue 3.4+ </script> - 跨级组件通信
provide / inject(祖先 → 后代),可以跨多层组件传值(如主题、配置)
// 祖先组件 provide('theme', 'dark'); // 后代组件 const theme = inject('theme'); - 全局事件总线(Vue 2) / mitt(Vue 3),任意组件之间的通信,但难维护、容易混乱;Vue 3 推荐用 mitt 替代 eventBus
// mitt 示例 const emitter = mitt(); emitter.emit('customEvent', data); emitter.on('customEvent', handler); - 兄弟组件通信
- 借助公共父组件,中转 props / emit 或使用 eventBus/mitt、Vuex/Pinia 实现共享状态
- 全局状态管理
- Vuex(Vue 2/3)或 Pinia(Vue 3 推荐),管理共享状态;集中式管理,适合大型应用
- URL / query / localStorage 等外部机制,利用路由参数、localStorage、sessionStorage 进行状态共享
$refs / defineExpose直接访问组件方法/属性(父访问子)
- 组合式 API 特有的通信方式(Vue 3)
defineModel()(Vue 3.4+),更强大、类型安全的 v-model 语法糖;支持多个模型绑定
const [title, setTitle] = defineModel('title');
2、Vue 中的插槽
答:插槽(Slot)用于实现组件内容分发,你可以把插槽理解为 Vue 的”占位符“,它允许父组件向子组件传递任意的 DOM 内容,实现灵活的组件组合。
- 默认插槽
<!-- 子组件 -->
<template>
<div class="card">
<slot></slot>
</div>
</template>
<!-- 父组件 -->
<MyCard>
<p>我是插入到卡片里的内容</p>
</MyCard>
- 具名插槽(多个 slot 时使用)
<!-- 子组件 -->
<template>
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</template>
<!-- 父组件 -->
<CustomLayout>
<template v-slot:header>头部</template>
<p>默认插槽内容</p>
<template v-slot:footer>底部</template>
</CustomLayout>
- 作用域插槽(高级特性):插槽内容需要依赖子组件提供的数据,子传父的逆向思维方式。
<!-- 子组件(提供数据给插槽) -->
<template>
<div v-for="user in users" :key="user.id">
<slot :user="user">{{ user.name }}</slot>
</div>
</template>
<script>
export default {
props: ['users']
};
</script>
<!-- 父组件(使用插槽 + 接收数据 -->
<UserList :users="userList">
<template v-slot="{ user }">
<p>{{ user.name }} - {{ user.email }}</p>
</template>
</UserList>
注:插槽和 props 的区别
props是父组件传数据给子组件;slot是父组件传“结构”(HTML)给子组件;- 作用域插槽是子组件向
slot“回传”数据给父。
3、封装组件时要注意哪些
答:封装组件应该遵守“高内聚,低耦合”的原则,确保具有良好的可复用性、可维护性和灵活性。
组件封装的基本原则:
- 高内聚:组件内部职责清晰,逻辑集中,避免组件过于庞大;
- 低耦合:与外部依赖关系弱,参数传递清晰,能灵活组合;
- 单一职责:一个组件尽量只做一件事情;
- 可组合:支持与其他组件组合使用(如插槽、自定义事件等);
- 响应式:支持 Vue 的响应式系统,与外部数据同步;
- 可配置:提供足够的 props、事件和插槽供调用者使用;
具体做法:
- 明确组件用途和命名
- 命名规范:使用 PascalCase 命名,如
UserCard.vue - 文件结构清晰,建议一个
.vue文件中只定义一个组件
- 命名规范:使用 PascalCase 命名,如
- 合理使用
props和emits- 使用
defineProps定义传参,类型清晰、默认值明确 - 使用
defineEmits明确组件暴露哪些事件 - 避免滥用
v-model,可以用defineModel支持双休绑定
- 使用
- 支持插槽 slot
- 使用默认插槽、具名插槽或作用域插槽增强灵活性
- 状态管理清晰
- 使用
ref、reactive、computed、watch管理组件内部状态 - 如果组件状态复杂,尽量拆分逻辑为
composables(抽离组件逻辑的函数模块)
- 使用
- 处理响应式与 model,支持
v-model写法,支持外部传入响应式值 - 样式隔离
- 使用
<style scoped>或 CSS Modules 避免污染全局 - 支持外部自定义
class/style,通过class、style、:class传入
- 使用
- 避免硬编码
- 文本、图标、样式都应由 props 控制,不要写死
- 性能优化
- 使用
v-show/v-if合理控制DOM - 使用
watchEffect替代过度使用watch - 使用
defineExpose公开内部方法给父组件 - 使用
defineOptions({ name: 'XxxComp' })提升调试体验
- 使用
4、在 Vue.js 中如何实现自定义指令(custom directive)的高级功能?
答:Vue 提供了directive允许注册自定义的指令 (Custom Directives)。
- 自定义指令的生命周期钩子(Vue3)
// vue3
app.directive('example', {
created(el, binding, vnode, prevVnode) {},
beforeMount(el, binding, vnode, prevVnode) {},
mounted(el, binding, vnode, prevVnode) {},
beforeUpdate(el, binding, vnode, prevVnode) {},
updated(el, binding, vnode, prevVnode) {},
beforeUnmount(el, binding, vnode, prevVnode) {},
unmounted(el, binding, vnode, prevVnode) {}
});
// vue2
Vue.directive('example', {
bind(el, binding, vnode) {}, // 只调用一次,指令首次绑定到元素时。
inserted(el, binding, vnode) {}, // 元素插入父节点时调用
update(el, binding, vnode, oldVnode) {}, // 组件更新前,可能多次调用。
componentUpdated(el, binding, vnode, oldVnode) {}, // 组件和子组件全部更新后。
unbind(el, binding, vnode) {} // 指令解绑时调用(如 v-if 销毁)。
});
- 参数 + 修饰符支持(增强指令灵活性):通过
binding.arg和binding.modifiers自定义指令的行为,让指令具备多种模式,提高可复用性。
// <input v-focus:delay.300 />
app.directive('focus', {
mounted(el, binding) {
if (binding.arg === 'delay') {
setTimeout(() => el.focus(), parseInt(Object.keys(binding.modifiers)[0]) || 300);
} else {
el.focus();
}
}
});