今天咱们就来聊聊 TinyVue 中插槽和事件的编码规范,帮你避开那些"导航错误"级别的坑。这些都是实打实的经验总结,踩过的坑我帮你填了,你就别再掉进去。

TinyVue 的组件事件名称遵循 Vue 规范。这意味着什么?意味着你在用 TinyVue 的时候,不用学一套新的命名规则,直接按照 Vue 的标准来就行。
Vue 的规范是:在模板中使用 kebab-case(短横线分隔)形式。
<!-- 正确写法 -->
<tiny-button @click="handleClick">点我</tiny-button>
<tiny-select @change="handleChange">选择</tiny-select>
<tiny-input @blur="handleBlur">输入</tiny-input><!-- 错误写法 -->
<tiny-button @Click="handleClick">点我</tiny-button> <!-- 大写开头 -->
<tiny-select @onChange="handleChange">选择</tiny-select> <!-- on前缀 -->
<tiny-input @blur_event="handleBlur">输入</tiny-input> <!-- 下划线 -->
这些错误写法就像你给朋友寄快递,地址写成"北京市朝阳区"而不是"北京市-朝阳区",快递小哥看着一脸懵——格式不对,投递失败。
这个属性是 TinyVue 特有的,而且很容易踩坑,所以我们重点聊聊。
默认行为:代码逻辑更改响应式变量时,不会触发 change 事件。
这是什么意思?看个例子:
<template>
<tiny-select v-model="selectedValue" @change="handleChange">
<tiny-option :value="1" label="选项1"></tiny-option>
<tiny-option :value="2" label="选项2"></tiny-option>
</tiny-select>
</template><script>
import { Select, Option } from '@opentiny/vue'export default {
components: {
TinySelect: Select,
TinyOption: Option
},
data() {
return {
selectedValue: 1
}
},
methods: {
handleChange(value) {
console.log('用户手动选择了:', value)
}
},
mounted() {
// 这种通过代码修改的方式,默认不会触发 change 事件!
this.selectedValue = 2
}
}
</script>
在上面的例子中,用户手动点击选择会触发 change 事件,但 mounted 中通过代码修改 selectedValue 则不会触发。
这在大多数场景下是合理的——用户操作触发事件,程序修改不触发事件,就像你手动关灯会触发"关灯"事件,但定时器自动关灯就不算你"主动关灯"。
但有些场景你需要代码修改也触发事件,比如表单联动场景——选了省份后自动切换城市,你需要城市的变化也被 change 事件捕获。这时候就需要 change-compat 属性了:
<template>
<tiny-select
v-model="selectedCity"
change-compat
@change="handleCityChange"
>
<tiny-option
v-for="city in cities"
:key="city.value"
:value="city.value"
:label="city.label"
></tiny-option>
</tiny-select>
</template>
设置 change-compat 为 true 后,不管是用户操作还是代码修改,都会触发 change 事件。
避坑提醒:别滥用 change-compat!如果你在大部分场景下都开了这个属性,可能会遇到事件循环的问题——A 改了触发 change,change 里又改了 B,B 的 change 又改了 A……这就像两个人互相说"你先请",谁都不进门,场面一度僵持。
以下是 TinyVue 常用组件的典型事件,帮助你快速上手:
| 组件 | 常见事件 | 说明 |
|---|---|---|
| Button | click | 按钮点击 |
| Input | input, change, blur, focus, clear | 输入框各种交互 |
| Select | change, visible-change | 选中值变化、下拉框展开/收起 |
| Checkbox | change | 选中状态变化 |
| Radio | change | 选中项变化 |
| Switch | change | 开关状态变化 |
| DatePicker | change, pick | 日期变化 |
| Form | validate | 表单校验完成 |
插槽(Slot)就像组件身上的"口袋"——组件本身提供结构和功能,口袋里的东西由你来决定。TinyVue 的组件都设计了合理的插槽,让你可以灵活定制组件的内容。
默认插槽是最基础的,就像外套上的主口袋——最显眼,最常用:
<!-- TinyVue 的 Alert 组件有默认插槽 -->
<tiny-alert>
这里是警告内容,我想说什么就说什么
</tiny-alert><!-- Dialog 组件的默认插槽用于自定义内容区 -->
<tiny-dialog v-model="visible" title="提示">
<p>这是对话框的正文内容</p>
<p>可以放任何你想放的东西</p>
</tiny-dialog>
具名插槽就像外套上的多个口袋——胸前的口袋放名片,侧面的口袋放手机,各有各的位置。在 TinyVue 中,很多组件提供了多个具名插槽:
<tiny-dialog v-model="visible" title="提示">
<!-- 头部插槽 -->
<template #header>
<div class="custom-header">
<h3>自定义标题栏</h3>
<span class="subtitle">副标题</span>
</div>
</template> <!-- 默认插槽(内容区) -->
<template #default>
<p>这是自定义的内容区域</p>
</template> <!-- 底部插槽 -->
<template #footer>
<tiny-button @click="visible = false">取消</tiny-button>
<tiny-button type="primary" @click="handleConfirm">确认</tiny-button>
</template>
</tiny-dialog>
注意我们用的是 Vue3 的 #slotName 语法(即 v-slot:slotName 的缩写),而不是 Vue2 的 slot="slotName" 语法。这是因为 TinyVue 同时支持 Vue2 和 Vue3,但编码规范建议:
#slotName 语法slot="slotName" 语法<!-- Vue3 写法 -->
<template #header>自定义头部</template><!-- Vue2 写法 -->
<template slot="header">自定义头部</template>
写错语法就像在英国用美式英语——虽然对方大概能理解,但总觉得不对劲,而且某些场景下确实会出问题。
作用域插槽是最强大的插槽类型,它不仅能让你塞东西,还能让组件给你"递数据"。就像那个外套口袋不仅有空间,还内置了一个小屏幕,能显示口袋里物品的详细信息。
<!-- TinyVue Grid 表格组件的作用域插槽 -->
<tiny-grid :data="tableData">
<!-- 自定义操作列 -->
<tiny-grid-column title="操作">
<template #default="{ row, rowId }">
<tiny-button size="mini" @click="editRow(row)">编辑</tiny-button>
<tiny-button size="mini" type="danger" @click="deleteRow(rowId)">删除</tiny-button>
</template>
</tiny-grid-column> <!-- 自定义状态列 -->
<tiny-grid-column title="状态">
<template #default="{ row }">
<span :class="row.status === 'active' ? 'tag-success' : 'tag-danger'">
{{ row.status === 'active' ? '启用' : '禁用' }}
</span>
</template>
</tiny-grid-column>
</tiny-grid>
在这里,{ row, rowId } 就是 Grid 组件通过作用域插槽给你递的数据。你可以用这些数据来定制每一行的展示方式。
避坑提醒:作用域插槽的数据对象名是组件定义的,不同组件传递的数据不一样。别想当然地写 { data }——Grid 传的是 { row },Select 可能传的是 { option }。具体看组件文档或源码里的插槽定义。
| 规范 | 说明 | 反例 |
|---|---|---|
| 使用对应版本的插槽语法 | Vue3 用 #name,Vue2 用 slot="name" | Vue3项目用 slot="name" |
| 具名插槽用 template 包裹 | 保证插槽内容正确渲染 | 直接写内容不用template |
| 作用域插槽解构要精准 | 只取需要的数据 | 解构所有字段 |
| 不要在插槽里做复杂逻辑 | 插槽内容应该简洁,复杂逻辑抽到方法里 | 插槽里写50行逻辑 |
TinyVue 的组件在模板中使用时,前缀统一为 tiny-:
<!-- 正确 -->
<tiny-button>按钮</tiny-button>
<tiny-input v-model="val"></tiny-input>
<tiny-select v-model="sel"></tiny-select><!-- 错误 -->
<Button>按钮</Button> <!-- 没有前缀 -->
<TinyButton>按钮</TinyButton> <!-- 前缀大小写错误 -->
<opentiny-button>按钮</opentiny-button> <!-- 前缀名错误 -->
这就像医院里叫医生——要叫"张医生",不能叫"Doctor Zhang"或者"张大夫"(虽然意思一样,但在这个医院的规定下就是不对)。tiny- 就是 TinyVue 的"职称前缀"。
在 script 部分,组件名保持原始的 PascalCase 形式:
import { Button, Input, Select } from '@opentiny/vue'export default {
components: {
TinyButton: Button,
TinyInput: Input,
TinySelect: Select
}
}
统一注册前缀为 Tiny 是推荐的做法,这样模板里就能自然地用 tiny-button(Vue 会自动把 PascalCase 的 TinyButton 转换为 kebab-case 的 tiny-button)。
如果你不加 Tiny 前缀直接注册:
// 不推荐的写法
components: {
Button: Button, // 模板里要写 <button>,会和原生HTML标签冲突!
Input: Input, // 同样会和原生 <input> 冲突!
}
这就像你给自己孩子起名叫"桌子"——听起来没啥问题,但在一个已经有桌子这个词的语言环境里,就容易混淆了。原生 HTML 的 <button> 和 <input> 已经占位了,TinyVue 的组件必须加上 tiny- 前缀来区分。
TinyVue 的 Icon 使用方式比较特殊——Icon 是函数,需要调用后才能使用:
<template>
<!-- 正确:调用 IconEdit() -->
<tiny-icon :icon="IconEdit()"></tiny-icon> <!-- 错误:直接传 IconEdit -->
<tiny-icon :icon="IconEdit"></tiny-icon>
</template><script>
import { Icon } from '@opentiny/vue'
import { IconEdit } from '@opentiny/vue-icon'export default {
components: {
TinyIcon: Icon
},
data() {
return {
IconEdit // 注意:传递的是函数的调用结果
}
}
}
</script>
为什么要这样设计?因为 Icon 函数会返回一个组件定义(VNode 或组件选项对象),直接传函数名不调用的话,你传的是函数本身而不是函数的结果——就像你给餐厅一张菜单而不是一道菜,厨师看着菜单不知道该给你做什么。
很多组件的 icon 属性也需要函数调用方式:
<template>
<tiny-button :icon="IconSearch()" type="primary">搜索</tiny-button>
<tiny-button :icon="IconEdit()">编辑</tiny-button>
<tiny-button :icon="IconDel()" type="danger">删除</tiny-button>
</template><script>
import { Button } from '@opentiny/vue'
import { IconSearch, IconEdit, IconDel } from '@opentiny/vue-icon'export default {
components: {
TinyButton: Button
},
data() {
return {
IconSearch,
IconEdit,
IconDel
}
}
}
</script>
终极避坑:IconEdit() 要带括号!不带括号就是传函数引用,带了括号才是传函数结果。这一个括号的差别,就是你能不能看到图标的关键。
把上面的所有规范串起来,写一个综合案例:
<template>
<tiny-form :model="formData" :rules="rules" ref="formRef" label-width="80px">
<tiny-form-item label="用户名" prop="username">
<tiny-input
v-model="formData.username"
@blur="handleBlur"
placeholder="请输入用户名"
>
<template #prefix>
<tiny-icon :icon="IconUser()"></tiny-icon>
</template>
</tiny-input>
</tiny-form-item> <tiny-form-item label="角色" prop="role">
<tiny-select
v-model="formData.role"
change-compat
@change="handleRoleChange"
>
<tiny-option :value="1" label="管理员"></tiny-option>
<tiny-option :value="2" label="普通用户"></tiny-option>
</tiny-select>
</tiny-form-item> <tiny-form-item label="状态" prop="active">
<tiny-switch v-model="formData.active" @change="handleStatusChange"></tiny-switch>
</tiny-form-item> <tiny-form-item>
<tiny-button type="primary" @click="handleSubmit">提交</tiny-button>
<tiny-button @click="handleReset">重置</tiny-button>
</tiny-form-item>
</tiny-form>
</template><script>
import {
Form, FormItem, Input, Select, Option,
Switch, Button, Icon
} from '@opentiny/vue'
import { IconUser } from '@opentiny/vue-icon'export default {
components: {
TinyForm: Form,
TinyFormItem: FormItem,
TinyInput: Input,
TinySelect: Select,
TinyOption: Option,
TinySwitch: Switch,
TinyButton: Button,
TinyIcon: Icon
},
data() {
return {
IconUser,
formData: {
username: '',
role: 2,
active: true
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
}
}
},
methods: {
handleBlur() {
console.log('输入框失焦')
},
handleRoleChange(val) {
// change-compat 让代码修改也能触发
console.log('角色变化:', val)
},
handleStatusChange(val) {
console.log('状态变化:', val)
},
handleSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
console.log('提交数据:', this.formData)
}
})
},
handleReset() {
this.$refs.formRef.resetFields()
}
}
}
</script>
这个案例涵盖了:
最后给你一张速查表,贴在显示器旁边,写代码时瞄一眼:
| 规范项 | 正确做法 | 常见错误 |
|---|---|---|
| 事件命名 | @change、@blur | @onChange、@Change |
| change-compat | 需要代码修改也触发时才开 | 默认就开,导致事件循环 |
| 插槽语法(Vue3) | #header、#default | slot="header" |
| 插槽语法(Vue2) | slot="header" | #header |
| 作用域插槽 | #default="{ row }" | #default="{ data }" |
| 组件前缀 | <tiny-button> | <Button>、<opentiny-button> |
| 组件注册 | TinyButton: Button | Button: Button |
| Icon 使用 | IconEdit() | IconEdit |
| 依赖包 | @opentiny/vue | @opentiny/vue-button(单独包) |
规范这东西,就像交通规则——你觉得它限制了你的自由,但正是因为有了规则,大家才能安全高效地通行。TinyVue 的编码规范并不复杂,核心就是:
#name,Vue2 用 slot="name"tiny-IconEdit() 而不是 IconEdit掌握了这些规范,你写 TinyVue 代码的时候就像老司机开车——不用刻意想规则,但行为已经完全合规。坑?不存在的。