少踩坑!TinyVue 插槽、事件编码规范详解
你有没有写过这样的代码:给组件绑了个事件,怎么都不触发?翻来覆去查 bug,最后发现是事件名写错了------少了个横线或者大小写不对。这种感觉,就像你打车去机场,结果导航把你带到了火车站------不是目的地不对,是路线搞错了。
今天咱们就来聊聊 TinyVue 中插槽和事件的编码规范,帮你避开那些"导航错误"级别的坑。这些都是实打实的经验总结,踩过的坑我帮你填了,你就别再掉进去。
一、事件规范------别让你的事件"迷路"
1.1 事件命名:跟着 Vue 规范走
TinyVue 的组件事件名称遵循 Vue 规范。这意味着什么?意味着你在用 TinyVue 的时候,不用学一套新的命名规则,直接按照 Vue 的标准来就行。
Vue 的规范是:在模板中使用 kebab-case(短横线分隔)形式。
vue
<!-- ✅ 正确写法 -->
<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> <!-- 下划线 -->
这些错误写法就像你给朋友寄快递,地址写成"北京市朝阳区"而不是"北京市-朝阳区",快递小哥看着一脸懵------格式不对,投递失败。
1.2 change-compat:一个容易被忽略的属性
这个属性是 TinyVue 特有的,而且很容易踩坑,所以我们重点聊聊。
默认行为 :代码逻辑更改响应式变量时,不会触发 change 事件。
这是什么意思?看个例子:
vue
<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 属性了:
vue
<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......这就像两个人互相说"你先请",谁都不进门,场面一度僵持。
1.3 常见组件事件一览
以下是 TinyVue 常用组件的典型事件,帮助你快速上手:
| 组件 | 常见事件 | 说明 |
|---|---|---|
| Button | click | 按钮点击 |
| Input | input, change, blur, focus, clear | 输入框各种交互 |
| Select | change, visible-change | 选中值变化、下拉框展开/收起 |
| Checkbox | change | 选中状态变化 |
| Radio | change | 选中项变化 |
| Switch | change | 开关状态变化 |
| DatePicker | change, pick | 日期变化 |
| Form | validate | 表单校验完成 |
二、插槽规范------给组件"塞东西"的正确姿势
2.1 什么是插槽?
插槽(Slot)就像组件身上的"口袋"------组件本身提供结构和功能,口袋里的东西由你来决定。TinyVue 的组件都设计了合理的插槽,让你可以灵活定制组件的内容。
2.2 默认插槽
默认插槽是最基础的,就像外套上的主口袋------最显眼,最常用:
vue
<!-- TinyVue 的 Alert 组件有默认插槽 -->
<tiny-alert>
这里是警告内容,我想说什么就说什么
</tiny-alert>
<!-- Dialog 组件的默认插槽用于自定义内容区 -->
<tiny-dialog v-model="visible" title="提示">
<p>这是对话框的正文内容</p>
<p>可以放任何你想放的东西</p>
</tiny-dialog>
2.3 具名插槽------多个口袋,各有分工
具名插槽就像外套上的多个口袋------胸前的口袋放名片,侧面的口袋放手机,各有各的位置。在 TinyVue 中,很多组件提供了多个具名插槽:
vue
<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,但编码规范建议:
- Vue3 项目 :使用
#slotName语法 - Vue2 项目 :使用
slot="slotName"语法
vue
<!-- Vue3 写法 -->
<template #header>自定义头部</template>
<!-- Vue2 写法 -->
<template slot="header">自定义头部</template>
写错语法就像在英国用美式英语------虽然对方大概能理解,但总觉得不对劲,而且某些场景下确实会出问题。
2.4 作用域插槽------口袋里的"智能夹层"
作用域插槽是最强大的插槽类型,它不仅能让你塞东西,还能让组件给你"递数据"。就像那个外套口袋不仅有空间,还内置了一个小屏幕,能显示口袋里物品的详细信息。
vue
<!-- 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 }。具体看组件文档或源码里的插槽定义。
2.5 插槽使用的编码规范总结
| 规范 | 说明 | 反例 |
|---|---|---|
| 使用对应版本的插槽语法 | Vue3 用 #name,Vue2 用 slot="name" |
Vue3项目用 slot="name" |
| 具名插槽用 template 包裹 | 保证插槽内容正确渲染 | 直接写内容不用template |
| 作用域插槽解构要精准 | 只取需要的数据 | 解构所有字段 |
| 不要在插槽里做复杂逻辑 | 插槽内容应该简洁,复杂逻辑抽到方法里 | 插槽里写50行逻辑 |
三、组件前缀规范------tiny- 不是随便叫的
3.1 模板中的组件前缀
TinyVue 的组件在模板中使用时,前缀统一为 tiny-:
vue
<!-- ✅ 正确 -->
<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 的"职称前缀"。
3.2 script 中的组件注册
在 script 部分,组件名保持原始的 PascalCase 形式:
javascript
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 前缀直接注册:
javascript
// 不推荐的写法
components: {
Button: Button, // 模板里要写 <button>,会和原生HTML标签冲突!
Input: Input, // 同样会和原生 <input> 冲突!
}
这就像你给自己孩子起名叫"桌子"------听起来没啥问题,但在一个已经有桌子这个词的语言环境里,就容易混淆了。原生 HTML 的 <button> 和 <input> 已经占位了,TinyVue 的组件必须加上 tiny- 前缀来区分。
四、Icon 的特殊使用方式------函数式调用
4.1 Icon 不是普通组件
TinyVue 的 Icon 使用方式比较特殊------Icon 是函数,需要调用后才能使用:
vue
<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 或组件选项对象),直接传函数名不调用的话,你传的是函数本身而不是函数的结果------就像你给餐厅一张菜单而不是一道菜,厨师看着菜单不知道该给你做什么。
4.2 在按钮等组件中使用 Icon
很多组件的 icon 属性也需要函数调用方式:
vue
<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() 要带括号!不带括号就是传函数引用,带了括号才是传函数结果。这一个括号的差别,就是你能不能看到图标的关键。
五、综合实战:一个规范的表单组件页面
把上面的所有规范串起来,写一个综合案例:
vue
<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>
这个案例涵盖了:
- 事件命名规范:kebab-case 事件名
- change-compat:Select 组件的联动场景
- 插槽使用:Input 的 prefix 插槽
- Icon 函数调用:IconUser()
- 组件前缀:统一的 Tiny 前缀注册
六、编码规范速查表
最后给你一张速查表,贴在显示器旁边,写代码时瞄一眼:
| 规范项 | 正确做法 | 常见错误 |
|---|---|---|
| 事件命名 | @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 的编码规范并不复杂,核心就是:
- 事件名遵循 Vue 规范,用 kebab-case
- 理解 change-compat 的含义,按需开启
- 插槽语法匹配 Vue 版本 ,Vue3 用
#name,Vue2 用slot="name" - 组件前缀统一用 Tiny ,模板中自动转为
tiny- - Icon 必须函数调用 ,
IconEdit()而不是IconEdit
掌握了这些规范,你写 TinyVue 代码的时候就像老司机开车------不用刻意想规则,但行为已经完全合规。坑?不存在的。
🏠 TinyVue 官网:opentiny.design 📦 GitHub 仓库:github.com/opentiny/ti...