1. v-bind
<el-popover placement="top-start" :width="400" trigger="click">
<template #reference>
<el-button style="margin-right: 10px">我要反馈</el-button>
</template>
<section>
……
</section>
- width 在这里是html的属性而不是css的属性,可以不带单位,默认为px
- ":" 是v-bind:的简写,用作将JavaScript表达式的值绑定在HTML属性上,也就是说这里的400是表达式的求值结果
- el-popover: 弹出框,placement->弹出框的位置,triggrt->触发方式
2. template
<template #reference>
<el-button style="margin-right: 10px">我要反馈</el-button>
</template>
组件就像一个“可复用的盒子”,而插槽(slot)就是盒子里预留的空位,让外部传内容进来
默认插槽(default slot)
<div class="card">
<slot></slot>
</div>
<MyCard>
<p>巧克力馅</p>
</MyCard>
vue在渲染时就会将巧克力馅注入模具中:
<div class="card">
<p>巧克力馅</p>
</div>
所以默认插槽不需要命名,不需要写#default、也不需要
<template>,是靠什么知道要将p放到slot里的?
vue编译器在“编译模板”时,会记录组件关系
vue模板编译器在编译阶段(不是运行时)会做两件事:
- 它看到
<MyCard>不是原生标签(不是div,span),就会认定它是一个组件 - 它会把
<MyCard>里面的内容里面的内容保存为一个插槽函数,在渲染<MyCard>组件时,把这个函数通过slot传进去
vue的具名插槽(named slot)
具名插槽就是给插槽起一个名字,这样父组件可以指定要把内容放到哪个位置,例如:
<el-popover placement="top-start" :width="400" trigger="click">
<template #reference>
<el-button style="margin-right: 10px">我要反馈</el-button>
</template>
</el-popover>
这里没有slot的原因是,它被ElementUI封装在了<el-popover>组件内部:
<template>
<div>
<slot name="reference"></slot>
<slot></slot>
</div>
</template>
<template #reference>
<el-button style="margin-right: 10px">我要反馈</el-button>
</template>
2. el-form表单
el-form
const ruleFormRef = ref()
const ruleForm = reactive({
type: '',
desc: '',
})
<el-form
label-position="top"
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="feedback-ruleForm"
status-icon
>
- label-position: 标签相对输入框的位置
- ref: 为表单设置引用名
- :model: 绑定表单对象,输入框的值会自动同步到"ruleForm"中
- label-width="auto":标签宽度自动调整
el-form-item
<el-form-item label="反馈类型" prop="type">
<el-radio-group v-model="ruleForm.type">
<el-radio value="BUG" name="type">BUG</el-radio>
<el-radio value="优化建议" name="type">优化建议</el-radio>
<el-radio value="其他" name="type">其他</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="desc">
<el-input v-model="ruleForm.desc"
type="textarea"
placeholder="请输入描述信息"
:autosize="{ minRows: 4}"/>
</el-form-item>
- el-form-item:表单中的一项,对应一个字段
- prop:对应表单模型ruleForm中的字段名,用于绑定和验证
v-model:
- 负责实现双向绑定,将用户输入的数据同步到组件 prop:
- 用于与表单验证规则配合使用,是一个标识字段,让表单组件知道每个
<el-from-item>对应哪个数据字段prop="type"让 Element Plus 知道这一项验证(例如必填、格式验证等)是作用于ruleForm.type的
验证规则
const rules = reactive({
type: [
{
required: true,
message: '请选择反馈类型',
trigger: 'change',
},
],
desc: [
{
required: true,
message: '请输入描述信息',
trigger: 'blur',
}
],
})
搭配:
<el-form :rules="rules">
用来告诉表单每个字段的验证条件,提示信息以及触发时间
规则:
{
required: true,
message: '请选择反馈类型',
trigger: 'change',
}
- required: true 表示这个字段是必填项
- message: 验证失败时显示的提示文字
- trigger: 'change' 什么时候触发验证,这里change表示当用户选择或者更改选项时进行验证,blur表示用户离开输入框时触发验证
el-button提交按钮
<el-button type="primary" @click="submitForm(ruleFormRef)">
提交
</el-button>
@click="submitForm(ruleFormRef)": 点击时调用一个名为submitForm方法,并把ruleFormRef(表单引用)作为参数传入
为什么这里传入的是组件实例而不是数据对象 el-form有很多内置方法:
- validate():触发表单校验
- resetFields(): 重置所有字段
- clearValidate():清除校验结果 这些方法是挂载在组件实例上,而不是在数据对象中
校验函数(异步)
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
}
})
}
之所以是异步函数,因为validate会遍历表单的每个el-form-item,调用每个字段的validate方法,每个字段可能运行异步校验函数(比如调用后端接口),等所有字段的校验promise都完成后,返回最终结果
也就是说,整个过程需要异步等待所有校验完成
js异步原理
JavaScript本身运行在单线程环境中,同一时间只能执行一个任务,是怎么实现异步的?
异步的关键:事件循环
当你调用异步函数(比如 setTimeout、axios.get、formEl.validate())时,JavaScript不会停下来等待它完成,而是:
- 把这项任务交给浏览器或Node.js的底层线程去处理(比如网络请求、定时器等)
- JS主线程继续往下执行别的代码
- 当异步任务完成时,底层系统会把“回调函数”放进消息队列
- JS主线程空下来后,事件循环(Event Loop)会取出回调任务来执行
- JS脚本在执行时是单线程的,但运行它的环境(比如浏览器或Node.js)是多线程系统
比如浏览器中:
| 模块 | 作用 | 是否独立线程 |
|---|---|---|
| JS 引擎线程 | 执行 JS 代码 | ✅ 主线程(唯一) |
| GUI 渲染线程 | 渲染页面 | ✅ 独立线程 |
| 定时器线程 | 管理 setTimeout / setInterval | ✅ 独立线程 |
| 网络线程 | 处理 fetch / XHR 请求 | ✅ 独立线程 |
| 事件触发线程 | 处理用户事件、I/O 回调 | ✅ 独立线程 |