首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >vue v-model 双向绑定

vue v-model 双向绑定

作者头像
jgrass
发布2024-12-25 18:00:15
发布2024-12-25 18:00:15
4150
举报
文章被收录于专栏:蔻丁杂记蔻丁杂记

回顾从 vue2 到 vue3 v-model 双向绑定的写法变化

🍕 场景

v-model 双向绑定,用于处理表单输入绑定,类似于 react 中的受控组件。

代码语言:javascript
复制
// React 受控组件function App() {  const [text, setText] = useState("");
  return (    <>      <h3>{text}</h3>      <input        value={text}        onInput={(e) => {          setText(e.target.value);        }}      ></input>    </>  );}

vue 的 v-model 本质与 react 受控组件是一样的,只是加了一个语法糖封装。

🍕 vue2 表单 v-model

代码语言:javascript
复制
<template>  <div>    <h2>FullName: {{ fullName }}</h2>    <h3>Email: {{ email }}</h3>
    <input v-model="firstName" />    <input :value="lastName" @input="(e) => (lastName = e.target.value)" />
    <input v-model.trim="email" placeholder="your email here" />  </div></template>
<script>export default {  name: "HelloWorld",  data() {    return {      firstName: "",      lastName: "",      email: "",    };  },  computed: {    fullName() {      return this.firstName + " " + this.lastName;    },  },};</script>

表单输入绑定 — Vue.js

这个例子中,firstName 使用 v-model 的基础写法,lastName 是还原 v-model 的“本来面目”。

需要注意的是,这里对 input 标签,绑定的是 value 属性和 input 事件,不同的 input 标签类型,对应的属性和事件不同,详见官方文档。

email 数据添加了修饰符,可以做一些额外的处理

🍕 vue2 父子组件 v-model

下面这个案例展示对于自定义组件,如何使用 v-model。

在组件间使用 v-model,一个隐含的场景是,数据是由父组件提供的,子组件可能会修改数据,然后通知父组件更新数据。

不管是 vue 还是 react,都是单向数据流的设计,子组件不应该直接修改父组件给过来的数据,而是通知父组件,让父组件处理,完成所谓的双向绑定。

PS 如果数据本身就是子组件产生的,那直接通过事件告知父组件即可,这种场景没有双向绑定,也就不需要 v-model。

代码语言:javascript
复制
// Foo 组件,子组件<template>  <div>    <!-- <input :value="value" @input="(e) => this.$emit('input', e.target.value)" /> -->    <input      :value="firstName"      @input="(e) => this.$emit('updateFristName', e.target.value)"    />
    <input      :value="lastName"      @input="(e) => this.$emit('update:lastName', e.target.value)"    />
    <input      :value="email"      @input="(e) => this.$emit('update:email', e.target.value.trim())"      placeholder="your email here"    />
    <p>{{ firstName }} {{ lastName }} {{ email }}</p>  </div></template>
