天天看点

vue 双向数据绑定 v-model 和 .sync 修饰符就是一个语法糖

正言

vue 是单向数据流,一般从父组件传到子组件的数据只允许在父组件里进行修改,子组件里只允许读取数据而不能进行数据修改,这样可以避免数据混乱,减少维护上带来的问题。不过 vue 官方也提供了两种方法来实现父子组件间的双向数据通信。

方式一

v-model 指令只能用于 input、textarea、单选框、复选框等类型的输入控件,也可以用于组件上,他是一个语法糖,可用于子父组件的双向通信

// home.vue
<template>
  <div class="home">
    <div>在父组件的值 ::: {{ testVal }}</div>
    <hr>
    <!-- 方式一 -->
    <child v-model="testVal"></child>
  </div>
</template>

<script>
import child from '@/views/child.vue'

export default {
  name: 'home',
  components: {
    child
  },
  data () {
    return {
      testVal: '我是初始值'
    }
  }
}
</script>
           
// child.vue
<template>
  <div>
    <!-- v-model -->
    <div>我是子组件 - v-model:::<input type="text" :value="value" @input="valChange" placeholder="子组件"></div>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: [
    'value'
  ],
  data () {
    return {
    }
  },
  methods: {
    valChange (e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>
           

初始页面,如下图:

vue 双向数据绑定 v-model 和 .sync 修饰符就是一个语法糖

修改子组件 input 里的数据,如下图:

vue 双向数据绑定 v-model 和 .sync 修饰符就是一个语法糖

这样就可以实现从子组件改变数据,父组件也跟着一起改变。

v-model 是一个语法糖,上面的写法是下面写法的一个简版:

// home.vue
<template>
  <div class="home">
    <div>在父组件的值 ::: {{ testVal }}</div>
    <hr>
    <!-- v-model 原理 -->
    <child :value="testVal" @input="value => testVal = value"></child>
  </div>
</template>

<script>
import child from '@/views/child.vue'

export default {
  name: 'home',
  components: {
    child
  },
  data () {
    return {
      testVal: '我是初始值'
    }
  }
}
</script>
           

从上面可以看出,v-model 其实是默认绑定一个属性 value,并且监听 input 事件(input 事件是 h5 出的,有兴趣的可以 点击 了解下)。v-model 的实现可以分以下步骤:

1)所以子组件里可以从 props 中获取到父组件传递过来 value (props: [ ‘value’])。

2)当子组件的输入框输入值时,会通过 @input=“valChange” 触发事件 valChange 从而执行 this.$emit(‘input’, e.target.value)。

3)父组件里的 @input=“value => testVal = value” 被触发,又修改了父组件里的 testVal 数据。实现了子组件和父组件数据同时修改。

方式二

.sync 修饰符不能和表达式一起使用,例如:v-bind:val.sync=“testVal + 'aa‘”,等于号右边是表达式,所以是无效的。同样的 .sync 也是一个语法糖。

// home.vue
<template>
  <div class="home">
    <div>在父组件的值 ::: {{ testVal }}</div>
    <hr>
    <!-- 方式二 -->
    <child v-bind:val.sync="testVal"></child>
  </div>
</template>

<script>
import child from '@/views/child.vue'

export default {
  name: 'home',
  components: {
    child
  },
  data () {
    return {
      testVal: '我是初始值'
    }
  }
}
</script>
           
// child.vue
<template>
  <div>
    <!-- .sync -->
    <div>我是子组件 - .sync:::<input type="text" :value="val" @input="valChange2" placeholder="子组件"></div>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: [
    'val'
  ],
  data () {
    return {
    }
  },
  methods: {
    valChange2 (e) {
      this.$emit('update:val', e.target.value)
    }
  }
}
</script>
           
vue 双向数据绑定 v-model 和 .sync 修饰符就是一个语法糖
vue 双向数据绑定 v-model 和 .sync 修饰符就是一个语法糖

这样也可以实现从子组件改变数据,父组件也跟着一起改变。

.sync 是一个语法糖,上面的写法是下面写法的一个简版:

// home.vue
<template>
  <div class="home">
    <div>在父组件的值 ::: {{ testVal }}</div>
    <hr>
    <!-- .sync 原理 -->
    <child :val="testVal" @update:val="value => testVal = value"></child>
  </div>
</template>

<script>
import child from '@/views/child.vue'

export default {
  name: 'home',
  components: {
    child
  },
  data () {
    return {
      testVal: '我是初始值'
    }
  }
}
</script>
           

从上面可以看到 .sync 的实现过程是这样:

1)父组件自定义属性 val

2)子组件通过 props: [ ‘val’] 获取到父组件的数据

3)子组件通过 @input=“valChange2” 在 input 值发生变化时,触发 this.$emit(‘update:val’, e.target.value)

4)父组件通过 @update:val=“value => testVal = value” 监听事件,拿到数据后更新父组件数据。