背景
vue开发时,新手经常会在控制台看到一个警告:vue.runtime.esm.js:4448 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
这段警告大致意思是:
vue.runtime.esm.js:4448 [Vue 警告]:避免直接改变 prop,因为每当父组件重新渲染时,该值都会被覆盖。相反,请使用基于 prop 值的数据或计算属性。正在变异的道具:“value”
这涉及到一个vue框架的特性–单向数据流
Vue.js 是一个用于构建用户界面的渐进式框架,它设计时考虑了易用性和灵活性。在 Vue.js 中,数据绑定是核心特性之一,它允许开发者将数据从组件的模型同步到视图层。Vue 的数据绑定机制遵循单向数据流的原则,这意味着数据的流动是单向的,从父组件流向子组件。
单向数据流的概念:
-
数据从父组件流向子组件:在 Vue 组件中,父组件可以通过 props 将数据传递给子组件。子组件不能直接修改通过 props 接收到的 props 数据,但可以将 props 数据复制到自己的 data 中,然后进行修改。
-
事件从子组件流向父组件:子组件不能直接修改父组件的数据,但可以通过自定义事件(使用
$emit
)将数据变化通知给父组件。父组件监听这些事件,并根据需要更新自己的状态。 -
避免数据的双向绑定问题:单向数据流有助于避免组件之间的数据同步问题,使得数据的流向更加清晰和可预测。
单向数据流的优点:
- 可预测性:数据流动的单一方向使得状态变化更容易追踪。
- 易于调试:由于数据流的清晰性,开发者可以更容易地定位问题。
- 组件的独立性:组件之间通过 props 和事件进行通信,减少了组件间的耦合。
实现单向数据流的模式:
- Props:父组件通过 props 将数据传递给子组件。
- 事件:子组件通过
$emit
发出事件,父组件监听这些事件并响应。 - 计算属性:基于 props 的数据,子组件可以创建计算属性来处理数据。
- 方法:子组件可以定义方法来响应事件,但这些方法不能直接修改 props。
示例:
<template>
<div class="inputComp-container">
<h4>子组件</h4>
<el-input v-model="value" placeholder="请输入内容"></el-input>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
}
}
}
</script>
<template>
<div>
<h3>父组件</h3>
<span>{{ word }}</span>
<inputComp :value="word"></inputComp>
</div>
</template>
<script>
import inputComp from './inputComp'
export default {
components: {
inputComp
},
data () {
return {
word: ''
}
}
}
</script>
这么写,子组件会直接修改props
传入的value
属性,从而打破了单向数据流,vue就会在控制台报警告。
解决方法1(emit事件)
子组件:
<template>
<div class="inputComp-container">
<h4>子组件</h4>
<el-input v-model="inputWord" @change="$emit('change', inputWord)" placeholder="请输入内容"></el-input>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
inputWord: ''
}
}
}
</script>
父组件:
<template>
<div>
<h3>父组件</h3>
<span>{{ word }}</span>
<inputComp :value="word" @change="($event) => word = $event"></inputComp>
</div>
</template>
<script>
import inputComp from './inputComp'
export default {
components: {
inputComp
},
data () {
return {
word: ''
}
}
}
</script>
解决方法2(利用v-model语法糖)
本质和上面的方法一致,通过emit
事件给父组件,去修改父组件的值i,从而不会打破单向数据流。
只需要子组件emit
特定的input
时间,父组件即可直接写v-model
,不用单独写对input
事件的处理。
子组件:
<template>
<div class="inputComp-container">
<h4>子组件</h4>
<el-input v-model="inputWord" @change="$emit('input', inputWord)" placeholder="请输入内容"></el-input>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
inputWord: ''
}
}
}
</script>
父组件:
<template>
<div>
<h3>父组件</h3>
<span>{{ word }}</span>
<inputComp v-model="word"></inputComp>
</div>
</template>
<script>
import inputComp from './inputComp'
export default {
components: {
inputComp
},
data () {
return {
word: ''
}
}
}
</script>
解决方法3(.sync
修饰符)推荐
vue官方也提供了一个修饰符去解决这个问题:
官方介绍:https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6
用法代码:
子组件:
<template>
<div class="inputComp-container">
<h4>子组件</h4>
<el-input v-model="inputWord" @change="$emit('update:value', inputWord)" placeholder="请输入内容"></el-input>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
inputWord: ''
}
}
}
</script>
父组件:
<template>
<div>
<h3>父组件</h3>
<span>{{ word }}</span>
<inputComp :value.sync="word"></inputComp>
</div>
</template>
<script>
import inputComp from './inputComp'
export default {
components: {
inputComp
},
data () {
return {
word: ''
}
}
}
</script>
以上解决方案,均适用于Vue2版本,部分方法可能vue3有变化,具体看实际效果。