理论
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
翻译一下就是:Props 中的变量对于子组件来说是只读的,不能由子组件进行修改。
尝试通过下面的方法直接修改属性值
this.$props.modelValue = true;
Vue 将提出警告
Set operation on key “modelValue” failed: target is readonly.
且修改该属性值并不能修改父组件对应的变量(仅仅适用于基础数据类型)。
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态,且 Vue 无法为此向你发出警告。作为一个通用规则,应该避免修改任何 prop,包括对象和数组,因为这种做法无视了单向数据绑定,且可能会导致意料之外的结果。
正确的修改方法是触发子组件事件,父组件监听该事件并执行修改父组件的函数,以 v-model
为例,v-model
本身就是传递给子组件的 modelValue
属性,然后监听 update:modelValue
事件来做到双向数据绑定,所以我们也可以通过该方法来在代码中修改 modelValue
this.$emit("update:modelValue", !this.$props.modelValue);
v-model
将自动监听 update:modelValue
事件并将 modelValue
修改为触发事件时传递的参数值(即 $emit 的第二个参数)。
需要注意的是,这种通过触发事件-监听事件的数据流模式并不能马上生效,如果使用以下代码
this.$emit("update:modelValue", !this.$props.modelValue);
console.log(this.$props.modelValue);
会发现输出到控制台的仍然是 modelValue
原先的值,这是因为触发事件-监听事件的模式下属性值的修改需要时间,改用以下代码会发现输出正常:
this.$emit("update:modelValue", !this.$props.modelValue);
setTimeout(()=> {console.log(this.$props.modelValue)}, 1000)
实践
由于 Vant 只实现了左边放 label
的输入框,想基于此实现一个 label
在右边的输入框
这其中遇到的问题除了把 label
右置并根据 label
的内容自适应宽度外,就是 Vant 中的 <van-field />
与我需要实现的 <right-label-input />
以及放置 <right-label-input>
的父组件之间的数据流了。
错误做法
为了能够与 <van-field />
一致使用 v-model
,我在 <right-label-input />
下设置 modelValue
属性,然后将 <van-field>
的 v-model="modelValue"
<template>
<van-field :placeholder="placeholder" :name="name" v-model="modelValue" :disabled="disabled"/>
</template>
<script>
export default {
name: "RightLabelInput",
props: {
modelValue: "",
},
}
</script>
无疑,这种做法违背了单向数据流原则,在 VanField
修改时将直接影响 RightLabelInput
的 modelValue
值,而 modelValue
是 RightLabelInput
的属性,不能直接修改。
可能正确的做法
<template>
<van-field :placeholder="placeholder"
:name="name"
v-model="input"
:disabled="disabled"
/>
</template>
<script>
export default {
name: "RightLabelInput",
props: {
placeholder: "",
name: "",
label: "",
modelValue: "",
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
input: this.modelValue, // 绑定到 data 的变量,防止直接修改 Props
}
},
beforeMount() {
this.input = this.modelValue
},
watch: {
input(newInput, oldInput) {
this.$emit("update:modelValue", newInput); // 监听 input 在修改时发起事件,由父组件修改 modelValue 的值
},
modelValue(newValue, oldValue) {
this.input = newValue;
}
}
}
</script>
<style scoped>
</style>
关键词:单向数据流