VUE:deep和编译时宏(macro)

让deep生效

问题

<uni-easyinput class="input" suffixIcon="search"
v-model.trim="searchVal"
placeholder="请输入搜索关键字..." 
@iconClick="handleSearch" 
@confirm="handleSearch"></uni-easyinput>

<style lang="scss" scoped>
:deep(.is-input-border){
border-radius: 50px; // 设置超大圆角 → 变成胶囊形状
border-color:$uni-color-primary !important;
}
</style>

想修改 uni-easyinput 内部的 .is-input-border 元素样式。

但因为 uni-easyinput 默认是 样式隔离 的,即使你用了 :deep(),在某些平台(尤其是微信小程序)也可能无效

如果上面这段代码是出现在page里面就没事,但是如果出现在components里面就有问题。因为在 uni-app原生小程序 中,每个自定义组件默认是 样式隔离(isolated – 样式隔离,内外互不影响) 的。

这样外部样式(包括父组件)也 无法穿透到组件内部,deep就失效了。

解决

defineOptions({
  options: {
    styleIsolation: 'shared'
  }
})

styleIsolation 是什么

uni-app原生小程序 中,每个自定义组件默认是 样式隔离(isolated) 的,即:

  • 组件内部的样式 不会影响外部
  • 外部样式(包括父组件)也 无法穿透到组件内部

但有时我们希望 父组件能修改子组件的样式(比如你代码中用 :deep(.is-input-border) 修改 uni-easyinput 的边框),这就需要关闭样式隔离。

说明
'isolated'(默认) 样式隔离,内外互不影响
'apply-shared' 页面样式可影响组件,但组件样式不影响页面
'shared' 完全共享样式,组件和页面样式互相影响(等同于无隔离)

✅ 在 uni-app 中,styleIsolation: 'shared' 允许你在父组件中通过 :deep() 或普通类名直接覆盖子组件(如 uni-easyinput)的内部样式。

代码含义

defineOptionsVue 3.3+ 引入的一个宏(macro),用于在 <script setup> 语法中定义组件的选项(options),而无需使用传统的 export default 对象写法。

这段代码的作用是:设置当前组件的样式隔离策略为 shared(共享)

编译时宏(macro)

是什么

编译

在前端开发中,“编译”指的是:**把开发者写的源代码(如 .vue 文件)转换成浏览器或小程序能执行的最终代码(如 JavaScript、WXML 等)的过程。**这个过程由构建工具(如 Vite、Webpack + vue/compiler-sfc)完成。

