Skip to content

组件通信

  • props 用来接受父组件传递过来的数据
  • emit 用来子组件传递数据给父组件

props 接受数据

  • props 数据是只读的,用来接受父组件传递过来的数据。

  • 在子组件中使用defineProps() 来声明 props

ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
const props = defineProps({
	money: {
		type: Number,
		default: 0,
	},
	message: {
		type: String,
		default: '',
	},
})
console.log(props.message)
</script>

emit 修改数据

  • props 数据是只读的,不能直接修改。
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
const props = defineProps({
	money: {
		type: Number,
		default: 0,
	},
	message: {
		type: String,
		default: '',
	},
})

// 下面代码会报错,应为props是只读的,不能直接修改
props.money = 100;

修改方式-computed (推荐)

  • 通过计算属性的 get 读取 props 的值,set 时通过 emit 通知父组件更新,既保持响应性,又遵守单向数据流。
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
const props = defineProps({
	money: {
		type: Number,
		default: 0,
	},
	message: {
		type: String,
		default: '',
	},
})
// 定义 emit 事件(触发父组件更新)
const emit = defineEmits(['update:money'])

// 计算属性:get 读 props,set 触发 emit
const localMony = computed({
	get() {
		return props.money; // 从 props 读取值
	},
	set(newValue) {
		emit('update:money', newValue); // 通知父组件更新
	},
});
</script>

本地复制 props 的值(适合复杂场景)

  • 如果需要在子组件内部对值进行复杂处理(如临时修改、验证等),可以在子组件中声明一个本地变量,初始化时复制 props 的值,修改时同步通过 emit 通知父组件。
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
const props = defineProps({
	message: {
		type: String,
		default: '',
	},
	money: {
		type: Number,
		default: 0,
	},
})

const localValue = ref(props.message)
// 监听 props 变化,同步到本地变量(父组件主动修改时生效)
watch(
  () => props.message,
  (newVal) => {
    localValue.value = newVal
  }
)

// 定义 emit 事件(触发父组件更新)
const emit = defineEmits(['update:message'])

// 本地值变化时,同步到父组件
const syncToParent = () => {
  emit('update:message', localValue.value)
}
</script>

完整案例

父组件

vue
<template>
	<div class="father">
		<h2>我是父组件</h2>
		<!-- 单一数据,多个绑定 -->
		<Child1
			v-model:money="父给子的零花钱"
			v-model:message="父给子的消息"
		></Child1>
		<!-- 对象数据,绑定一大推-->
		<Child2 v-model:info="info"></Child2>
		<!-- 自定义数据,自定义触发事件 -->
		<Child3
			:info="info"
			@handleUpdateInfo="handleUpdateInfo"
		></Child3>
	</div>
</template>
ts
<script lang="ts" setup>
import Child1 from "./Child1.vue"
import Child2 from "./Child2.vue"
import Child3 from "./Child3.vue"

const info = ref({
	name: '张三',
	age: 18,
	sex: '男',
	email: 'zhangsan@example.com',
})

// 处理子组件更新事件
const handleUpdateInfo = (newInfo: typeof info.value) => {
	info.value = newInfo;
}
</script>

子组件1

  • v-model 传递多个单字段数据
vue
<template>
	<div class="son">
		<h2>我是子组件1</h2>
		<!-- 使用方式1:使用props数据 -->
		<p>{{ props.info }}</p>
		<p>{{ props.money }}</p>
		<!-- 使用方式2:可以省略props前缀 -->
		<p>{{ info }}</p>
		<p>{{ money }}</p>

		<button @click="updata()">修改数据</button>
	</div>
</template>
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
// defineProps 是vue3提供的方法,不需要引入可以直接使用
let props = defineProps({
	money: {
		type: Number,
		default: 0,
	},
	message: {
		type: String,
		default: '',
	},
})

// 定义 emit 事件(触发父组件更新)
const emit = defineEmits(['update:money','update:message'])

// 更新数据
const updata = () => {
	localMony.value = 100;
	localMessage.value = 'hello';
	// 更新父组件数据
	emit("update:money",localMony.value)
	emit("update:message",localMessage.value)
}

子组件2

  • v-model 传递对象数据
vue
<template>
	<div class="son">
		<h2>我是子组件2</h2>
		<p>{{ info.name }}</p>
		<p>{{ info.age }}</p>
		<p>{{ info.sex }}</p>
		<p>{{ info.email }}</p>

		<button @click="updata()">修改数据</button>
	</div>
</template>
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
let props = defineProps({
	info: {
		type: Object,
		default: () => ({
			name: '张三',
			age: 18,
			sex: '男',
			email: 'zhangsan@example.com',
		}),
	}
})

// JavaScript中,对象是引用类型
// 当父组件将一个响应式对象通过props传递给子组件时,
// 子组件接收的不是对象的副本,而是对象的引用地址(即指向同一个 Proxy 代理对象)。
// 这就是说:父组件和子组件访问的是同一个响应式对象(共享同一份内存地址)。
// 所以这里不需要进行额外处理。能够动态拿到父组件传递过来的对象数据。

// 定义 emit 事件(触发父组件更新)
const emit = defineEmits(['update:info'])

// 更新数据
const updata = () => {
	// props数据是只读的不能修改
	// 所以这需要创建一个新的对象来存储修改后的数据
	const newInfo = {
		...props.info,
	}
	newInfo.age = 20;
	newInfo.email = 'lisi@example.com';
	// 触发父组件更新事件
	emit('update:info', newInfo);
}

子组件3

  • 自定义字段和回调方法传递对象数据
vue
<template>
	<div class="son">
		<h2>我是子组件2</h2>
		<p>{{ info.name }}</p>
		<p>{{ info.age }}</p>
		<p>{{ info.sex }}</p>
		<p>{{ info.email }}</p>

		<button @click="updata()">修改数据</button>
	</div>
</template>
ts
<script lang="ts" setup>
// 需要使用defineProps方法去接受父子卷传递过来的数据
// defineProps 是vue3提供的方法,不需要引入可以直接使用
let props = defineProps({
	info: {
		type: Object,
		default: () => ({
			name: '张三',
			age: 18,
			sex: '男',
			email: 'zhangsan@example.com',
		}),
	}
})

// JavaScript中,对象是引用类型
// 当父组件将一个响应式对象通过props传递给子组件时,
// 子组件接收的不是对象的副本,而是对象的引用地址(即指向同一个 Proxy 代理对象)。
// 这就是说:父组件和子组件访问的是同一个响应式对象(共享同一份内存地址)。
// 所以这里不需要进行额外处理。能够动态拿到父组件传递过来的对象数据。

// 定义 emit 事件(触发父组件更新)
const emit = defineEmits(['handleUpdateInfo'])

// 更新数据
const updata = () => {
	// props数据是只读的不能修改
	// 所以这需要创建一个新的对象来存储修改后的数据
	const newInfo = {
		...props.info,
	}
	newInfo.age = 20;
	newInfo.email = 'lisi@example.com';
	// 触发父组件更新事件
	emit('handleUpdateInfo', newInfo);
}

注意事项

  • v-model 在组件上的本质是语法糖:v-model="x" 等价于 :modelValue="x" @update:modelValue="x = $event"

  • 因此,在子组件中使用 v-model 时,需要确保组件有 modelValue 属性和 update:modelValue 事件。

  • 同理:v-model:message="x" 等价于 :message="x" @update:message="x = $event"

  • 在子组件中使用 v-model:message="x" 时,需要确保组件有 message 属性和 update:message 事件。