v模型和子组件?

时间:2017-11-15 15:58:16

标签: vue.js vuejs2

我有一个表单并使用v-model绑定输入:

<input type="text" name="name" v-model="form.name">

现在我想提取输入并将其作为自己的组件,然后如何将子组件的值绑定到父对象form.name

9 个答案:

答案 0 :(得分:63)

As stated in the documentation

  

v-model只是语法糖:

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">

为自定义组件实现v-model指令:

  • 为组件指定value道具
  • value方法中将数据属性设置为data道具的值(因为您不应该从组件中修改道具的值)。
  • 发生input事件,只要数据属性发生更改,就会发出

这是一个简单的例子:

Vue.component('my-input', {
  template: `
    <div>
      My Input:
      <input v-model="inputVal">
    </div>
  `,
  props: ['value'],
  data() {
    return { inputVal: this.value }
  },
  watch: {
    inputVal(val) {
      this.$emit('input', val);
    }
  }
})

new Vue({
  el: '#app',
  data() {
    return { foo: 'a', bar: 'b' }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
  <!-- using v-model... -->
  <my-input v-model="foo"></my-input>
  
  <!-- is the same as this... -->  
  <my-input :value="bar" @input="bar = $event"></my-input>

  {{ foo }}<br>
  {{ bar }}
</div>

答案 1 :(得分:13)

在您的主要实例中使用sync,如果您使用的是vue&gt; 2.2您需要在组件中使用emit

检查此文档:   - https://alligator.io/vuejs/upgrading-vue-2.3/#propsync

一个简单的例子(使用vue 2.5):

Vue.component('my-input', {
	template: '<input v-on:keyup="onChange($event)" :value="field"></div>',
	props: ["field"],
	methods: {
		onChange: function (event) {
			this.$emit('update:field', event.target.value);
		}
	}
});

var vm = new Vue({
	el: '#app',
	data:{val: ''},
});
h1 span { color: red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<div id='app'>
 <h1>
   value
   <span>{{ val }}</span>
 </h1>
	<my-input :field.sync="val">
   </my-input>
 </div>

答案 2 :(得分:5)

Vue 2解决方案

您可以像这样将所有属性和侦听器(包括v-model)从父级转发到子级:

<input v-bind="$attrs" v-on="$listeners" />

这是documentation for $attrs

包含无法识别为道具的父范围属性绑定(classstyle除外)。当组件没有任何声明的道具时,它实质上包含所有父作用域绑定(classstyle除外),并且可以通过v-bind=" $attrs"向下传递给内部组件-在创建高阶组件时很有用

请确保将inheritAttrs设置为false,以避免将属性应用于根元素(默认情况下,所有属性都应用于根)。

这是documentation for $listeners

包含父作用域v-on事件侦听器(不带.native修饰符)。可以通过v-on="$listeners"将此信息传递到内部组件-在创建透明包装器组件时很有用

由于v-model只是v-bind + v-on的简写,因此也会转发。

请注意,此技术自Vue 2.4.0(2017年7月)开始可用,该功能被描述为“更容易创建包装器组件”。

Vue 3解决方案

Vue 3 删除了$listeners对象,因为侦听器现在也位于$attrs对象中。因此,您只需要这样做:

<input v-bind="$attrs" />

这是documentation for $attrs

包含父范围的属性绑定和事件,这些绑定和事件未被识别(提取)为组件道具或自定义事件。当组件没有任何声明的prop或自定义事件时,它实质上包含所有父作用域绑定,并且可以通过v-bind="$attrs"向下传递到内部组件-在创建高阶组件时很有用。

如果您的组件只有一个根元素(Vue 3允许多个根元素),那么仍然需要将inheritAttrs设置为false,以避免将属性应用于根元素。

这里是documentation for inheritAttrs

默认情况下,未被识别为道具的父范围属性绑定将“掉线”。这意味着当我们有一个单根组件时,这些绑定将作为常规HTML属性应用于子组件的根元素。在创作包装目标元素的组件或另一个组件时,这可能并非总是期望的行为。通过设置 inheritAttrsfalse,可以禁用此默认行为。这些属性可通过$attrs实例属性使用,并且可以使用v-bind明确绑定到非根元素。

与Vue 2的另一个区别是$attrs对象现在包括classstyle

这里是a snippet from "Disabling Attribute Inheritance"

通过将inheritAttrs选项设置为false,您可以控制将属性应用于其他元素以使用组件的$attrs属性,该属性包括组件{{1 }}和props属性(例如emitsclassstyle侦听器等)

答案 3 :(得分:1)

您还可以在子组件上指定:value@input事件,并利用事件而不是创建监视。

MyInput.vue

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

<script>
export default {
  props: ['value']
};
</script>

Screen.vue

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

<script>
import MyInput from './MyInput.vue';

export default {
  components: { MyInput },
  data() {
    return {
      name: ''
    }
  }
};
</script>

答案 4 :(得分:1)

下面的示例向您展示如何从父组件到子组件设置模型以及如何在它们之间同步数据。当您将应用程序表单拆分为不同的组件并在不同的上下文中使用它们时,这非常有用。这样,您可以在不同位置使用表单片段(组件),而无需重复自己的操作。

父组件

<template lang="pug">

  .parent
    Child(:model="model")
    br


    label(for="c") Set "c" from parent  
    input(id="c", v-model="model.c")

    .result.
      <br>
      <span> View from parent :</span>
      <br>
      a = {{ model.a }} 
      <br>
      b = {{ model.b }}
      <br>
      c = {{ model.c }}


</template>

<script>

import Child from './components/child.vue'

export default {

name: "App",

components: {
  Child
  },

  data() {
    return {
      // This model is set as a property for the child
      model: {
        a: 0,
        b: 0,
        c: 0
      }
    }
  },




};
</script>

儿童组件

<template lang="pug">

  .child
    label(for="a") Set "a" from child  
    input(id="a", v-model="internalModel.a", @input="emitModel")
    br
    br

    label(for="b") Set "b" from child  
    input(id="b", v-model="internalModel.b", @input="emitModel")

    .result
      br
      span View from child
      br
      | a = {{ internalModel.a }} 
      br
      | b = {{ internalModel.b }}
      br
      | c = {{ internalModel.c }}

</template>

<script>


export default {

  name: 'Child',
  props: {
    model: {
      type: Object
    }
  },

  data() {
    return {
      internalModel: {
        a:0,
        b:0,
        c:0
      }
    }
  },

  methods: {
    emitModel() {
      this.$emit('input', this.internalModel)
    }
  },
  mounted() {
    this.internalModel = this.model;
  }

}
</script>

答案 5 :(得分:1)

将数据绑定到自定义复选框或复选框集与将其绑定到文本输入完全不同:

https://www.smashingmagazine.com/2017/08/creating-custom-inputs-vue-js/

<template>
  <label>
    <input type="checkbox" :checked="shouldBeChecked" :value="value" @change="updateInput">
    {{ label }}
  </label>
</template>
<script>
export default {
  model: {
    prop: 'modelValue',
    event: 'change',
  },
  props: {
    value: {
      type: String,
    },
    modelValue: {
      default: false,
    },
    label: {
      type: String,
      required: true,
    },
    // We set `true-value` and `false-value` to the default true and false so
    // we can always use them instead of checking whether or not they are set.
    // Also can use camelCase here, but hyphen-separating the attribute name
    // when using the component will still work
    trueValue: {
      default: true,
    },
    falseValue: {
      default: false,
    }
  },
  computed: {
    shouldBeChecked() {
      if (this.modelValue instanceof <span class="hljs-built_in">Array) {
        return this.modelValue.includes(this.value);
      }
      // Note that `true-value` and `false-value` are camelCase in the JS
      return this.modelValue === this.trueValue;
    }
  },
  methods: {
    updateInput(event) {
      let isChecked = event.target.checked;

      if (this.modelValue instanceof Array) {
        let newValue = [...this.modelValue];

        if (isChecked) {
          newValue.push(this.value);
        } else {
          newValue.splice(newValue.indexOf(this.value), 1);
        }

        this.$emit('change', newValue);
      } else {
        this.$emit('change', isChecked ? this.trueValue : this.falseValue);
      }
    }
  }
}
</script>

答案 6 :(得分:1)

使用以下内容,您可以传递所有输入属性,例如占位符:

Vue.component('my-input', {
  template: `<div>
    <input v-bind="$attrs" :value="value" @input="$emit('input', $event.target.value)">
    </div>`,
  inheritAttrs: false,
  props: ["value"],
})
new Vue({
  el: '#app',
  data: () => ({
    name: "",
  }),
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
  <div>Name: {{name}}</div>
  <input placeholder="Standard Input" v-model="name">
  <my-input placeholder="My Input" v-model="name"></my-input>
</div>

答案 7 :(得分:0)

对于Vue 3

已接受的答案中提到的value道具已变为modelValue,并且emit事件也进行了相应的修改:

https://v3.vuejs.org/guide/migration/v-model.html#migration-strategy

^通过实施已接受的答案并进行了迁移策略中建议的一些更改,使其正常工作。

答案 8 :(得分:0)

除了上面的方法,还有一个更简单的实现

父组件

const value = ref('');

// provide value
provive('value', value);

子组件

// inject value
const value = inject('value');

<input v-modelValue="value" />