目录
输入/输出格式化(inputFormat/outputFormat)
Vue中$props、$attrs和$listeners的使用详解
前言
vue2版本
el-form-renderer是基于element的表单渲染器,动态渲染,数据驱动
el-form-renderer/README-zh.md at dev · FEMessage/el-form-renderer · GitHub
vue3版本
自己改的 clone 下来拉到自己项目
https://gitee.com/childe-jia/form-render.git
el-form-renderer是基于 element-ui 封装的表单渲染器,但不局限于 element-ui 组件。在完整继承了 element 的form表单属性的基础上进行了简单扩展,一些非表单组件或者封装的自定义组件,如图片上传、富文本等也可进行整合,从而用户能够通过使用一段预设的数据渲染出一个完整的表单。
起步
# Step1 确认你已经正确安装并使用了 element-ui
yarn add @femessage/el-form-renderer
<template>
<el-form-renderer :content="content"></el-form-renderer>
</template>
<script>
import ElFormRenderer from '@femessage/el-form-renderer'
export default {
components: {
ElFormRenderer,
},
data() {
return {
content: [],
}
},
}
</script>
使用
支持 el-form 上的所有属性。
readonly 只读
form v-model 的值。传入 后会优先使用
disabled:禁用所有表单项目
content:[ObjectArray] 定义表单的内容,每一个 Object 代表一个原子表单 el-input, el-select, ...,一切 el-form-item 上的属性都在此声明,而对于 el-input 等之上的属性在 $el 属性上进行声明,该 Object 上还存在其他属性,例如: id, type,label, options可选的,等。还有hidden定义其是否隐藏等属性
- id: id: string 每一个原子都存在 id,用于存储该原子的值,不能重复
- type: string 可以是element提供的所有表单组件类型,如传入'input',则渲染出'el-input,当type="group"时使用 items内依然遵循同一层级的id不重复的原则
- readonly 只读的 当 type === 'input' 时展示文本值, 当 type === 'select' 时展示对应。 label 对于其他组件等同于 disabled = true
- default: 默认值
- options ({label: string; value?: any}[])具有选择功能的原子表单可用此定义可选项 select, radio-group, radio-button, checkbox-group, checkbox-button
- hidden 传入一个方法,并返回 boolean,返回 true 时则隐藏该表单项 * formValue 为当前 form 值,item 为当前表单项的定义
- el 用于定义具体原子表单(如el-input)的属性,比如定义el-input的placeholder
- component component适用于渲染局部注册组件和自定义组件,而type适用于带el-前缀的全局组件
- label 设置el表单项的标签
- inputFormat:用于处理输入值,输入的值包括:1. default;2. v-model;3. updateForm。参数为整个表单的值对象或 updateForm 传入的对象,如果 inputFormat 返回 undefined,则不会更新此表单项
- outputFormat 用于处理输出值,参数为对应组件返回值,如果处理后的值是对象类型,会覆盖(Object.assign)到整个表单的值上
- rules 设置el表单项的规则
- on 监听表单项发出的事件(该事件发出的内容,updateForm-与methods.updateForm相同)
resetFields() | 重置表单为初始值 | |
getFormValue() | {strict: Boolean — } 默认 false | 当 strict 为 true 时,只返回设置的表单项的值, 过滤掉冗余字段 |
updateForm() | newValue:object--key是项的id,value是新值 | 更新表单值 |
setOptions() |
| 更新options |
getComponentById() |
| 获取自定义组件 |
update-form && getFormValue
- update-form 更新表单方法 默认情况下,updateForm 来者不拒,不在表单设置内的值,也可以存储进去
- getFormValue 默认情况下,通过 updateForm 设置的所有值都会输出。 如果只想输出根据 content 设置的表单项的值,可传入 {strict: true}
<template>
<div class="update-form">
<el-form-renderer :content="content" inline ref="formRender">
<el-button @click="setValue">更新表单</el-button>
<div>
<el-button type="primary" @click="getValue(false)">获取数据</el-button>
<el-button type="primary" @click="getValue(true)"
>获取数据过滤掉字段</el-button
>
</div>
</el-form-renderer>
<pre>{{ value }}</pre>
</div>
</template>
<script>
export default {
name: "update-form",
data() {
return {
value: {},
content: [
{
id: "name",
type: "input",
label: "name",
el: {
placeholder: "name",
},
},
{
id: "area",
type: "select",
label: "area",
el: {
placeholder: "area",
},
options: [
{
label: "shanghai",
value: "shanghai",
},
{
label: "beijing",
value: "beijing",
},
],
},
],
};
},
methods: {
getValue(strict) {
const value = this.$refs.formRender.getFormValue({ strict });
this.value = value;
},
setValue() {
this.$refs.formRender.updateForm({
name: "alvin",
area: "shanghai",
// 设置冗余字段
extraKey: "extraValue",
});
},
},
};
</script>
表单项动态显示或隐藏(hidden)
以通过 hidden
控制某一表单项的显示或隐藏。
<template>
<div>
<el-form-renderer :content="content"></el-form-renderer>
</div>
</template>
<script>
import ElFormRenderer from "@femessage/el-form-renderer";
export default {
components: {
ElFormRenderer,
},
data() {
return {
content: [
{
type: "select",
id: "selected",
label: "选择项目",
options: [
{
label: "项目A",
value: "optionA",
},
{
label: "项目B",
value: "optionB",
},
],
},
{
label: "资料",
type: "input",
id: "data",
el: {
placeholder: "项目B的具体内容",
},
hidden: (form, item) => {
return this.hiddenChange(form, item);
},
},
],
};
},
methods: {
hiddenChange(form, item) {
console.log(form); //form 收集的数据
console.log(item); //触发元素
return form.selected !== "optionB";
},
},
};
</script>
表单数据联动(on)
可以通过 on 来监听 blur , focus 等事件来实现表单联动 监听表单项发出的事件
<template>
<div>
<el-form-renderer :content="content"></el-form-renderer>
</div>
</template>
<script>
import ElFormRenderer from "@femessage/el-form-renderer";
export default {
components: {
ElFormRenderer,
},
data() {
return {
content: [
{
label: "英文名",
type: "input",
id: "fullName",
on: {
blur: ([event], updateForm) => {
const value = event.target.value;
const lastName = value.split(" ")[1]; // 通过空格分割出内容
updateForm({ lastName }); // 更新其他表单项
},
},
},
{
label: "姓氏",
type: "input",
id: "lastName",
},
],
};
},
};
</script>
输入/输出格式化(inputFormat/outputFormat)
拿 日期范围选择器 为例,组件输出的值是一条字符串,但后端接口格式是两个字段 {startDate, endDate},则此时需要对数据进行格式化处理
inputFormat 转换输入的数据, 使其变成表单项需要的数据格式
<template>
<el-form-renderer :content="content" ref="form" />
</template>
<script>
export default {
data() {
return {
content: [
{
el: {
type: 'daterange',
placeholder: '选择日期',
valueFormat: 'yyyy-MM-dd'
},
type: 'date-picker',
id: 'date',
label: '日期',
// 接口设计的时间范围是两个字段 '2019-07-23','2019-07-24'
// 处理后的值为 [ '2019-07-23', '2019-07-24' ]
inputFormat: row => ([row.startDate, row.endDate])
}
]
}
}
}
</script>
outputFormat 转换输出的数据, 使其变成需要的(接口期望的)数据格式
<script>
export default {
data() {
return {
content: [
{
el: {
type: 'daterange',
placeholder: '选择日期',
valueFormat: 'yyyy-MM-dd'
},
type: 'date-picker',
id: 'date',
label: '日期',
// 处理前的值为 date: [ '2019-07-23', '2019-07-24' ]
// 处理后的值为 {startDate: '2019-07-23', endDate: '2019-07-24'}
outputFormat: val => {
if (!val) {
return {startDate: '', endDate: ''}
}
return {
startDate: val[0],
endDate: val[1]
}
}
}
]
}
}
}
</script>
set-options
使用setOptions更新选择选项
<template>
<el-form-renderer ref="form" :content="content" inline>
<el-button @click="setOptions">更新options</el-button>
</el-form-renderer>
</template>
<script>
export default {
name: "select-demo",
data() {
return {
content: [
{
id: "area",
type: "select",
label: "select",
el: {
placeholder: "select",
},
options: [
{
label: "shanghai",
value: "shanghai",
},
{
label: "beijing",
value: "beijing",
},
],
},
],
};
},
methods: {
setOptions() {
this.$refs.form.setOptions("area", [
{
label: "guangzhou",
value: "guangzhou",
},
{
label: "hangzhou",
value: "hangzhou",
},
]);
},
},
};
</script>
get-component-by-id 获取组件
<template>
<div>
<el-form-renderer inline :content="content" ref="form">
</el-form-renderer>
<el-button @click="getComponent('id')">获取id input</el-button>
<el-button @click="getComponent('first')">获取first name input</el-button>
</div>
</template>
<script>
export default {
data() {
return {
data: {},
content: [
{
id: 'id',
type: 'input',
label: 'id',
el: {
placeholder: 'id'
}
},
{
type: 'group',
label: 'name',
id: 'name',
items: [
{
id: 'first',
label: 'first name',
type: 'input'
},
{
id: 'last',
label: 'last name',
type: 'input'
}
]
}
]
}
},
methods: {
getComponent(id){
console.log(this.$refs.form.getComponentById(id))
}
},
}
</script>
el-form-renderer 实践案例
案例一
A 系统有一个解析简历的功能,后端接口只能解析电话、邮箱,也即接口只返回 phone、email 两个字段。后来接口更新了,支持解析姓名:
后端:简历解析接口更新了,现在会返回多一个字段 name,你前端那边也更新一下吧。 前端:您随便加,接口直接更新就行了,前端不用改。 后端:这么神奇的吗?这是怎么做到的?
那么前端是如何做到接口返回多一个字段,自己却不用修改代码的呢?
分析
原因在于使用了 el-form-renderer 使用了 updateForm 来更新表单值。 updateForm 方法接受一个对象,只要传入对象的 key 与表单的 id 对应上即可更新数据。代码片段如下:
<template>
<el-form-renderer :content="content" ref="form" />
</template>
<script>
export default {
data() {
return {
content: [
{
type: 'input',
id: 'name',
label: '名称'
},
{
type: 'input',
id: 'phone',
label: '电话'
},
{
type: 'input',
id: 'email',
label: '邮箱'
},
// ...
],
}
},
methods: {
async fetch() {
const data = await fetchData() // data: Object
// data 中返回多了一个字段 name,也不需要修改代码
this.$refs.form.updateForm(data)
}
}
}
</script>
所以,即使后端丰富了这个 data ,前端也可以“照吃不误”
如果直接使用 el-form 则无法完成这种操作:你需要手动去更新每个与 el-form-item 绑定的 data 值
<template>
<el-form ref="form" :model="form">
<el-form-item label="名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
form: {
// 每一个表单项需要主动绑定
name: '',
phone: '',
email: '',
},
}
},
methods: {
async fetch() {
const {name} = await fetchData() // data: Object
this.form.phone = data.phone
this.form.email = data.email
// data 中返回多了一个字段 name,需要多写下面一行代码
this.form.name = name
}
}
}
</script>
案例二
场景
B 系统的表单页面比较多,其中不乏带有复杂组件的表单,如下图红框片所示:
直接使用 el-form 开撸,整个页面耦合在一起代码超过 1000 行。
使用 el-form-renderer 后,通过拆分组件,整个页面代码量在 300 行左右,业务组件代码量在 100~300 行之间。
明显能感觉到页面简洁了许多,维护性大大提高。
那么,el-rorm-renderer 是怎么做到精简主页面代码的呢?
分析
秘诀在于 el-form-renderer 支持通过 component 属性渲染自定义组件、在组件内部定义检验规则,提高了拆分页面的可能性。
下面代码示例中,把选择优惠券的表格,抽离成了一个单独的组件。
<!--表单主页面-->
<template>
<el-form-renderer :content="content" ref="form" />
</template>
<script>
import SelectTableList from './select-table-list.vue'
export default {
data() {
return {
content: [
// ...
{
id: 'selectedCoupon',
// 渲染自定义 table 组件
component: SelectTableList,
label: '选择优惠券'
},
// ...
],
}
}
}
</script>
下面是自定义 table 组件示例代码。
<!--自定义 table 组件示例代码-->
<template>
<div class="select-table-list">
<el-button type="primary" size="small" @click="visible = true">选择</el-button>
<el-table :data="selectedList" border></el-table>
<!--
省略一些代码
-->
</div>
</template>
<script>
export default {
name: 'select-table-list',
// 自定义校验规则
rules: [
{
required: true,
message: '自定义组件的提醒消息'
}
],
props: ['value'],
data() {
return {
visible: false,
selectedList: []
}
},
methods: {
confirm() {
const selectedVal = 'table选中的值'
// 更新 value 值,这样 el-form-renderer 可以通过 getFormValue() 拿到该值
this.$emit('input', selectedVal)
this.visible = false
}
}
}
</script>
自定义组件接入指南
el-form-renderer 的 type
有限, 默认只能渲染普通的表单项, 假如现在要渲染一个上传组件, type
就不够用了, 那怎么办呢? 这时候 component 选项就派上用场了
本文将介绍如何开发符合 el-form-renderer 接入标准的自定义组件, 实现对自定义组件的渲染
自定义组件接入的关键是在组件内部实现 v-model
建议在自定义组件上绑定 $attrs 和 $listeners
el-form-renderer 对 v-model 的要求是:
- 有一个 props 为 value
- 对外触发 input 事件
<template>
<el-form-renderer :content="content" />
</template>
<script>
import MyInput from "@/components/my-input.vue";
export default {
data() {
return {
content: [
{
component: MyInput,
id: "myInput",
label: "label",
// 传入组件属性
el: {
placeholder: "请输入一个 title",
// type: "submit", // submit button
title: "这是一个标题", // custom defined props
},
// 传入组件事件
on: {
focus: ([event], updateForm) => {
console.log(event.target.value); // output: input value
},
customEvent: ([value, msg], updateForm) => {
console.log(msg); // output: 'message'
},
},
},
{
id: "document",
type: "input",
el: {
type: "textarea",
},
},
],
};
},
};
</script>
<template>
<div>
<!-- 自定义组件 my-input -->
<el-input
:value="value"
@input="onInput"
v-bind="$attrs"
v-on="$listeners"
/>
</div>
</template>
<script>
export default {
props: {
value: String,
title: String,
},
watch: {
value(value) {
this.$emit("customEvent", value, "message");
},
},
methods: {
onInput(val) {
this.$emit("input", "my-input: " + val);
},
},
};
</script>
需要注意,on 中的 function 定义,组件 emit 事件的 payload 将以「数组」的方式,回调到第一个参数
第二个参数为 updateForm 方法
实战记录
添加全局类名 省去 inline属于 一行排列
去掉 inline与 label-width
一行五个 用 在搜索
.render-form {
display: flex;//将.render-form元素设置为弹性布局。
flex-direction: row; //子元素按行排列
flex-wrap: wrap; //如果子元素溢出容器宽度,会换行显示
flex-shrink: 0;//子元素不会收缩。
flex-grow: 0;//子元素不会扩展。
background: #fff;//背景颜色为白色。
margin: -20px -20px 0px;//外边距,上边距为0,左右各为-20px。
padding: 20px 20px 0;//内边距,上边距为0,左右各为20px。
gap: 0 calc(4% / 4); //设置子元素之间的间隔,水平间隔为0,垂直间隔为容器宽度的4%除以4。
// justify-content: space-between;
.el-form-item {
width: 18%;
min-width: 300px;
display: flex;
flex-shrink: 0;
flex-grow: 0;
&:last-child {
width: auto;
min-width: 180px;
}
&.date-range {
width: 440px;
}
.el-form-item__content {
flex: 1;
margin-left: 0 !important;
.el-date-editor--datetimerange.el-input,
.el-date-editor--datetimerange.el-input__inner {
width: 100%;
}
}
}
}
一行两个用于编辑
.render-form-edit {
display: flex; //将.render-form元素设置为弹性布局。
flex-direction: row; //子元素按行排列
flex-wrap: wrap; //如果子元素溢出容器宽度,会换行显示
flex-shrink: 0; //子元素不会收缩。
flex-grow: 0; //子元素不会扩展。
background: #fff; //背景颜色为白色。
margin: -20px -20px 0px; //外边距,上边距为0,左右各为-20px。
padding: 20px 20px 0; //内边距,上边距为0,左右各为20px。
gap: 0 calc(8% / 2); //设置子元素之间的间隔,水平间隔为0,垂直间隔为容器宽度的4%除以4。
// justify-content: space-between;
.el-form-item {
width: 48%;
min-width: 300px;
display: flex;
flex-shrink: 0;
flex-grow: 0;
&:last-child {
min-width: 180px;
}
&.date-range {
width: 440px;
}
.el-form-item__content {
flex: 1;
margin-left: 0 !important;
.el-date-editor--datetimerange.el-input,
.el-date-editor--datetimerange.el-input__inner,
.el-input-number,
.el-cascader,
.el-select
{
width: 100%;
}
}
}
}
页面内添加 一行一个
<style lang="scss" scoped>
.el_edit_renderer ::v-deep {
// justify-content: space-between;
.el-form-item {
width: 48%;
min-width: 300px;
display: flex;
.el-form-item__content {
flex: 1;
.el-input,
.el-select,
.el-input-number {
width: 100%;
}
}
}
}
</style>
添加表单校验规则 rules
<el-form-renderer ref="formRenderer" label-width="150px" :content="content">
</el-form-renderer>
data() {
return {
content:[
{
el: {
placeholder: "代理商名称",
clearable: true,
},
label: "代理商名称",
type: "input",
id: "company",
rules: [{ required: true, message: "请输入代理商名称", trigger: "blur" }],
},
]
};
},
// 表单提交
handleSubmit() {
this.loadingBtn = true;
this.$refs.formRenderer.validate(async (valid, object) => {
if (valid) {
let params = this.$refs.formRenderer.getFormValue({ strict: true });
try {
await packageApi.create({ ...this.form, ...params });
this.getList();
this.$message({
message: "操作成功",
type: "success",
duration: 5 * 1000,
});
this.centerDialogVisible = false;
this.loadingBtn = false;
} catch (error) {
console.log(error);
this.loadingBtn = false;
}
return;
}
this.$message({
message: "请完善信息",
type: "warning",
duration: 5 * 1000,
});
});
},
v-model
<template>
<el-form-renderer label-width="100px" :content="content" v-model="form" ref="form">
<el-form-item>
<el-button @click="resetForm">reset</el-button>
<el-button @click="setValue">设置名字为小明</el-button>
</el-form-item>
<pre>{{form}}</pre>
</el-form-renderer>
</template>
<script>
export default {
data () {
return {
form: {
"name": "",
"type": [],
"startDate": "2019-01-01",
"endDate": "2019-01-02",
"region": [],
"date": [
"2019-01-01",
"2019-01-02"
]
},
content: [
{
type: 'input',
id: 'name',
label: 'name',
attrs: { 'data-name': 'form1' },
el: {
size: 'mini',
placeholder: 'test placeholder'
},
rules: [
{ required: true, message: 'miss name', trigger: 'blur' },
{ min: 3, max: 5, message: 'length between 3 to 5', trigger: 'blur' }
]
}, {
type: 'select',
id: 'region',
label: 'region',
options: [
{
label: 'shanghai',
value: 'shanghai'
},
{
label: 'beijing',
value: 'beijing'
},
{
label: 'guangzhou',
value: 'guangzhou'
},
],
el: {filterable: true, multiple: true, multipleLimit: 2},
rules: [
{ required: true, message: 'miss area', trigger: 'change' }
]
}, {
type: 'date-picker',
id: 'date',
label: 'date',
el: {
type: 'daterange',
valueFormat: 'yyyy-MM-dd'
},
rules: [
{ required: true, message: 'miss date', trigger: 'change' }
],
inputFormat: (row) => {
if (row.startDate && row.endDate) {
return [row.startDate, row.endDate]
}
},
outputFormat: (val) => {
if (!val) {
return {startDate: '', endDate: ''}
}
return {
startDate: val[0],
endDate: val[1]
}
}
}, {
type: 'switch',
id: 'delivery',
label: 'delivery'
}, {
type: 'checkbox-group',
id: 'type',
label: 'type',
default: [],
options: [{
label: 'typeA'
}, {
label: 'typeB'
}, {
label: 'typeC'
}],
rules: [
{ type: 'array', required: true, message: 'miss type', trigger: 'change' }
]
}, {
type: 'radio-group',
id: 'resource',
label: 'resource',
options: [{
label: 'resourceA',
value: 'A',
}, {
label: 'resourceB',
value: 'B'
}],
rules: [
{ required: true, message: 'miss resource', trigger: 'change' }
]
}, {
type: 'input',
id: 'desc',
label: 'desc',
el: {
type: 'textarea'
},
rules: [
{ required: true, message: 'miss desc', trigger: 'blur' }
]
}
]
}
},
methods: {
resetForm() {
this.$refs.form.resetFields();
},
setValue() {
this.form.name = '小明'
},
}
}
</script>
自定义下拉组件
Myselect
<template>
<el-select
:value="value"
placeholder="请选择"
reserve-keyword
@change="selsctchange"
:remote="remote"
v-bind="$attrs"
:loading="loading"
:filterable="filterable"
@clear="handleClear"
:remoteMethod="remoteMethod"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.status == 0"
>
<span style="float: left">{{ item.label }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>
</el-option>
</el-select>
</template>
<script>
export default {
props: {
remote: {
type: Boolean,
default: true,
},
filterable: {
type: Boolean,
default: true,
},
remoteMethod: {
type: Function,
default: () => {},
},
value: {
type: String,
default: "",
},
},
created() {},
computed: {
options() {
let parent = this.$parent;
while (parent && !parent.options) {
parent = parent.$parent;
}
return parent.options || [];
},
},
data() {
return {
loading: false,
};
},
methods: {
selsctchange(val) {
console.log(val);
this.$emit("change", val);
},
handleClear(val) {
this.$emit("clear", val);
},
},
};
</script>
页面使用
<el-form-renderer ref="formRenderer" label-width="150px" :content="content">
</el-form-renderer>
data() {
return {
content:[
{
component: Myselect,
id: "agentId",
label: "选择代理商",
el: {
placeholder: "请搜索代理商",
remoteMethod: (query) => {
this.remoteMethod(query, "formRenderer");
},
remote: true,
filterable: true,
clearable: true,
loading: false,
},
hidden: () => store.getters.userName == "agent",
on: {
clear: ([event], updateForm) => {
this.$refs.formRenderer.setOptions("agentId", []);
},
},
options: [],
rules: [{ required: true, message: "请选择代理商", trigger: "blur" }],
},
]
};
},
import Myselect from "@/views/account/components/myselect.vue";
components: {
Myselect,
},
async remoteMethod(query, ref) {
console.log(query);
let refs = ref || "queryForm";
let agentList = [];
let formComponent = this.$refs[refs].getComponentById("agentId");
formComponent.loading = true;
if (!query) {
this.$refs[refs].setOptions("agentId", agentList);
formComponent.loading = false;
return;
}
let res = await agentApi.findAgentList({ company: query });
const data = res.data;
agentList = data.map((item) => ({
label: item.company,
value: item.agentId,
status: item.status,
}));
this.$nextTick(() => {
formComponent.loading = false;
this.$refs[refs].setOptions("agentId", agentList);
});
},
扩展阅读
Vue中$props、$attrs和$listeners的使用详解
当在Vue.js中使用组件时,了解$props
、$attrs
和$listeners
可以帮助你更好地管理组件之间的数据传递和事件处理。让我详细解释它们的用法:
-
$props
:$props
是一个包含了从父组件传递给子组件的所有属性的对象。- 这些属性是只读的,意味着你不能在子组件中直接修改它们。
- 通过访问
this.$props
,你可以轻松地查看和使用这些属性
示例:
// 父组件
<template>
<child-component :message="parentMessage" />
</template>
<script>
export default {
data() {
return {
parentMessage: "Hello from parent!",
};
},
};
</script>
// 子组件
<template>
<div>{{ $props.message }}</div>
</template>
<script>
export default {
props: {
message: String,
},
};
</script>
$attrs
:
$attrs
是一个包含了传递给子组件但未被子组件声明的属性的对象。- 这在需要将属性传递给子组件但不想声明所有属性时非常有用。
- 可以使用
v-bind="$attrs"
将这些属性绑定到子组件的HTML元素上。
// 父组件
<template>
<child-component title="Child Title" />
</template>
// 子组件
<template>
<div v-bind="$attrs">{{ title }}</div>
</template>
$listeners
:
$listeners
是一个包含了父组件传递给子组件的所有事件监听器的对象。- 这使得子组件可以监听和响应父组件触发的事件。
- 使用
v-on="$listeners"
将这些事件监听器绑定到子组件的元素上。
// 父组件
<template>
<child-component @click="handleClick" />
</template>
<script>
export default {
methods: {
handleClick() {
// 处理点击事件
},
},
};
</script>
// 子组件
<template>
<button v-on="$listeners">Click me</button>
</template>
v-model
当我们讨论Vue.js时,v-model
是一个非常强大且方便的指令,用于实现双向数据绑定。它使得在Vue组件中管理用户输入和组件状态变得非常简单。
什么是v-model?
v-model
实际上是Vue.js提供的一种语法糖,用于实现表单元素和组件状态之间的双向数据绑定。它背后的原理是通过绑定数据属性和监听输入事件来保持数据同步。
如何在表单元素上使用v-model?
首先,让我们看一下如何在表单元素上使用 v-model
。假设我们有一个输入框,我们想要将其值绑定到Vue组件的数据属性。我们可以这样做:
<template>
<div>
<input v-model="message" />
<p>输入的内容:{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: ""
};
}
};
</script>
在上面的示例中,v-model
指令将 <input>
元素的值绑定到了组件的 message
数据属性上。这意味着当用户在输入框中键入内容时,message
的值会自动更新,反之亦然。
在自定义组件上使用v-model
v-model
也可以用于自定义组件,使其支持双向数据绑定。要实现这一点,我们需要在组件内部使
用 model
选项,同时定义 value
和 input
属性。以下是一个简单的示例:
<template>
<div>
<custom-input v-model="message" />
<p>输入的内容:{{ message }}</p>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
components: {
CustomInput
},
data() {
return {
message: ''
};
}
};
</script>
在上面的示例中,我们在父组件中使用 v-model
将 message
数据属性绑定到了 CustomInput
组件上。在 CustomInput
组件内部,我们需要定义 value
和 input
属性:
<template>
<input :value="value" @input="$emit('input', $event)" />
</template>
<script>
export default {
props: ['value']
};
</script>
这样,当我们在父组件中使用 v-model
来操作 message
数据属性时,会同时影响到 CustomInput
组件内部的输入框值。
总结一下,v-model
是Vue.js中用于实现双向数据绑定的强大工具,不仅适用于表单元素,还可以用于自定义组件。通过简单的语法糖,它使得数据在视图和组件之间的同步变得轻而易举。这是Vue.js框架中一个非常有用的功能,有助于构建交互性强、响应式的应用程序。