深入理解v-model和sync

1,947 阅读1分钟

简介

在开发中经常需要封装一些常用组件,像弹框、对话框等,无论是自己封装还是在第三方组件基础上进行二次封装(为了统一基本配置,精简代码以及后期维护),都会面对比较常见的父子组件通信,用的比较多的 props(接收父组件传来的数据) 和 $emit(将事件和数据发射出去),通过 v-model.sync 语法糖可以简化当一个子组件需要改变了一个 prop 的值时,会通知其父组件进行同步的修改。

v-model

Vue2 中最开始比较常见的就是对表单元素默认绑定

  • text,textarea 使用 value 属性 与 input 事件
  • select 使用 value 与 change 事件
  • radio checkbox 使用 checked 与 change 事件

如 input 的绑定


<input v-model="message" />

<!-- 是以下的简写: -->

<input :value="message" @input='($event) => message = $event.target.value' />

  • message 赋值到 input 的 value 属性,更改了 input 的 value 值,是绑定中的单向绑定
  • 通过监听 input 组件的 input 事件,更改 message 值,完成双向绑定

Vue2.2.0+ 新增自定义组件的 v-model,可以自定义属性(prop)和事件(event)名称,这样更加灵活也可以避免默认的 value 属性值有其它的用处

父组件


<template>
  <children v-model="message"></children>
</template>
<script>
import children from "./children.vue";
export default {
  components: {
    children
  },
  data() {
    return {
      message: "父组件数据"
    }
  }
}
</script>

子组件


<template>
  <h1>{{ message }}</h1>
  <p @click='update'></p>
</template>
<script>
export default {
  model: {
    prop: "message", //父组件设置 v-model 时,将变量值传给子组件的 message
    event: "updateMsg" //父组件监听事件
  },
  props: {
    message: String //定义和model的prop相同的props来接收
  },
  update() {
    //将message传到父组件v-model,实现双向控制,不需要父组件再请@updateMsg接收数据更新message
    this.$emit("updateMsg", "子组件更新数据")
  }
}
</script>

.sync

Vue2.3.0+ 新增,默认以 update:prop 模式触发事件,注意.sync 绑定的 prop 不能是表达式(:title.sync="message + '!'")这种绑定,只能是属性名


<div :title.sync="message" ></div>

<!-- 是以下的简写: -->

<div :title="message" @update:title="message = $event" ></div>

Vue3 v-model 统一替代

一个组件中只能有一个 v-model,但是可以有多个 .sync,在 Vue3 移除了 .sync 修饰符和组件的 model 选项,统一使用 v-model,默认 prop 由 value 改为 modelValue,event 由 input 改为 update:modelValue,相当于结合了 v-model 和 .sync 的优点


<div v-model:title="message" ></div>

<!-- 是以下的简写: -->

<div :title="message" @update:title="message = $event" ></div>

对象数组处理

  • 如果父组件传入的值是简单数据类型(string、number 和 boolean 等)时,子组件必须要使用 $emit 去通知父组件更新数据,如果需要根据值变化来进行其它操作,则父组件需要使用 watch 来监听值的变化
  • 如果父组件传入的值是引用类型时,子组件修改了对象属性,不需要 $emit 去通知父组件更新数据,父组件的数据会直接变化,因为父子组件引用的是同一个对象,封装表单组件时为了便捷有使用这个特性,但不推荐子组件直接修改父组件中的参数,因为子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解,无法追溯,比如子组件和父组件同时在修改这个值,可以通过插槽v-slot来传递表单数据

elementui 对话框二次封装

结合业务统一封装对话框样式和基本功能,方便后期统一维护,以visible控制对话框是否显隐为例


<!-- dialogPage.vue -->
<template>
  <el-dialog :visible.sync="visible">
    <div slot="title" class="dialog-title">标题</div>
    <div class="dialog-content">
      <slot></slot>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button @click="onCancel">取消</el-button>
      <el-button type="primary">确定</el-button>
    </div>
  </el-dialog>
</template>
<script>
export default {
  props: {
    childVisible: {
      type: Boolean,
      default() {
        return false
      }
    }
  },
  computed: {
    // 计算属性默认只有 getter, 在修改变量时需要提供一个 setter 函数
    // 读取一个变量的时候会触发该变量的 getter,修改该变量时候会触发他的 setter
    // 通过计算属性来接收父组件传值,也可以在data中定义数据,使用watch监听可以达到相同效果
    visible: {
      // getter
      get() {
        return this.childVisible
      },
      // setter
      set(val) {
      // 当visible改变的时候,触发 update:childVisible 方法,把最新值传递给父组件,同步修改 childVisible 的值
        this.$emit("update:childVisible", val);
      }
    }
  },
  methods: {
    onCancel(){
      this.visible = false    //  计算属性赋值,触发 set 函数
    }
  }
}
</script>

组件使用时,只需要通过 .sync 绑定 childVisible 值即可,子组件变化会同步到 childVisible 值


<template>
  <dialogPage childVisible.sync="childVisible"></dialogPage>

  <!-- .sync 类似 
  <dialogPage 
     childVisible="childVisible" 
     @update:childVisible="(val) => childVisible = val">
  </dialogPage> 
 简写 -->
  
</template>
<script>
import dialogPage from "./dialogPage.vue";
export default {
  components: {
    dialogPage
  },
  data() {
    return {
      childVisible: false
    }
  }
}
</script>