从 vue2 聊起
v-model 这个指令主要用于处理双向绑定,对于原生元素可以直接这样写
js
<input v-model="message" placeholder="请输入内容" />
当时写起来真是觉得厉害,加一个 v-model 就解决了双向绑定的问题。但随着项目的进展,很快就遇到了需要对组件进行 v-model 的操作,比如使用 ElementUI 的情况下
js
<el-input v-model="username" placeholder="请输入用户名"></el-input>
居然也不需要额外的操作,于是那时候形成了一个意识,需要双向绑定时就直接 v-model 即可,但很快就栽了跟头。
有一个需求是需要对 input 做一个封装,于是很自然就写成了
js
<my-input v-model="username" placeholder="请输入用户名"></el-input>
<template>
<div>
<input placeholder="请输入内容" />
</div>
<template>
可想而知,v-model 完全没有生效,在这里卡了很久。上网查了相关资料才知道,原来 v-model 没有那么厉害。它的本质实际上只是做了个转化,也就是:
js
<input v-model="message" placeholder="请输入内容" />
// 相当于
<input :value="message" @input="message = $event" placeholder="请输入内容"/>
由于 <input>
元素自带了 input 事件与 value 属性,所以自然就能监听到。此时进行输入时,会触发 @input,立刻进行赋值,所以 message 立刻进行了更新,也就是双向绑定的效果。
但转头一想,不对啊,为什么 <el-input>
这种组件也能支持呢,回去一看文档,原来这个组件也已经进行了类似的封装。
注:最新的 el-input 已经不再支持 v-model 了,笔者以前是用老版本的
大概就是
js
// 父组件 v-model 编译后
<el-input :value="message" @input="message = $event" placeholder="请输入内容"/>
// 子组件
<template>
<div>
<input placeholder="请输入内容" :value="value" @input="$emit('input',$event.target.value)" />
</div>
<template>
不得不提,语法糖虽好,让新手能很简单入手,但也隔开了后面的逻辑,出了 bug 理解成本和修改成本直线上升。 反观 react 的双向绑定就显得通俗易懂了
js
// 父组件
<MyInput value={message} onChange={e => setMessage(e.target.value)} />
// 子组件
<input value={value} onChange={onChange} />
再探 vue3
vue2 的写法其实还能理解,拆成编译后的代码比较清晰。但 vue3 就显得拉胯了,看一段示例
js
// 父组件
<MyInput v-model="message" />
// 子组件
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
有的同学可能已经有点迷糊了,这个 modelValue
是哪来的,这个 update:modelValue
又是什么?
老规矩,先看它编译成了什么吧
js
<MyInput v-model="message" />
// 编译后
<MyInput :modelValue="message" @update:modelValue="message = $event" />
这下可能就能理解了,v-model 用 modelValue 作为了值,update:modelValue 作为了更新函数,并不关心你的传入参数叫什么,始终保持这个名字。
当然,可以手动进行命名
js
<MyInput v-model:message="message" />
// 编译后
<MyInput :message="message" @update:message="message = $event" />
为什么 vue3 会将 v-model 设计成这样呢?其主要优点在于,可以同时兼容多个 v-model
js
<MyInput v-model:title="title" v-model:content="content" />
// 编译后
<MyInput :title="title" @update:title="title = $event" :content="content" @update:content="content = $event" />
// 子组件
<template>
<input :value="title" @input="$emit('update:title', $event.target.value)" placeholder="标题" />
<input :value="content" @input="$emit('update:content', $event.target.value)" placeholder="内容" />
</template>