往组件传递数据
在 Vue 3 中,使用 defineProps
来定义组件的 props
时,有两种主要方式:
-
使用 defineProps({ item: Object })
:
这种方式直接定义 props
为一个对象,其中 item
是 Object
类型。这种方法比较灵活,适用于不需要精确定义 props
类型的情况。
-
使用 defineProps(['itemData'])
:
这种方式仅指定 props
的名称,而不指定其类型或结构。这种方法适用于简单的情况,尤其是当你不关心 props
的类型时。
区别与选择
-
类型定义
defineProps({ item: Object })
:明确指定 item
为 Object
类型,适用于需要定义类型的情况,但类型定义较为宽泛。
defineProps(['itemData'])
:只定义 props
名称,Vue 自动推断类型,类型推断更为宽泛,不适合严格的类型检查。
-
TypeScript 支持
defineProps({ item: Object })
:在 TypeScript 项目中,可以使用类型来增强类型检查和代码提示。
defineProps(['itemData'])
:无法提供类型检查和代码提示,对于复杂项目或需要类型安全的项目不太适用。
选择
- 如果你需要在 TypeScript 项目中保持类型安全和代码提示,使用
defineProps
并明确指定类型是更好的选择。
- 如果你不需要类型检查,或者项目较小、简单,使用
defineProps(['itemData'])
也是一种简洁的选择。
示例代码
使用 defineProps({ item: Object })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<template>
<div>
<!-- 使用传递过来的 item 值 -->
<span>{{ item.name }}</span>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
// 定义 props 接收 item
const props = defineProps({
item: Object
});
</script>
|
使用 defineProps(['itemData'])
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<template>
<div>
<!-- 使用传递过来的 itemData 值 -->
<span>{{ itemData.name }}</span>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
// 定义 props 接收 itemData
const props = defineProps(['itemData']);
</script>
|
在这个例子中,itemData
是从父组件传递的 prop
。这两种方法的选择取决于你对类型安全和代码提示的需求。
defineOptions({ inheritAttrs: false })
defineOptions
是 Vue 3.3 引入的一个功能,用于在 <script setup>
中定义组件选项。inheritAttrs
属性决定了是否将未被组件 props
接受的属性自动应用到组件的根元素上。
当你设置 inheritAttrs: false
时,未被接受的属性不会自动应用到组件的根元素上,你需要手动处理这些属性。这样可以更精细地控制组件的行为和样式。
自动继承: 默认情况下,未声明的属性会自动添加到组件的根元素上。
控制传递: 可以使用 inheritAttrs: false 来禁用这种默认行为,并通过 $attrs 手动控制这些属性的传递。
在你的上下文中,defineOptions({ inheritAttrs: false })
的使用可以防止未定义的属性传递到 <router-link>
和 <el-menu-item>
,从而避免潜在的样式或行为问题。
改进的完整示例
结合 defineOptions
和前面解决下划线问题的建议,这里是一个改进的完整示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
<template>
<!-- 有子节点:展示item标题,并递归item.children -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="String(item.id)">
<template #title>
<Icon :icon="item.meta.icon" />
<span>{{ item.meta.title }}</span>
</template>
<my-menu-item v-for="citem in item.children" :item="citem" :key="citem.id"></my-menu-item>
</el-sub-menu>
<!-- 没有子节点,展示item本身 -->
<router-link :to="to" class="no-underline">
<el-menu-item v-if="!item.children || item.children.length === 0" :index="String(item.id)">
<template #title>
<Icon :icon="item.meta.icon" />
<span>{{ item.meta.title }}</span>
</template>
</el-menu-item>
</router-link>
</template>
<script setup lang="ts">
import { MenuItem } from "@/types/component/menuItem";
import { Icon } from '@iconify/vue';
// 使用 defineOptions 关闭 inheritAttrs
defineOptions({
inheritAttrs: false,
});
// 定义 props
const props = defineProps<{
item: MenuItem;
}>();
const to = computed(() => {
return props.item.path ? { path: props.item.path } : null;
});
</script>
<style>
.no-underline {
text-decoration: none;
}
</style>
|
解释
defineOptions({ inheritAttrs: false })
: 关闭 inheritAttrs
,确保未定义的属性不会自动应用到组件的根元素上。这样可以更精细地控制属性的传递。
to
计算属性: 确保 to
始终有一个有效值或为 null
。
- 样式处理: 添加
no-underline
类以移除下划线。
这种方法确保了你的菜单项在没有子节点时不会显示下划线,同时也使得属性的传递和处理更加明确。
$props和$attr
在 Vue 中,$props
和 $attrs
是两个特殊的对象,用于处理组件的属性传递和未被接收的属性。
$props
$props
是一个对象,包含当前组件的所有 props。当你使用 v-bind="$props"
时,实际上是将当前组件接收到的所有 props 传递给子组件。这在需要将所有接收到的 props 原样传递给另一个组件时非常有用。
$attrs
$attrs
是一个对象,包含父组件传递但未被当前组件显式接收(通过 props
定义)的所有 attribute。这些 attributes 会被自动添加到当前组件的根元素上,除非使用 inheritAttrs: false
禁用这种行为。通过 v-bind="$attrs"
可以将这些属性传递给子组件或元素。
结合使用 $props 和 $attrs
在你的示例中,使用 v-bind="$props"
和 v-bind="$attrs"
可以确保所有传递给当前组件的属性和 props 都能被正确地传递到嵌套的子组件中。这在封装组件时特别有用,可以避免重复定义和手动传递属性。
示例解释
假设我们有一个 MyMenuItem
组件,并希望将所有传递给它的 props 和未被显式接收的 attributes 传递给内部的 <router-link>
和 <a>
元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
<template>
<!-- 处理有子节点的情况 -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="String(item.id)">
<template #title>
<Icon :icon="item.meta.icon" />
<span>{{ item.meta.title }}</span>
</template>
<my-menu-item v-for="citem in item.children" :item="citem" :key="citem.id"></my-menu-item>
</el-sub-menu>
<!-- 处理没有子节点的情况 -->
<router-link
v-else
v-bind="$props"
custom
v-slot="{ isActive, href, navigate }"
>
<a
v-bind="$attrs"
:href="href"
@click="navigate"
:class="isActive ? activeClass : inactiveClass"
>
<el-menu-item :index="String(item.id)">
<template #title>
<Icon :icon="item.meta.icon" />
<span>{{ item.meta.title }}</span>
</template>
</el-menu-item>
</a>
</router-link>
</template>
<script setup lang="ts">
import { MenuItem } from "@/types/component/menuItem";
import { Icon } from '@iconify/vue';
// 使用 defineOptions 关闭 inheritAttrs
defineOptions({
inheritAttrs: false,
});
// 定义 props
const props = defineProps<{
item: MenuItem;
}>();
const to = computed(() => {
return props.item.path ? { path: props.item.path } : null;
});
// 定义类名
const activeClass = 'menu-item-active';
const inactiveClass = 'menu-item-inactive';
</script>
<style>
.no-underline {
text-decoration: none;
}
.menu-item-active {
/* 添加激活状态的样式 */
}
.menu-item-inactive {
/* 添加非激活状态的样式 */
}
</style>
|
解释
v-bind="$props"
: 将 MyMenuItem
组件接收到的所有 props 传递给 <router-link>
。
v-bind="$attrs"
: 将未被 MyMenuItem
组件显式接收的 attributes 传递给 <a>
标签。
inheritAttrs: false
: 禁用将未被显式接收的 attributes 自动添加到组件根元素的默认行为,确保手动控制这些 attributes 的传递。
这种方法确保了属性的传递和行为的一致性,同时简化了代码的维护和可读性。
props和attrs的区别
props
和 attrs
在 Vue 中都有属性传递的功能,但它们有不同的用途和行为。
props
- 定义:
props
是组件显式声明的属性,用于从父组件传递数据到子组件。
- 作用: 组件通过
props
接收父组件传递的数据,并且可以在组件内使用这些数据。
- 类型检查:
props
可以进行类型检查和默认值设置。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- 父组件 -->
<child-component :message="parentMessage"></child-component>
<!-- 子组件 -->
<template>
<div>{{ message }}</div>
</template>
<script setup>
const props = defineProps({
message: String
});
</script>
|
在这个示例中,ChildComponent
声明了一个 message
prop,从父组件接收 parentMessage
的值。
attrs
- 定义:
attrs
是父组件传递但未在子组件中显式声明为 props
的属性。
- 作用:
attrs
通常用于将任意的、未声明的属性传递给组件的根元素或子元素。
- 自动继承: 默认情况下,未声明的属性会自动添加到组件的根元素上。
- 控制传递: 可以使用
inheritAttrs: false
来禁用这种默认行为,并通过 $attrs
手动控制这些属性的传递。
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- 父组件 -->
<child-component custom-attr="someValue"></child-component>
<!-- 子组件 -->
<template>
<div v-bind="$attrs"></div>
</template>
<script setup>
defineOptions({
inheritAttrs: false
});
</script>
|
在这个示例中,ChildComponent
没有声明 custom-attr
作为 props
,因此它会被自动添加到组件的根元素上(除非使用 inheritAttrs: false
)。
区别总结
-
声明方式:
props
是显式声明的属性,用于传递特定的数据。
attrs
是未显式声明的属性,用于传递任意的、额外的属性。
-
类型检查和默认值:
props
可以进行类型检查和默认值设置。
attrs
不支持类型检查和默认值设置。
-
传递方式:
props
需要在子组件中显式声明。
attrs
是自动传递的,但可以使用 inheritAttrs: false
来手动控制。
使用示例
以下是一个更详细的示例,展示如何使用 props
和 attrs
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<!-- 父组件 -->
<template>
<child-component :message="parentMessage" custom-attr="someValue"></child-component>
</template>
<script setup>
const parentMessage = 'Hello from parent';
</script>
<!-- 子组件 -->
<template>
<div>
<!-- 使用 props -->
<p>{{ message }}</p>
<!-- 使用 attrs -->
<div v-bind="$attrs"></div>
</div>
</template>
<script setup>
defineOptions({
inheritAttrs: false
});
const props = defineProps({
message: String
});
</script>
|
在这个示例中,ChildComponent
显式声明了一个 message
prop 来接收父组件的数据,并通过 v-bind="$attrs"
将未声明的 custom-attr
属性传递给内部的 div
元素。