破酥 | C4iN
Vue3中的响应式丢失问题

Vue3中的响应式丢失问题

最近在写一个自定义首页的小玩意,写的过程中遇到子组件根据父组件传递的props来确定大小无法正确使用的问题,稍微研究了一下是DOM的加载问题,在这里记录下。

突然发现好久没改博客了orz

问题描述

vue3使用querySelector等获取元素只能在onMounted之类DOM挂载时获取

1
2
3
4
5
6
7
8
9
10
11
12
13
const handleSize = () => {
const board = document.getElementById("board")
if (board) {
CanvasWidth.value = board.offsetWidth - 40
CanvasHeight.value = board.offsetHeight - 40
console.log("boardX: ", CanvasWidth.value, "boardY: ", CanvasHeight.value)
}
}

onMounted(() => {
handleSize()
// console.log("boardX: ", CanvasWidth.value, "boardY: ", CanvasHeight.value)
})

但是这种情况下子组件已经被挂载到DOM中,我们知道onMounted 钩子用来在组件完成初始渲染并创建 DOM 节点运行代码,此时传递给子组件的参数并不会起作用。也就是说,当子组件接收到父组件传来的props的时候,DOM已经挂载完了,且数据更新时子组件没有响应。

问题分析

怎么想还是很奇怪,既然我们父组件传给子组件的值是响应式的,那么这个值改变的时候子组件应该也会触发重新挂载DOM。顺藤摸瓜,defineProps是不是有之前没注意过的事情呢。通过在工作台中输出props的值,我们会发现父组件传入的数据变化时,props的值并没有响应数据变化,说明可能存在响应式丢失的问题。

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<script setup lang="ts">
import { ref, toRef } from "vue";

const props = defineProps<{
row: number,
column: number,
canvasX: number,
canvasY: number,
test: string
}>()

const ItemWidth = ref(props.canvasX / props.column)
const ItemHeight = ref(props.canvasY / props.row)

console.log("row ", props.row, "column ", props.column)
console.log("X: ", props.canvasX, "Y: ", props.canvasY)
console.log("itemX: ", ItemWidth.value, "itemY: ", ItemHeight.value)

const msg = toRef(props, 'data');
</script>

<template>
{{ canvasX }}
{{ ItemWidth }}
<div class="item-container" :style="{width: ItemWidth - 10 + 'px', height: ItemHeight - 10 + 'px'}">

</div>
</template>

<style scoped>
.item-container {
margin: 5px;
border-radius: 10px;
background: #242424;
}
</style>

输出结果:

我们可以看到props是更新的,但是我们的ItemWidth, ItemHeight。等等,这里好像哪里不对

解决方法之一就是强制重新挂载DOM:

1
2
3
4
5
6
7
8
9
10
const handleSize = () => {
const board = document.getElementById("board")
if (board) {
CanvasWidth.value = board.offsetWidth - 40
CanvasHeight.value = board.offsetHeight - 40
console.log("boardX: ", CanvasWidth.value, "boardY: ", CanvasHeight.value)
boardKey.value++ // 增加 key 值,触发重新挂载
}
}

1
2
3
4
5
6
7
8
9
<div class="drag-board" id="board" :key="boardKey">
<div class="drag-row" v-for="indexX in row" :key="indexX">
<CapDragGridItem v-for="indexY in column" :key="indexY"
:row="row"
:column="column"
:canvasX="CanvasWidth"
:canvasY="CanvasHeight"/>
</div>
</div>

我们回过头来看刚才不对的地方:

1
2
const ItemWidth = ref(props.canvasX / props.column)
const ItemHeight = ref(props.canvasY / props.row)

ItemWidthItemHeight 不会自动更新。如果 props.canvasXprops.column 发生变化,这两个值不会自动改变,除非手动更新它们。哈哈,这就是问题所在。当需要手动更新某个值,并且希望它是响应式的时,可以使用 ref。而在我们需求下是希望自动更新的。某个值是自动计算得出,并且在依赖的响应式数据变化时自动更新时,可以使用 computed

修改后的代码:

1
2
const ItemWidth = computed(()=> props.canvasX / props.column)
const ItemHeight = computed(() => props.canvasY / props.row)

或者

1
2
3
4
5
<template>
<div class="item-container" :style="{width: canvasX / column - 10 + 'px', height: canvasY / row - 10 + 'px'}">

</div>
</template>

PS

如果我们在CSS中使用100%之类的来给定容器大小,要注意DOM加载时的大小不一定就是最后的大小

总结:我是傻逼。不过还是学了点东西的orz

Author:破酥 | C4iN
Link:https://c4in1.github.io/2024/08/02/vue/Vue3中的DOM问题/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可