Vue3使用

往组件传递数据

在 Vue 3 中,使用 defineProps 来定义组件的 props 时,有两种主要方式:

  1. 使用 defineProps({ item: Object }) 这种方式直接定义 props 为一个对象,其中 itemObject 类型。这种方法比较灵活,适用于不需要精确定义 props 类型的情况。

  2. 使用 defineProps(['itemData']) 这种方式仅指定 props 的名称,而不指定其类型或结构。这种方法适用于简单的情况,尤其是当你不关心 props 的类型时。

区别与选择

  1. 类型定义

    • defineProps({ item: Object }):明确指定 itemObject 类型,适用于需要定义类型的情况,但类型定义较为宽泛。
    • defineProps(['itemData']):只定义 props 名称,Vue 自动推断类型,类型推断更为宽泛,不适合严格的类型检查。
  2. 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>

解释

  1. defineOptions({ inheritAttrs: false }): 关闭 inheritAttrs,确保未定义的属性不会自动应用到组件的根元素上。这样可以更精细地控制属性的传递。
  2. to 计算属性: 确保 to 始终有一个有效值或为 null
  3. 样式处理: 添加 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>

解释

  1. v-bind="$props": 将 MyMenuItem 组件接收到的所有 props 传递给 <router-link>
  2. v-bind="$attrs": 将未被 MyMenuItem 组件显式接收的 attributes 传递给 <a> 标签。
  3. inheritAttrs: false: 禁用将未被显式接收的 attributes 自动添加到组件根元素的默认行为,确保手动控制这些 attributes 的传递。

这种方法确保了属性的传递和行为的一致性,同时简化了代码的维护和可读性。

props和attrs的区别

propsattrs 在 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)。

区别总结

  1. 声明方式:

    • props 是显式声明的属性,用于传递特定的数据。
    • attrs 是未显式声明的属性,用于传递任意的、额外的属性。
  2. 类型检查和默认值:

    • props 可以进行类型检查和默认值设置。
    • attrs 不支持类型检查和默认值设置。
  3. 传递方式:

    • props 需要在子组件中显式声明。
    • attrs 是自动传递的,但可以使用 inheritAttrs: false 来手动控制。

使用示例

以下是一个更详细的示例,展示如何使用 propsattrs:

 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 元素。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计