vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue3中defineProps的使用

vue3中defineProps的使用详解

作者:BillKu

本文介绍了Vue组件中props的三种声明方式:数组写法、对象写法和TypeScript类型声明写法,数组写法适用于快速原型开发,但缺乏类型校验;对象写法提供了基本类型校验和默认值设置;TypeScript类型声明写法结合了Vue的运行时校验和TypeScript的静态类型检查

1、数组写法

const props = defineProps(["applyBasicInfo", "operateCommandType"]);

 这是一个非常基础的 Vue props 声明方式,适用于简单场景。以下是详细解释:

代码含义

const props = defineProps(["applyBasicInfo", "operateCommandType"]);
defineProps({
  applyBasicInfo: null,       // 无类型检查
  operateCommandType: null     // 无类型检查
})

关键特性分析

1.极简声明

2.适用场景

3.实际效果

<!-- 父组件 -->
<ChildComponent 
  :applyBasicInfo="任何类型的数据" <!-- 可以是对象、字符串、数字等 -->
  operateCommandType="任何内容"    <!-- 可以是任意类型 -->
/>

<!-- 子组件 -->
<script setup>
// 使用时需要自行确保类型正确
console.log(props.applyBasicInfo) // 可能是任意类型
</script>

与其他声明方式的对比

特性数组写法对象写法(完整)TypeScript 类型写法
类型校验❌ 无✅ Vue 运行时校验✅ 编译时校验
默认值❌ 不能设置✅ 通过 default 设置✅ 通过 withDefaults 设置
必需性控制❌ 默认都是可选✅ 通过 required 控制✅ 通过 ? 符号控制
类型提示❌ 无🟡 基础类型提示✅ 完整类型提示
代码安全性🔴 低(可能运行时出错)🟡 中(有基础校验)🟢 高(编译时拦截错误)

潜在风险示例

<!-- 父组件传递错误类型 -->
<ChildComponent 
  :applyBasicInfo="123"        <!-- 传递数字 -->
  operateCommandType           <!-- 未传递 -->
/>

<!-- 子组件使用时 -->
<script setup>
// 可能引发运行时错误
props.applyBasicInfo.name      // 报错:数字没有 name 属性
props.operateCommandType       // 拼写错误也不会警告(正确是 operateCommandType)
</script>

为什么要用这种写法?

如何改进?

基础改进(添加类型校验)

const props = defineProps({
  applyBasicInfo: {
    type: Object,
    required: true
  },
  operateCommandType: {
    type: String,
    default: "view"
  }
});

高级改进(TypeScript)

interface ApplyBasicInfo {
  id: number;
  name: string;
}

const props = defineProps<{
  applyBasicInfo: ApplyBasicInfo;
  operateCommandType?: string;
}>();

最佳实践建议

仅用于临时代码:正式项目建议使用完整声明

配合 JSDoc 使用(如果不用 TypeScript):

/**
 * @typedef {Object} ApplyBasicInfo
 * @property {number} id
 * @property {string} name
 */

const props = defineProps(["applyBasicInfo", "operateCommandType"]);

渐进式增强:后续逐步替换为完整声明

这种写法就像骑自行车不戴头盔——简单快捷,但不够安全。建议新手仅在快速验证想法时使用,正式开发中尽量使用更完善的 props 声明方式。

2、对象写法(纯 JavaScript 项目的写法,运行时 props 声明)

