让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)的内部样式。
代码含义
defineOptions 是 Vue 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
defineOptions 是 Vue 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 default或defineOptions()里:defineOptions({ name: 'X', props: { ... }, // ❌ 实际上 props 不能在这里定义(需用 defineProps) emits: [...] // ❌ 同理,要用 defineEmits })注意:
defineOptions只能用于非响应式、非逻辑性的元选项,如name、inheritAttrs、compatConfig等。不能定义props、emits、data、methods等。
情况 二:在 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'(这是字符串,不是类型)
- ✅ 正确:
作用:
- 开发环境下提供类型检查警告(非报错)
- 影响
default和required的行为(如 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 自动提示。
- 不能同时使用运行时选项(如
default、validator)。 - 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 这个组件会触发哪些自定义事件(如 click、update: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>组件 emitsubmit事件时,请调用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事件。