vue3 v-model指令

193 阅读4分钟

v-model 是 Vue 提供的一个语法糖指令,用于在表单输入元素和组件上创建双向数据绑定。在表单元素中,v-model 会根据输入类型自动选择相应的方式来更新数据。

下面是常用的几种表单元素

1.输入框

v-model 等同于 :value 和 @change

    <input v-model="str" />
    <br />
    <!--    等同于-->
    <input :value="str" @change="str = $event.target.value" />

2.复选框

v-model 等同于 :checked 和 @change

    <input type="checkbox" v-model="isChecked" />
    <!--等同于-->
    <input
      type="checkbox"
      :checked="isChecked"
      @change="isChecked = $event.target.checked"
    />

3.多个复选框

v-model 等同于 :checked 和 @change

<template>
  <div class="v-model-container">
    <!--  多个复选框  -->
    <input type="checkbox" v-model="isCheckedArray" value="Bob" />
    <input type="checkbox" v-model="isCheckedArray" value="LiHua" />
    <!--等同于-->
    <input
      type="checkbox"
      :checked="isCheckedArray.includes('Bob')"
      value="Bob"
      @change="changeInput"
    />
    <input
      type="checkbox"
      :checked="isCheckedArray.includes('LiHua')"
      value="LiHua"
      @change="changeInput"
    />
  </div>
</template>
<script setup lang="ts">
let isChecked = ref(true)
let isCheckedArray = ref(['Bob'])
//Change方法需要手动实现
let changeInput = (event: any, value: any) => {
  if (event.target.checked) {
    isCheckedArray.value.push(value)
  } else {
    isCheckedArray.value = isCheckedArray.value.filter((item) => item !== value)
  }
}
</script>
<style lang="scss" scoped>
 
</style>

4.单选框

v-model 等同于 :checked 和 @change

 
    <input type="radio" v-model="selected" value="A" />
    <input type="radio" v-model="selected" value="B" />
    <!--等同于-->
    <input
      type="radio"
      v-model="selected"
      :checked="selected === 'A'"
      @change="selected = 'A'"
      value="A"
    />
    <input
      type="radio"
      v-model="selected"
      :checked="selected === 'B'"
      @change="selected = 'B'"
      value="B"
    />

5.下拉框

v-model 等同于 :value和 @change

    <select v-model="selected">
      <option value="A" label="A"></option>
      <option value="B" label="B"></option>
    </select>
    <!--等同于-->
    <select :value="selected" @change="selected = $event.target.value">
      <option value="A" label="A"></option>
      <option value="B" label="B"></option>
    </select>

6.组件

v-model 在组件中使用时,它提供了一种简洁的方式来实现父子组件之间的数据双向绑定。

6.1 先看底层机制

子组件

一个名为 modelValue 的 prop,父组件传递给子组件的值;

一个名为 update:modelValue 的事件,使用emit触发来更新父组件传过来的值。

<template>
  <div>
    <h2>父组件传过来的值:{{ props.modelValue }}</h2>
    <button @click="emit('update:modelValue', 2)">修改父组件的值</button>
  </div>
</template>
<script setup lang="ts">
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<style lang="scss" scoped></style>

父组件

父组件中的v-model会被拆分为 :modelValue="count" 和 @update:modelValue="changeCount($event)"

<template>
  <div>
    <child :modelValue="count" @update:modelValue="changeCount($event)"></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
let changeCount = (val) => {
  count.value += val
}
</script>

这样写可以直观的感受到vue单向数据流的机制 1.父组件传递数据(向下流动) 1.父组件通过props将数据传递给子组件 2.数据是只读的,子组件不能直接修改 2.子组件触发事件(向上通知) 1.子组件通过emit发送事件(如update:modelValue) 2.事件可以携带新数据($event) 3.父组件相应事件(更新数据) 1.父组件监听子组件事件,更新自己的数据 2.更新后的数据会重新通过props流向子组件

6.2 v-model基本使用

在vue3最新版本中提供了一个宏函数,并且官方也推荐使用该宏函数 defineModel()

父组件 父组件使用v-model绑定一个值

<template>
  <div>
    <child v-model="count"></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
</script>

子组件 defineModel() 返回的值是一个特殊的 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用: 1. 它的 .value 和父组件的 v-model 的值同步; 2. 当它被子组件变更了,会触发父组件绑定的值一起更新; 3. 父组件值的变更也会自动同步到子组件; defineModel()等同于 6.1 中的prop 和 emit

<template>
  <div>
    <h2>父组件传过来的值:{{ model }}</h2>
    <button @click="update">修改父组件的值</button>
  </div>
</template>
<script setup lang="ts">
const model = defineModel()
function update() {
  model.value+=2
}
</script>
<style lang="scss" scoped></style>
6.3 带参数的 v-model

Vue3中引入的一个重要特性,它允许你在单个组件上使用多个 v-model 绑定,每个绑定可以针对不同的数据属性。

不在参数的 v-model 默认绑定到 modelValue prop 和 update:modelValue事件

