Code前端首页关于Code前端联系我们

Vue3.2里的defineProps咋用?从基础到实战全讲透!

terry 2小时前 阅读数 34 #Vue
文章标签 2 defineProps

做Vue3项目时,组件通信里“父传子”靠props,而Vue3.2在<script setup>语法里,用defineProps来声明props更简洁,但不少同学刚接触时,会疑惑“咋声明类型?默认值咋设?和TS咋结合?”今天把这些问题拆碎了讲明白。

defineProps是干啥的?和Vue2 props有啥不一样?

简单说,defineProps是Vue3给<script setup>语法专门设计的“声明props的工具”,以前Vue2写组件,得在export default里写props选项,现在用<script setup>更简洁——不用额外导入,直接在脚本里写defineProps就能声明父组件要传的参数。

举个对比:
Vue2选项式写法:

export default {
  props: { String,
    count: {
      type: Number,
      required: true
    }
  }
}

Vue3 <script setup>写法:

<script setup>
const props = defineProps({ String,
  count: {
    type: Number,
    required: true
  }
})
</script>

核心区别:

  • Vue2的props是选项式API的配置项;Vue3的defineProps编译时宏(编译器自动处理,不用手动import)。
  • 写法更内聚:Vue3把props声明直接放在<script setup>里,和组件逻辑贴得更近,不用来回翻代码找props配置。

基础类型props咋声明?数组和对象写法有啥区别?

声明props分“数组写法”“对象写法”,场景不同用法不同。

数组写法:简单场景快速声明

如果只需要声明props名称,不需要类型验证、默认值,用数组最方便:

const props = defineProps(['title', 'isShow', 'count'])

这种写法适合“父组件传啥类型都能接受”的简单场景,比如内部工具类组件,团队内约定好传值类型。

对象写法:精细控制props规则

如果要限制类型、设默认值、做必填校验,必须用对象写法,对象的key是props名称,value是配置项(类型、默认值、validator等)。

举个按钮组件的例子(限制类型+默认值):

const props = defineProps({
  // 基础类型:直接写构造函数
  btnText: String, 
  // 复杂配置:是否禁用、默认值
  isDisabled: {
    type: Boolean,  // 限制传Boolean类型
    default: false  // 没传时默认false
  },
  // 必传参数
  maxCount: {
    type: Number,
    required: true  // 父组件必须传这个值
  }
})

这里type可以是JS内置构造函数(String/Number/Boolean等),也能是自定义类(比如自己写的User类)。

复杂类型&自定义验证的props咋处理?

实际开发中,props经常是对象、数组,甚至需要“传的值必须满足某个规则”,这时候得用类型约束+验证函数

场景1:对象类型props(带验证)

比如做用户信息卡片,需要父组件传user对象(包含idnameavatar),为了防止传错值,加验证:

const props = defineProps({
  user: {
    type: Object,
    required: true,
    // 自定义验证函数:返回true才合法
    validator: (user) => {
      return user.id !== undefined && user.name && user.avatar
    }
  }
})

父组件如果传了没有iduser,Vue会在控制台报错,提前拦截错误。

场景2:数组类型props(带默认值)

如果props是数组,默认值必须用函数返回(否则所有组件实例会共享同一个数组,一个改了全乱套)。

标签列表”组件,默认展示空数组:

const props = defineProps({
  tags: {
    type: Array,
    // 注意:对象/数组的默认值必须用函数返回新实例
    default: () => []  
  }
})

要是写成default: [],多个组件实例会共享同一个空数组,某个实例push数据后,其他实例的tags也会被改——这是JS引用类型的“坑”,用函数返回新数组就能避免。

给props设置默认值有啥讲究?

默认值分“基本类型”“复杂类型(对象/数组)”,写法不一样。

基本类型(字符串、数字、布尔):直接赋值

比如按钮组件的默认文案:

const props = defineProps({
  btnText: {
    type: String,
    default: '提交' // 基本类型直接写默认值
  }
})

复杂类型(对象、数组):必须用函数返回

前面讲数组时提过,对象同理,比如配置对象的默认值:

const props = defineProps({
  config: {
    type: Object,
    // 函数返回新对象,避免引用共享
    default: () => ({ theme: 'light', size: 'medium' })
  }
})

如果直接写default: { theme: 'light' },所有用这个组件的地方,config的默认值都是同一个对象引用,只要有一个组件修改了config.theme,其他组件的默认值也会被改——这绝对是你不想看到的bug!

组件里咋访问defineProps声明的props?

defineProps返回的是响应式的props对象,直接用props.xxx访问就行。

比如模板里渲染:

<template>
  <div class="card">
    <h2>{{ props.title }}</h2>
    <p>{{ props.description }}</p>
  </div>
