Vue清單要聚焦某筆欄位:用function refs按ID找元素

清單重新排序後,程式用ref陣列的第幾個元素聚焦,結果跳到另一筆輸入欄位。先分清資料ID與DOM陣列位置。Vue官方template refs文件提醒,v-for產生的ref陣列不保證與來源陣列順序相同;不要把某個位置當成穩定的資料識別。

需要聚焦指定項目時,可使用function ref,在元素建立時按ID存進Map,卸載時移除。這讓查找以「哪一筆」為主,而不是猜它現在在第幾個位置。本文處理元素查找,不改清單資料或輸入保存。

key與ref負責不同事情

key讓Vue辨認列表節點;ref則讓程式取得元素或元件參照。清單有穩定key,並不表示你可以把ref陣列任意索引,當作與資料永遠一對一的查找表。

下面每個項目都有數字ID,輸入是實際DOM元素。function ref收到元素時保存;收到null時代表對應參照需要移除。

<script setup>
import { ref } from 'vue'
const items = ref([{ id: 1 }, { id: 2 }, { id: 3 }])
const fields = new Map()
function setField(id, element) {
  if (element) fields.set(id, element)
  else fields.delete(id)
}
function focusField(id) {
  fields.get(id)?.focus()
}
</script>
<template>
  <input v-for="item in items" :key="item.id"
    :ref="element => setField(item.id, element)">
  <button @click="focusField(2)">聚焦第二筆ID</button>
</template>

Map不是拿來顯示資料的響應式來源,只做這份元件內的元素查找。按鈕傳的是ID2,不是陣列索引2;即使清單位置變了,仍以同一個識別找對應輸入。項目的ID需要唯一且穩定,不應每次排序就重新編號。

如果Map的key用數字,外部輸入卻傳字串2,兩者不相等。先保持資料契約一致,或在適當入口明確驗證與轉型,不要在查不到元素時再猜是不是ref失效。記錄合成ID的型別,往往能快速排除這種差異。

卸載時收到null要刪除

function ref不只在建立元素時收到值,元素卸載時也會收到null。若只set、不delete,Map可能留下已經離開DOM的元素。之後再按聚焦,程式還拿得到舊參照,卻不能達成目前畫面上的操作。

清理分支應與建立分支放在同一個小函式,讓生命週期責任清楚。不要把舊元素永久留在全域Map,直到下一次開頁才重設。若一筆資料被刪除,它在查找表裡也應消失。

注意刪掉Map項目,不代表從來源資料刪掉項目。這兩個動作是資料層與DOM參照層的各自工作。範例的setField只更新元素查找,真正移除一筆仍由items的變動觸發渲染。

真清單重排後,仍按ID聚焦

本文案例使用Vue3.5.43與真Chromium,起始ID為1、2、3。按反轉後,實際輸入順序是3、2、1;接著按聚焦ID2,活動元素仍是ID2。沒有把反轉後位置硬寫成某個陣列索引。

再把ID2從來源移除,Map的key只剩1與3;接著聚焦ID3,活動元素為3。這兩項一起核對了查找與null卸載清理,不是只看第一次建立時Map裡有三個元素。

讀者可以在測試清單為input加 data-id,用開發者工具查看活動元素。測試不需接資料庫或真表單提交;先把ID到DOM的對應核清楚,再接回編輯錯誤提示或快捷聚焦功能。

本文的Map清理觀察來自本地列表移除,不包括其他自訂元件的生命週期。若ref指向的是子元件而非input,取得的是元件參照,可用介面也會不同;不能直接對任何元件ref都呼叫DOM focus。

新增後聚焦,要等元素建立

如果同一個事件先加入項目,再立刻呼叫focus,新的input可能尚未進入DOM,Map也還沒有收到它。先修改資料,等待Vue更新,再查找元素。

import { nextTick } from 'vue'
items.value.push({ id: 4 })
await nextTick()
focusField(4)

這段需在async操作流程內執行。nextTick等待Vue更新,不會把不存在的項目創造出來;若條件渲染沒有顯示該欄位,Map仍可能沒有它。聚焦前應核對來源與當前顯示條件,避免把空查找當成操作已完成。

同樣地,使用者在等待期間又刪掉或切換項目,更新後的Map可能已經不同。可選鏈避免報錯,但你仍要依功能決定是否顯示其他提示。沒有拋例外,與指定欄位真的獲得焦點是兩個結果。

同一ID不該代表兩個同時存在的欄位

若一筆資料有多個輸入,而Map只用item.id當key,後一個欄位會覆蓋前一個。這時應把欄位識別納入查找契約,例如用穩定的組合key,或讓每筆ID對應一組具名欄位。不要因為某次聚焦恰好成功,就忽略覆蓋順序。

範例只有每筆一個input,因此數字ID足夠。真表單若同一筆有名稱與價格,要明確表達想聚焦哪一個。這是元素識別的需求,不必為了它重新編號資料,也不應把DOM位置當成欄位名稱。

如果有多個列表元件,各自保持局部Map比較容易管理。別讓不同清單同樣的ID1寫進同一個全域Map,再互相覆蓋。參照的生命週期與查找範圍應跟真正擁有那些元素的元件一致。

聚焦行為還要看實際元素狀態

找到元素後,要確認它仍在目前畫面且可接受焦點。收合區塊、其他顯示條件或欄位設定,可能讓找到的參照不適合立即操作。Map只提供查找,不會自動展開面板或解釋為什麼某欄位不能聚焦。

需要避免聚焦時捲動,可依DOM focus API使用適當選項,例如 focus({ preventScroll: true })。這是互動設計選擇:錯誤欄位若在畫面之外,完全不捲動也可能讓使用者不知道焦點在哪裡。按你的表單需求決定,不要把一個選項套到全站。

保留重排、刪除與新增三份測試

修正後測清單反轉、移除目前項目與新增後聚焦,分別看活動元素ID與Map key。三種情況能涵蓋位置改變、參照清理與建立時序,避免只在最初順序正確時驗一次。

最後搜尋其他直接使用ref陣列索引的地方,確認哪些真的需要按ID查找。不要為了這個單一功能重寫整個列表;把元素對應與卸載清理做清楚,就能讓聚焦操作從猜位置改成查指定項目。

這份普通Map專門保存DOM參照,不是讓模板依它計算業務數量。若畫面需要顯示項目數,應讀items等真正的資料來源;不要把Map大小當成資料筆數。DOM可能受條件渲染影響,Map只代表目前已註冊的元素,與後端共有多少項目是不同的資訊。

除錯時也別把整個DOM物件長期保存或輸出到正式紀錄。用合成ID、key是否存在與活動元素識別,就能比較本篇的查找結果。這樣紀錄較清楚,也不會把表單內容、元素屬性或不相關頁面資料混入查修輸出。核到指定ID之後,再按實際功能確認是否需要移動捲動位置或顯示提示。

參考資料

原文鏈接:https://wntheme.com/vue-function-refs-map-by-id/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?