Vue3 自定义组件使用v-model

发布时间 2023-08-05 17:24:30作者: 伊卡洛斯之翼Chris

Vue 的数据流传递是单向的,也就说数据只能由父组件通过props传递给子组件,子组件无法修改父组件传过来的状态,这样做为了避免因为子组件修改父组件的状态而导致数据流混乱以至难以理解。所以千万不要在子组件中直接修改 props。

子组件如果想要将数据传递给父组件,就需要使用 Vue 提供的事件机制,利用emit()方法将数据传递过去。但是请注意,这并没有改变 Vue 单向数据流的本质,数据还是由父组件流向子组件。

Vue Data Flow

在 Vue 中,使用v-model指令可以在<input><checkbox><select>等 HTML 原生表单元素上实现双向数据绑定,它负责监听用户的输入事件并更新数据,但是v-model只是一个语法糖,本质还是使用事件机制,就是上面说的emit()方法来实现双向数据。有如下一个输入框

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

其实等价于下面这种形式:

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

Vue2 自定义组件 v-model

v-model指令不仅可以在原生的 HTML 元素上使用,也可以在自定义组件上使用。

我们封装一个子组件BaseForm.vue

<!-- 子组件 BaseForm.vue -->
<script>
  export default {
    name: 'BaseForm',
    // 自定义prop和事件
    model: {
      event: 'form:update',
      prop: 'formData',
    },
    props: {
      // v-model绑定的值
      formData: {
        type: Object,
        default: () => ({}),
      },
    },
    data() {
      return {
        // 使用currentValue避免子组件直接修改props
        // 使用父组件传递过来的props初始化这个值
        currentValue: {
          ...this.formData,
        },
      };
    },
  };
</script>
<template>
  <form class="base-form">
    <div class="form-control">
      <input type="text" placeholder="username or email" v-model="currentValue.username" />
    </div>
    <div class="form-control">
      <input type="password" placeholder="password" v-model="currentValue.password" />
    </div>
    <div class="form-control">
      <input type="checkbox" v-model="currentValue.checked" />
    </div>
    <div class="form-control">
      <button @click="$emit('form:update', currentValue)">Submit</button>
    </div>
  </form>
</template>

默认情况下,组件上的v-model会利用名为value的 prop 和名为input的事件,如果想要传递自定义的的 prop 和事件,可以通过model选项进行指定。这里我们把 prop 指定为formData,事件指定为form:update,但是需要注意的是,在父组件中需要为v-model指定参数为formData,例如:

<script>
  export default {
    data() {
      return {
        loginForm: {},
      };
    },
    methods: {
      handleFormUpdate(formData) {
        console.log(formData);
      },
    },
  };
</script>
<BaseForm v-model:formData="loginForm" @form:update="handleFormUpdate"></BaseForm>

如果项目中使用了 ESLint 的话,使用这种语法,eslint 会提示'v-model' directives require no argument,意思是不建议在v-model上指定参数,如果真的需要的话,可以关闭这条规则:'vue/no-v-model-argument': 'off'。一般情况下使用默认的value就够了。

最后,在通过自定义的事件form:update将数据传给父组件:

<button @click="$emit('form:update', currentValue)">Submit</button>

Vue3 自定义组件 v-model

Vue3 中的v-model有些许变化:

  1. 内部原生<input>元素的value属性绑定到名为modelValue的 prop
  2. 原生input事件触发时,会触发update:modelValue自定义事件
<script setup>
  defineProps(['modelValue']);
  defineEmits(['update:modelValue']);
</script>

<template>
  <div class="base-input">
    <input
      type="text"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

如果我们想要像 Vue2 中指定自定义的 prop 和事件,不需要用model选项,直接在defineProps()defineEmits()方法中指定即可:

<script setup>
  import { reactive } from 'vue';

  const props = defineProps({
    formData: {},
  });
  defineEmits(['form:update']);

  const currentValue = reactive({ ...props.formData });
</script>

在父组件中使用:

<BaseForm v-model:form-data="loginForm" @form:update="handleFormUpdate" />