模型和子组件?

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

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

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

113774 次浏览

正如文件 所述,

v-model是一种语法糖,表示:

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

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

  • 为组件指定一个 value支撑
  • 使用 计算机二进制引导程序作为内部值的计算属性(因为您不应该从组件内部修改道具的值)
  • 为返回 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中的突发变化

根据文档 ,Vue 3中的 v-model 实现有重大变化:

  • - > modelValue
  • - > update:modelValue

在主实例中使用 sync,如果使用 vue > 2.2,则需要在组件中使用 emit

看看这个: - https://alligator.io/vuejs/upgrading-vue-2.3/#propsync

一个简单的例子(vu2.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>

在子组件中指定一个 :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>

屏幕

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

屏幕

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


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


export default {
components: { MyInput },


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

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

父组件

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


Vue 2的解决方案

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

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

以下是 $attrs 的文档:

包含不被识别(和提取)为道具的父范围属性绑定(除了 classstyle)。当一个组件没有任何声明的道具时,它实际上包含所有的父范围绑定(除了 classstyle) ,并且可以通过 v-bind=" $attrs"-在创建高阶组件时非常有用传递给内部组件。

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

以下是 $listener 的文档:

包含父范围 v-on 事件侦听器(没有 .native修饰符)。这可以通过 v-on="$listeners"-在创建透明包装器组件时非常有用传递给内部组件。

因为 v-model只是 v-bind + v-on的简写,所以它也被转发。

请注意,这种技术从 Vue 2.4.0(2017年7月)开始就可用了,在 Vue 2.4.0中,这个特性被描述为“更容易创建包装器组件”。

Vue 3的解决方案

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

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

下面是 $attrs的文件:

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

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

这是 inheritAttrs的文件

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

Vue 2的另一个不同之处是 $attrs对象现在包括 classstyle

以下是 来自“禁用属性继承”的片段:

通过将 inheritAttrs选项设置为 false,您可以控制应用到其他元素属性以使用组件的 $attrs属性,该属性包括组件 propsemits属性 (例如 ABC5、 ABC6、 v-on听众等)中未包含的所有属性。

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

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>

使用以下方法可以传递所有输入属性,比如占位符:

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>

为了 Vue 3

在接受的答案中提到的 value支柱已经成为 modelValue,并且发射事件也相应地被修改:

Https://v3-migration.vuejs.org/breaking-changes/v-model.html#migration-strategy

^ 通过在迁移策略中建议的少量更改来实现已接受的答案,从而使其工作。

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

父组件

const value = ref('');


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

子组件

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


<input v-modelValue="value" />