<template>
  <div>
    <h2>父组件</h2>
    <p style="margin: 20px 0px">数量:{{ count }}</p>
    <child v-model="count"></child>
    <!--    等价于-->
    <child
      :modelValue="count"
      @update:modelValue="(newValue) => (count = newValue)"
    ></child>
  </div>
</template>

带参数的v-model 允许你绑定指定的属性名

<template>
  <div>
    <h2>父组件</h2>
    <p style="margin: 20px 0px">数量:{{ count }}</p>
    <child v-model:count="count"></child>
    <!--    等价于-->
    <child
      :count="count"
      @update:count="(newValue) => (count = newValue)"
    ></child>
  </div>
</template>

这样看上去仿佛毫无意义,其实不然。它的作用主要在于允许一个组件同时控制多个数据属性,既可以在一个组件上实例上创建多个 v-model 双向绑定

比如下面的例子:可以修改单个属性,使绑定关系更加语义化和明确

父组件

<template>
  <div>
    <h1>父组件</h1>
    <div style="margin: 20px 0px">
      姓:
      <span>{{ user.firstName }}</span>
      名:
      <span>{{ user.lastName }}</span>
      邮箱:
      <span>{{ user.email }}</span>
    </div>
    <child
      v-model:firstName="user.firstName"
      v-model:lastName="user.lastName"
      v-model:email="user.email"
    ></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
 
let count: number = ref(0)
let user = ref({
  firstName: '张',
  lastName: '三三',
  email: '123456789@163.com',
})
</script>

子组件

<template>
  <div>
    <h2>父组件传过来的值</h2>
    <div class="content">
      姓:
      <input v-model="firstName" />
      名:
      <input v-model="lastName" />
      邮箱:
      <input v-model="email" />
    </div>
  </div>
</template>
<script setup lang="ts">
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')
function update() {
  // count.value += 2
}
</script>
<style lang="scss" scoped>
.content {
  margin-top: 20px;
}
</style>
6.4 v-model修饰符

v-model 修饰符是Vue提供的一种便捷方式,用于在数据绑定到组件或原生元素时对数据进行预处理或特殊处理。 常用的内置修饰符:

.lazy:将input事件改为change事件,只在输入框失去焦点或按回车时更新数据

.trim:自动去除用户输入的首尾空白字符

.number:自动将用户输入转为数值类型 除了内置修饰符之外还有可以自定义修饰符

6.5 自定义修饰符

为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 defineModel() 传入 get 和 set 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值

下面是一个例子,展示了如何利用 set 选项来应用 capitalize (首字母大写) 修饰符:

父组件

<template>
  <div>
    <child v-model.capitalize="text"></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
let text: string = ref('')
</script>

子组件 defineModel() 返回一个数组,包含两个元素:[model, modifiers]

model:用于双向绑定的响应式引用;

modifiers:包含所有使用的修饰符, 键是修饰符名称,值为 true,如果没有修饰符,返回空对象 {};

<template>
  <div>
    <input v-model="model" />
  </div>
</template>
<script setup lang="ts">
const [model, modifiers] = defineModel({
// modifiers:{ capitalize: true }
  set: (val) => {
    if (modifiers.capitalize) {
      return val.charAt(0).toUpperCase() + val.slice(1)
    }
    return val
  },
})
// console.log(model, modifiers)
</script>
<style lang="scss" scoped>
</style>
6.6 带参数的 v-model 修饰符

在 Vue 3 中,带参数的 v-model 修饰符可以让你更精确地控制不同数据属性的处理方式。这种功能特别适合需要处理多个双向绑定值的复杂组件。

父组件

<template>
  <div>
    <child
      v-model:first-name.capitalize="first"
      v-model:last-name.uppercase="last"
      v-model:age.number="age"
    ></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let first: any = ref(undefined)
let last: any = ref(undefined)
let age: any = ref(undefined)
</script>

子组件

处理修饰符

<template>
  <div>
    <label>First Name</label>
    <input v-model="firstName" />
    <label>last Name</label>
    <input v-model="lastName" />
    <label>age</label>
    <input v-model="age" />
  </div>
</template>
<script setup lang="ts">
const [lastName, lastNameModifiers] = defineModel('lastName')
const [firstName, firstNameModifiers] = defineModel('firstName', {
  set(value) {
    let processed = value
    if (firstNameModifiers.capitalize) {
      processed =
        processed.charAt(0).toUpperCase() + processed.slice(1).toLowerCase()
    }
    if (firstNameModifiers.trim) {
      processed = processed.trim()
    }
    return processed
  },
})
 
const [age, ageModifiers] = defineModel('age', {
  set(value) {
    console.log(value)
    let processed = value
    if (ageModifiers.number) {
      processed = Number(processed) || 0
    }
    if (ageModifiers.positive) {
      processed = Math.abs(processed)
    }
    return processed
  },
})
// console.log(model, modifiers)
</script>
<style lang="scss" scoped>
.content {
  margin-top: 20px;
}
</style>