<script>export default {  name: "FooItem",  model: {    prop: "firstName",    event: "updateFristName",  },  props: {    // value: String,    firstName: String,    lastName: String,    email: {      type: String,      default: "https://www.cnblogs.com/jasongrass",    },  },  data() {    return {};  },};</script>

这里子组件中是没有任何 v-model 这个指令的,因为 v-model 有两个功能,一个是提供数据,一个是修改数据(在事件回调中),而子组件是不能修改父组件提供的数据的,会破坏单向数据流。

所以这里子组件只是通过 props 接受数据,需要修改数据时,只触发事件,具体的事件处理和数据的实际修改,在父组件中完成。

具体写法上,上面的子组件代码中,涉及到了三种写法。

子组件 1. 默认写法

在上面代码中被注释的部分,即默认的数据名称是 value,默认的事件名称是 input

文档:自定义事件 — Vue.js

代码语言:javascript
复制
<input :value="value" @input="(e) => this.$emit('input', e.target.value)" />

子组件 2. 修改默认写法

默认写法有两个问题,一是不够语义化,在数据比较多的时候,value 具体的业务含义会很不直观,影响代码可读性;二是在其它场景下,可能不能满足需求,如使用单选框、复选框等不同的表单元素时。

此时就可以自定义,如上面的 firstName,默认的 v-model 双向绑定属性名称,变成了 firstName, 事件变成了 updateFristName。

代码语言:javascript
复制
model: {  prop: "firstName",  event: "updateFristName",}
<input  :value="firstName"  @input="(e) => this.$emit('updateFristName', e.target.value)"/>

子组件 3. 多个数据的双向绑定

这里就是 lastName 和 email 两个属性,不考虑事件触发,其实这就是两个普通的属性。

修饰符 .sync — Vue.js

特殊之处在于,这里在期望数据改变时,触发 update:myPropName 事件,以通知父组件修改相关的数据。

代码语言:javascript
复制
<input  :value="lastName"  @input="(e) => this.$emit('update:lastName', e.target.value)"/>
代码语言:javascript
复制
// FooContainer 组件,父组件<template>  <div>    <h2>FullName: {{ fullName }}</h2>    <h3>Email: {{ email }}</h3>    <!-- :lastName.sync="lastName" -->    <FooItem      v-model="firstName"      :lastName="lastName"      @update:lastName="        (e) => {          lastName = e;        }      "      :email.sync="email"    ></FooItem>  </div></template>
<script>import FooItem from "./Foo.vue";export default {  name: "FooContainer",  components: {    FooItem,  },  data() {    return {      firstName: "",      lastName: "",      email: "",    };  },  computed: {    fullName() {      return this.firstName + " " + this.lastName;    },  },};</script>
<style scoped></style>

父组件 1. 默认写法

如上面的 firstName,如果需要将父组件中的 firstName 数据,作为子组件的默认 v-model 数据绑定,直接写 v-model="firstName"

这样就会实现与子组件默认 model 的双向绑定

父组件 2. 修改默认写法

修改默认写法,是针对子组件而言的。对于父组件,只要是绑定子组件的 model(因为只有一个),写法就是 v-model="firstName"

父组件 3. 多个数据的双向绑定

如这里的 lastName 和 email 数据,多个数据的绑定,可以对 v-bind 使用 .sync 修饰符。

.sync 修饰符 — Vue.js

本质上就是以下写法的语法糖

代码语言:javascript
复制
<FooItem    :lastName="lastName"    @update:lastName="    (e) => {        lastName = e;    }    "></FooItem>

🍕 vue3 v-model 的变化

主要变化体现在自定义组件的 v-model 上,vue2 中一个组件只有一个 model 定义,其它的是通过 v-bind 的 .sync 修饰符来实现的。 在语法上容易混淆 v-model 和 v-bind 的用法,不是很直观。

v-model | Vue 3 迁移指南

以下是对变化的总体概述:

  • 非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改: prop:value -> modelValue; 事件:input -> update ;
  • 非兼容:v-bind 的 .sync 修饰符和组件的 model 选项已移除,可在 v-model 上加一个参数代替;
  • 新增:现在可以在同一个组件上使用多个 v-model 绑定;
  • 新增:现在可以自定义 v-model 修饰符。

🍕 vue3 表单 v-model

这部分没有什么变化,详见文档:表单输入绑定 | Vue.js

代码语言:javascript
复制
<template>  <div>    <div>      <h2>FullName: {{ fullName }}</h2>      <h3>Email: {{ email }}</h3>
      <input v-model="firstName" />      <input :value="lastName" @input="(e) => (lastName = e.target.value)" />
      <input v-model.trim="email" placeholder="your email here" />    </div>  </div></template>
<script setup>import { ref, reactive, toRefs, computed } from "vue";
const info = reactive({  firstName: "",  lastName: "",  email: "",});const { firstName, lastName, email } = toRefs(info);
const fullName = computed(() => {  return firstName.value + " " + lastName.value;});</script>

🍕 vue3 父子组件 v-model

组件 v-model | Vue.js

在 vue 3.4 版本之后,使用了 defineModel 宏,处理 v-model 双向绑定写法上就简单多了。

代码语言:javascript
复制
// Foo 组件,子组件<template>  <div>    <input :value="model" @input="(e) => (model = e.target.value)" />    <input v-model="model" />
    <input :value="lastName" @input="(e) => (lastName = e.target.value)" />    <input v-model="lastName" />
    <input :value="email" @input="updateEmail" placeholder="your email here" />
    <p>{{ model }} {{ lastName }} {{ email }}</p>  </div></template>
<script setup>const model = defineModel();const lastName = defineModel("lastName");const [email, emailModifiers] = defineModel("email");
const updateEmail = (e) => {  const inputValue = e.target.value;  if (emailModifiers.upper) {    console.log(inputValue);    email.value = inputValue ? inputValue.toUpperCase() : "";  } else {    email.value = inputValue;  }};</script>
<style lang="less" scoped></style>

子组件 1. 默认写法

model 定义

const model = defineModel();

model 使用

<input v-model="model" />

默认写法就是在使用 defineModel 时,不指定 model 的名称,则内部默认名称是 modelValue, 对应的更新事件名称是 update:modelValue, 但这两个默认名称,都不需要体现在代码中。

代码中直接使用 defineModel 的返回值,可以自定义命名,如这里是 model,它是一个 ref, 可以直接读取或修改,如果是修改,则底层会自动调用 update:modelValue 事件,通知父组件处理。

注意,这里在子组件中,可以直接使用 v-model,而不是必须写成 <input :value="model" @input="(e) => (model = e.target.value)" /> 这样手动绑定 value 和 触发事件 的方式。因为这里 v-model 绑定的是一个 ref 代理,内部在修改数据时,没有真实修改数据,而是触发事件。

在 vue3.4 之前,不支持这样写的时候,可以自定义一个计算属性,将 input 标签的 value 绑定到这个计算属性中, 计算属性的 get 方法中返回 model, 计算属性的 set 方法中,触发 update 事件。但这样还是需要手动添加并封装一个计算属性。

代码上省心很多,但这里仍然遵守数据单向流的设计原则(虽然看起来像是直接在修改数据),如果父组件不对事件做处理(当然,通常父组件对事件的处理,也是被自动封装在了 v-model 指令中),则子组件对数据的“修改”,也是无效的。

子组件 2&3. 修改默认写法 和 多个 v-model

在使用 defineModel 之后,不管是默认写法,还是定义多个 v-model,都进行了风格上的统一。直接使用 defineModel 定义即可。

const lastName = defineModel("lastName");

子组件,处理自定义修饰符

代码语言:javascript
复制
<script setup>const [email, emailModifiers] = defineModel("email");
const updateEmail = (e) => {  const inputValue = e.target.value;  if (emailModifiers.upper) {    console.log(inputValue);    email.value = inputValue ? inputValue.toUpperCase() : "";  } else {    email.value = inputValue;  }};</script>
代码语言:javascript
复制
// FooContainer 组件,父组件<template>  <div>    <h2>FullName: {{ fullName }}</h2>    <h3>Email: {{ email }}</h3>    <Foo      v-model="fristName"      v-model:lastName="lastName"      v-model:email.upper="email"    ></Foo>  </div></template>
<script setup>import { computed, ref } from "vue";import Foo from "./Foo.vue";
const fristName = ref("");const lastName = ref("");const email = ref("");
const fullName = computed(() => {  return fristName.value + " " + lastName.value;});</script>

父组件的写法也简单直接了很多,对于默认 model, 直接使用 v-model="fristName" 这样的方式绑定,对于其它命名的 model, 使用 v-model:lastName="lastName" 进行绑定。 v-model 内部自动处理了监听子组件对应事件,并修改对应数据的操作。


🍕 总结

vue 3.4 之后,对 v-model 进行了很多优化,引入 defineModel 统一了 vue2 各种 model 的写法,方便地支持了多个 v-model。

但仍然需要注意,本质上 v-model 还是没有改变单向数据流这个设计原则,只是实现细节被封装起来了,在开发中需要有这个意识。

🍕 参考文档

Vue2

表单输入绑定 — Vue.js

组件 v-model | Vue.js

自定义组件的 v-model & .sync 修饰符 — Vue.js

Vue3

v-model | Vue 3 迁移指南

表单输入绑定 | Vue.js

组件 v-model | Vue.js

原文链接: https://cloud.tencent.com/developer/article/2481553

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024年4月21日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🍕 场景
  • 🍕 vue2 表单 v-model
  • 🍕 vue2 父子组件 v-model
    • 子组件 1. 默认写法
    • 子组件 2. 修改默认写法
    • 子组件 3. 多个数据的双向绑定
    • 父组件 1. 默认写法
    • 父组件 2. 修改默认写法
    • 父组件 3. 多个数据的双向绑定
  • 🍕 vue3 v-model 的变化
  • 🍕 vue3 表单 v-model
  • 🍕 vue3 父子组件 v-model
    • 子组件 1. 默认写法
    • 子组件 2&3. 修改默认写法 和 多个 v-model
    • 子组件,处理自定义修饰符
  • 🍕 总结
  • 🍕 参考文档
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档