【Vue3】组件通信方式

目录

props / emit

provide / inject

Pinia

expose / ref

attr

v-model

mitt.js

Slots

Teleport


props / emit

父组件使用props向子组件传递数据,子组件则使用事件(events)向父组件反馈信息。

父组件向子组件传递数据

// Parent.vue 发送数据
<child :msg2="msg2"></child>
<script setup lang="ts">
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg2 = ref<string>("这是发送给子组件的消息")
    //或者对于复杂类型
    const msg2 = reactive<string>(["这是继承组件的消息"])
</script>

// Child.vue 接受数据
<script setup lang="ts">
    // 无需引入,直接使用
    // import { defineProps } from "vue"
    interface Props {
      msg1: string
      msg2: string
    }
    const props = withDefaults(defineProps<Props>(), {
      msg1: '',
      msg2: '',
    })
    console.log(props) // { msg2: "这是继承组件的消息" }
</script>

子组件向父组件传递数据

// Child.vue 发送
<template>
    // 方法1
    <button @click="emit('myClick')">Button</button>
    // 方法2
    <button @click="handleClick">Button</button>
</template>
<script setup lang="ts">
    
    //方法一适用于Vue3.2版本,无需导入
    // import { defineEmits } from "vue"
    // Corresponding to method one
    const emit = defineEmits(["myClick","myClick2"])
    // 对应于方法二
    const handleClick = ()=>{
        emit("myClick", "这是发送给父组件的消息")
    }
    
    // 方法二不适合Vue3.2版本,useContext()已被弃用
    import { useContext } from "vue"
    const { emit } = useContext()
    const handleClick = () => {
        emit("myClick", "这是发送给父组件的消息")
    }
</script>

// Parent.vue 接收
<template>
    <child @myClick="onMyClick"></child>
</template>
<script setup lang="ts">
    import child from "./child.vue"
    const onMyClick = (msg: string) => {
        console.log(msg) 
    }
</script>

provide / inject

这种机制用于开发从父组件到其任何子组件的依赖注入,而不一定是直接的子组件。

  • provide:在父组件中可以通过 provide 提供需要向后代组件传递的信息
  • inject:在任何后代组件中接收我们要添加到该组件中的数据,无论组件嵌套多深,都可以直接使用。
// 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>

Pinia

Pinia 是一个全新的 Vue 状态管理库,旨在替代 Vuex。

// main.ts
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')

// /store/user.ts
import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
    state: () => {
        return { 
            count: 1,
            arr: []
        }
    },
    getters: { ... },
    actions: { ... }
})

// Page.vue
<template>
    <div>{{ store.count }}</div>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const store = userStore()
// deconstruction
// const { count } = userStore()
</script>

expose / ref

父组件可使用引用直接访问子组件实例或元素。

//父组件代码
<template>
  <div>父组件:拿到子组件的message数据:{{ msg }}</div>
  <button @click="callChildFn">调用子组件的方法</button>
 
  <hr>
 
  <Child ref="com" />
</template>
 
<script setup>
import { ref, onMounted } from 'vue'
import Child from './components/Child.vue'
 
const com = ref(null) // 通过 模板ref 绑定子组件
 
const msg = ref('')
 
onMounted(() => {
  // 在加载完成后,将子组件的 message 赋值给 msg
  msg.value = com.value.message
})
 
function callChildFn() {
  // 调用子组件的 changeMessage 方法
  com.value.changeMessage('你好,中国!')
 
  // 重新将 子组件的message 赋值给 msg
  msg.value = com.value.message
}
</script>

//子组件
<template>
  <div>子组件:{{ message }}</div>
</template>
 
<script setup>
import { ref } from 'vue'
 
const message = ref('我来自中国!')
 
function changeMessage(data) {
  message.value = data
}
 
defineExpose({
  message,
  changeMessage
})
</script>

attr

attrs:包含父作用域的非属性集合,不包括类和样式(用于子组件获取父组件中没有通过props接收的属性)。

// Parent.vue 传值
<child :msg1="msg1" :msg2="msg2" title="子组件"></child>
<script setup>
    import child from "./child.vue";
    import { ref, reactive } from "vue";
    const msg1 = ref("信息1");
    const msg2 = ref("信息2");
</script>
 
// Child.vue 接收
<script setup>
    // import { defineProps, useAttrs } from "vue";
    // 子组件接收msg1
    const props = defineProps({
        msg1: String
    })
    
    const attrs = useAttrs();
    // 因为子组件接收了msg1,所以打印的结果中不会包含msg1, { msg2:"信息1", title: "子组件" }
    // 如果子组件没有接收msg1,打印的结果就是 { msg1: "信息1", msg2:"信息12", title: "子组件" }
    console.log(attrs); 
</script>

v-model

支持多数据块的双向数据绑定

// Parent.vue
<child v-model:key="key" v-model:value="value"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const key = ref("1111")
    const value = ref("2222")
