在 Vue 中,组件之间的通信有很多种方式。通过以下七组代码示例,我们可以清晰地看到不同的组件通信方式。
第一组:父组件通过 props
传值,子组件通过 watch
监听变化(父传子)
父组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
<Child :data="data"/> //通过:绑定属性传值给子组件
</template>
<script setup>
import Child from './child.vue'
import { ref } from 'vue'
const msg = ref('')
const data = ref('')
const add = () => {
data.value = msg.value
}
</script>
子组件:
vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({ //子组件通过defineProps接受父组件传过来的值
data: {
type: String,
default: ''
}
})
const list = ref(['html', 'css', 'js'])
watch(() => props.data, (newVal) => {
if (newVal) {
list.value.push(newVal)
}
})
</script>
解释:
在这组代码中,父组件通过 v-model
获取用户输入,并将其传递给子组件。在父组件中,当点击"添加"按钮时,data
的值会更新为输入框的内容,这个更新的值通过 props
传递给子组件。子组件通过 watch
监听 props.data
的变化,一旦 data
发生变化,它就会将新的值推入 list
中。
第一组改版:父组件通过 props
传递数组,子组件显示数组内容(这并不算新增方式,只是逻辑换一下可以不写watch监听)
父组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
<Child :list="list"/>
</template>
<script setup>
import Child from './child.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const msg = ref('')
const add = () => {
list.value.push(msg.value)
}
</script>
子组件:
vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
defineProps({
list: {
type: Array,
default: () => [],
},
});
</script>
解释:
在这个例子中,父组件持有一个名为 list
的数组,并通过 v-model
获取用户输入。在点击"添加"按钮后,父组件会将输入框的内容添加到 list
中,并通过 props
将这个数组传递给子组件。子组件通过 props.list
接收这个数组并在模板中展示出来。
第二组:父组件通过 provide
传递数据,子组件通过 inject
获取数据(父传子)
父组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
<Child/>
</template>
<script setup>
import Child from './child.vue'
import { ref, provide, readonly } from 'vue'
const msg = ref('')
const list = ref(['html', 'css', 'js'])
const add = () => {
// list.value.push(msg.value)
console.log(list.value);
}
provide('list', readonly(list.value)) // 向下提供只读数据
</script>
子组件:
vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { inject } from 'vue'
const list = inject('list') // 注入只读数据
list.push('vue') //无法新增
</script>
解释:
在这组代码中,父组件使用 provide
向下传递了一个 list
数据。这个 list
被标记为只读 (readonly
) 以防止子组件修改它。子组件通过 inject
获取到父组件提供的 list
数据。在这个例子中,子组件将一个新值 "vue"
添加到 list
中,但实际上这个修改会失败,因为父组件提供的数据是只读的。
第三组:子组件通过发布订阅向父组件传值(子传父)
父组件:
vue
<template>
<Child @add="handle"/> //订阅add事件
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import Child from './child.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const handle = (val) => {
list.value.push(val)
}
</script>
子组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('')
const emit = defineEmits(['add']) // 发布订阅
const add = () => {
emit('add', msg.value)
}
</script>
解释:
在这组代码中,父组件通过 @add
监听子组件 add
事件,并在事件发生时调用 handle
方法,将子组件传递过来的数据 (msg.value
) 添加到 list
数组中。子组件通过 defineEmits
定义了一个名为 add
的事件,当用户点击"添加"按钮时,子组件会触发该事件,并将输入框的内容传递给父组件。
第四组:通过 defineExpose
暴露数据给父组件(子传父)
子组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('')
const list = ref(['html', 'css', 'js'])
const add = () => {
list.value.push(msg.value) // 把输入框中的值添加到 list 数组中
}
// 使用 defineExpose 暴露数据给父组件
defineExpose({
list
})
</script>
<style lang="css" scoped></style>
父组件:
vue
<template>
<Child ref="childRef"/> //接收到子组件暴露的数据
<div class="body">
<ul>
<li v-for="(item, index) in childRef?.list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import Child from './child.vue'
import { ref } from 'vue'
const childRef = ref(null)
setTimeout(() => {
console.log(childRef.value.list) // 访问子组件暴露的 list 数据
}, 1000)
</script>
<style lang="css" scoped></style>
解释:
在这组代码中,子组件通过 defineExpose
将 list
数组暴露给父组件。父组件通过 ref="childRef"
获取对子组件的引用,进而访问子组件暴露的 list
数据。这种方式可以在父组件中直接访问子组件的内部状态或者方法。
第五组:通过 v-model
绑定和 emits
更新父组件的数据
子组件:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('')
const props = defineProps(['list']) // 接收父组件传来的 list 数据
const emits = defineEmits(['update:list']) // 发送 update:list 事件
const add = () => {
const arr = props.list
arr.push(msg.value) // 将输入框的值添加到 list 中
emits('update:list', arr) // 触发事件并更新父组件的 list
}
</script>
<style lang="css" scoped></style>
父组件:
vue
<template>
<Child v-model:list="list"/> <!-- 使用 v-model 双向绑定 -->
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import Child from './child.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js']) // 父组件的 list 数据
</script>
<style lang="css" scoped></style>
解释:
这组代码展示了如何使用 v-model
实现双向绑定和更新父组件的数据。父组件通过 v-model:list
将 list
数据与子组件进行双向绑定。当用户在子组件中点击"添加"按钮时,子组件会通过 emits
发出一个 update:list
事件,并将更新后的 list
传递给父组件,父组件的数据也随之更新。
第六组:使用一个全局的共享数据管理组件间通信(兄弟组件通信)
body.vue
:
vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { list } from './bus.js' // 从 bus.js 引入共享数据
</script>
<style lang="css" scoped></style>
bus.js
:
javascript
import { ref } from 'vue'
export const list = ref(['html', 'css', 'js']) // 这里定义了一个全局共享的 list
head.vue
:
vue
<template>
<div class="head">
<input type="text" v-model="msg">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { list } from './bus.js' // 引入共享的 list 数据
const msg = ref('')
const add = () => {
list.value.push(msg.value) // 向 list 中添加输入框的内容
}
</script>
<style lang="css" scoped></style>
index.vue
:
vue
<template>
<Head />
<Body />
</template>
<script setup>
import Head from './head.vue'
import Body from './body.vue'
</script>
<style lang="css" scoped></style>
解释:
这里展示了一种常见的跨组件共享数据的方式------通过一个共享的 bus.js
文件来存储数据。在 bus.js
中,定义了一个 list
,它是一个响应式数据。head.vue
和 body.vue
组件都从这个 bus.js
中引入了 list
数据。head.vue
中的输入框内容会被添加到 list
中,而 body.vue
会实时展示这个 list
的内容。通过这种方式,组件之间可以共享同一个数据,保持同步更新。