Vue 3源码极客之:`Vue`的`v-model`:如何为自定义组件实现`v-model`。

各位观众老爷,大家好!我是你们的老朋友,今天咱们来聊聊Vue 3里一个非常实用且有趣的东西:v-model

v-model这玩意儿,用起来那是相当的顺手,特别是在处理表单元素的时候。但今天咱们不聊原生的inputtextarea,我们要聊的是:如何为自定义组件实现v-model。这就像是给你的乐高积木赋予了更强大的互动性,让你的组件更加灵活,更加“听话”。

一、v-model:表面的光鲜与内在的乾坤

先来简单回顾一下,v-model的基本用法。在原生的HTML元素上,它通常是这样用的:

<input type="text" v-model="message">

这行代码背后发生了什么?其实它就是一个语法糖,展开后等价于:

<input
  type="text"
  :value="message"
  @input="message = $event.target.value"
>

也就是说,v-model实际上做了两件事:

  1. 绑定了value属性到组件的数据 message
  2. 监听了input事件,并在事件发生时更新 message

OK,现在我们明白了,v-model的核心在于属性绑定和事件监听。那么,在自定义组件中,我们该如何模仿这种行为呢?

二、自定义组件的v-model:从零开始的探索

假设我们有一个自定义组件 MyInput,我们希望像这样使用v-model

<my-input v-model="myValue"></my-input>

那么,MyInput组件应该如何实现呢?

步骤1:定义props 和 emit

首先,我们需要在MyInput组件中定义一个 props 来接收父组件传递的值,并且定义一个 emit 来触发事件,将新的值传递给父组件。

// MyInput.vue
<template>
  <input
    type="text"
    :value="modelValue"
    @input="updateValue"
  >
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    modelValue: {
      type: String,
      default: ''
    }
  },
  emits: ['update:modelValue'],  // 注意这里的命名规则
  setup(props, { emit }) {
    const updateValue = (event) => {
      emit('update:modelValue', event.target.value);
    };

    return {
      updateValue
    };
  }
});
</script>
  • props: { modelValue }: 我们定义了一个名为 modelValueprop这是关键! v-model默认会查找名为 modelValue 的 prop,并将其作为组件的绑定值。你可以把它想象成 v-model 默认绑定到 value 属性一样,只不过在自定义组件中,我们需要显式地声明这个属性。
  • emits: ['update:modelValue']: 我们声明了一个名为 update:modelValue 的事件。 这也是关键! v-model 默认会监听一个名为 update:modelValue 的事件,并在事件触发时更新父组件中的绑定值。同样,你可以把它想象成 v-model 默认监听 input 事件一样,只不过在自定义组件中,我们需要显式地触发这个事件。
  • emit('update:modelValue', event.target.value): 当 input 元素的值发生变化时,我们触发 update:modelValue 事件,并将新的值传递给父组件。

步骤2:在父组件中使用

现在,我们可以在父组件中使用 MyInput 组件了:

// App.vue
<template>
  <my-input v-model="myValue"></my-input>
  <p>当前的值是:{{ myValue }}</p>
</template>

<script>
import { defineComponent, ref } from 'vue';
import MyInput from './components/MyInput.vue';

export default defineComponent({
  components: {
    MyInput
  },
  setup() {
    const myValue = ref('');

    return {
      myValue
    };
  }
});
</script>
  • 我们在 App.vue 中定义了一个名为 myValueref,并将其绑定到 MyInput 组件的 v-model 上。
  • MyInput 组件中的 input 元素的值发生变化时,update:modelValue 事件会被触发,myValue 的值也会被更新。

三、v-model 参数:更灵活的控制

Vue 3 允许我们通过 v-model 的参数来修改默认的行为。 这就像给你的遥控器加了几个自定义按钮,让你可以更精准地控制你的组件。

我们可以通过以下方式来使用 v-model 的参数:

<my-input v-model:title="myTitle" v-model:content="myContent"></my-input>

在这个例子中,我们使用了 v-model:titlev-model:content。 这意味着我们需要在 MyInput 组件中定义名为 titlecontent 的 prop,并且触发名为 update:titleupdate:content 的事件。

// MyInput.vue
<template>
  <div>
    <label for="title">Title:</label>
    <input
      type="text"
      id="title"
      :value="title"
      @input="updateTitle"
    >
    <br>
    <label for="content">Content:</label>
    <textarea
      id="content"
      :value="content"
      @input="updateContent"
    ></textarea>
  </div>
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    title: {
      type: String,
      default: ''
    },
    content: {
      type: String,
      default: ''
    }
  },
  emits: ['update:title', 'update:content'],
  setup(props, { emit }) {
    const updateTitle = (event) => {
      emit('update:title', event.target.value);
    };

    const updateContent = (event) => {
      emit('update:content', event.target.value);
    };

    return {
      updateTitle,
      updateContent
    };
  }
});
</script>
  • 我们定义了 titlecontent 两个 prop,分别对应标题和内容。
  • 我们触发了 update:titleupdate:content 两个事件,分别用于更新父组件中的 myTitlemyContent

在父组件中使用:

// App.vue
<template>
  <my-input
    v-model:title="myTitle"
    v-model:content="myContent"
  ></my-input>
  <p>Title: {{ myTitle }}</p>
  <p>Content: {{ myContent }}</p>
</template>

<script>
import { defineComponent, ref } from 'vue';
import MyInput from './components/MyInput.vue';

export default defineComponent({
  components: {
    MyInput
  },
  setup() {
    const myTitle = ref('');
    const myContent = ref('');

    return {
      myTitle,
      myContent
    };
  }
});
</script>

