通过封装一个自定义 UI 组件来学习 v-model 原理

v-model 是 vue 的响应式体系里很方便的一个指令,了解其源码可以加深对各种 UI 组件库的理解,在某些企业的面试中也会发挥作用。
用于 HTML 标签的 v-model
下面两种方式完全等价
1 | <input type="text" v-model="username"> |
1 | <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"/> |
方式 2 就是 v-model
的底层原理了。
:value="username"
是 JS 到页面的绑定@input=" xxx "
是页面输入到 JS 变量值的绑定
其中的 $event
其实就是原生 DOM 事件对象,由于事件可能为空,所以加了一个 TS 的断言。
不过这只是一个前置知识,为了引出下面:
用于自定义组件的 v-model
这是 v-model 源码的一个实际应用,常用于封装自定义组件的 UI 组件库。
首先需要知道,对于自定义组件,下面两种方式仍然是等价的:
1 | <LInput v-model="username"/> |
1 | <LInput :modelValue="username" |
$event
而不是 $event.target.value
或者其他乱七八糟的东西?因为:对于自定义事件,$event
是触发该事件的时候所传递的数据
不过到这里,v-model 并不会发挥作用。我们需要先了解组件通信的一些原理:
- 在父传子上,我们使用
props
方式; - 在子传父上,我们使用绑定自定义事件的方式。
这时候就有思路了,显然我们需要写一个自定义组件来实现这两种通信,例如 src/components/LInput.vue
:
1 | <script setup lang="ts"> |
到这里,我们实际上自己封装了一个自定义组件,该组件同样具有原生 HTML 元素的 v-model 功能。
自定义 modelValue 名来定义多个 v-model
实际上,一个自定义组件可以绑定多个 modelValue:
1 | <LInput v-model:name="username" v-model:password="password" /> |
v-model:name
这种写法实际上就是把源码写法中的 modelValue
给重命名了。值得注意的是,自定义事件名会变成:@update:name=" xxx "
,前面的 update
是固定的。
然后调整自定义组件的源码:
1 | <script setup lang="ts"> |
可以看到,现在在自定义组件内,我们同时使用了两个 input,这两个都可以实现数据的双向绑定。
想学的更深?
本文中其他一些可以优化的点:
使用 computed 简化双向绑定
- [ ] 在自定义组件内,使用 computed 简化双向绑定逻辑
1 | <script setup lang="ts"> |
v-model 的多样性
-
[ ] v-model 在原生元素上的多样性:
-
checkbox / radio:编译为 :checked 和 @change 事件。
-
select:编译为 :value 和 @change 事件。
-
有 .lazy 修饰符的输入框:v-model.lazy=“msg” 会使用 @change 而不是 @input 事件。
v-model 修饰符
- [ ] v-model 修饰符
自定义组件也可以支持修饰符,例如 v-model.capitalize=“myText”。
当父组件这样使用时,子组件会通过一个特殊的 prop modelModifiers 来接收修饰符。
TypeScript 类型增强
自定义组件内的声明部分可以这样写:
1 | const props = defineProps<{ |
- 标题: 通过封装一个自定义 UI 组件来学习 v-model 原理
- 作者: 三葉Leaves
- 创建于 : 2025-06-30 00:00:00
- 更新于 : 2025-07-10 13:40:50
- 链接: https://blog.oksanye.com/a19819de7a9b/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。