Svelte/SvelteKit中onMount更新HTML元素报错求助
问题描述
感染新冠期间开发基于Svelte和SvelteKit的Hangman游戏,大部分功能已完成,但在src/lib/HangGrid.svelte中操作Hangman部件的DOM元素时遇到错误。订阅numberWrong状态后,虽能触发响应,但操作节点类名时抛出TypeError,具体代码和报错信息如下:
相关代码
<script lang="ts"> import { numberWrong } from '$lib/stores' import { onMount } from 'svelte' $: console.log('numberWrong from grid', $numberWrong) $: wrong = $numberWrong - 1 let pieces_of_man: NodeListOf<Element> const setPiecesOfMan = (pieces: NodeListOf<Element>) => { pieces_of_man = pieces console.log(pieces_of_man) } onMount(async () => { setPiecesOfMan(document.querySelectorAll('.piece_of_man')) }) $: current_piece = pieces_of_man[wrong] // 触发错误的代码 $: current_piece.classList.remove('white') $: console.log('current_piece', current_piece) </script>
报错信息
[HMR][Svelte] Unrecoverable HMR error in
: next update will trigger a full reload
logError @ proxy.js?v=02947d2a:15
Proxy@ proxy.js?v=02947d2a:380
create_if_block_2 @ +page.svelte:61
create_if_block_1 @ +page.svelte:57
create_if_block @ +page.svelte:56
update @ +page.svelte:55
update @ index.mjs:1273
flush @ index.mjs:1233
Promise.then (async)
schedule_update @ index.mjs:1184
make_dirty @ index.mjs:2066
(anonymous) @ index.mjs:2104
handleSubmit @ +page.svelte:40
(anonymous) @ index.mjs:428HangGrid.svelte:18 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '-1')
at $$self.$$.update (HangGrid.svelte:18:21)
at init (index.mjs:2109:8)
at new HangGrid (HangGrid.svelte:23:47)
at createProxiedComponent (svelte-hooks.js?v=02947d2a:341:9)
at new ProxyComponent (proxy.js?v=02947d2a:242:7)
at new Proxy(proxy.js?v=02947d2a:349:11)
at Array.create_if_block_2 (+page.svelte:61:11)
at Array.create_if_block_1 (+page.svelte:57:16)
at Array.create_if_block (+page.svelte:56:16)
at Object.update [as p] (+page.svelte:55:19)
问题原因
- 初始化顺序冲突:组件初始化时
onMount尚未执行,pieces_of_man处于undefined状态,但响应式语句$: wrong = $numberWrong -1和$: current_piece = pieces_of_man[wrong]会立即执行,导致访问undefined[-1]抛出错误。 - 无边界值判断:即使
pieces_of_man已赋值,当wrong超出节点列表索引范围时,current_piece会变为undefined,后续访问classList仍会报错。
修复方案
方案1:添加守卫判断与初始值
修改响应式逻辑,确保只有在变量有效时才操作DOM:
<script lang="ts"> import { numberWrong } from '$lib/stores' import { onMount } from 'svelte' $: console.log('numberWrong from grid', $numberWrong) // 初始值设为-1,避免初始化时计算负数索引 $: wrong = $numberWrong > 0 ? $numberWrong -1 : -1 let pieces_of_man: NodeListOf<Element> = document.querySelectorAll('.piece_of_man') const setPiecesOfMan = (pieces: NodeListOf<Element>) => { pieces_of_man = pieces console.log(pieces_of_man) } onMount(async () => { setPiecesOfMan(document.querySelectorAll('.piece_of_man')) }) // 添加多层守卫,确保变量有效再执行DOM操作 $: if (pieces_of_man && wrong >=0 && wrong < pieces_of_man.length) { const current_piece = pieces_of_man[wrong] current_piece.classList.remove('white') console.log('current_piece', current_piece) } </script>
方案2:改用Svelte绑定(推荐)
放弃手动查询DOM节点,用Svelte的bind:this直接获取元素引用,更符合框架响应式理念:
<script lang="ts"> import { numberWrong } from '$lib/stores' $: wrong = $numberWrong -1 // 用数组存储每个部件的DOM引用 let manPieces: Element[] = [] $: if (wrong >=0 && wrong < manPieces.length) { manPieces[wrong].classList.remove('white') } </script> <!-- 给每个Hangman部件绑定引用 --> <div class="piece_of_man" bind:this={manPieces[0]}></div> <div class="piece_of_man" bind:this={manPieces[1]}></div> <!-- 按顺序添加剩余部件元素 -->
额外优化建议
使用Svelte的类绑定语法替代手动操作classList,代码更简洁且易维护:
{#each Array(6) as _, index} <!-- 假设Hangman有6个部件 --> <div class="piece_of_man" class:white={index >= $numberWrong}></div> {/each}
同时确保numberWrong的初始值为0,避免出现负数索引。
内容的提问来源于stack exchange,提问作者Tithos

