面试备战录

59 阅读3分钟

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文件中只定义一个组件
  • 合理使用propsemits
    • 使用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.argbinding.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();
    }
  }
});