四、v-model 修饰符:锦上添花的利器

Vue 3 还支持 v-model 修饰符,可以进一步控制 v-model 的行为。 这就像给你的瑞士军刀配上了更多的小工具,让你可以应对各种复杂的场景。

常用的修饰符包括:

  • .trim: 自动去除输入值的首尾空格。
  • .number: 将输入值转换为 Number 类型。
  • .lazy: 将 input 事件切换为 change 事件。

例如:

<input type="text" v-model.trim="message">

这个例子中,.trim 修饰符会自动去除 message 首尾的空格。

那么,如何在自定义组件中支持这些修饰符呢? 其实很简单,只需要在 emit 事件时,将修饰符传递给父组件即可。

假设我们要在 MyInput 组件中支持 .trim 修饰符:

// MyInput.vue
<template>
  <input
    type="text"
    :value="modelValue"
    @input="updateValue"
  >
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    modelValue: {
      type: String,
      default: ''
    },
    modelModifiers: { // 新增:接收修饰符
      type: Object,
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const updateValue = (event) => {
      let value = event.target.value;
      if (props.modelModifiers.trim) { // 判断是否使用了 .trim 修饰符
        value = value.trim();
      }
      emit('update:modelValue', value);
    };

    return {
      updateValue
    };
  }
});
</script>
  • props: { modelModifiers }: 我们新增了一个名为 modelModifiers 的 prop,用于接收修饰符。 v-model 会自动将修饰符传递给这个 prop,它是一个对象,包含了所有使用的修饰符。
  • if (props.modelModifiers.trim): 我们判断是否使用了 .trim 修饰符,如果使用了,则去除输入值的首尾空格。

在父组件中使用:

// App.vue
<template>
  <my-input v-model.trim="myValue"></my-input>
  <p>当前的值是:{{ myValue }}</p>
</template>

<script>
import { defineComponent, ref } from 'vue';
import MyInput from './components/MyInput.vue';

export default defineComponent({
  components: {
    MyInput
  },
  setup() {
    const myValue = ref('');

    return {
      myValue
    };
  }
});
</script>

五、总结:v-model 的奥秘与力量

通过上面的讲解,相信大家已经对自定义组件的 v-model 有了更深入的理解。 简单来说,要实现自定义组件的 v-model,需要做到以下几点:

  1. 定义 modelValue prop: 用于接收父组件传递的值。
  2. 触发 update:modelValue 事件: 用于将新的值传递给父组件。
  3. 使用 v-model 参数: 可以自定义 prop 和事件的名称。
  4. 使用 modelModifiers prop: 可以接收修饰符,并根据修饰符修改 v-model 的行为。

下面用表格总结一下v-model的要点:

功能 默认行为 自定义组件实现 备注
数据绑定 绑定到元素的 value 属性 定义一个名为 modelValue 的 prop,用于接收父组件传递的值。如果需要绑定到其他属性,可以使用 v-model:propName 这允许你将 v-model 绑定到组件内部的任何属性,而不仅仅是 value
事件监听 监听元素的 input 事件 触发一个名为 update:modelValue 的事件,并将新的值传递给父组件。如果使用了 v-model:propName,则需要触发 update:propName 事件。 这允许你监听组件内部的任何事件,并在事件触发时更新父组件中的绑定值。
修饰符支持 支持 .trim.number.lazy 等修饰符 定义一个名为 modelModifiers 的 prop,用于接收修饰符。根据 modelModifiers 对象中的属性值,修改 v-model 的行为。 例如,如果 modelModifiers.trimtrue,则去除输入值的首尾空格。 这允许你自定义 v-model 的行为,例如自动去除空格、将输入值转换为数字等。
简化语法 提供了 v-model 语法糖,简化了代码书写 无需额外操作。只要按照上述步骤实现自定义组件,就可以使用 v-model 语法糖。 v-model 语法糖会自动展开为 :value="value" @input="value = $event.target.value"v-bind:value="value" v-on:input="value = $event.target.value",从而简化代码书写。
双向数据绑定 实现了父子组件之间的双向数据绑定 通过 prop 和事件,实现了父子组件之间的双向数据绑定。父组件通过 prop 将数据传递给子组件,子组件通过事件将新的数据传递给父组件。 这使得父子组件之间的数据同步变得非常方便。
默认值 默认情况下,v-model 绑定到 value 属性 在定义 modelValue prop 时,可以设置默认值。例如,modelValue: { type: String, default: '' } 表示 modelValue 的默认值为 '' 这允许你在没有传递 modelValue prop 时,使用默认值。
类型检查 在定义 modelValue prop 时,可以设置类型。例如,modelValue: { type: String } 表示 modelValue 必须是字符串类型。 这可以帮助你避免类型错误。
事件名称约定 默认监听 input 事件 约定使用 update:modelValue 作为事件名称,以便 v-model 能够正确地监听事件。 遵循此约定可以确保 v-model 的行为符合预期。
单文件组件 适用于任何 Vue 组件 适用于任何 Vue 组件。 无论你使用单文件组件还是其他类型的 Vue 组件,都可以使用 v-model
多个 v-model 不支持同时使用多个 v-model 可以使用 v-model 参数来实现多个 v-model。例如,<my-input v-model:title="myTitle" v-model:content="myContent"></my-input> 这允许你同时绑定多个属性到不同的 v-model

掌握了这些技巧,你就可以像一位经验丰富的魔法师一样,赋予你的自定义组件强大的互动能力,让它们在你的项目中发挥更大的作用。

今天的讲座就到这里,希望对大家有所帮助。 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注