各位观众,掌声在哪里! 今天咱们来聊聊 Vue 3 里的 v-model
,这玩意儿就像咱们家里的遥控器,能控制电视,在 Vue 里面,它能控制组件的状态。 但是!遥控器坏了怎么办?咱自己做一个!所以,今天咱们要学习的就是如何实现一个自定义的 v-model
指令。
v-model
:Vue 的灵魂舞者
在 Vue 里面,v-model
可谓是灵魂人物,它能实现数据的双向绑定,简化我们的开发流程。 简单来说,v-model
就是一个语法糖,它背后其实做了两件事:
- 绑定
value
prop: 将父组件的数据传递给子组件。 - 监听
input
事件 (或其他自定义事件): 当子组件的数据发生改变时,通知父组件更新数据。
为什么要自定义 v-model
?
你可能会问,官方的 v-model
已经很好用了,为什么还要自定义? 原因是:
- 控制粒度更细: 官方
v-model
默认监听input
事件,但有时候我们需要监听其他事件,比如change
事件。 - 支持多个 prop: 官方
v-model
只能绑定一个 prop,但有时候我们需要同时绑定多个 prop。 - 特殊场景需求: 在某些特殊场景下,官方
v-model
可能无法满足我们的需求,需要自定义实现。
自定义 v-model
的基本姿势
要自定义 v-model
,我们需要使用 Vue 3 的 modelModifiers
和 emits
选项。 让我们先来看一个简单的例子:
// ChildComponent.vue
<template>
<div>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue'] // 声明组件会触发的事件
};
</script>
// ParentComponent.vue
<template>
<div>
<ChildComponent v-model="message" />
<p>Message: {{ message }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
message: 'Hello Vue!'
};
}
};
</script>
在这个例子中,ChildComponent
接收一个名为 modelValue
的 prop,并监听 input
事件,当输入框的值发生改变时,触发 update:modelValue
事件,并将新的值传递给父组件。 父组件使用 v-model="message"
将 message
数据绑定到 ChildComponent
上。
深入理解 modelValue
和 update:modelValue
modelValue
: 这是 Vue 3 中v-model
默认使用的 prop 名称。 你可以把它想象成一个“入口”,父组件通过这个 prop 将数据传递给子组件。update:modelValue
: 这是 Vue 3 中v-model
默认触发的事件名称。 你可以把它想象成一个“出口”,子组件通过这个事件将新的数据传递给父组件。
自定义事件名称:告别 update:modelValue
如果你不喜欢 update:modelValue
这个默认的事件名称,你可以使用 model
选项来修改它。
// ChildComponent.vue
<template>
<div>
<input
type="text"
:value="myValue"
@input="$emit('change-value', $event.target.value)"
/>
</div>
</template>
<script>
export default {
props: {
myValue: {
type: String,
default: ''
}
},
emits: ['change-value'],
model: {
prop: 'myValue',
event: 'change-value'
}
};
</script>
// ParentComponent.vue
<template>
<div>
<ChildComponent v-model="message" />
<p>Message: {{ message }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
message: 'Hello Vue!'
};
}
};
</script>
在这个例子中,我们使用了 model
选项来指定 prop 名称为 myValue
,事件名称为 change-value
。 这样,v-model="message"
就会将 message
数据绑定到 ChildComponent
的 myValue
prop 上,并在 ChildComponent
触发 change-value
事件时更新 message
数据。
modelModifiers
:让 v-model
拥有魔法
modelModifiers
允许我们为 v-model
添加修饰符,就像官方的 .lazy
、.number
、.trim
一样。 我们可以在父组件中使用这些修饰符,并在子组件中接收它们。
// ChildComponent.vue
<template>
<div>
<input
type="text"
:value="modelValue"
@input="handleChange"
/>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ''
},
modelModifiers: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue'],
methods: {
handleChange(event) {
let value = event.target.value;
if (this.modelModifiers.capitalize) {
value = value.toUpperCase();
}
this.$emit('update:modelValue', value);
}
}
};
</script>
// ParentComponent.vue
<template>
<div>
<ChildComponent v-model.capitalize="message" />
<p>Message: {{ message }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
message: 'hello vue!'
};
}
};
</script>
在这个例子中,我们在 ParentComponent
中使用了 v-model.capitalize="message"
,表示我们要对 message
数据应用 capitalize
修饰符。 在 ChildComponent
中,我们通过 this.modelModifiers.capitalize
来判断是否应用了 capitalize
修饰符,如果是,则将输入框的值转换为大写。
多 v-model
共舞:支持多个 prop 的绑定
Vue 3 允许我们为一个组件绑定多个 v-model
,这在某些场景下非常有用。
// ChildComponent.vue
<template>
<div>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</div>
</template>
<script>
export default {
props: {
firstName: {
type: String,
default: ''
},
lastName: {
type: String,
default: ''
}
},
emits: ['update:firstName', 'update:lastName']
};
</script>
// ParentComponent.vue
<template>
<div>
<ChildComponent v-model:first-name="firstName" v-model:last-name="lastName" />
<p>First Name: {{ firstName }}</p>
<p>Last Name: {{ lastName }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
firstName: 'John',
lastName: 'Doe'
};
}
};
</script>
在这个例子中,我们为 ChildComponent
绑定了两个 v-model
:v-model:first-name="firstName"
和 v-model:last-name="lastName"
。 这意味着 firstName
数据会绑定到 ChildComponent
的 firstName
prop 上,lastName
数据会绑定到 ChildComponent
的 lastName
prop 上。
v-model
的幕后英雄:emits
选项
emits
选项用于声明组件会触发的事件。 它是 Vue 3 中非常重要的一个选项,可以帮助我们更好地管理组件的事件。 在使用自定义 v-model
时,我们需要在 emits
选项中声明 update:modelValue
事件(或其他自定义事件),否则 Vue 会发出警告。
代码示例:一个完整的自定义 v-model
例子
让我们来看一个更完整的例子,演示如何使用自定义 v-model
来实现一个带格式化的输入框。
// FormattedInput.vue
<template>
<div>
<input
type="text"
:value="formattedValue"
@input="handleInput"
@blur="formatValue"
/>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
props: {
modelValue: {
type: Number,
default: 0
},
formatter: {
type: Function,
default: (value) => value.toFixed(2) // 默认保留两位小数
},
parser: {
type: Function,
default: (value) => parseFloat(value) // 默认转换为浮点数
}
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const rawValue = ref(props.modelValue.toString()); // 使用 ref 存储原始值
const formattedValue = computed({
get: () => props.formatter(Number(rawValue.value)), // 使用 formatter 格式化显示
set: (newValue) => {
rawValue.value = newValue; // 同步更新 rawValue
}
});
const handleInput = (event) => {
rawValue.value = event.target.value; // 实时更新 rawValue
};
const formatValue = () => {
const parsedValue = props.parser(rawValue.value); // 使用 parser 解析
emit('update:modelValue', parsedValue); // 触发 update:modelValue 事件
};
return {
rawValue,
formattedValue,
handleInput,
formatValue
};
}
};
</script>
// ParentComponent.vue
<template>
<div>
<FormattedInput v-model="price" :formatter="currencyFormatter" :parser="numberParser" />
<p>Price: {{ price }}</p>
</div>
</template>
<script>
import FormattedInput from './FormattedInput.vue';
export default {
components: {
FormattedInput
},
data() {
return {
price: 1234.5678
};
},
methods: {
currencyFormatter(value) {
return '$' + value.toFixed(2); // 货币格式化
},
numberParser(value) {
return parseFloat(value.replace('$', '')); // 移除货币符号并解析
}
}
};
</script>
在这个例子中,FormattedInput
组件接收 formatter
和 parser
两个 prop,用于格式化和解析输入框的值。 ParentComponent
使用 v-model
将 price
数据绑定到 FormattedInput
组件上,并传递了自定义的 currencyFormatter
和 numberParser
函数。
总结:v-model
的进阶之路
自定义 v-model
是 Vue 开发中的一项重要技能。 通过学习自定义 v-model
,我们可以更好地控制组件的状态,实现更灵活、更强大的功能。 记住,modelValue
、update:modelValue
、modelModifiers
和 emits
选项是自定义 v-model
的关键。
划重点,敲黑板!
为了方便大家理解,我把今天讲的重点整理成了一个表格:
概念 | 解释 |
---|---|
modelValue |
v-model 默认使用的 prop 名称,用于将父组件的数据传递给子组件。 |
update:modelValue |
v-model 默认触发的事件名称,用于将子组件的新数据传递给父组件。 |
model |
用于自定义 v-model 的 prop 名称和事件名称。 |
modelModifiers |
用于为 v-model 添加修饰符,就像官方的 .lazy 、.number 、.trim 一样。 |
emits |
用于声明组件会触发的事件,在使用自定义 v-model 时,需要在 emits 选项中声明 update:modelValue 事件(或其他自定义事件),否则 Vue 会发出警告。 |
希望今天的讲座对大家有所帮助! 记住,编程是一门实践的艺术,多写代码,多思考,才能真正掌握 v-model
的精髓。 下课!