Vue3 组件 v-model

Vue3 组件 v-model

  • [1. 基本使用](#1. 基本使用)
    • [1.1 简单示例](#1.1 简单示例)
    • [1.2 包装原生 DOM 元素](#1.2 包装原生 DOM 元素)
    • [1.3 底层机制(Props + 自定义事件)](#1.3 底层机制(Props + 自定义事件))
    • [1.4 支持简单验证](#1.4 支持简单验证)
      • [1.4.1 支持必填校验](#1.4.1 支持必填校验)
      • [1.4.2 提供一个默认值(不推荐使用,会有问题)](#1.4.2 提供一个默认值(不推荐使用,会有问题))
      • [1.4.3 指定类型](#1.4.3 指定类型)
  • [2. v-model 参数](#2. v-model 参数)
    • [2.1 给 v-model 命名](#2.1 给 v-model 命名)
    • [2.2. 多个 v-model 绑定](#2.2. 多个 v-model 绑定)
  • [3. v-model 修饰符](#3. v-model 修饰符)
    • [3.1 内置修饰符(表单修饰符)](#3.1 内置修饰符(表单修饰符))
    • [3.2 自定义修饰符](#3.2 自定义修饰符)
      • [3.2.1 使用方式(从 defineModel() 解构,并且在 set 中自定义修饰符行为)](#3.2.1 使用方式(从 defineModel() 解构,并且在 set 中自定义修饰符行为))
      • [3.2.2 不能用于初始值的转换(需要的话,建议改用计算属性)](#3.2.2 不能用于初始值的转换(需要的话,建议改用计算属性))
    • [3.3 带参数的修饰符(多个v-model 使用修饰符)](#3.3 带参数的修饰符(多个v-model 使用修饰符))

1. 基本使用

1.1 简单示例

v-model 可以在组件上使用以实现双向绑定。

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏。

子组件 Child.vue:

javascript 复制代码
<template>
  <div>Parent bound v-model is: {{ model }}</div>
  <button @click="update">Increment</button>
</template>

<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

父组件 App.vue:

javascript 复制代码
<template>
  <div>
    <Child v-model="num"/>
  </div>
</template>

<script setup>
import Child from '@/components/Child.vue'
import { ref, watch } from 'vue'

const num = ref(0)

watch(num, (newVal) => {
  console.log('监听到了变化:', newVal)
})

</script>

<style lang="scss" scoped>

</style>

可以看到,子组件使用 defineModel 定义的数据,在父组件上,可以直接使用 v-model 进行双向数据绑定。

1.2 包装原生 DOM 元素

基于 1.1 中的知识,我们可以很轻松地对 DOM 元素,比如 input 进行二次包装。

子组件 MyInput:

javascript 复制代码
<script setup>
const model = defineModel()
</script>

<template>
  <input v-model="model" />
</template>

父组件 App.vue:

javascript 复制代码
<template>
  <div>
    <h1>{{ msg }}</h1>
    <MyInput v-model="msg"/>
  </div>
</template>

<script setup>
import MyInput from '@/components/MyInput.vue'
import { ref } from 'vue'

const msg = ref('Hello World!')

</script>

<style lang="scss" scoped>

</style>

修改子组件内容,父组件数据同步修改。

1.3 底层机制(Props + 自定义事件)

defineModel 是一个便利宏。编译器将其展开为以下内容:

  • 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
  • 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。

在 3.4 版本前,我们通常会使用 Props 结合 自定义事件的方式,实现父子组件的双向数据绑定。比如:

子组件 MyInput.vue:

javascript 复制代码
<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

父组件 App.vue:

javascript 复制代码
<template>
  <h2>{{ msg }}</h2>
  <MyInput :modelValue="msg" @update:modelValue="$event => (msg = $event)" />
</template>

<script setup>
import MyInput from '@/components/MyInput.vue'
import { ref } from 'vue'

const msg = ref('Hello World!')

</script>

<style lang="scss" scoped></style>

虽然这样写,代码会相对冗长,但是底层确实是这么实现的,这么写也有助于理解双向数据绑定的视线机制。

1.4 支持简单验证

1.4.1 支持必填校验

关键代码:

javascript 复制代码
// 使 v-model 必填
const model = defineModel({ required: true })

子组件 MyInput.vue:

javascript 复制代码
<script setup>
const model = defineModel({ required: true })
</script>

<template>
  <input v-model="model" />
</template>

父组件 App.vue:

javascript 复制代码
<template>
  <h2>{{ msg }}</h2>
  <MyInput />
</template>

<script setup>
import MyInput from '@/components/MyInput.vue'
import { ref} from 'vue'

const msg = ref()
</script>

<style lang="scss" scoped></style>

1.4.2 提供一个默认值(不推荐使用,会有问题)

defineModel 也能通过选项设置一个默认值,比如:

javascript 复制代码
// 提供一个默认值
const model = defineModel({ default: 0 })

但是,这也可能会导致一个问题,就是父组件如果没有提供值(默认为undefined)的话,就会导致初始化的时候,父子组件数据不同步。

子组件 MyInput.vue:

javascript 复制代码
<script setup>
// 提供一个默认值
const model = defineModel({ default: 0 })
</script>

<template>
  <input v-model="model" />
</template> 

父组件 App.vue:

javascript 复制代码
<template>
  <h2>{{ msg }}</h2>
  <MyInput v-model="msg"/>
</template>

<script setup>
import MyInput from '@/components/MyInput.vue'
import { ref } from 'vue'

const msg = ref()
</script>

<style lang="scss" scoped></style>

1.4.3 指定类型

defineModel 也能通过选项设置值的类型,比如:

javascript 复制代码
// 指定类型
const model = defineModel({ type: String })

子组件MyInput.vue:

javascript 复制代码
<script setup>
// 指定类型
const model = defineModel({ type: String })
</script>

<template>
  <input v-model="model" />
</template> 

父组件 App.vue:

javascript 复制代码
<template>
  <h2>{{ msg }}</h2>
  <MyInput v-model="msg"/>
</template>

<script setup>
import MyInput from '@/components/MyInput.vue'
import { ref } from 'vue'

const msg = ref(123)
</script>

<style lang="scss" scoped></style>


2. v-model 参数

2.1 给 v-model 命名

组件上的 v-model 也可以接受一个参数:

javascript 复制代码
<MyComponent v-model:title="bookTitle" />

在子组件中,我们可以通过将字符串作为第一个参数传递给 defineModel() 来支持相应的参数:

javascript 复制代码
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>


如果需要额外的 prop 选项,应该在 model 名称之后传递:

javascript 复制代码
const title = defineModel('title', { required: true })

2.2. 多个 v-model 绑定

2.1 给 v-model 命名 中,我们知道可以给 v-model 绑定参数(相当于给双向绑定的prop进行命名)。这其实就意味着,我们可以在子组件中创建多个不同名称的 v-model,实现多个数据的双向绑定。

子组件 UserName.vue:

javascript 复制代码
<template>
  <div class="user-name">
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
  </div>
</template>

<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<style lang="scss" scoped>
.user-name{
  margin-top: 10px;
}
input{
  margin-right: 10px;
}
</style>

父组件 App.vue:

javascript 复制代码
<template>
  <div>{{first + ' ' + last}}</div>
  <UserName
    v-model:first-name="first"
    v-model:last-name="last"
  />
</template>

<script setup>
import UserName from '@/components/UserName.vue'
import { ref } from 'vue'

const first = ref('Sheldon')
const last = ref('Zhou')
</script>

<style lang="scss" scoped>

</style>

3. v-model 修饰符

3.1 内置修饰符(表单修饰符)

v-model 内置的修饰符主要为 .trim.number.lazy。详情可参照 《Vue3 表单输入绑定》的 2.2 表单修饰符的使用 部分。下面是一些使用实例

javascript 复制代码
<template>
  <!-- lazy修饰符演示 -->
  <input type="text" v-model.lazy="mess1" />
  <p>你输入的是:{{ mess1 }}</p>
  <p>类型为{{ typeof mess1 }}</p>
  <p>长度为{{ mess1.length }}</p>

  <!-- number修饰符演示 -->
  <input type="text" v-model.number="mess2" />
  <p>你输入的是:{{ mess2 }}</p>
  <p>类型为{{ typeof mess2 }}</p>
  <p>长度为{{ mess2.length }}</p>

  <!-- trim修饰符演示 -->
  <input type="text" v-model.trim="mess3" />
  <p>你输入的是:{{ mess3 }}</p>
  <p>类型为{{ typeof mess3 }}</p>
  <p>长度为{{ mess3.length }}</p>
</template>

<script setup>
import { ref } from 'vue'
const mess1 = ref('')
const mess2 = ref('')
const mess3 = ref('')
</script>

<style scoped></style>

3.2 自定义修饰符

3.2.1 使用方式(从 defineModel() 解构,并且在 set 中自定义修饰符行为)

我们来创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

javascript 复制代码
<MyComponent v-model.capitalize="myText" />

子组件 MyComponent.vue:

javascript 复制代码
<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

父组件 App.vue:

javascript 复制代码
<template>
  <MyComponent v-model.capitalize="myText" />
</template>

<script setup>
import MyComponent from '@/components/MyComponent.vue'
import { ref } from 'vue'

const myText = ref('')
</script>

<style lang="scss" scoped>

</style>

输入 hello 时,会将首字母自动转为大写 H。

3.2.2 不能用于初始值的转换(需要的话,建议改用计算属性)

如果我们在 3.2.1 中的 App.vue 的初始值直接赋值 hello:

javascript 复制代码
const myText = ref('hello')

会发现,初始值并不会将首字母转为大写。因为这违背了 v-model 双向绑定的初衷(即双向绑定)。

因此,遇到这种需要对初始值进行修改的情况,我们最好在初始化的时候就对数据进行一次处理,或者直接弃用自定义修饰符,转而使用计算属性来实现。

3.3 带参数的修饰符(多个v-model 使用修饰符)

这里是另一个例子,展示了如何在使用多个不同参数的 v-model 时使用修饰符。

子组件 UserName.vue:

javascript 复制代码
<template>
  <div class="user-name">
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
  </div>
</template>

<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>

<style lang="scss" scoped>
.user-name{
  margin-top: 10px;
}
input{
  margin-right: 10px;
}
</style>

父组件 App.vue:

javascript 复制代码
<template>
  <UserName
    v-model:first-name.capitalize="first"
    v-model:last-name.uppercase="last"
  />
</template>

<script setup>
import UserName from '@/components/UserName.vue'
import { ref } from 'vue'

const first = ref('Sheldon')
const last = ref('Zhou')
</script>

<style lang="scss" scoped>

</style>

上一章 《Vue3 自定义事件

相关推荐
OpenTiny社区3 小时前
如何使用 TinyEditor 快速部署一个协同编辑器?
前端·vue.js
胖虎2653 小时前
打造梦幻粒子动画效果:基于 Vue 的 Canvas 实现方案
vue.js
嗷呜~3 小时前
error 找不到模块“../views/Login.vue”或其相应的类型声明
typescript·vue3
加蓓努力我先飞4 小时前
Vue3小兔鲜-(二)
前端·javascript·css·vue3
李剑一4 小时前
vite框架下大屏适配方案
前端·vue.js·响应式设计
胖虎2654 小时前
从零搭建 Vue3 富文本编辑器:基于 Quill可扩展方案
vue.js
濑户川5 小时前
Vue3 计算属性与监听器:computed、watch、watchEffect 用法解析
前端·javascript·vue.js
前端九哥6 小时前
我删光了项目里的 try-catch,老板:6
前端·vue.js
顽疲6 小时前
SpringBoot + Vue 集成阿里云OSS直传最佳实践
vue.js·spring boot·阿里云