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

Състояние

Накратко: Състоянието (state) е стойност, която компонентът помни между рисуванията; във Vue се пази с ref, а всяка промяна обновява компонента в страницата.

Проблем

Бутонът не брои

Картата на продукта има бутон „Добави“ и брояч „В кошницата“. Натисни „Добави“ три пъти.

Vue

Конзолата казва count = 1, 2, 3. На картата пише 0. Грешка няма. Променливата count се сменя, а екранът не я следва. Къде е разликата?

Идеята

Обикновената променлива не стига

Картата не се рисува от твоя код, а от библиотеката. Тя рисува наново само когато знае, че нещо се е сменило. count++ сменя променливата, но не казва на никого.

<script setup> тече веднъж за всяка карта, а шаблона го пуска Vue. Vue пуска шаблона наново само когато се смени нещо, което следи. Обикновена let променлива Vue не следи. Кликът сменя count, но шаблонът не тръгва наново, затова картата остава с 0.

Трябват данни, които компонентът помни между две рисувания и чиято промяна го рисува наново. Това се казва състояние (state).

Идеята

useState и ref

<script setup>
import { ref } from 'vue';
const count = ref(0);
function add() {
count.value++;
}
</script>
<template>
<p>В кошницата: {{ count }}</p>
</template>
  • ref(0) връща кутия, а числото е в count.value. В скрипта пишеш .value.
  • В шаблона Vue разопакова кутията: пишеш {{ count }}, без .value.
  • Когато шаблонът чете count, Vue го запомня. Когато count.value се смени, Vue пуска шаблона наново.

Обработчикът на клика се дава като функция: @click="add". Във Vue може и @click="add()" или @click="count++": шаблонът сам ги обвива във функция, която тече при клик.

Vue

Виж го

Виж какво става при клик

<script setup>
const count = ref(0);
function addThree() {
count.value++;
count.value++;
count.value++;
}
</script>
<template>
<article>
<button @click="addThree">+3</button>
<p>В кошницата: {{ count }}</p>
</article>
</template>
Стъпка по стъпка, с думи
  1. Картата пази count в ref: кутия, чиято стойност е count.value. Vue следи кой я чете.
  2. Vue пуска шаблона. Той чете count и връща описание: бутон и текст с 0. Vue запомня, че шаблонът чете count.
  3. Първия път описанието става истински елементи в DOM-а. Страницата вече показва „В кошницата: 0“.
  4. Клик на +3: браузърът вика addThree.
  5. Три пъти count.value++. Кутията се сменя веднага: 1, 2, 3. Vue планира шаблона да тръгне наново, но само веднъж.
  6. Обработчикът свърши. Vue пуска шаблона веднъж, с count = 3, и той връща ново описание.
  7. Vue сравнява новото описание със старото. Бутонът е същият, сменен е само текстът. В DOM-а се сменя само той.
  8. Броячът показва 3. Промяната на ref е веднага, а рисуването е веднъж, след обработчика.

Картата не се строи наново. Библиотеката сравнява новото описание със старото и сменя в DOM-а само разликата: текста. Бутонът е същият елемент и фокусът остава на него. Това е разликата с render() от модула за DOM, която сменяше всичко.

Пробвай

Поправи брояча

Това е картата от началото. Направи count състояние, така че след всеки клик на „Добави“ картата да показва новия брой.

Vue

Идеята

Кутията и числото

Минералната вода се продава и на стек от 6 бутилки. Удобно е бутонът за стек да вика add() шест пъти. Тук двете библиотеки се държат различно.

count.value винаги е текущото число, затова count.value++ шест пъти дава 6. Шаблонът при това не тръгва шест пъти. Vue събира промените и го пуска веднъж, след обработчика.

Грешката тук е друга. Кутията е count, а count.value е число. Числото, сложено в друга променлива, е копие:

let bottles = count.value;
bottles += 6; // сменя копието, count остава същият

Vue следи кутията, а не всяка променлива, в която е било числото. Пиши в кутията: count.value += 6.

Пробвай

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

„Добави стек“ трябва да добави 6 бутилки. Кой ред е виновен?

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

Пробвай

Стек вода

Направи addBox() така, че „Добави стек (6 бр.)“ да добавя 6 бутилки към това, което вече е в кошницата. Проверката натиска „Добави“ веднъж, после стека два пъти и очаква 1, 7 и 13.

Vue

Идеята

Всяка карта помни своето

Състоянието е на екземпляра, а не на кода. Каталогът има две карти от един и същ ProductCard, и всяка има свой count:

Vue

Натисни „Добави“ на хляба два пъти и на млякото веднъж: 2 и 1. Библиотеката пази състоянието на всяка карта до мястото ѝ в дървото на компонентите. Махнеш ли картата от страницата, състоянието ѝ изчезва с нея. Когато две карти трябва да показват едно и също число, то трябва да е в общия им родител. За това е урокът за споделеното състояние в този модул.

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

ref и reactive. reactive({ count: 0 }) прави обекта следим и няма .value, но работи само с обекти и не може да се смени целият. ref работи с всяка стойност, затова документацията на Vue го препоръчва като основен.

Кога тръгва шаблонът. Vue пуска шаблона наново в следващата микрозадача, след като кодът в момента свърши. await nextTick() чака това рисуване, например за да прочетеш новия DOM.

Разопаковането. В шаблона се разопаковат ref-ове от най-горно ниво на <script setup>. Ref вътре в обикновен обект ({ total: ref(0) }) не се разопакова в израз като {{ cart.total + 1 }}.

Options API. По-стар Vue код пише компонента като обект с data(), methods и this.count. Новият код е със <script setup> и Composition API.

Упражнение

Избери броя

Каталогът има три карти. Във всяка добави брой, който започва от 1, бутон „−“ (име за екранния четец: „По-малко Бял хляб“) и бутон „+“ („Повече Бял хляб“) и ред „2 × 1,20 € = 2,40 €“ с броя, цената и сумата. „−“ е забранен (disabled), когато броят е 1. Всяка карта брои сама.

Vue

Тест

Провери се

1. Защо let count = 0 и count++ в компонента не сменят екрана?
2. Къде пишеш .value на const count = ref(0)?
3. count.value е 0. Обработчикът прави count.value++ три пъти. Какво става?
4. Какво прави let bottles = count.value; bottles += 6;?
5. На страницата има две ProductCard, всяка със свой count. Купувачът натиска „Добави“ в първата. Какво става?

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