问题:Unexpected mutation of "xxxx" prop
在接手别人的代码时,发现代码其中一个组件报错:Unexpected mutation of "xxxx" prop
<el-col :md="6" v-for="(item, index) in queryFormArr" :key="index">
<el-input
v-model.trim="item.val"
class="font-xs item-form-doc"
v-if="item.type == 'number'"
@input="inputCode(item.val, index)"
></el-input>
</el-col>
props: ['queryArr', 'flag'],
watch: {
queryArr: {
// 筛掉label为空的
handler(list) {
if (!list) return
this.queryFormArr = list.filter(item => item.label)
},
immediate: true,
deep: true
}
},
methods: {
inputCode(val, index) {
if (!this.game_reg.test(val)) {
// 提示:Unexpected mutation of "queryArr" prop.
this.queryArr[index].val = ''
}
}
}
分析
该错误由Eslint插件提醒,产生原因是由于在该组件内,修改了父组件传来的props值queryArr。
我们都知道,对于父传子的props值,是不允许在子组件内直接修改的。
一般如果我们需要修改传递来的props值,通常有两个做法比较方便:
1. 在子组件内定义新变量承接props值,对新变量进行各种赋值或修改操作达到交互效果,原本的这个props值咱们就放在那不动它。
2. 在子组件内通过emit方法,把要修改的queryArr新值通过emit传递给父组件,让父组件在其内部进行修改,而不是在子组件内直接对props值操作。
props: ['queryArr', 'flag'],
watch: {
queryArr: {
// 筛掉label为空的
handler(list) {
if (!list) return
this.queryFormArr = list.filter(item => item.label)
},
immediate: true,
deep: true
}
},
methods: {
inputCode(val, index) {
if (!this.game_reg.test(val)) {
// Eslint提示:Unexpected mutation of "queryArr" prop.
this.queryArr[index].val = ''
}
}
}
那么这里我还有一个疑问:
既然我们知道在Vue的单向传递流机制中,不允许子组件直接修改props值,为什么在这里明明修改了,还生效了,仅仅是Eslint插件检测到这种错误写法,而控制台没有任何报错,交互效果也能正常实现呢?
这里就涉及到修改的props值的类型区别了,请看以下代码:
props: ['queryArr', 'flag'],
watch: {
queryArr: {
// 筛掉label为空的
handler(list) {
if (!list) return
this.queryFormArr = list.filter(item => item.label)
},
immediate: true,
deep: true
}
},
methods: {
inputCode(val, index) {
if (!this.game_reg.test(val)) {
// 【修改props的queryArr】
// Eslint提示:Unexpected mutation of "queryArr" prop.
this.queryArr[index].val = ''
// 【修改props的flag】
// TypeError: Cannot read properties of undefined (reading 'type')
this.flag = 'newFlag'
}
}
}
一种是改变引用类型的props,另一种是改变值类型的props。
在上述代码种可以看到,在改变queryArr时,值可以改变并在页面上生效;
而改变flag时,值不能被改变,并且控制台会报错。
这是因为,queryArr是一个引用数据类型,父组件传递来的queryArr只是一个引用,也就是传了一个内存地址(指针)。子组件改变的是queryArr上面的值,并没有改变这个指针,所以系统认为子组件没有引起父组件的值改变,于是就没有报错提示了。
解决
不管怎么说,我们需要解决这个Eslint的提醒,方法有如下三种:
1. 禁掉工作区的Eslint插件(不提倡)
简单粗暴,但是不提倡,禁掉以后更麻烦了,原因不多说。
2. 修改'vue/no-mutating-props'
Eslint插件官方对此给出说明:vue/no-mutating-props
'vue/no-mutating-props'是一个ESLint规则,默认值为false。
它用于在Vue.js的单文件组件中禁止对props进行直接修改,确保props是只读的,以避免副作用和不可预知的行为。
想要不让Eslint禁止你修改props值,具体做法是在ESLint 配置文件中加入以下配置:
// .eslintrc.cjs
module.exports = {
..........
..........
rules: {
// 加入以下配置,让Eslint允许你修改props值
'vue/no-mutating-props': [
'error',
{
shallowOnly: false //默认值为true
}
]
}
}
3. 用新变量代替对props值的修改
其实在代码中可以看到,子组件内部是用了新变量queryFormArr来处理props.queryArr值的,不明白为何当初的开发者没有直接修改queryFormArr。
不管怎么说,用新变量代替props值去修改是更正确的做法。
若涉及到需要将修改后的新值传递回父组件,用emit触发即可。
props: ['queryArr', 'flag'],
watch: {
queryArr: {
// 筛掉label为空的
handler(list) {
if (!list) return
this.queryFormArr = list.filter(item => item.label)
},
immediate: true,
deep: true
}
},
methods: {
inputCode(val, index) {
if (!this.game_reg.test(val)) {
// 修改queryFormArr,而不是props值queryArr
this.queryFormArr[index].val = ''
}
}
}