概念 Java 前端(Vue / uni-app)
源代码 .java 文件(人类可读) .vue.ts.scss 等(开发者写的高级语法)
编译器 / 构建工具 javac Vite、Webpack + vue/compiler-sfc、uni-app 编译器
编译产物 .class 字节码(JVM 可执行) .js.css.wxml.json(浏览器或小程序可执行)
运行环境 JVM(Java 虚拟机) 浏览器(Chrome/V8)、微信小程序引擎、iOS/Android 客户端
是否需要编译? 是(不能直接运行 .java 是(浏览器不能直接运行 .vue<script setup>

前端从源码到 CPU 执行: 前端“编译”通常只是第一层转换,而最终在设备上执行时,现代运行环境(如 V8、JavaScriptCore、小程序引擎)往往还会进行进一步的编译或解释,甚至生成字节码或机器码

.vue / .ts 源码
     ↓ ① 构建工具编译(Vite/Webpack/uni-app)
标准 JS + CSS + HTML / WXML
     ↓ ② 运行时引擎加载(V8 / JSC / 小程序引擎)
     → 可能生成字节码(Bytecode)
     → 可能 JIT 编译成机器码(Machine Code)
     ↓
CPU 执行

编译时与运行时

  • 编译时(Compile Time):代码还没运行,还在被工具处理、转换。
  • 运行时(Runtime):代码已经被浏览器/小程序加载并执行。

宏(Macro)

在编程中,“宏”通常指:一种在代码被真正执行前,由编译器/预处理器自动替换或生成代码的机制。

它不是普通函数,而是一种“代码模板”或“指令”,告诉编译器:“看到我,就按规则替换成别的代码”。

Vue 的编译时宏是一些看起来像函数调用,但实际上不会在运行时存在的特殊标识符。

它们只在 SFC(单文件组件)被编译时 被 Vue 编译器识别,并转换成对应的运行时逻辑

作用
defineProps 声明 props
defineEmits 声明 emits
defineOptions 定义组件选项(如 name、styleIsolation)
defineSlots 声明插槽(类型提示用)
withDefaults 为 props 设置默认值

为什么叫“宏”?类比 C 语言

在 C 语言中:

#define PI 3.14159

这叫“宏定义”。编译时,所有 PI 都会被替换成 3.14159,程序运行时根本不知道 PI 存在。

defineOptions({
  options: {
    styleIsolation: 'shared'
  }
})
  • 这段代码在 运行时不会执行
  • uni-app 的编译器在处理 .vue 文件时,会读取这个调用的内容,然后:
    • 如果目标平台是微信小程序 → 生成 Component({ options: { styleIsolation: 'shared' } })
    • 如果是 H5 → 可能忽略(因为 H5 没有样式隔离)
  • 最终打包的代码里,没有 defineOptions 函数调用

编译时宏”——只在编译阶段起作用,运行时不存在

defineOptions

defineOptionsVue 3.3+ 新增的一个编译时宏(macro),用于在 <script setup> 语法中定义组件的选项(component options),而无需使用传统的 export default {} 写法。

作用

<script setup> 中,你无法直接写:

export default {
  name: 'MyComp',
  inheritAttrs: false
}

因为 <script setup> 本身就是一个“隐式”的 setup() 函数。
于是 Vue 提供了 defineOptions 来补充这些“元信息”。

正确用法(纯 Vue 3.3+):

<script setup>
import { defineOptions } from 'vue'

defineOptions({
  name: 'SearchBar',
  inheritAttrs: false
})
</script>

注意:在 Vue 单文件组件中,通常不需要显式导入 defineOptions,它会被自动注入。

options 是什么

情况 一:在纯 Vue 中

  • 没有 options 这个顶层字段

  • 组件选项直接平铺在 export defaultdefineOptions() 里:

    defineOptions({
      name: 'X',
      props: { ... },     // ❌ 实际上 props 不能在这里定义(需用 defineProps)
      emits: [...]        // ❌ 同理,要用 defineEmits
    })
    

    注意:defineOptions 只能用于非响应式、非逻辑性的元选项,如 nameinheritAttrscompatConfig 等。不能定义 propsemitsdatamethods

情况 二:在 uni-app(尤其是编译到小程序时)

  • options 是一个特殊字段,用于传递 小程序平台特有的组件配置
  • 这些配置会最终被编译进小程序原生的 Component({ options: { ... } }) 中。

小程序原生写法(微信为例):

Component({
  options: {
    styleIsolation: 'shared',   // 样式隔离策略
    addGlobalClass: true,       // 是否应用全局样式
    multipleSlots: true         // 是否启用多 slot
  }
})

uni-app 如何映射?uni-app 要求你在 defineOptions把小程序特有选项放在 options 对象内

// uni-app 中的写法
defineOptions({
  options: {                    // ← 这个 "options" 是 uni-app 规定的容器
    styleIsolation: 'shared',
    addGlobalClass: true
  }
})

关键点:这里的 options 不是 Vue 的概念,而是 uni-app 为了对接小程序底层而设计的“通道”

defineProps

基本概念

defineProps 是 Vue 3 在 <script setup> 语法糖中用于声明组件接收的 props 的编译宏(compile-time macro)。它不是普通函数,而是在编译阶段被 Vue 编译器处理的特殊语法

  • 作用:在 <script setup> 中声明组件接收的外部属性(props)。
  • 返回值:一个响应式对象(reactive),包含所有传入的 prop 值。
  • 编译宏特性
    • 无需导入(自动可用)。
    • 只能在 <script setup> 的顶层调用。
    • 参数必须是字面量(literal)或类型注解(TypeScript)。

三种主要语法形式

仅声明 prop 名称(数组形式)

适用于简单场景,不指定类型或默认值。

const props = defineProps(['title', 'visible'])
  • 等价于 Options API 中:

    props: ['title', 'visible']
    
  • 所有 prop 类型为 any(无类型检查)。

⚠️ 不推荐用于生产项目,缺乏类型安全。

案例

<template>
  <view class="container">
    <Demo title="欢迎使用 UniApp" :visible="true" />
    <Demo title="隐藏的内容" :visible="false" />
    <!-- 注意:也可以传字符串 'false',但会被转为 true(因为非空字符串是 truthy) -->
    <!-- 所以布尔值建议用 v-bind 或 : -->
  </view>
</template>

<script setup>
import Demo from '@/pages/defineProps1/component.vue'
</script>

<style>
.container {
  padding: 20px;
}
</style>



<template>
  <view class="demo-box">
    <text class="title">标题:{{ props.title }}</text>
    <text class="status">是否可见:{{ props.visible ? '是' : '否' }}</text>
  </view>
</template>

<script setup>
// 仅用数组形式声明 props(无类型、无默认值)
const props = defineProps(['title', 'visible'])
</script>

<style>
.demo-box {
  padding: 20px;
  background-color: #f5f5f5;
  margin: 10px;
}
.title {
  font-size: 18px;
  font-weight: bold;
}
.status {
  margin-top: 10px;
  color: #666;
}
</style>

Vue 不会对 visible 的类型做任何限制或检查Vue 不会报错,全部接受!

  • 父组件传 :visible="true"props.visible === true(布尔)
  • 父组件传 visible="true"props.visible === "true"(字符串)
    • :v-bind 的缩写,表示 把引号内的内容当作 JavaScript 表达式执行
    • 没有 : 就是普通 HTML 属性,永远是字符串
  • 父组件传 :visible="123"props.visible === 123(数字)
  • 父组件不传 → props.visible === undefined

所以无法从子组件代码本身知道 visible 到底应该是什么类型。类型语义完全依赖开发团队的约定或文档

运行时声明(对象形式)

基本结构回顾

const props = defineProps({
  title: String,
  count: {
    type: Number,
    required: true
  },
  items: {
    type: Array,
    default: () => []
  },
  validatorProp: {
    type: String,
    validator: (value) => ['a', 'b'].includes(value)
  }
})
type:声明期望的数据类型
  • :必须是原生构造函数(不是字符串!)

    • ✅ 正确:String, Number, Boolean, Array, Object, Date, Function, Symbol
    • ❌ 错误:'string', 'number'(这是字符串,不是类型)
  • 作用

    • 开发环境下提供类型检查警告(非报错)
    • 影响 defaultrequired 的行为(如 Boolean 特殊处理)
  • 多类型支持:传入数组

    prop: {
      type: [String, Number]
    }
    
required:是否必传
  • 默认值false

  • 仅对非 Boolean 类型有效:因为 Vue 对 Boolean prop 有特殊规则:即使不传,也可能为 true(见下文)

  • 示例:如果父组件未传 id,控制台会警告(仅开发环境)。

    id: {
      type: Number,
      required: true
    }
    
default:默认值
  • 基础类型(String, Number, Boolean):可直接写值

    name: {
      type: String,
      default: 'Guest'
    }
    
  • 对象或数组:必须用工厂函数返回。否则多个组件实例会共享同一个引用,导致状态污染!

    items: {
      type: Array,
      default: () => []
    },
    config: {
      type: Object,
      default: () => ({ theme: 'light' })
    }
    
  • Boolean 的特殊情况

    • 如果没有 default,且父组件没传,默认为 false
    • 但如果你写了 default: true,那未传时就是 true
validator:自定义验证函数
  • 签名(value) => boolean

  • 返回 true 表示合法,false 则在开发环境抛出警告

  • 示例

    status: {
      type: String,
      validator: (value) => ['loading', 'success', 'error'].includes(value)
    }
    
  • 注意

    • 不影响程序运行(生产环境无警告)
    • 不能异步验证(必须同步返回布尔值)

Boolean 类型的特殊行为(非常重要!)

在原生 HTML 中,有些属性是 “存在即为真” 的,比如:

<!-- 这个按钮是禁用的 -->
<button disabled>提交</button>

<!-- 这个按钮也是禁用的!即使写了 "false" -->
<button disabled="false">提交</button>

✅ 在浏览器眼里:只要写了 disabled 这个 attribute(不管值是什么),就表示“启用 disabled 状态”。

这是 HTML 规范,不是 Vue 发明的!

在VUE中,当你的组件声明了一个 Boolean 类型的 prop,比如:

// 子组件
const props = defineProps({
  visible: Boolean
})

Vue 会模仿 HTML 的行为,对这个 prop 做特殊处理:

父组件怎么写 Vue 怎么理解
<MyComp visible /> “用户写了 visible 这个 attribute” → 视为 true
<MyComp visible="anything" /> “attribute 存在” → 还是 true(哪怕值是 "false""0"
<MyComp :visible="false" /> : 表示 JS 表达式 → 执行 false → 得到 false
<MyComp /> 没写这个 attribute → 默认 false

💡 关键区别:

  • 没有 : → 当作 HTML attribute → 存在即 true
  • :(即 v-bind → 当作 JavaScript 表达式 → 按 JS 规则计算

案例

<template>
  <!-- 正确传参 -->
  <Card :count="42" visible mode="dark" />

  <!-- 缺少 required prop → 控制台警告 -->
  <Card visible />

  <!-- 传非法 mode → 警告 -->
  <Card :count="10" mode="invalid" />
</template>

<script setup>
import Card from '@/pages/defineProps2/card.vue'
</script>

<template>
	<view class="card" v-if="props.visible">
		<text class="title">{{ props.title || '无标题' }}</text>
		<text>计数:{{ props.count }}</text>
		<text>标签数:{{ props.tags.length }}</text>
		<text v-if="props.mode">模式:{{ props.mode }}</text>
	</view>
</template>

<script setup>
	const props = defineProps({
		// 简单类型
		title: String,

		// 必填数字
		count: {
			type: Number,
			required: true
		},

		// 数组默认值
		tags: {
			type: Array,
			default: () => []
		},

		// 布尔值(注意特殊行为)
		visible: {
			type: Boolean,
			default: true
		},

		// 带校验的字符串
		mode: {
			type: String,
			validator: (val) => ['dark', 'light'].includes(val)
		}
	})
</script>

下面是控制台的报错

13:37:55.116 [Vue warn]: Missing required prop: "count" \n at <Card>\nat <DefineProps2>\nat <AsyncComponentWrapper>\nat <PageBody>\nat <Page>\nat <Anonymous>\nat <KeepAlive>\nat <RouterView>\nat <Layout>\nat <App>
13:37:55.177 [Vue warn]: Invalid prop: custom validator check failed for prop "mode". \n at <Card>\nat <DefineProps2>\nat <AsyncComponentWrapper>\nat <PageBody>\nat <Page>\nat <Anonymous>\nat <KeepAlive>\nat <RouterView>\nat <Layout>\nat <App>

基于类型的声明(TypeScript 泛型形式)

Vue 3.3+ 推荐方式,结合 TypeScript 提供极致类型安全。

<script setup lang="ts">
interface Props {
  title?: string          // 可选
  count: number           // 必填
  enabled: boolean        // Boolean 类型特殊处理
}

const props = defineProps<Props>()
</script>

特点:

  • 类型即文档,IDE 自动提示。
  • 不能同时使用运行时选项(如 defaultvalidator)。
  • Boolean 类型 prop 会自动推导为可选(即使没加 ?),符合 Vue 行为。

重要规则与限制

必须位于 <script setup> 顶层

// ✅ 正确
const props = defineProps({ ... })

// ❌ 错误:不能在函数、条件、循环内调用
if (true) {
  const props = defineProps(...) // 报错
}

参数必须是字面量或类型注解

// ❌ 错误:不能引用外部变量
const options = { title: String }
defineProps(options)

// ✅ 正确:直接写对象
defineProps({ title: String })

能解构 props(会丢失响应性)

// ❌ 危险:失去响应式更新
const { title } = defineProps({ title: String })

// ✅ 安全:保持响应式
const props = defineProps({ title: String })
// 模板中用 {{ props.title }}

若必须解构,可用 toRefs(但一般不必要):

import { toRefs } from 'vue'
const { title } = toRefs(props)

defineEmits

defineEmits是什么

defineEmits 是 Vue 3 在 <script setup> 语法糖中用于声明和使用组件 emit 事件的编译宏(compile-time macro),与 defineProps 配合,构成了子组件与父组件通信的核心机制。

声明:告诉 Vue 这个组件会触发哪些自定义事件(如 clickupdate:modelValue 等)。

使用:返回一个 emit 函数,用于在子组件中主动触发事件,通知父组件。

📌 类比:

  • defineProps:父 → 子(数据流入)
  • defineEmits:子 → 父(事件流出)

emit

是什么

emit 函数是 Vue 3(以及 Vue 2)中用于子组件向父组件发送自定义事件的核心机制。

  • 类型:一个由 Vue 提供的函数
  • 作用:触发一个自定义事件(custom event)
  • 使用位置:只在子组件内部调用
  • 配合对象:父组件通过 v-on(或 @)监听这个事件

基本语法

emit(eventName, ...args)

  • eventName:字符串,事件名称(如 'submit', 'update:title'
  • ...args:可选,任意数量的参数,传递给父组件的监听函数

emit 能传什么参数?

几乎任何 JavaScript 值都可以:

emit('event1')     // 不传参数
emit('event2', 'string')
emit('event3', 42)
emit('event4', { id: 1, name: 'Alice' })
emit('event5', [1, 2, 3])
emit('event6', new Date())
emit('event7', someFunction)

父组件如何接收emit

  • 子组件通过 emit('submit', ...) 触发的是名为 'submit' 的事件。父组件必须用 @submit(即 v-on:submit)才能监听到这个事件。如果你写成 @submitt@onSubmit 或其他名字,就监听不到

  • 父组件的监听函数会按顺序接收这些参数:

    // 子组件
    emit('userAction', 'edit', { id: 100 })
    
    // 父组件
    function onUserAction(actionType, payload) {
      console.log(actionType) // 'edit'
      console.log(payload)    // { id: 100 }
    }
    

三种主要语法形式

仅声明事件名(数组形式)

案例

<template>
  <SimpleForm @submit="onSubmit" />
</template>

<script setup>
import SimpleForm from '@/pages/defineEmits1/SimpleForm.vue'

// 当子组件 emit('submit', ...) 时,此函数被调用
function onSubmit(data) {
  // 如果子组件传了字符串,这里 .id 就是 undefined
  console.log('收到数据 ID:', data.id) // 可能报错:Cannot read property 'id' of string
}
</script>


<template>
	<view>
		<button @click="handleClick1">正确提交</button>
		<button @click="handleClick2">错误提交(拼写错)</button>
		<button @click="handleClick3">传非法参数</button>
	</view>
</template>

<script setup>
	// 仅声明事件名
	const emit = defineEmits(['submit'])

	function handleClick1() {
		emit('submit', {
			id: 1001,
			name: '正确数据'
		}) // ✅
	}

	function handleClick2() {
		emit('submitt', {
			id: 1002
		}) // ❌ 'submitt'拼写错误,事件无效!
	}

	function handleClick3() {
		emit('submit', "我不是对象") // ❌ 传了字符串,但父组件可能崩溃
	}
</script>

以下是控制台内容:

14:56:33.920 收到数据 ID: [number] 1001 at pages/defineEmits1/defineEmits1.vue:11
14:56:41.154 [Vue warn]: Component emitted event "submitt" but it is neither declared in the emits option nor as an "onSubmitt" prop.
14:56:51.039 收到数据 ID: undefined at pages/defineEmits1/defineEmits1.vue:11

讲解

const emit = defineEmits(['submit'])

  • 告诉 Vue 编译器:“我这个组件可能会触发一个叫 submit 的自定义事件”,返回一个 emit 函数,供后续调用。
  • 注意:这只是“声明”,不是注册监听器,也不是建立连接!
  • 如果定义多个事件:const emit = defineEmits(['submit', 'cancel'])

@click="handleClick1":点击触发函数handleClick1,接着触发emit函数

emit('submit', { id: 1001, name: '正确数据' })

  • Vue 拿到事件名 'submit' 和参数 { id: 1001, ... }
  • 在当前组件实例上查找是否有父组件监听了 @submit
    • 这个“查找”是通过 组件树的父子关系 自动完成的(Vue 内部维护了组件实例的层级结构)
  • 如果找到了监听函数(比如父组件写了 @submit="onSubmit"),就立即调用它,并把参数传过去
  • 如果没找到,就什么都不做(静默忽略)
  • emit 并不知道父组件是谁,也不需要知道。它只是“广播”一个事件,Vue 框架负责根据模板中的绑定关系,把事件派发给正确的监听者。

<SimpleForm @submit="onSubmit" />

  • 在父组件的模板中,将子组件的 submit 事件父组件的 onSubmit 方法 绑定起来。
    • 父组件中处理事件的函数名字完全自由,和事件名没有绑定关系。<SimpleForm @submit="handleData" /> 写这个方法名也可以。
  • Vue 在创建组件实例时,会把这个绑定关系记录下来:“当 <SimpleForm> 组件 emit submit 事件时,请调用 onSubmit 函数”

为什么叫“仅声明事件名”?

因为你在数组里只写了事件的名字(字符串),没有说明

  • 这个事件要不要传参数?
  • 参数应该是什么结构?(比如必须有 id 字段?)
  • 参数类型是否合法?

换句话说:Vue 只知道“有这个事件”,但不知道“怎么用才对”

缺点

缺点 1:无法校验事件名拼写错误

假设你声明了:

const emit = defineEmits(['submit', 'cancel'])

但不小心写错了:

emit('submitt', { id: 1 }) // 多了一个 t!

Vue**不会报错!**父组件监听的 @submit 也收不到这个事件(因为名字是 submitt)。bug 静默发生,极难排查!

缺点 2:无法约束参数类型或结构

你可以随便传任何东西:

emit('submit', "hello")        // 传字符串
emit('submit', 42)             // 传数字
emit('submit', null)           // 传 null
emit('submit', { name: 'Bob' }) // 缺少 id 字段

而父组件可能期望的是:

// 父组件期望:{ id: number, name: string }
function onSubmit(data) {
  console.log(data.id.toUpperCase()) // 如果 data.id 是数字,这里就崩了!
}

运行时直接报错,但开发时毫无提示。

运行时对象声明(带参数校验)

是什么

这是 defineEmits 的一种写法,通过传入一个对象来声明事件,并可为每个事件提供一个验证函数(validator),用于在运行时检查事件参数是否合法

const emit = defineEmits({
  // 1. 只声明事件名(无校验)
  close: null,

  // 2. 带校验函数:接收 payload,返回 boolean
  submit: (payload) => {
    // 校验逻辑
    return Boolean(payload && payload.id)
  }
})

validator 函数

  • 参数validator(payload) 接收你 emit 时传的第一个参数(即 payload)

  • 如需多参数,建议合并成一个对象:

    emit('event', arg1, arg2, arg3)
    // validator 只能拿到 arg1!
    
    // 可以改成
    emit('event', { arg1, arg2, arg3 })
    
  • 返回值

    • true 或 truthy 值 → 合法,事件正常触发
    • false 或 falsy 值 → 开发环境下控制台警告(生产环境静默忽略)
  • 作用:在开发阶段帮你捕获错误的事件调用

  • 注意:它不会阻止事件触发,也不会抛出异常,只是警告

  • 不要在 validator 中抛异常

    // ❌ 错误:会导致应用崩溃
    submit: (p) => {
      if (!p.id) throw new Error('...')
    }
    
  • 简单事件无需校验

    // 无参数事件,直接写 null
    const emit = defineEmits({
      close: null,
      refresh: null
    })
    
  • 校验逻辑尽量轻量,避免复杂计算,影响性能。

案例

<template>
  <view>
    <ValidatedForm 
      @submit="handleSubmit" 
      @close="onClose" 
    />
  </view>
</template>

<script setup>
import ValidatedForm from '@/pages/defineEmits2/ValidatedForm.vue'

function handleSubmit(data) {
  console.log('✅ 收到合法提交:', data)
}

function onClose() {
  console.log('对话框关闭')
}
</script>



<template>
	<view class="form">
		<button @click="testValid">合法提交</button>
		<button @click="testInvalid">非法提交(缺少 id)</button>
		<button @click="testString">传字符串(非法)</button>
		<button @click="closeDialog">关闭</button>
	</view>
</template>

<script setup>
	// 使用运行时对象声明 + 参数校验
	const emit = defineEmits({
		// 无参数事件,无需校验
		close: null,

		// 带校验的 submit 事件
		submit: (payload) => {
			// 校验规则:必须是对象,且包含 id(数字或字符串)
			if (typeof payload !== 'object' || payload === null) {
				console.warn('[submit] 参数必须是对象')
				return false
			}
			if (payload.id == null) {
				console.warn('[submit] 对象必须包含 id 字段')
				return false
			}
			return true // 合法
		}
	})

	function testValid() {
		emit('submit', {
			id: 1001,
			name: 'Alice'
		}) // ✅ 合法
	}

	function testInvalid() {
		emit('submit', {
			name: 'Bob'
		}) // ❌ 缺少 id
	}

	function testString() {
		emit('submit', "not an object") // ❌ 不是对象
	}

	function closeDialog() {
		emit('close') // ✅ 无参数事件
	}
</script>

以下是控制台日志:

16:02:41.318 ✅ 收到合法提交: {id: 1001, name: "Alice"} at pages/defineEmits2/defineEmits2.vue:14
16:02:43.082 [submit] 对象必须包含 id 字段
16:02:43.132 [Vue warn]: Invalid event arguments: event validation failed for event "submit".
16:02:43.140 ✅ 收到合法提交: {name: "Bob"} at pages/defineEmits2/defineEmits2.vue:14
16:02:55.376 [submit] 参数必须是对象
16:02:55.404 [Vue warn]: Invalid event arguments: event validation failed for event "submit".
16:02:55.404 ✅ 收到合法提交: not an object at pages/defineEmits2/defineEmits2.vue:14
16:02:57.536 对话框关闭 at pages/defineEmits2/defineEmits2.vue:18

Vue 的约定:声明用驼峰,模板用短横线

为了兼顾 JavaScript 的命名习惯(驼峰)和 HTML 的限制(小写),Vue 做了自动转换

地方 推荐命名 示例
子组件内部(JS/TS) 驼峰 camelCase defineEmits(['selectBuy'])
父组件模板(HTML) 短横线 kebab-case @select-buy="handler"

✅ Vue 会自动把 select-buy 映射到 selectBuy 事件。

×

喜欢就点赞,疼爱就打赏