</template>
<script setup>
const props = defineProps(['title', 'description'])
// 脚本里也能直接用
console.log(props.title)
</script>

⚠️ 注意:不能直接解构props!比如const { title } = props,解构后title会变成普通变量,失去响应性(父组件更新title时,这里不会跟着变),如果非要解构,得用toRefs

import { toRefs } from 'vue'
const props = defineProps(['title'])
const { title } = toRefs(props) // title变成响应式的ref

和TypeScript结合,defineProps咋写更优雅?

现在很多项目用TS,defineProps结合TS能让类型约束更严谨,核心思路是用接口(Interface)或类型别名(Type)定义props类型,再传给defineProps

场景1:纯TS类型约束(无默认值)

比如文章组件需要title(必传字符串)和author(可选字符串):

interface ArticleProps { string;
  author?: string; // 可选属性
}
const props = defineProps<ArticleProps>()

这样TS会在编译时检查:父组件传title必须是字符串,传author要么不传,传的话也得是字符串。

场景2:TS类型+默认值

如果要给可选属性设默认值,得用withDefaults宏(因为直接在defineProps对象里写default,和TS泛型结合会有类型冲突):

interface ArticleProps { string;
  showCount?: number;
}
// withDefaults接收两个参数:defineProps<类型>() 和 默认值对象
const props = withDefaults(defineProps<ArticleProps>(), {
  showCount: 5 // 没传showCount时,默认显示5条
})

withDefaults会自动推导类型,保证默认值和接口定义的类型一致,既严谨又方便。

用defineProps容易踩哪些坑?

看似简单的defineProps,实际开发稍不注意就会掉坑里,这几个“雷区”要避开:

坑1:解构props导致响应性丢失

前面提过,直接const { title } = props会让title变成非响应式,父组件更新title时,子组件里的title不会同步变化。

解决:要么直接用props.title,要么用toRefs解构。

坑2:直接修改props

props是父组件传的,子组件只能读,不能改!如果在子组件里写props.title = '新标题',Vue会直接报错。

正确做法:子组件要改props,得通过emit通知父组件,让父组件来修改(比如emit('update:title', '新标题'))。

坑3:运行时和类型检查混淆

TS的类型约束是“编译时检查”,如果父组件用JS写(没开TS检查),传错类型不会被发现,这时候运行时验证(对象写法里的typevalidator就很重要。

比如团队里有人用JS写父组件,给user传了个字符串,TS编译时不报错,但运行时type: Object的验证会触发报错,帮你拦截错误。

坑4:对象/数组默认值没写函数

前面反复强调:对象、数组的默认值必须用函数返回新实例,如果偷懒写成default: []default: {},多个组件实例会共享同一个引用,出现“一个组件改了,其他组件跟着变”的诡异bug。

实际项目中,defineProps咋优化组件设计?

用好defineProps不止是“能跑就行”,更要让组件易读、易维护、少踩坑,分享几个实战技巧:

技巧1:清晰分类props

把props按“业务数据”和“UI配置”分开声明,代码结构更清晰。

const props = defineProps({
  // 业务数据:用户信息
  user: { type: Object, required: true },
  // UI配置:卡片风格、是否显示边框
  cardStyle: { type: String, default: 'normal' },
  showBorder: { type: Boolean, default: true }
})

技巧2:用TS接口统一定义

复杂组件的props类型,单独用接口声明,放在组件顶部或单独文件里。

// 单独的types.ts文件
export interface UserCardProps {
  user: { id: number; name: string; avatar: string };
  showDesc: boolean;
  maxLine?: number;
}
// 组件里导入使用
import { UserCardProps } from './types.ts'
const props = defineProps<UserCardProps>()

其他开发者看接口就知道组件需要啥参数,不用翻组件逻辑。

技巧3:合理用默认值和验证

  • 默认值:减少父组件传参压力(比如大部分场景用默认风格,就给UI配置项设默认值)。
  • 验证函数:复杂业务场景下,提前拦截非法值(比如用户id必须是数字,用validator验证)。

技巧4:结合文档工具自动生成文档

如果用Storybook这类工具,defineProps声明的props能自动生成组件文档,比如给props加注释:

const props = defineProps({
  /** 卡片标题 */ String,
  /** 是否禁用按钮 */
  isDisabled: { type: Boolean, default: false }
})

Storybook能读取这些注释,生成交互文档,团队协作效率翻倍。

吃透defineProps,Vue3组件通信的“父传子”就稳了,从基础声明到复杂场景,从TS结合到避坑技巧,核心是让props的声明更清晰、更严谨,实际项目里多练,遇到问题再回头看这些细节,组件设计能力自然就上去了~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门