Vue 3 <script setup>
中 props
默认值的处理
大家好,今天我们来深入探讨 Vue 3 中使用 <script setup>
语法糖时,如何有效地处理 props
的默认值。这是在构建可复用组件时一个至关重要的方面,它能提高组件的健壮性和易用性。
为什么需要 props
默认值?
在组件开发中,props
用于父组件向子组件传递数据。然而,并非所有 props
都总是由父组件提供。有时,我们希望在父组件未提供某个 prop
时,子组件能够使用一个预定义的默认值。这有几个重要的好处:
- 提高组件的鲁棒性: 避免因缺少
prop
而导致的错误。 - 简化父组件的使用: 父组件可以选择性地传递
prop
,简化代码。 - 增强组件的可配置性: 允许组件在没有明确配置的情况下也能正常工作。
在 <script setup>
中定义 props
首先,让我们回顾一下在 <script setup>
中如何定义 props
。Vue 3 提供了 defineProps
宏来实现这个功能。defineProps
接受两种主要形式的参数:
- 类型声明: 使用 TypeScript 类型声明来定义
props
的类型和可选性。 - 选项对象: 使用一个包含各种选项的对象来更详细地配置
props
。
接下来,我们将分别介绍如何在两种方式下处理 props
的默认值。
使用类型声明定义 props
的默认值
当使用 TypeScript 类型声明时,我们可以通过将 prop
定义为可选类型,并结合默认值表达式来实现。
1. 使用 ?
标记可选 prop
:
<script setup lang="ts">
import { defineProps } from 'vue';
interface Props {
message?: string; // message 是可选的
}
const props = defineProps<Props>();
</script>
<template>
<div>{{ props.message || '默认消息' }}</div>
</template>
在这个例子中,message
被定义为 string?
,这意味着它是一个可选的字符串。在模板中,我们使用 props.message || '默认消息'
来判断 message
是否有值。如果 message
未定义(undefined),则使用 ‘默认消息’ 作为替代。
2. 使用默认值表达式:
<script setup lang="ts">
import { defineProps } from 'vue';
interface Props {
count?: number;
items?: string[];
}
const props = defineProps<Props>();
const defaultCount = 0;
const defaultItems: string[] = [];
</script>
<template>
<div>
<p>Count: {{ props.count ?? defaultCount }}</p>
<ul>
<li v-for="item in props.items ?? defaultItems" :key="item">{{ item }}</li>
</ul>
</div>
</template>
在这个例子中,我们定义了两个可选的 props
:count
(类型为 number
) 和 items
(类型为 string[]
)。我们使用 ??
(nullish coalescing operator)来提供默认值。如果 props.count
是 null
或 undefined
,则使用 defaultCount
(0) 作为默认值。对于 props.items
也类似,如果它是 null
或 undefined
,则使用 defaultItems
(空数组) 作为默认值。这种方式在 prop
的默认值比较复杂,需要一些计算才能得到的情况下非常有用。
3. 使用 withDefaults
(推荐):
Vue 3 提供了一个专门用于处理 props 默认值的辅助函数 withDefaults
。这个函数可以增强类型推断,并使代码更清晰。
<script setup lang="ts">
import { defineProps, withDefaults } from 'vue';
interface Props {
message: string;
count?: number;
items?: string[];
}
const props = withDefaults(defineProps<Props>(), {
message: '默认消息',
count: 0,
items: () => [] // 对于数组或对象类型的默认值,应该使用工厂函数
});
</script>
<template>
<div>
<p>Message: {{ props.message }}</p>
<p>Count: {{ props.count }}</p>
<ul>
<li v-for="item in props.items" :key="item">{{ item }}</li>
</ul>
</div>
</template>
withDefaults
接受两个参数:
defineProps<Props>()
:定义props
的类型。- 一个对象:包含
props
的默认值。
需要注意的是,对于数组或对象类型的默认值,必须使用工厂函数。这是因为如果直接使用字面量,所有组件实例将会共享同一个对象,这可能会导致意外的副作用。使用工厂函数可以确保每个组件实例都拥有独立的默认值对象。
withDefaults
相比于 ||
和 ??
的优势在于:
- 类型安全: TypeScript 能够更好地推断
props
的类型,减少类型错误。 - 代码清晰: 默认值集中在一个地方,更容易阅读和维护。
- 性能优化: 避免在模板中进行额外的判断,提高渲染性能。
使用选项对象定义 props
的默认值
当使用选项对象来定义 props
时,我们可以利用 default
选项来设置默认值。
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
message: {
type: String,
default: '默认消息'
},
count: {
type: Number,
default: 0
},
items: {
type: Array,
default: () => [] // 对于数组或对象类型的默认值,应该使用工厂函数
},
author: {
type: Object,
default: () => ({
name: '默认作者',
age: 18
})
},
level: {
type: Number,
validator: (value: number) => {
return value >= 1 && value <= 5;
},
default: 3
}
});
</script>
<template>
<div>
<p>Message: {{ props.message }}</p>
<p>Count: {{ props.count }}</p>
<ul>
<li v-for="item in props.items" :key="item">{{ item }}</li>
</ul>
<p>Author: {{ props.author.name }} (Age: {{ props.author.age }})</p>
<p>Level: {{ props.level }}</p>
</div>
</template>
在这个例子中,我们使用选项对象来定义 props
。每个 prop
都有一个对应的对象,其中包含 type
和 default
选项。
type
:指定prop
的类型。default
:指定prop
的默认值。
同样需要注意的是,对于数组或对象类型的默认值,必须使用工厂函数。
选项对象还允许我们使用 validator
选项来对 prop
的值进行校验。在上面的例子中,level
prop
使用 validator
来确保其值在 1 到 5 之间。如果父组件传递的值不满足校验条件,Vue 将会发出警告。
类型声明 vs. 选项对象
那么,在 <script setup>
中,我们应该选择哪种方式来定义 props
呢?
特性 | 类型声明 (withDefaults) | 选项对象 |
---|---|---|
类型安全 | 强 | 较弱 (需要手动指定类型) |
代码简洁 | 更简洁 | 相对冗长 |
可读性 | 高 | 稍低 |
默认值处理 | withDefaults |
default 选项 |
验证 | 需要额外的工具 | 内置 validator 选项 |
总的来说,推荐使用类型声明结合 withDefaults
。它提供了更好的类型安全性和代码简洁性。但是,如果需要对 prop
进行复杂的校验,或者需要支持一些高级特性,选项对象可能更适合。
默认值与 required
选项
当使用选项对象定义 props
时,我们可以使用 required
选项来指定 prop
是否为必需的。
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
message: {
type: String,
required: true // message 是必需的
},
count: {
type: Number,
default: 0
}
});
</script>
在这个例子中,message
被标记为 required: true
,这意味着父组件必须提供 message
prop
。如果父组件未提供 message
,Vue 将会发出警告。
如果一个 prop
被标记为 required: true
,那么它就不应该再有 default
选项。因为如果 prop
是必需的,那么提供默认值就没有意义了。
避免常见的错误
在使用 <script setup>
处理 props
默认值时,需要注意以下几点:
- 不要在
setup
函数内部修改props
:props
应该是只读的。如果需要修改props
的值,应该使用computed
属性或者将数据复制到data
中。 - 对于数组或对象类型的默认值,必须使用工厂函数: 否则,所有组件实例将会共享同一个对象,这可能会导致意外的副作用。
- 避免过度使用默认值: 虽然默认值可以提高组件的鲁棒性,但是过度使用可能会导致组件的行为难以预测。应该根据实际情况谨慎使用默认值。
- 注意类型安全: 使用 TypeScript 可以帮助我们避免类型错误。应该尽可能使用 TypeScript 来定义
props
的类型。 - 理解
withDefaults
的工作原理:withDefaults
并不是简单地将默认值赋值给props
。它会创建一个新的props
对象,并将默认值合并到其中。这意味着,即使父组件传递了null
或undefined
,withDefaults
仍然会使用默认值。
示例:一个带有默认值的可复用按钮组件
下面是一个带有默认值的可复用按钮组件的示例:
<template>
<button
:class="[
'my-button',
type ? `my-button--${type}` : '',
{ 'my-button--disabled': disabled }
]"
:disabled="disabled"
@click="$emit('click')"
>
<slot />
</button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, withDefaults } from 'vue';
interface Props {
type?: 'primary' | 'secondary' | 'tertiary';
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
type: 'primary',
disabled: false
});
defineEmits(['click']);
</script>
<style scoped>
.my-button {
padding: 10px 20px;
border-radius: 5px;
border: none;
cursor: pointer;
font-size: 16px;
}
.my-button--primary {
background-color: #007bff;
color: white;
}
.my-button--secondary {
background-color: #6c757d;
color: white;
}
.my-button--tertiary {
background-color: #f8f9fa;
color: #212529;
border: 1px solid #ced4da;
}
.my-button--disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
在这个组件中,我们定义了两个可选的 props
:type
和 disabled
。type
prop
可以是 ‘primary’、’secondary’ 或 ‘tertiary’ 中的一个,默认值为 ‘primary’。disabled
prop
是一个布尔值,表示按钮是否禁用,默认值为 false
。
父组件可以使用这个组件,并选择性地传递 type
和 disabled
props
。如果没有传递这些 props
,组件将会使用默认值。
使用 provide/inject
与默认值相结合
在一些复杂的情况下,你可能需要使用 provide/inject
来传递 props
。 结合默认值,可以提供更灵活的配置方式。
// 父组件
<script setup lang="ts">
import { provide } from 'vue';
const config = {
theme: 'dark',
size: 'large'
};
provide('app-config', config);
</script>
<template>
<ChildComponent />
</template>
// 子组件
<script setup lang="ts">
import { inject } from 'vue';
interface Config {
theme?: 'light' | 'dark';
size?: 'small' | 'medium' | 'large';
}
const defaultConfig = {
theme: 'light',
size: 'medium'
};
const config = inject<Config>('app-config', defaultConfig);
console.log(config.theme); // 如果父组件没有提供 theme,则输出 'light'
</script>
在这个例子中,父组件使用 provide
提供了 app-config
。 子组件使用 inject
注入 app-config
,并指定了 defaultConfig
作为默认值。如果父组件没有提供 app-config
,或者只提供了部分配置,子组件将会使用默认值。
小结
今天我们深入探讨了 Vue 3 <script setup>
中 props
默认值的处理。
- 推荐使用类型声明结合
withDefaults
来定义props
的默认值,以获得更好的类型安全性和代码简洁性。 - 对于数组或对象类型的默认值,必须使用工厂函数。
- 了解
required
选项和validator
选项的用法。 - 避免常见的错误,例如在
setup
函数内部修改props
。 - 结合
provide/inject
使用默认值,可以提供更灵活的配置方式。
掌握这些技巧,可以帮助我们构建更健壮、更易用的 Vue 组件。
总结
以上内容涵盖了使用 <script setup>
语法定义 props
时如何处理默认值的几种方法。通过类型声明和 withDefaults
或者使用选项对象,你可以根据实际需求选择最合适的方式来为你的组件提供合理的默认行为。 记住,对于数组和对象类型的默认值,工厂函数是必须的,以避免组件实例间共享数据。