英文:
Sibling components sharing state instead of have individual one. Vue
问题
I wrote a component that uploads files to the server. It accepts external parameters and has internal ones like const a = ref<>();
. Then I create multiple instances of it in the parent component and give them different props, but they are bugging and share one state.
I transfer different props to the children, but even if I give them the same props, they're not supposed to share internal state variables like IsActive
, but they do.
ParentComponent.vue
<el-form ...>
<el-tabs>
<el-tab-pane>
<el-form-item>
<file-uploader
:fileList="itemODS.tech_cond_files"
@updateFiles="
async (files) => {
itemODS.tech_cond_files = files;
const response = await saveOdsOisId({
id: itemODS.id,
ois_id: itemODS.ois_id,
tech_cond_files: itemODS.tech_cond_files,
});
}
"
></file-uploader>
</el-form-item>
<el-form-item>
<file-uploader
:fileList="itemODS.project_files"
@updateFiles="
async (files) => {
itemODS.project_files = files;
const response = await saveOdsOisId({
id: itemODS.id,
ois_id: itemODS.ois_id,
project_files: itemODS.project_files,
});
}
"
></file-uploader>
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
fileUploader.vue
<template>
<div class="main">
<div id="content" draggable="false" :class="getContentClass()">
<input .../>
<el-table :data="localFileList" style="width: 100%">
<el-table-column prop="name">
<template #default="scope">
<span class="el-link" @click="handleFileClick(scope.row)">{{ scope.row.name }}</span>
</template>
<el-table-column .../>
</el-table>
<button .../>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
// ...
const localFileList = ref<FileEmbed[]>([]);
const fileService = useFileService();
const overlay = ref<HTMLElement>();
const isActove = ref<boolean>(false);
// ...
const props = defineProps<{
fileList: FileEmbed[];
keepDeletedFile?: boolean;
}>();
watch(
() => props.fileList.length,
() => {
localFileList.value = props.fileList;
}
);
const emit = defineEmits<{
updateFiles: [files: FileEmbed[]];
}>();
async function uploadFile(event: Event) {
const target = event.target as HTMLInputElement;
if (target.files != null && target.files.length >= 0) {
const file = await fileService.uploadFile(target.files[0]);
localFileList.value.push(file);
emit("updateFiles", localFileList.value);
}
}
function handleDelete(index: number) {
// ...
emit("updateFiles", localFileList.value);
}
</script>
英文:
I wrote component that upload file to the server. It accepts external parameters and has internal one like const a = ref<>();
. Then I create multiple instances of it in parent, and give them different props, but they are bugging and share one state.
I transfer to children different props, but even if I'll give them same props, they're not supposed to share internal state variables like IsActive
, but they do.
ParentComponent.vue
<el-form ...>
<el-tabs>
<el-tab-pane>
<el-form-item>
<file-uploader
:fileList="itemODS.tech_cond_files"
@updateFiles="
async (files) => {
itemODS.tech_cond_files = files;
const response = await saveOdsOisId({
id: itemODS.id,
ois_id: itemODS.ois_id,
tech_cond_files: itemODS.tech_cond_files,
});
}
"
></file-uploader>
</el-form-item>
<el-form-item>
<file-uploader
:fileList="itemODS.project_files"
@updateFiles="
async (files) => {
itemODS.project_files = files;
const response = await saveOdsOisId({
id: itemODS.id,
ois_id: itemODS.ois_id,
project_files: itemODS.project_files,
});
}
"
></file-uploader>
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
fileUploader.vue
<template>
<div class="main">
<div id="content" draggable="false" :class="getContentClass()">
<input <...>/>
<el-table :data="localFileList" style="width: 100%">
<el-table-column prop="name">
<template #default="scope">
<span class="el-link" @click="handleFileClick(scope.row)">{{
scope.row.name
}}</span>
</template>
<el-table-column .../>
</el-table>
<button .../>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from "vue";
<...>
const localFileList = ref<FileEmbed[]>([]);
const fileService = useFileService();
const overlay = ref<HTMLElement>();
const isActove = ref<boolean>(false);
<...>
const props = defineProps<{
fileList: FileEmbed[];
keepDeletedFile?: boolean;
}>();
watch(
() => props.fileList.length,
() => {
localFileList.value = props.fileList;
}
);
const emit = defineEmits<{
updateFiles: [files: FileEmbed[]];
}>();
async function uploadFile(event: Event) {
const target = event.target as HTMLInputElement;
if (target.files != null && target.files.length >= 0) {
const file = await fileService.uploadFile(target.files[0]);
localFileList.value.push(file);
emit("updateFiles", localFileList.value);
}
}
function handleDelete(index: number) {
<...>
emit("updateFiles", localFileList.value);
}
</script>
答案1
得分: 1
I think your main problem is that you use document.getElementById()
on non-unique ids - having several components means having several elements with the same id, and document.getElementById()
will always give you the first, no matter from which component you call it.
To access HTML elements in Vue, use template refs instead of document.getElementById()
, just assign a ref
attribute to the element:
<input ref="uploader" .../>
and then in the script, declare a ref with the name passed to the ref
attribute:
const uploader = ref()
Vue will then bind the HTML element to the variable.
英文:
I think your main problem is that you use document.getElementById()
on non-unique ids - having several components means having several elements with the same id, and document.getElementById()
will always give you the first, no matter from which component you call it.
To access HTML elements in Vue, use template refs instead of document.getElementById()
, just assign a ref
attribute to the element:
<input ref="uploader" .../>
and then in the script, declare a ref with the name passed to the ref
attribute:
const uploader = ref()
Vue will then bind the HTML element to the variable.
However, better yet, don't use direct DOM manipulation at all. Using template refs is necessary when you need to access element functionality (like getting the context from a canvas element). In almost all other cases, it goes against the design principles of Vue, which offers better mechanisms to achieve your goal.
For example, you use a ref on the file input element to trigger click events, which is fine, but all other elements are used to set listeners with element.addEventListener()
, which you can do better by using the same Vue syntax as you do with @click
in the <el-button>
.
See playground without addEventListener
So replacing the getElementById()
statements with template refs, or even better, replacing the addEventListener()
calls with Vue event listener statements should fix the issue.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论