const props = defineProps({
  applyBasicInfo: {
    type: Object, // 要求必须是 Object 类型
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  operateCommandType: {
    type: String, // 要求必须是 String 类型
    default: "info-view" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});

数组简写 vs 对象写法

// 简写方式(不推荐)
defineProps(['applyBasicInfo', 'operateCommandType'])

// vs 

// 当前对象写法(推荐)
defineProps({
  applyBasicInfo: { type: Object, required: true },
  operateCommandType: { type: String, default: "info-view" }
})
特性数组写法对象写法
类型检查❌ 无✅ 基础类型检查
默认值❌ 不可设置✅ 可设置
必填控制❌ 默认全部可选✅ 精确控制
代码安全性🔴 低🟡 中等

这段代码使用 Vue 的 defineProps 方法声明组件需要接收的两个 props(组件参数),并设置了参数的基本验证规则。 

3、TypeScript 的类型声明写法

3.1、使用标准的PropType进行对象类型定义

import type { PropType } from "vue";
import type { ApplyBasicInfo } from "@/interface";

const props = defineProps({
  applyBasicInfo: {
    type: Object as PropType<ApplyBasicInfo>, // 使用标准的PropType进行对象类型定义
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  operateCommandType: {
    type: String,
    default: "info-view" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});

关键概念拆解

1. 类型导入部分

import type { PropType } from "vue";
import type { ApplyBasicInfo } from "@/interface";

2. 核心 props 声明

applyBasicInfo: {
  type: Object as PropType<ApplyBasicInfo>, // 类型断言
  required: true
}

双重效果

3. 对比普通对象声明

// 普通写法(只有 Vue 校验)
applyBasicInfo: {
  type: Object,  // 只知道是个对象,不清楚具体结构
  required: true
}

// 当前写法(Vue校验 + TS类型)
applyBasicInfo: {
  type: Object as PropType<ApplyBasicInfo>, // 明确对象结构
  required: true
}

工作原理图示

graph TD
    A[父组件传递 props] --> B{Vue 运行时校验}
    B -->|通过| C[组件内使用]
    B -->|不通过| D[控制台警告]
    C --> E[TypeScript 类型检查]
    E -->|类型正确| F[正常开发]
    E -->|类型错误| G[IDE 提示错误]

为什么要这样写?

优势对比表

特性普通 Object 类型当前写法
代码提示只知道是对象,无具体属性提示✅ 显示 ApplyBasicInfo 的所有属性
重构安全性修改属性时不会报错✅ 修改接口定义会引发相关代码报错
参数校验只校验是否是对象✅ 运行时对象校验 + 类型结构校验
协作效率需要查文档看数据结构✅ 直接通过提示查看数据结构

示例类型定义

// 假设在 @/interface 中
interface ApplyBasicInfo {
  id: number;          // 申请ID
  applicant: string;   // 申请人姓名
  status: 'pending' | 'approved'; // 申请状态
  createTime: Date;    // 创建时间
}

在组件中的实际使用

智能提示示例

// 正确使用(有提示)
props.applyBasicInfo.id          // 提示 number 类型
props.applyBasicInfo.applicant   // 提示 string 类型

// 错误使用(立即报错)
props.applyBasicInfo.APPlicant   // ❌ 拼写错误提示
props.applyBasicInfo.status = 'rejected' // ❌ 类型不匹配

对应模板使用

<template>
  <!-- 显示申请人信息 -->
  <div>{{ props.applyBasicInfo.applicant }}</div>
  
  <!-- 根据操作类型显示不同内容 -->
  <div v-if="operateCommandType === 'info-view'">
    查看模式
  </div>
</template>

常见错误及解决方案

错误1:未定义接口

// 错误提示:Cannot find name 'ApplyBasicInfo'
// 解决方案:确保类型文件正确定义并导出
// 在 @/interface.ts 中:
export interface ApplyBasicInfo { ... }

错误2:错误类型传递

<!-- 父组件传递错误数据结构 -->
<MyComponent :apply-basic-info="{ id: '100' }" />
<!-- 错误:id 应该是 number -->

错误3:缺少必要属性

// 组件内使用时
const submit = () => {
  console.log(props.applyBasicInfo.createTime) // 如果接口中有该属性
}
// 当父组件传递的对象缺少 createTime 时,TS 会报错

不同技术栈的写法对比

场景JavaScript 写法TypeScript 基础写法当前进阶写法
props 类型type: Objecttype: ObjectObject as PropType<T>
代码提示显示 Object 类型显示具体接口属性
类型安全
维护成本高(需口头沟通数据结构)中(需查看接口定义)低(直接提示数据结构)

最佳实践建议

接口定义规范

interface ApplyBasicInfo {
  /**
   * 申请的唯一标识
   * @example 123456
   */
  id: number;
}

props 校验增强

applyBasicInfo: {
  type: Object as PropType<ApplyBasicInfo>,
  required: true,
  validator: (value: ApplyBasicInfo) => {
    return !!value.id // 添加自定义校验逻辑
  }
}

默认值处理技巧

// 对于复杂对象,建议提供默认值函数
applyBasicInfo: {
  type: Object as PropType<ApplyBasicInfo>,
  default: () => ({ id: 0, applicant: '', status: 'pending' })
}

这种写法是 Vue 3 项目中平衡类型安全和开发效率的典型实践,既能享受 TypeScript 的类型提示优势,又保留了 Vue 原有的 props 校验机制,特别适合中大型项目维护使用。

3.2、混合写法(结合了 Vue 的运行时 props 校验和 TypeScript 类型提示的混合写法)

import type { ApplyBasicInfo } from "@/interface";

const props = defineProps({
  applyBasicInfo: {
    type: Object as () => ApplyBasicInfo, // 使用箭头函数返回类型
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  operateCommandType: {
    type: String,
    default: "info-view" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});

这是一个结合了 Vue 的运行时 props 校验和 TypeScript 类型提示的混合写法代码,特别适用于 Vue 3 + TypeScript 项目。以下是逐层解析:

代码结构解析

import type { ApplyBasicInfo } from "@/interface"; // 导入类型

const props = defineProps({
  applyBasicInfo: {
    type: Object as () => ApplyBasicInfo, // 核心类型声明
    required: true
  },
  operateCommandType: {
    type: String,
    default: "info-view"
  }
});

关键部分详解

1. 类型导入

import type { ApplyBasicInfo } from "@/interface";

作用:导入自定义类型(通常定义在 src/interface.ts 或类型声明文件中)

示例类型定义

// 假设在 @/interface 中
interface ApplyBasicInfo {
  id: number;
  name: string;
  status: 'pending' | 'approved';
  createTime: Date;
}

2. defineProps 函数

const props = defineProps({...});

3. applyBasicInfo 属性

{
  type: Object as () => ApplyBasicInfo,
  required: true
}
// 使用时可以获得类型提示
props.applyBasicInfo.id // 提示 number 类型
props.applyBasicInfo.name // 提示 string 类型

4. operateCommandType 属性

{
  type: String,
  default: "info-view"
}

代码特点

特性说明
双重校验Vue 运行时类型检查 + TypeScript 静态类型提示
明确契约通过 ApplyBasicInfo 接口明确定义数据结构
渐进式类型不需要完全迁移到 TypeScript 也能获得部分类型优势
代码可维护性接口修改只需改动一处定义

不同写法的对比

这种写法纯 TypeScript 写法纯 JavaScript 写法
类型提示✅ 完整类型提示✅ 完整类型提示❌ 无
运行时校验✅ Vue 会校验 Object 类型❌ 需额外配置✅ 标准校验
默认值支持✅ 直接通过 default 设置❌ 需要使用 withDefaults✅ 原生支持
代码复杂度🟡 中等🟢 简洁(类型优先)🔴 高(需自行维护类型约束)

为什么这样写?

平衡开发体验

渐进增强

明确数据契约

// 父组件使用时
<MyComponent 
  :apply-basic-info="{
    id: 123,           // ✅ 必须 number
    name: '张三',       // ✅ 必须 string
    status: 'pending',  // ✅ 必须是枚举值
    createTime: new Date() // ✅ 必须 Date
  }"
/>

常见错误示例

// 错误1:缺少必填字段
props.applyBasicInfo.missingProp // ❌ TS 报错:属性不存在

// 错误2:类型不匹配
props.applyBasicInfo.id = "100" // ❌ TS 报错:不能将 string 赋给 number

// 错误3:错误的值类型
<MyComponent :apply-basic-info="'字符串'" /> 
// ❌ Vue 控制台警告:期望 Object,得到 String

在组件中的使用

<script setup>
// 获得智能提示
const submit = () => {
  console.log(props.applyBasicInfo.id) // 正确:number 类型
  console.log(props.operateCommandType.toUpperCase()) // 正确:string 方法
}

// 类型安全操作
const statusLabel = computed(() => {
  switch(props.applyBasicInfo.status) {
    case 'pending': return '审批中';
    case 'approved': return '已通过';
    // case 'rejected' 会触发 TS 错误(如果接口中未定义)
  }
})
</script>

最佳实践建议

保持接口同步:当 ApplyBasicInfo 类型修改时,需要同步更新所有使用它的组件

默认值处理:对于复杂对象 prop,建议:

// 使用解构默认值
const { applyBasicInfo = DEFAULT_INFO } = toRefs(props)

文档注释:在接口定义处添加详细注释

interface ApplyBasicInfo {
  /** 申请ID,唯一标识 */
  id: number;
  /** 申请人姓名 */
  name: string;
  // ...
}

这种写法特别适合需要逐步引入 TypeScript 的 Vue 项目,既能享受类型系统的优势,又不会完全颠覆原有的开发模式。

3.3、与3.1等效的TypeScript 的类型声明写法

import { toRefs } from "vue";
import type { ApplyBasicInfo } from "@/interface";

const props = defineProps<{
  applyBasicInfo: ApplyBasicInfo;
  operateCommandType: string;
}>();

这段代码是 Vue 3 组合式 API 中使用 TypeScript 的类型声明写法,主要用于定义组件的 props。让我们分步详细解释:

defineProps 的作用

泛型参数 <...>

props 具体定义

{
  applyBasicInfo: ApplyBasicInfo;
  operateCommandType: string;
}

a) applyBasicInfo

b) operateCommandType

interface ApplyBasicInfo {
  id: number;
  applicantName: string;
  applyDate: Date;
  // ...其他字段
}

最终结果

{
  applyBasicInfo: { /* 具体数据 */ },
  operateCommandType: "edit" // 示例值
}

在组件中的使用

// 父组件使用
<ChildComponent 
  :applyBasicInfo="myApplyInfo" 
  operateCommandType="edit"
/>

// 子组件内使用
console.log(props.applyBasicInfo.applicantName)
console.log(props.operateCommandType)

类型检查

对比 JavaScript 版本

如果是纯 JavaScript,等效写法可能是:

const props = defineProps({
  applyBasicInfo: {
    type: Object,
    required: true
  },
  operateCommandType: {
    type: String,
    default: 'view'
  }
})

注意事项

实例代码

<script setup lang="ts" name="ApplySampleOtherTable">

// 1、数组写法
const props = defineProps(["applySampleData", "applySampleTableScrollTop", "operateCommandType"]);
</script>
<script setup lang="ts" name="ApplySampleOtherTable">

// 2、对象写法
const props = defineProps({
  applySampleData: {
    type: Object, // 要求必须是 Object 类型
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  applySampleTableScrollTop: {
    type: Number, // 要求必须是 Number 类型
    default: 0 // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  },
  operateCommandType: {
    type: String, // 要求必须是 String 类型
    default: "info-add" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});
</script>
<script setup lang="ts" name="ApplySampleOtherTable">
import type { ApplySample } from "@/interface";
import type { PropType } from "vue";

// 3、TypeScript 的类型声明写法1:使用标准的PropType进行对象类型定义
const props = defineProps({
  applySampleData: {
    type: Object as PropType<ApplySample[]>, // 要求必须是 Object 类型,类型为ApplySample[]
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  applySampleTableScrollTop: {
    type: Number, // 要求必须是 Number 类型
    default: 0 // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  },
  operateCommandType: {
    type: String, // 要求必须是 String 类型
    default: "info-add" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});
</script>
<script setup lang="ts" name="ApplySampleOtherTable">
import type { ApplySample } from "@/interface";

// 4、TypeScript 的类型声明写法2:结合了 Vue 的运行时 props 校验和 TypeScript 类型提示的混合写法
const props = defineProps({
  applySampleData: {
    type: Object as () => ApplySample[], // 要求必须是 Object 类型,类型为ApplySample[],使用箭头函数返回类型
    required: true // 表示父组件必须传递这个prop,不能与 default 同时使用
  },
  applySampleTableScrollTop: {
    type: Number, // 要求必须是 Number 类型
    default: 0 // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  },
  operateCommandType: {
    type: String, // 要求必须是 String 类型
    default: "info-add" // 表示当父组件没有传递时使用默认值,不能与 required 同时使用
  }
});
</script>
<script setup lang="ts" name="ApplySampleOtherTable">
import type { ApplySample } from "@/interface";

// 5、TypeScript 的类型声明写法3:vue3的写法,不设置默认值
const props = defineProps<{
  applySampleData: ApplySample[];
  applySampleTableScrollTop: number;
  operateCommandType: string;
}>();
</script>
<script setup lang="ts" name="ApplySampleOtherTable">
import type { ApplySample } from "@/interface";

// 6、TypeScript 的类型声明写法3:vue3的写法,设置默认值
const props = withDefaults(
  // withDefaults 是 Vue3 提供的一个工具函数,专门用于处理类型声明的 props 默认值。
  defineProps<{
    applySampleData: ApplySample[];
    applySampleTableScrollTop: number;
    operateCommandType: string;
  }>(),
  {
    // 数组类型,使用工厂函数返回默认值
    applySampleData: () => [],
    // 数字类型,使用默认值
    applySampleTableScrollTop: 0,
    // 字符串类型,使用默认值
    operateCommandType: "info-add"
  }
);
</script>

在 Vue 3 的 <script setup> 语法中,当使用 withDefaults 为对象类型的 prop 设置默认值时,必须使用工厂函数返回默认对象(用箭头函数 () => {} ),这是 Vue 设计层面的强制要求,主要目的是避免多个组件实例共享同一个对象引用的问题。

<script setup lang="ts" name="CommonApplyItemTable">
import type { ApplyItem } from "@/interface";

const props = withDefaults(
  defineProps<{
    // 受理项目列表
    applyItemList: ApplyItem[];
    // 受理模式,1:样品检相同项目;2:样品检不同项目
    dataType: 1 | 2;
    // 操作指令类型:新增删除:info-new;修改:info-modify;查看:info-view
    operateCommandType: "info-add" | "info-modify" | "info-view";
  }>(),
  {
    applyItemList: () => {
      let applyItemList: ApplyItem[] = [];
      return applyItemList;
    },
    dataType: 1,
    operateCommandType: "info-view"
  }
);
</script>
<script setup lang="ts" name="CommonApplyBasicInfoForm">
import type { ApplyBasicInfo, ApplyBasicInfoOperateCommandType } from "@/interface";

const props = withDefaults(
  defineProps<{
    // 受理基础信息
    applyBasicInfo: ApplyBasicInfo;
    // 操作指令类型
    operateCommandType: ApplyBasicInfoOperateCommandType;
  }>(),
  {
    // 对象类型,使用工厂函数返回默认值
    applyBasicInfo: () => {
      return {
        id: 0,
        verifyType: null,
        verifyTypeName: "",
        outerApplyId: "",
        acceptType: "",
        acceptTypeName: "",
        acceptDate: "",
        acceptGroupId: null,
        acceptGroupName: "",
        acceptPerson: "",
        acceptPersonName: "",
        reportDate: "",
        wspjType: 0,
        sjdwName: "",
        sjdwAddress: "",
        sjdwRegion: "",
        cydwName: "",
        cydwAddress: "",
        cyDate: "",
        cyPerson: "",
        cyType: "",
        wsdwName: "",
        wsdwAddress: "",
        wsPerson: "",
        lxPerson: "",
        lxPhone: "",
        email: "",
        payType: "",
        createTime: ""
      };
    },
    // 字符串类型,使用默认值
    operateCommandType: "info-view"
  }
);

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文