2025-03-19

Vue.js:ref 對象更動後,要如何在 DOM 的變更結束後,執行 function

我這邊有段 vue code:

<script setup lang="ts">
interface CartItem {
	id: any;
	itemID: any;
	name: string;
	price: number;
	count: number;
}
let cartItem = ref<CartItem[]>([])


</script>
<template>
	<aside>
		<div class="cart-item" v-for="(item) in cartItem">
			<span v-text="item.name"/>
			<span v-text="'$' + item.price"/>
			<input type="number" v-model="item.count" />

		</div>
	</aside>
</template>

這段 code,每次 cartItem 新增一個項目後,就會自動建立一組 <div class="cart-item" />;反之,每刪除一個項目,就會刪掉一個<div class="cart-item" />

我現在要的,是每新增或刪除完一個項目後,自動執行一段 function。

例如新增完畢後,自動 focus 到最新的 input 中。

function(){
	let lastItem = document.querySelector('.cart-item input[type=number]') as HTMLInputElement
	lastItem.focus()			
}

先講原先的錯誤思路

Vue.js 是藉由物件的變化,來新增/刪除 <div class="cart-item" />,所以我原本是這樣寫

function (){
	// 前略……
	cartItem.push({
		itemID: "cf16c709-6d7f-42f4-b5d6-2aac114c9187",
		name: "商品α",
		price: 120,
		count: 1, 
	})

	let lastItem = document.querySelector('.cart-item input[type=number]') as HTMLInputElement
	lastItem.focus()	
}

但後來發現這種做法,focus的會是 Vue 建立新的 .cart-item 前,最後一個 <div class="cart-item" /><input type="number">

例如原先是

<aside>
	<!-- 1. --> <div class="cart-item"> <!-- 內部中略 --> </div>
	<!-- 2. --> <div class="cart-item" />
	<!-- 3. --> <div class="cart-item" />
</aside>

執行完這個 function 後,會有 4 個 <div class="cart-item" /> ,但 focus 的會是第 3 個 <div class="cart-item" /> 的 input

解決辦法

這邊可以用Vue.js 的偵聽器 來解決此問題

watch(
	function(){return cartItem.length || 0},
	function(newValue, oldValue) {
		if(newValue <= oldValue){
			return
		}
		nextTick()
			.then(function(){
				let lastInput = document.querySelector(".cart-item:last-of-type input[type=number]") as HTMLInputElement
				if(lastInput){
					lastInput.focus()
				}
			})
	}
)

這段程式碼的意思是,偵測 cartItem 的長度,如果是新增(即 array 長度是增加的情況),繼續執行。

執行 nextTick() ,確保 cartItem 的變動導致的 DOM 變更都處理完畢後,執行我們原先想要的動作——「新增完畢後,自動 focus 到最新的 input 中」

參考資料

Chat GPT、Vue 官網

沒有留言:

張貼留言