Vue 3 <script setup>
中 Props 与 Emit 的处理
大家好,今天我们深入探讨 Vue 3 中 <script setup>
语法糖下 props
和 emit
的处理方式。 <script setup>
极大地简化了组件的编写,但同时也引入了一些新的概念和处理方法。我们将详细讲解如何在 <script setup>
中定义和使用 props
,以及如何触发自定义事件 (emit
),并探讨一些常见的使用场景和注意事项。
1. props
的定义与使用
在 <script setup>
中,props
的定义主要有两种方式:使用 defineProps
宏和使用 withDefaults
宏。
1.1 defineProps
宏
defineProps
是一个编译器宏,它会自动处理 props
的类型推断和运行时验证。 它只能在 <script setup>
中使用,无需显式导入。
1.1.1 基于类型的声明
这是最简洁的方式,直接使用 TypeScript 的类型注解来定义 props
的类型。
<template>
<div>
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
<p>Message: {{ message }}</p>
<p v-if="isAdmin">Admin User</p>
</div>
</template>
<script setup lang="ts">
// 基于类型的 props 定义
const props = defineProps<{
name: string;
age: number;
message?: string; // 可选的 prop
isAdmin?: boolean; // 可选的 prop
}>();
// 在模板中直接使用 props
console.log("Name:", props.name);
console.log("Age:", props.age);
console.log("Message:", props.message);
console.log("Is Admin:", props.isAdmin);
</script>
在这个例子中,我们定义了 name
(字符串类型),age
(数字类型),message
(可选字符串类型) 和 isAdmin
(可选布尔类型) 四个 props
。 message?
和 isAdmin?
后面的 ?
表示这些 prop
是可选的。
1.1.2 基于对象的声明
这种方式更接近 Vue 2 的 props
定义方式,可以提供更详细的配置,例如类型、是否必须、验证器等。
<template>
<div>
<p>Title: {{ title }}</p>
<p>Count: {{ count }}</p>
</div>
</template>
<script setup lang="ts">
// 基于对象的 props 定义
const props = defineProps({
title: {
type: String,
required: true,
},
count: {
type: Number,
default: 0,
validator: (value: number) => {
return value >= 0; // 验证 count 是否大于等于 0
},
},
});
console.log("Title:", props.title);
console.log("Count:", props.count);
</script>
在这个例子中,title
被定义为字符串类型,并且是必须的 (required: true
)。 count
被定义为数字类型,默认值为 0 (default: 0
),并且有一个验证器函数,确保 count
的值大于等于 0。
1.2 withDefaults
宏
withDefaults
宏用于为 defineProps
定义的 props
提供默认值。 它接受两个参数:defineProps
的返回值和一个包含默认值的对象。
<template>
<div>
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
<p>Message: {{ message }}</p>
</div>
</template>
<script setup lang="ts">
// 定义 props
const props = withDefaults(
defineProps<{
name: string;
age?: number;
message?: string;
}>(),
{
age: 18, // age 的默认值
message: "Hello!", // message 的默认值
}
);
console.log("Name:", props.name);
console.log("Age:", props.age);
console.log("Message:", props.message);
</script>
在这个例子中,如果父组件没有传递 age
和 message
props
,它们将分别使用默认值 18 和 "Hello!"。 name
没有默认值,因为它是必须的。
1.3 props
的使用场景
- 组件配置:
props
用于配置组件的行为和外观,例如设置组件的尺寸、颜色、文本内容等。 - 数据传递:
props
用于将数据从父组件传递到子组件,例如传递用户信息、列表数据等。 - 状态同步: 结合
emit
,props
可以实现父子组件之间的状态同步,例如子组件修改数据后通知父组件更新。
2. emit
的定义与使用
在 <script setup>
中,emit
的定义和使用也变得更加简洁。 使用 defineEmits
宏来声明组件可以触发的事件,然后使用返回的 emit
函数来触发这些事件。
2.1 defineEmits
宏
defineEmits
也是一个编译器宏,用于声明组件可以触发的自定义事件。 它可以接受一个数组或一个对象作为参数。
2.1.1 基于数组的声明
如果只需要声明事件的名称,可以使用数组。
<template>
<button @click="handleClick">Click me</button>
</template>
<script setup lang="ts">
// 定义 emit
const emit = defineEmits(["update", "delete", "custom-event"]);
const handleClick = () => {
// 触发 update 事件
emit("update", { id: 1, name: "Updated Name" });
// 触发 delete 事件
emit("delete", 123);
// 触发自定义事件
emit("custom-event", "Some data");
};
</script>
在这个例子中,我们声明了 update
、delete
和 custom-event
三个事件。 handleClick
函数中分别触发了这三个事件,并传递了不同的参数。
2.1.2 基于对象的声明
如果需要对事件的参数进行验证,可以使用对象。
<template>
<button @click="handleClick">Click me</button>
</template>
<script setup lang="ts">
// 定义 emit
const emit = defineEmits({
update: (id: number, data: { name: string }) => {
// 验证 id 是否是数字
if (typeof id !== 'number') {
console.warn('Invalid id type.');
return false;
}
// 验证 data 是否有 name 属性
if (!data || typeof data.name !== 'string') {
console.warn('Invalid data format.');
return false;
}
return true;
},
delete: (id: number) => {
// 验证 id 是否大于 0
if (id <= 0) {
console.warn('Invalid id value.');
return false;
}
return true;
},
});
const handleClick = () => {
// 触发 update 事件
emit("update", 1, { name: "Updated Name" });
// 触发 delete 事件
emit("delete", 123);
};
</script>
在这个例子中,我们使用对象来定义 emit
。 update
事件的验证函数检查 id
是否是数字,以及 data
是否有 name
属性。 delete
事件的验证函数检查 id
是否大于 0。 如果验证失败,会输出警告信息,并且事件不会被触发。
2.2 emit
的使用场景
- 子组件通知父组件:
emit
最常见的用途是子组件通知父组件发生了某些事情,例如用户点击了按钮、表单提交成功等。 - 父子组件通信: 结合
props
,emit
可以实现父子组件之间的双向通信,例如父组件传递初始值给子组件,子组件修改值后通知父组件更新。 - 跨组件通信: 通过 Vue 的事件总线或者 Vuex 等状态管理工具,
emit
可以实现跨组件通信。
3. props
和 emit
的最佳实践
- 清晰的命名:
props
和emit
的命名应该清晰、简洁,能够准确表达其含义。 - 明确的类型: 尽可能使用 TypeScript 来定义
props
的类型,提高代码的可读性和可维护性。 - 适当的验证: 对于重要的
props
,应该进行验证,确保其值的有效性。 - 避免过度通信: 避免在父子组件之间进行过度通信,只传递必要的数据和事件。
- 使用常量: 对于常用的事件名称,可以定义为常量,避免拼写错误。
4. 案例分析
我们来分析一个实际的案例,一个简单的计数器组件。
<template>
<div>
<button @click="decrement">-</button>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 定义 props
const props = defineProps({
initialCount: {
type: Number,
default: 0,
},
});
// 定义 emit
const emit = defineEmits(['update-count']);
// 使用 ref 创建响应式状态
const count = ref(props.initialCount);
// 递增函数
const increment = () => {
count.value++;
emit('update-count', count.value);
};
// 递减函数
const decrement = () => {
count.value--;
emit('update-count', count.value);
};
</script>
在这个例子中,initialCount
是一个 prop
,用于设置计数器的初始值。 update-count
是一个事件,用于通知父组件计数器的值发生了变化。 count
是一个响应式状态,用于存储计数器的当前值。 increment
和 decrement
函数分别用于递增和递减计数器的值,并触发 update-count
事件。
父组件可以使用这个计数器组件,并监听 update-count
事件来更新自己的状态。
<template>
<div>
<Counter :initialCount="parentCount" @update-count="handleUpdateCount" />
<p>Parent Count: {{ parentCount }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Counter from './Counter.vue';
const parentCount = ref(10);
const handleUpdateCount = (newCount: number) => {
parentCount.value = newCount;
};
</script>
在这个例子中,父组件将 parentCount
作为 initialCount
prop
传递给 Counter
组件,并监听 update-count
事件,当 Counter
组件的值发生变化时,handleUpdateCount
函数会被调用,更新 parentCount
的值。
5. 常见问题与注意事项
defineProps
和defineEmits
只能在<script setup>
中使用。props
是只读的,不能在组件内部直接修改。 如果需要修改props
的值,应该通过emit
触发事件,通知父组件更新。- 避免在
props
中使用对象或数组作为默认值。 因为所有组件实例会共享同一个对象或数组,修改一个组件实例的默认值会影响其他组件实例。 应该使用函数来返回新的对象或数组。
例如,正确的写法:
<script setup lang="ts">
const props = defineProps({
items: {
type: Array,
default: () => [], // 使用函数返回新的数组
},
config: {
type: Object,
default: () => ({}), // 使用函数返回新的对象
},
});
</script>
错误的写法:
<script setup lang="ts">
const props = defineProps({
items: {
type: Array,
default: [], // 错误:所有组件实例共享同一个数组
},
config: {
type: Object,
default: {}, // 错误:所有组件实例共享同一个对象
},
});
</script>
- 使用
validator
函数进行props
验证时,应该返回一个布尔值。 如果验证失败,应该输出警告信息,方便调试。 - 在触发
emit
事件时,应该传递必要的数据,避免传递过多无用的数据。
6. 表格总结
特性 | 描述 | 使用方式 |
---|---|---|
defineProps |
定义组件的 props ,支持类型推断和运行时验证。 |
defineProps<{ name: string; age?: number }>() 或 defineProps({ name: { type: String, required: true } }) |
withDefaults |
为 defineProps 定义的 props 提供默认值。 |
withDefaults(defineProps<{ age?: number }>(), { age: 18 }) |
defineEmits |
声明组件可以触发的自定义事件,支持参数验证。 | defineEmits(['update', 'delete']) 或 defineEmits({ update: (id: number) => boolean }) |
emit 函数 |
触发自定义事件。 | emit('update', 123) |
7. 总结
我们深入探讨了 Vue 3 <script setup>
中 props
和 emit
的处理方式,包括 defineProps
和 withDefaults
宏的用法,以及 defineEmits
宏的用法。通过这些工具,我们可以更简洁、更高效地编写 Vue 组件,并实现父子组件之间的通信。希望今天的讲解能够帮助大家更好地理解和使用 Vue 3 的 <script setup>
语法糖。
关键点回顾:
defineProps
定义props
,withDefaults
提供默认值。defineEmits
声明自定义事件,emit
函数触发事件。- TypeScript 类型注解和验证器保证数据有效性。