Към съдържанието
wannacode

Обновяване на обекти и масиви

Накратко: Обект или масив в състоянието на Vue се мени на място, защото Vue следи промените през прокси; работи и нов масив в .value, но не и обикновен масив без ref.

Проблем

„Махни“ не маха

Кошницата има три реда, всеки с бутон „Махни“. Натисни „Махни“ на хляба.

Vue

Конзолата казва редове: 2. На екрана са трите реда и старата сума. Данните са сменени, а екранът не ги следва, и грешка няма.

remove слага нов масив без хляба в cart. Новата стойност е в променливата. Защо Vue не рисува?

Идеята

Как библиотеката вижда промяната

С число е ясно: 1 и 2 са различни. Масивът и обектът обаче са референции: променливата сочи обект в паметта. Има два въпроса. Дали променливата сочи друг обект? И дали съдържанието на същия обект е сменено? Всяка библиотека пита по свой начин, и тук разликата се вижда в кода.

Vue пита втория въпрос. ref([...]) обвива масива в прокси (Proxy): обект, който стои пред масива и вижда всяко четене и писане. push, splice и line.qty++ минават през проксито. То записва промяната и казва на шаблона, който чете масива, да тръгне наново. Затова във Vue масивът се сменя на място, и това е нормалният път.

Проксито обаче не вижда променливата, която го сочи. В примера let cart = reactive([...]) и после cart = cart.filter(...): променливата вече сочи нов, обикновен масив. Шаблонът чете стария масив през старото прокси и никой не му казва. С const cart = ref([...]) новият масив влиза в cart.value, а ref-ът вижда тази смяна.

Виж го

Виж: същият масив или нов

<script setup>
const cart = ref([bread]);
function addMilk() {
cart.value.push(milk);
}
function addCheese() {
cart.value = [...cart.value, cheese];
}
</script>
Стъпка по стъпка, с думи
  1. cart е ref. В него е масивът, обвит в прокси: обект, който стои пред масива и вижда всяко четене и писане.
  2. cart.value.push(milk) минава през проксито. То записва: масивът се смени.
  3. Проксито казва на шаблона, който чете cart, че трябва да тръгне наново.
  4. Шаблонът тръгва и екранът показва двата реда.
  5. Може и така: нов масив в cart.value. Ref-ът вижда смяната на .value.
  6. Ref-ът казва на шаблона и той тръгва наново с новия масив.
  7. Екранът показва трите реда. Във Vue и двата пътя работят.
  8. Правилото във Vue: сменяй през ref-а. Обикновена променлива или копие на число Vue не вижда.
И двата пътя минават през ref-а: push през проксито на масива, а новият масив през .value. Vue не вижда само това, което заобикаля и двете.

Идеята

Промяна през проксито

Кошницата има три действия: добави ред, смени броя на един ред, махни ред.

// добави
cart.value.push({ ...product, qty: 1 });
// смени един ред: редът също е прокси
const line = cart.value.find((l) => l.id === id);
line.qty++;
// махни
const index = cart.value.findIndex((l) => l.id === id);
cart.value.splice(index, 1);
  • Vue следи и обектите вътре в масива: line от cart.value е прокси и line.qty++ се вижда.
  • Работи и другият път: нов масив в cart.value, например cart.value = cart.value.filter((l) => l.id !== id). Ref-ът вижда смяната на .value.
  • Не се вижда промяна, която заобикаля ref-а: обикновена променлива с нов масив или копие на число.

Пробвай

Поправи „Махни“

Това е кошницата от началото. Поправи remove така, че редът да изчезва и сумата да се смята наново. Проверката маха хляба, после сиренето.

Vue

Пробвай

Намери грешката

„+“ не вдига броя. Кой ред е виновен?

Избери реда с грешката.

Идеята

Един ред от масива

Купувачът натиска „+“ на хляба. Трябва да се смени qty на един обект, а той е в масив.

Във Vue line.qty++ е правилният път: редът от cart.value е прокси и Vue вижда промяната. Грешката е да извадиш числото от обекта:

let { qty } = line; // копие на числото
qty++; // сменя копието

Деструктурирането и let n = line.qty дават копие. Vue следи обекта line, а не всяка променлива, в която е било числото. Пиши в обекта: line.qty++.

По-точно, за любопитните

Дълбоко следене. ref и reactive правят следими и обектите вътре, при първото им четене. shallowRef следи само .value: с него промяна вътре не се вижда и се слага нов масив, както в React.

reactive и деструктуриране. const { total } = reactive({ total: 0 }) дава число, а не следима стойност. toRefs(obj) превръща полетата в ref-ове, които остават свързани с обекта.

Новият масив. cart.value = [...cart.value, line] работи и прави нов масив, както се прави в React. Полезно е, когато старият масив трябва да остане същият, например за „Отмени“.

structuredClone не приема прокси: structuredClone(cart.value) хвърля грешка. Първо се взима обикновеният масив с toRaw(cart.value).

Пробвай

„+“ и „−“

Направи more и less. „+“ вдига броя на реда с 1, а „−“ го сваля с 1. Когато броят е 1, „−“ маха реда. Сумата следва броя.

Vue

Упражнение

Каталог и кошница

Каталогът има четири продукта с бутон „Добави“ (името за екранния четец: „Добави Бял хляб“). Направи add и remove. Първият клик на „Добави“ прави ред с брой 1, а следващите вдигат броя на същия ред, вместо да правят нов. „Махни“ маха реда. Когато кошницата е празна, пише „Кошницата е празна“.

Vue

Тест

Провери се

1. let cart = reactive([...]); и после cart = cart.filter(...). Защо екранът не се сменя?
2. Кое вдига броя на един ред правилно?
3. Работи ли във Vue cart.value = [...cart.value, line]?
4. const cart = [ ... ] без ref и после cart.push(line). Какво става?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·