面试精讲 - vue3组件之间的通信

父子组件通信

vue采用的是单向数据流的方式进行通信。数据修改权始终在父组件身上。

  1. :bind props

父组件通过:bind 绑定data里的数据状态。 或者setup函数里的数据状态。

js 复制代码
    <ChildOne :msg1="msg1" :msg2="msg2"/>

而子组件通过props或者defineProps来限定数据类型与数据是否必须。不过vue3.2之后这种编译宏就无需显示的导入了。

js 复制代码
export default {
    props:["msg1","msg2"],
    setup(props){
        console.log(props)
    }
}
js 复制代码
defineProps({
    // msg2:String
msg2:{
    type:String,
    required: true
}
})

2.emit

子组件首先通过定义defineEmit定义子组件能触发哪些事件,接着使用emit给固定的事件推送消息。

js 复制代码
<template>
    <div>
        <button @click="handleClick">按钮</button>
        <button @click="emit('myClick','hello')">按钮</button>
    </div>
</template>

<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['myClick'])

const handleClick = () => {
    emit('myClick','hello')
}
</script>

而父组件通过自定义事件来接受参数。

js 复制代码
<template>
   <EmitChild @myClick="onMyClick"></EmitChild>
</template>

<script setup>
import EmitChild from './EmitChild.vue'
const onMyClick = (msg) => {
   console.log('子组件的消息:' + msg)
}
</script>

3.expose ref 在vue2中父组件能通过获取子组件对象获取所有的属性和方法,这种方法不安全。vue3引入了编译宏defineExpose,父组件只能访问子组件Expose的方法和属性。提高了组件的封装性。 子组件通过defineExpose来向外暴露本身的属性。父组件通过组件的ref来绑定组件的对象。获取属性

js 复制代码
<script setup>
import { ref } from 'vue'

const internalCounter = ref(0)
const publicApi = {
  getCount: () => internalCounter.value,
  increment: () => internalCounter.value++
}

defineExpose(publicApi)
</script>
js 复制代码
<script setup>
const childRef = ref(null)

// 只能通过暴露的API操作
childRef.value?.increment()
console.log(childRef.value?.getCount())
</script>

4.v-model :其实是自定义事件和:bind的缩写

js 复制代码
<CustomInput v-model="searchText" />

<!-- 等价于 -->
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

5.provide inject

可以给后代组件使用,无论嵌套多深的后代组件都能使用。但是他的数据流向不清晰,可能还会出现祖父明明冲突,父组件会覆盖。

js 复制代码
// Parent.vue
<script setup>
    import { provide } from "vue"
    provide("name", "沐华")
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const name = inject("name")
    console.log(name) // 沐华
</script>

6.attrs 父组件通过给子组件传递属性。 接着子组件通过useAttrs hooks来接收。他能访问到没有被props显示声明的属性。 并且可以做到组件属性穿透传递

js 复制代码
<template>
  <MiddleLayer 
    data-important="true" 
    @deep-event="handleDeepEvent"
  />
</template>

<script setup>
const handleDeepEvent = (val) => {
  console.log('Received from deep child:', val)
}
</script>
js 复制代码
<template>
  <!-- 完全透明的中间层 -->
  <DeepChild v-bind="$attrs" />
</template>
js 复制代码
<template>
  <button 
    :data-important="$attrs.dataImportant"
    @click="triggerEvent"
  >
    Click me
  </button>
</template>

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()

const triggerEvent = () => {
  if(attrs.onDeepEvent) {
    attrs.onDeepEvent('Data from deep')
  }
}
</script>

7.mitt 他是一个基于发布订阅模式的一个监听系统 我们将mitt放到全局属性配置中。在使用的地方通过上下文获取。确保他是单例模式。

  1. 入口配置事件总线
js 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'

const app = createApp(App)
app.config.globalProperties.$eventBus = mitt()  // 此行是关键配置
app.mount('#app')

2.发送方

js 复制代码
<script setup>
import { getCurrentInstance } from 'vue'

const sendNotification = () => {
  const bus = getCurrentInstance().appContext.config.globalProperties.$eventBus
  bus.emit('message', { 
    from: 'Sender',
    content: '重要通知'
  })
}
</script>

<template>
  <button @click="sendNotification">发送全局通知</button>
</template>

3.接收方

js 复制代码
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { eventBus } from '../main.js'

const handler = (msg) => {
  console.log('收到消息:', msg)
}

// 组件挂载时监听
onMounted(() => {
  eventBus.on('message', handler)
})

// 组件销毁时移除
onUnmounted(() => {
  eventBus.off('message', handler)
})
</script>

8.pinia

js 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app') // 对应你当前文件的 <div id="app">
js 复制代码
// store.js
import { defineStore } from 'pinia';

export const useMainStore = defineStore('main', {
  state: () => ({
    count: JSON.parse(localStorage.getItem('count')) || 0,
  }),
  actions: {
    increment() {
      this.count++;
      localStorage.setItem('count', JSON.stringify(this.count)); // 保存到 localStorage
    },
  },
});
js 复制代码
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
// ✅ 正确解构方式
const { count, doubleCount } = storeToRefs(store)
// ✅ 方法可直接解构(方法引用是稳定的)
const { increment } = store
</script>

pinia还可以对数据进行持久化。可以通过插件进行配置,也可以自己手动配置,将数据持久化到localstorage或者sessionStorage;

js 复制代码
// main.js
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(createPersistedState({
  storage: localStorage,       // 默认存储位置
  key: id => `__persisted__${id}`, // 自定义存储键名
  serializer: {               // 序列化配置
    serialize: JSON.stringify,
    deserialize: JSON.parse
  }
}))

app.use(pinia)
js 复制代码
import { defineStore } from 'pinia';
import { ref, computed, watch } from 'vue';

export const useMainStore = defineStore('main', () => {
  // 状态
  const count = ref(Number(localStorage.getItem('count')) || 0);

  // 计算属性
  const doubleCount = computed(() => count.value * 2);

  // 监听状态变化
  watch(count, (newValue) => {
    localStorage.setItem('count', newValue);
  });

  return { count, doubleCount };
});
相关推荐
计算机学姐1 小时前
基于Asp.net的驾校管理系统
vue.js·后端·mysql·sqlserver·c#·asp.net·.netcore
念九_ysl7 小时前
深入解析Vue3单文件组件:原理、场景与实战
前端·javascript·vue.js
Jenna的海糖7 小时前
vue3如何配置环境和打包
前端·javascript·vue.js
灵感__idea8 小时前
Vuejs技术内幕:数据响应式之3.x版
前端·vue.js·源码阅读
鱼樱前端8 小时前
📚 Vue Router 4 核心知识点(Vue3技术栈)面试指南
前端·javascript·vue.js
计算机-秋大田8 小时前
基于Spring Boot的宠物健康顾问系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
程序员大澈9 小时前
1个基于 Three.js 的 Vue3 组件库
javascript·vue.js
程序员大澈9 小时前
3个 Vue Scoped 的核心原理
javascript·vue.js
计算机学姐9 小时前
基于Asp.net的教学管理系统
vue.js·windows·后端·sqlserver·c#·asp.net·visual studio