</script>

// Child.vue
<template>
    <button @click="handlerClick">Button</button>
</template>
<script setup>
    
    const emit = defineEmits(["key","value"])
    
    const handlerClick = () => {
        emit("update:key", "New key")
        emit("update:value", "New value")
    }
</script>

mitt.js

在 Vue3 中,用于跨组件通信的 EventBus 已不再可用,Vue 官方推荐使用 mitt 或 tiny-emitter

mitt.js其原理与 EventBus 相同。优点:

  • 足够小,仅有 200bytes
  • 支持全部事件的监听和批量删除
  • 不依赖 Vue 实例,可以跨框架使用,React 或者 Vue,甚至 jQuery 项目都能使用同一套库。
// mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt;

// component A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// component B 
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>

Slots

插槽允许父组件控制子组件的部分内容。它们对于创建可重复使用且灵活的组件模板非常有用。

默认插槽

// Parent.vue
<FancyButton>
  Click me! <!-- slot content -->
</FancyButton>

// Child.vue
<button class="fancy-btn">
  <slot></slot> <!-- slot outlet -->
</button>

命名插槽

命名插槽是基于默认插槽的一种分类,可以理解为将内容匹配到相应的占位符。

// Parent.vue
<template>
  <Child>
    <template v-slot:monkey>
      <div>monkey</div>
    </template>

    <button>点击</button>
  </Child>
</template>

// Child.vue
<template>
  <div>
    <!-- default slots -->
    <slot></slot>
    <!-- named slots -->
    <slot name="monkey"></slot>
  </div>
</template>

范围插槽

// Parent.vue
<template>
  <!-- v-slot="{scope}" 用于接收从子组件向上传递的数据 -->
  <!-- :list="list" 将列表传递给子组件t -->
  <Child v-slot="{scope}" :list="list">
    <div>
      <div>Name: {{ scope.name }}</div>
      <div>Occupation: {{ scope.occupation }}</div>
      <hr>
    </div>
  </Child>
</template>

<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'

const list = ref([
  { name: 'Jhon', occupation: 'Thundering'},
  ...
])
</script>

// Child.vue
<template>
  <div>
    <!-- 使用 :scope="item" 遍历 -->
    <slot v-for="item in list" :scope="item" />
  </div>
</template>

<script setup>
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

Teleport

Teleport 是 Vue3 中新增的特性,它允许你将组件的子节点渲染到 DOM 结构中的任何地方,这样可以实现跨组件的通信。

// Parent.vue
<template>
  <teleport to="body">
    <div class="popup">
      <slot></slot>
    </div>
  </teleport>
</template>

<style>
.popup {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>
// Child.vue
<template>
  <div>
    <button @click="togglePopup">Toggle Popup</button>
    <Popup v-if="showPopup">
      <div>Popup Content</div>
    </Popup>
  </div>
</template>

<script>
import Popup from './Parent.vue';

export default {
  components: {
    Popup
  },
  data() {
    return {
      showPopup: false
    };
  },
  methods: {
    togglePopup() {
      this.showPopup = !this.showPopup;
    }
  }
};
</script>

Vue 3 中,有多种方式可以实现组件之间的通信。以下是一些常见的方法和代码示例: 1. Props & Events(父组件传递数据给子组件,子组件通过事件向父组件发送消息): - 父组件传递数据给子组件(props): ```vue <template> <child-component :message="parentMessage"></child-component> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent, }, data() { return { parentMessage: 'Hello from parent', }; }, }; </script> ``` - 子组件接收父组件传递的数据(props): ```vue <template> <div>{{ message }}</div> </template> <script> export default { props: ['message'], }; </script> ``` - 子组件通过事件向父组件发送消息(events): ```vue <template> <button @click="sendMessage">Send Message</button> </template> <script> export default { methods: { sendMessage() { this.$emit('messageSent', 'Hello from child'); }, }, }; </script> ``` - 父组件接收子组件发送的消息(events): ```vue <template> <div> <child-component @messageSent="handleMessage"></child-component> <p>Received message: {{ receivedMessage }}</p> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent, }, data() { return { receivedMessage: '', }; }, methods: { handleMessage(message) { this.receivedMessage = message; }, }, }; </script> ``` 2. Provide & Inject(祖先组件提供数据,后代组件注入数据): - 祖先组件提供数据(provide): ```vue <template> <div> <child-component></child-component> </div> </template> <script> import { provide } from 'vue'; export default { setup() { provide('message', 'Hello from ancestor'); }, }; </script> ``` - 后代组件注入数据(inject): ```vue <template> <div>{{ message }}</div> </template> <script> import { inject } from 'vue'; export default { setup() { const message = inject('message'); return { message, }; }, }; </script> ``` 这是一些常见的 Vue 3 组件通信方式的代码实现。根据实际需求,你可以选择合适的方式来进行组件通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值