Vue多次复用子组件出现ref状态共享冲突如何解决?
问题根因
- 核心原因是子组件内的dialog使用了固定全局id
keypadDialog,HTML规范要求id全局唯一,当你复用两次子组件时,页面会存在两个id为keypadDialog的DOM元素。Metro4的Metro.dialog.open('#keypadDialog')方法只会匹配页面中第一个出现的对应id的dialog,所以你操作第二个子组件的输入框时,实际打开、修改的都是第一个子组件的dialog和输入值,就出现了状态联动的假象。 - 另外你当前的子组件没有实现和父组件的双向绑定逻辑,值只会存在子组件内部,也不符合复用组件的通用设计。
修复方案
第一步:动态生成子组件内的dialog id,保证全局唯一
可以给子组件加prop接收唯一标识,也可以内部生成随机id,保证每个子组件的dialog id不重复。
第二步:实现子组件的v-model双向绑定逻辑,兼容父组件取值需求
第三步:修改对应的dialog操作逻辑,用动态id操作当前实例的dialog
修改后的 Child.vue 代码
<template> <!-- 用动态id,保证每个实例唯一 --> <div style="overflow-y: auto;" class="dialog" data-role="dialog" :id="dialogId"> <div style="text-align: center;"> <input v-model="numberKey" class="mt-2" style="font-size:20px; text-align:center; width:90%; height: 60px" readonly /> </div> <br /> <div style="text-align:center"> <button style="width:90px; height:90px;" class="button outline" @click="setVal('1')">1</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('2')">2</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('3')">3</button> </div> <div style="text-align: center"> <button style="width:90px; height:90px;" class="button outline" @click="setVal('4')">4</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('5')">5</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('6')">6</button> </div> <div style="text-align: center"> <button style="width:90px; height:90px;" class="button outline" @click="setVal('7')">7</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('8')">8</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('9')">9</button> </div> <div style="text-align: center"> <button style="width:90px; height:90px;" class="button outline" @click="setVal('A')">A</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('0')">0</button> <button style="width:90px; height:90px;" class="button outline" @click="setVal('S')">S</button> </div> <div style="text-align: center"> <button style="width:90px; height:90px;" class="button outline" @click="setVal('/')">/</button> <button style="width:90px; height:90px;" class="button outline" @click="clearSingle()"><span class="mif-arrow-left"></span></button> <button style="width:90px; height:90px;" class="button outline" @click="clearAll()"><span class="mif-cross-light mt-2"></span></button> </div> <div class="dialog-actions"> <div style="text-align:center;"> <button style="height: 60px; width:80%" class="button dark outline mt-2" @click="closeKeypad()">Ok</button> </div> </div> </div> <div v-if="showKeypad"> <input @click="openKeypad()" type="text" data-role="input" v-model="numberKey" data-search-button="true" readonly> </div> </template> <script lang="ts"> import { defineComponent, ref, unref, computed } from 'vue'; import Metro from 'metro4'; import TabellenKomponente from '../Components/TabellenKomponente.vue' export default defineComponent({ // 支持v-model事件 emits: ['update:modelValue'], components: { TabellenKomponente }, props: { showKeypad: { type: Boolean, default: false }, // v-model绑定的入参 modelValue: { type: String, default: '' }, // 可选父传唯一标识,默认内部生成随机id keypadId: { type: String, default: () => `keypad_${Math.random().toString(36).slice(2)}` } }, setup(props) { // 动态id计算 const dialogId = computed(() => props.keypadId) // 本地状态同步父组件传入的v-model值 const numberKey = ref(props.modelValue) return { filterStat: true, dialogId, numberKey, value: ref(""), counter: 0, } }, watch: { // 监听父组件v-model值变化,同步到本地 modelValue(newVal) { this.numberKey = newVal } }, updated() { if (this.showKeypad && this.counter == 0) { this.counter++; } }, methods: { openKeypad() { Metro.init(); // 用当前实例的动态id打开dialog Metro.dialog.open(`#${this.dialogId}`) }, setVal(val) { this.numberKey = `${this.numberKey}${val}` }, clearSingle() { this.numberKey = this.numberKey.slice(0, -1); }, clearAll() { this.numberKey = ""; }, closeKeypad() { this.value = unref(this.numberKey) // 同步值到父组件v-model this.$emit('update:modelValue', this.numberKey) Metro.init(); // 用当前实例的动态id关闭dialog Metro.dialog.close(`#${this.dialogId}`) }, } }) </script>
父组件使用示例(Parent.vue)
如果需要分别拿到两个子键盘的输入值,直接绑定不同的变量即可:
<template> <TestRefError :showKeypad="shKeyPad" v-model="val1"></TestRefError> <TestRefError :showKeypad="shKeyPad" v-model="val2"></TestRefError> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import TestRefError from '../Components/TestReferror.vue' import Metro from 'metro4'; export default defineComponent({ components: { TestRefError}, setup() { Metro.init() // 分别存储两个键盘的输入值 const val1 = ref('') const val2 = ref('') return { shKeyPad:true, val1, val2 } } }) </script>
补充说明
你之前尝试的reactive、unref都没用是因为问题根本不是响应式对象共享,而是DOM id重复导致的第三方库操作了错误的DOM元素,和Vue的响应式逻辑无关。修改为动态id后每个子组件只会操作自己的dialog实例,状态自然就隔离了,同时v-model的实现也不影响你原来的按钮点击赋值逻辑,本地的numberKey依然是ref声明的响应式变量,可以正常赋值修改。
内容的提问来源于stack exchange,提问作者lela_98
相关产品推荐
相关产品推荐

