【问题标题】:v-model and child components?v-model 和子组件?
【发布时间】:2018-04-28 22:41:21
【问题描述】:

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

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

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

【问题讨论】:

标签: vue.js vuejs2


【解决方案1】:

As stated in the documentation,

v-model 是语法糖:

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

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

  • 为组件指定 value 属性
  • 使用 computed setter 为内部值创建一个计算属性(因为您不应从组件内修改 prop 的值)
  • 为返回 value 属性值的计算属性定义一个 get 方法
  • 为计算属性定义一个set 方法,该方法在属性更改时发出带有更新值的input 事件

这是一个简单的例子:

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

new Vue({
  el: '#app',
  data() {
    return { 
      foo: 'bar' 
    }
  }
})
<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="foo" @input="foo = $event"></my-input>

  {{ foo }}
</div>

感谢 @kthornbloom 发现之前实施的问题。

Vue 3 中的重大变化

Per the documentation,Vue 3 中的 v-model 实现发生了重大变化:

  • value -> modelValue
  • input -> update:modelValue

【讨论】:

  • v-model 自动执行此操作,将变量设置为绑定到发出的值。您也可以通过子组件标签上的@input 显式监听它。
  • foo 在父作用域中用作v-modelmy-input 组件(&lt;my-input v-model="foo"&gt;&lt;/my-input&gt;)。这意味着foo 的值作为value 属性传递给my-input 组件,当my-input 组件发出inputVal 的值时,父作用域中的v-model 正在监听该值input 事件并自动设置foo
  • @GaryO,不,这是因为您不应该修改道具的值。 vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
  • @thanksd - 在您的示例中,如果您复制其中一个输入字段然后编辑其中一个,为什么第二个字段的值不会更新?如果它是原生元素而不是组件,它会。
  • @kthornbloom 哈哈,因为我的例子并不完全正确,你是第一个注意到的。该组件不会更新,因为它没有对 value 属性的更改做出反应。更新了我的示例,使其按预期工作。感谢您的提问!
【解决方案2】:

在子组件上指定一个:value 属性和一个@input 事件,然后你可以在父组件中使用v-model 语法。

Vue 2

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: () => ({
    name: ''
  })
};
</script>

Vue 3

MyInput.vue

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

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

Screen.vue

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

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

export default {
  components: { MyInput },

  data: () => ({
    name: ''
  })
};
</script>

【讨论】:

  • 这应该是公认的答案。这是最简单、最直接的版本。当您需要做的只是通过 v-model 时,不需要在子组件中监视或复制数据
  • 迄今为止最简单的答案
  • 这就是答案。
  • 这里的缺点是 v-modal 你不能为你的道具​​分配一个名字。所以我将 Cameron 的答案与下一个答案结合使用 .sync&lt;input :value="myVal" @input="$emit('update:myVal', $event.target.value)"&gt; 并在父组件中:&lt;my-input myVal.sync="name" /&gt;
  • 顺便说一句,如果您像我一样使用 Vuetify 的 &lt;v-text-field&gt; 而不是 &lt;input&gt;,请将 $event.target.value 替换为 $event
【解决方案3】:

在你的主实例中使用sync,如果你使用vue > 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>

【讨论】:

    【解决方案4】:

    Vue 2 的解决方案

    您可以将所有属性和侦听器(包括v-model)从父级转发到子级,如下所示:

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

    这里是documentation for $attrs

    包含不被识别(和提取)为道具的父范围属性绑定(classstyle 除外)。当组件没有任何声明的 props 时,它本质上包含所有父范围绑定(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

    包含不被识别(和提取)为组件道具或自定义事件的父范围属性绑定和事件。当组件没有任何声明的 props 或自定义事件时,它本质上包含所有父范围绑定,并且可以通过 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属性,其中包括组件propsemits属性未包含的所有属性(例如,classstylev-on 听众等)。

    【讨论】:

    • 这是迄今为止最好的答案 - 非常简单
    【解决方案5】:

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

    父组件

    <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>
    
    

    【讨论】:

    • 我不知道这个解决方案是否有负面影响,但它对我来说很有意义!感谢分享!
    【解决方案6】:

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

    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>
    

    【讨论】:

      【解决方案7】:

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

      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>

      【讨论】:

        【解决方案8】:

        适用于 Vue 3

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

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

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

        【讨论】:

          【解决方案9】:

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

          父组件

          const value = ref('');
          
          // provide value
          provive('value', value);
          

          子组件

          // inject value
          const value = inject('value');
          
          <input v-modelValue="value" />
          

          【讨论】:

            猜你喜欢
            • 2019-02-14
            • 2021-03-10
            • 1970-01-01
            • 2021-04-07
            • 2021-02-21
            • 2019-02-27
            • 2021-12-23
            • 1970-01-01
            • 2018-08-25
            相关资源
            最近更新 更多