v-model语法糖 用于父组件与子组件值绑定(Unexpected mutation of “XXX” prop vue/no-mutating-props)

v-model

最开始接触v-model,是用于input原生组件中,可以实现值的双向绑定,在input中输入 ,text也会同步更新

<input v-model="text" />

其实际是一个语法糖,展开如下

<input
  v-bind:value="text"
  @input="text = $event.target.value"
/>

通过这样的方式实现了值的双向绑定

父子组件

我们使用v-model语法糖,同样可以实现父组件传递给子组件的值实现‘双向绑定’,实现‘子组件中的值发生改变,相对应的父组件的值会自动更新改变’

但是首先我们需要记住的原则是,prop是单向数据流在子组件中是不能修改prop的,否则就会出现Unexpected mutation of “XXX” prop vue/no-mutating-props这样的报错。

那v-model是如何实现“双向数值绑定的呢”,其实还是需要子组件向父组件emit一个事件,不过父组件不需要再额外定义方法,只需使用v-model进行绑定即可

示例

定义一个father组件

<template>
  <div>
    <div>{{ value }}</div>
    <div>{{ value2 }}</div>
    <child v-model:value="value" v-bind:value2="value2"></child>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import child from "@/views/test/vmodel-test/child.vue";
let value = ref(111);
let value2 = ref({ value: 222 });
</script>

<style scoped></style>

其中value是通过v-model语法糖进行绑定的,其展开应该是这样子的:

<child
  :value="value"
  @update:value="newValue => value= newValue"
/>

那么我们就需要在子组件中定义一个emit事件为**@update:value**

<template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
  </div>
</template>

<script lang="ts" setup name="child">
defineEmits(["update:value"]);
const props = defineProps<{ value: number; value2: { value: number } }>();
console.log(props.value);
</script>

<style scoped></style>

通过emit向父组件传递事件,通过这样的方式实现修改value变量的值,当点击h1标签时,父组件value变量的值会发生相应的变化。

实验一

如果我们在子组件中再次使用v-model绑定到原生组件input上,会发生什么呢?

<template></template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
    <input type="text" v-model="value" />
  </div>
</template>

<script lang="ts" setup name="child">
defineEmits(["update:value"]);
const props = defineProps<{ value: number; value2: { value: number } }>();
console.log(props.value);
</script>

<style scoped></style>

显然是会报错的,因为input展开是这样的

<input
  v-bind:value="value"
  @input="value= $event.target.value"
/>

报错如下:

v-model cannot be used on a prop, because local prop bindings are not writable.

因为此时还是修改了value的值

我们需要采用最原始的写法了,当触发input事件时需要向父组件emit事件:

<template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
    <input type="text" :value="value" @input="$emit('update:value', $event.target.value)" />
  </div>
</template>

<script lang="ts" setup name="child">
defineEmits(["update:value"]);
const props = defineProps<{ value: number; value2: { value: number } }>();
console.log(props.value);
</script>

<style scoped></style>

效果如图,在input中输入值父组件和子组件的值都会更新:
在这里插入图片描述

实验二

如果子组件中继续嵌套一个组件,将value值继续传递给子组件,此时使用v-model情况是怎样的,会不会报错,会不会直接更新第一层父组件的value?

定义一个grandchild组件

<template>
  <div>
    {{ value }}
  </div>
</template>

<script lang="ts" setup>
type prop = {
  value: string;
};
const props = defineProps<prop>();
</script>

<style scoped></style>

在子组件中引入

<template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
    <input type="text" :value="value" @input="handleInput($event)" />
    <grand-child v-model:value="value"></grand-child>
  </div>
</template>

<script lang="ts" setup name="child">
import grandChild from "./grandchild.vue";
const emit = defineEmits(["update:value"]);
const props = defineProps<{ value: string; value2: { value: number } }>();
console.log(props.value);
const handleInput = event => {
  emit("update:value", event.target.value);
  console.log(event.target.value);
};
</script>

<style scoped></style>

此时发生报错
v-model cannot be used on a prop, because local prop bindings are not writable.
Use a v-bind binding combined with a v-on listener that emits update:x event instead.
prop是不能被修改的

那如何解决呢,有两种方法

方法一 定义emit事件

用emit方法向上传递,grandchild组件修改如下

<template>
  <div @click="$emit('handleClick', Math.random() + 1)">grand-child: {{ value }}</div>
</template>

<script lang="ts" setup>
defineEmits(["handleClick"]);

type prop = {
  value?: string;
  value2?: { value: number };
};
const props = defineProps<prop>();
console.log(props.value);
</script>

<style scoped></style>

child组件修改如下:

<template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
    <input type="text" :value="value" @input="handleInput($event)" />
    <grand-child :value="value" @handle-click="newValue => $emit('update:value', newValue)"></grand-child>
  </div>
</template>

<script lang="ts" setup name="child">
import grandChild from "./grandchild.vue";
const emit = defineEmits(["update:value"]);
const props = defineProps<{ value: string; value2: { value: number } }>();
console.log(props.value);
const handleInput = event => {
  emit("update:value", event.target.value);
  console.log(event.target.value);
};
</script>

<style scoped></style>

一层层向上emit,直到到达父组件更新value

方法二 使用计算属性

修改child组件中的value为计算属性,<grand-child v-model:value="value"></grand-child>使用v-model就不会报错:

<template>
  <div>
    <h1 @click="$emit('update:value', Math.random())">{{ value }}</h1>
    <h3>{{ value2 }}</h3>
    <input type="text" :value="value" @input="handleInput($event)" />
    <grand-child v-model:value="value"></grand-child>
  </div>
</template>

<script lang="ts" setup name="child">
import { computed } from "vue";
import grandChild from "./grandchild.vue";
const emit = defineEmits(["update:value"]);
const props = defineProps<{ value: string; value2: { value: number } }>();
console.log(props.value);
const handleInput = event => {
  emit("update:value", event.target.value);
  console.log(event.target.value);
};
const value = computed({
  get() {
    return props.value;
  },
  set(newValue: string) {
    emit("update:value", newValue);
  }
});
</script>

<style scoped></style>

接着grandchild中便可以继续使用update:value事件完成修改value的值

<template>
  <div @click="$emit('update:value', Math.random() + 10)">grand-child: {{ value }}</div>
</template>

<script lang="ts" setup>
defineEmits(["handleClick", "update:value"]);

type prop = {
  value?: string;
  value2?: { value: number };
};
const props = defineProps<prop>();
console.log(props.value);
</script>

<style scoped></style>

效果如图,触发grandchild的click事件,父组件中的value实现更新,再一次使用了语法糖v-model

在这里插入图片描述

实际使用场景 el-dialog的二次封装

对于el-dialog的数值绑定,官方给出的是v-model,即el-dialog子组件可能也要修改dialogVisible的值(可能是视图上的×图标)

 <el-dialog
    v-model="dialogVisible">
 </el-dialog>

但是如果此时我们对el-dialog进行了二次封装,即dialogVisible值是来自于父组件的prop,此时我们如果直接绑定的话便会报错Unexpected mutation of “XXX” prop vue/no-mutating-props,因此可以考虑使用方法二将其转换为计算属性,便不会再报错

v-model修饰符

参考官方文档即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值