商品頁一邊顯示原始順序,一邊用computed顯示低價優先。讀取排序清單後,原始清單卻也被排過了。先看getter裡是否直接對來源呼叫 sort()。JavaScript的sort會修改原陣列,不會替你建立一份只供顯示的新清單。
computed用來表達衍生值,getter應避免改來源。只需要排序展示時,先複製陣列再排序;這樣兩份清單可以共用項目資料,卻保留各自的排列。本文專門處理排序造成的來源修改,不調整清單key或商品保存功能。
sort的回傳值仍是原陣列
容易出問題的寫法很短,因而不一定會被注意。getter回傳了排序結果,但排序同時已經作用到items.value。
const items = ref([3, 1, 2])
const sorted = computed(() => items.value.sort((a, b) => a - b))
console.log(sorted.value)
console.log(items.value)
這裡的sort不是純計算。computed第一次被讀取時,getter才執行,原清單也就被排成1、2、3。只看宣告sorted那一行後的來源可能尚未變,等模板讀到它才出現變化,容易讓人誤判是哪個按鈕或元件改了資料。
在同一頁的不同區塊,如果都使用items,排序副作用就可能影響另一個區塊。這不是computed自動把所有列表綁成同一順序,而是你在getter裡修改了它們共用的來源。
複製外層,再對副本排序
對只需要改排列的用途,陣列展開可以建立一份新的外層陣列,再在副本上呼叫sort。
import { ref, computed } from 'vue'
const items = ref([3, 1, 2])
const sorted = computed(() => [...items.value].sort((a, b) => a - b))
console.log(sorted.value) // [1, 2, 3]
console.log(items.value) // [3, 1, 2]
來源與衍生清單現在有不同的外層陣列。sorted變成1、2、3,不要求items也跟著改。你仍可在一處顯示原順序,在另一處顯示排序結果,不必用watcher反覆把一份資料寫回另一份。
支援 toSorted()的執行環境也可用這個非原地排序方法;它提供排序後的新陣列。若專案的目標瀏覽器或執行環境不支援,使用複製後sort仍是清楚的替代。本文真案例採展開與sort,不把toSorted寫成已在所有讀者裝置驗證。
真案例同時核來源與衍生值
本文使用Vue3.5.43的響應式runtime,分別建立兩個起始為3、1、2的ref。一組在computed直接sort,一組先複製再sort。讀取各自的衍生值後,記錄來源排列。
直接sort的一組,來源從3、1、2變成1、2、3;複製後sort的一組,衍生值是1、2、3,來源仍是3、1、2。這份比較只測本機陣列與getter,不代表網站商品資料、使用者操作或正式UI已完成驗收。
讀者在測試元件中,可用兩段 v-for分別展示items與sorted。先看原始順序,再讓模板讀取sorted,觀察兩份清單是否各自保留預期排列。測試不需要接商品API,也不需要修改正式資料;先把陣列副作用核清楚再接回完整功能。
不要只斷言sorted正確。直接sort與副本sort都能得到同樣排序結果,差別藏在來源是否被改。因此測試需要同時保存來源前後順序。若不希望來源改動,就把來源順序未變也當成必要條件。
數字比較函式不能漏
JavaScript預設sort按字串的比較方式排序。對價格、分數等數字,應提供符合需求的比較函式;不然例如10與2可能不是按你期待的大小排列。本文用 (a, b) => a - b表示數字升序。
若清單是商品物件,就比較實際的數字欄位。資料如果是字串價格、缺值或不同幣別,先依資料契約整理,不能只把兩個欄位相減就假定合理。排序比較與陣列複製是兩件事:複製避免改來源,比較函式決定排列規則。
const byPrice = computed(() => [...products.value]
.sort((a, b) => a.price - b.price))
這段假定price都是同尺度有效數字。若不成立,應先定義缺值與型別的處理,再測對應資料。不要為了讓sort不報錯,任意把缺價格變成零,讓未知項目跑到最前面。
淺拷貝足以改排列,不代表物件也獨立
展開陣列只複製外層。陣列中的商品物件仍可能是同一個參照。對副本做sort,只是排列參照,不改商品欄位,因此通常不必為排序深拷貝整份資料。
但如果排序後又寫 sorted.value[0].price = ...,可能同樣改到來源商品物件。這已不是單純顯示排序的任務,應交回適當的資料修改流程。別把「外層陣列獨立」誤讀成「每筆資料也完全獨立且可任意編輯」。
同樣地,不應在computed getter裡呼叫保存API、增加資料或改另一個ref,讓衍生值讀取變成改狀態的入口。getter可以讀它需要的響應式來源,再回傳結果;真正的操作由事件或明確的流程負責。
來源本來就要排序時,別藏在computed裡
如果需求是使用者按按鈕後正式調整原始清單順序,就可以在那個事件流程更新來源,讓操作目的明確。這與「原順序不變,只提供另一份顯示」不同。先決定順序是否屬於業務狀態,再選改來源或產生衍生清單。
例如可拖動的編輯清單,順序可能要保存;搜尋頁的低價優先則可能只是顯示偏好。不能因為兩者都叫排序,就把同一個有副作用的getter共用到所有元件。讓使用者操作與派生顯示各有自己的位置。
如果來源先經過filter產生新陣列,再對那份結果排序,應確認排序的確是新結果,不是又回到原陣列。維護者可以把中間變數命名清楚,或保持一條可讀的衍生鏈,避免為了縮短程式把來源與副本混在一起。
修正後核兩份清單
將getter改成複製後sort,再核sorted與items的排列。接著加入一筆資料,確認computed仍能依來源更新排序,而原清單保留你選定的順序。這樣才能知道修正沒有把響應式連動一起切斷。
若仍有來源被改,搜尋其他sort、reverse或欄位賦值的位置,不要只責怪computed。找到真正修改資料的那一行,再按需求決定它是否應保留。排序畫面正確只是其中一項,來源沒有被意外改動同樣需要核對。
衍生清單本身也應當作顯示結果使用。不要在模板或事件裡直接對sorted.value再reverse,讓同一個computed快取結果被其他地方改動。需要降序時,將排序方向作為明確的來源條件,讓getter依條件計算,而不是每次點按鈕都翻動目前那份陣列。
測試方向切換時,核升序、降序與返回升序三條路,並確認原始來源一直維持既有順序。若兩個展示區塊同時使用衍生值,也要看它們是否按照相同規則顯示。這些比較能揭露某一處偷偷修改快取結果的問題,避免只測第一次開頁看起來正確就結束。

評論0