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

Бързина: кога се рисува наново

Накратко: Във Vue се рисува наново компонентът, който чете сменената стойност, и детето, ако props му са нови; първо се мери, после computed пази същия резултат.

Проблем

Всяка буква рисува целия списък

Горе има поле за търсене и бутон „Търси“. Списъкът се филтрира едва след „Търси“. ProductList пише в конзолата всеки път, когато се рисува. Напиши „мляко“ в полето и гледай конзолата.

Vue

Пет букви, пет нови реда „рисувам ProductList“. Списъкът е същият: още не си натиснал „Търси“. Тук продуктите са осем и не се усеща. В истинския каталог са 2000, всеки с картинка и цена, и полето започва да изостава след пръстите.

Идеята

Кога компонентът се рисува наново

Компонентът се рисува наново, когато се сменят данните, които показва. Но кое значи „данните му“, двете библиотеки решават различно. И двете дават верен резултат; разликата е колко работа вършат.

Vue следи кои реактивни стойности чете всеки компонент, докато се рисува. Когато се смени draft, Vue рисува наново само компонентите, които четат draft: тук App. Детето се рисува наново, ако props му са различни от предишните (сравнява ги един по един).

Броячът в ProductList.vue е в onBeforeMount и onBeforeUpdate: Vue ги вика преди първото и преди всяко следващо рисуване на компонента.

Тук шаблонът подава :products="visible()": функция, която филтрира. Тя се пуска при всяко рисуване на App и връща нов масив. За Vue това е нов prop, затова рисува и ProductList.

Първо мери, после поправяй. Брояч в конзолата, както тук, е най-простото мерене. Vue DevTools имат времева линия (Timeline) с рисуванията на компонентите и времето им. Поправяш само рисуването, което е и излишно, и бавно.

Виж го

Виж кой светва

<script setup>
const draft = ref('');
const query = ref('');
const visible = computed(() => filterByName(PRODUCTS, query.value));
</script>
<template>
<input v-model="draft" />
<button @click="query = draft">Търси</button>
<ProductList :products="visible" @add="add" />
</template>
<!-- ProductList.vue -->
<script setup>
defineProps(['products']);
onBeforeUpdate(() => console.log('рисувам ProductList'));
</script>
Стъпка по стъпка, с думи
  1. App пази текста в полето (draft) и търсенето (query). ProductList рисува картите. Конзолата брои колко пъти се рисува списъкът.
  2. Преди поправката шаблонът викаше функция, visible(). Всяко рисуване на App дава нов масив, затова Vue вижда нов prop и рисува ProductList наново.
  3. computed пази същия масив, докато query е същото. Vue следи какво чете всеки компонент и сравнява props на детето.
  4. Сега клавишът рисува само App и полето. Props на ProductList са същите, затова той е пропуснат, а броячът стои.
  5. „Търси“ сменя query. Масивът е нов, затова ProductList се рисува. Това рисуване е нужно: списъкът вече показва два продукта.
  6. Първо мери: брояч в конзолата или Profiler в DevTools. Поправяй само рисуванията, които са и излишни, и бавни.
Vue не рисува детето, ако props му са същите. Затова е важно какво подаваш: computed пази масива, а функция в шаблона дава нов при всяко рисуване.

Пробвай

Само „Търси“ рисува списъка

Направи така, че писането в полето и „Добави“ да не рисуват ProductList наново. Само „Търси“ трябва да го рисува, защото само тогава списъкът се сменя. Проверката пише „мляко“, добавя два продукта, натиска „Търси“ и брои редовете „рисувам ProductList“ в конзолата.

Vue

Пробвай

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

visible е computed, а списъкът пак се рисува при всяка буква. Кой ред е виновен?

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

Пробвай

Попълни

Как скъпият списък остава същият между рисуванията?

const visible = (
  () => PRODUCTS.filter((p) => p.name.includes(query.value)),
);

Идеята

Дълъг списък: рисувай по-малко

Най-бързият елемент е този, който не е нарисуван. Човек вижда 10–20 продукта на екрана, а не 2000. Затова дългият списък рисува част:

  • „Покажи още“: първите 20, после още 20 при клик. Помни се само колко са показани.
  • Страници: „1, 2, 3 …“, често с номера в адреса (/?page=2), за да се споделя.
  • Виртуален списък (windowing): рисува само редовете, които се виждат, и ги сменя при превъртане. Пише се трудно, затова се ползва библиотека: TanStack Virtual или vue-virtual-scroller.

Ключовете от урока за списъците помагат и тук: с key={p.id} (:key="p.id") добавените 20 продукта са нови елементи, а старите остават, без да се правят наново.

Идеята

Мързеливо зареждане

Не всяка част от страницата трябва веднага. Отзивите за продукта, картата с магазините, редакторът на профила: може да ги зареди едва когато човекът ги отвори. Така първото отваряне тегли по-малко код.

import { defineAsyncComponent } from 'vue';
const Reviews = defineAsyncComponent(() => import('./Reviews.vue'));

import('./Reviews.vue') е функция, която зарежда файла и връща Promise. defineAsyncComponent прави от нея компонент, който се ползва като всеки друг: <Reviews v-if="open" />.

Vue

Отвори конзолата: редът от reviews.js излиза едва след клика. Честно казано, в пясъчника всички файлове вече са в страницата, затова тук import() само пуска файла по-късно. В истинския проект Vite слага всеки такъв файл отделно и браузърът го тегли от сървъра при първия клик. Същото важи за страниците: във Vue Router component: () => import('./CartPage.vue') зарежда страницата при първия преход към нея.

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

v-memo. За много дълъг v-for Vue има v-memo="[p.id === selected]": редът се рисува наново само ако стойностите в масива се сменят. Документацията го нарича микрооптимизация за редки случаи. Сгрешен масив значи пропуснато обновяване.

Състоянието надолу. Полето със своя draft може да е отделен компонент SearchBox, който изпраща search нагоре само при „Търси“. Тогава буквите сменят само SearchBox.

computed със стабилен резултат. От Vue 3.4 нататък computed, който връща същото примитивно значение (например true), не задейства зависимите от него. За обект може да върнеш предишния (computed((old) => …)), ако съдържанието е същото.

Режимът на разработка е по-бавен. Предупрежденията и проверките струват време. Мери бързината на събраната версия (vite build и vite preview), а не само при разработка.

Упражнение

Покажи още

Каталогът има 10 продукта и рисува всичките. Покажи първите 4, а под тях „Показани 4 от 10“ и бутон „Покажи още“, който добавя още 4. Когато всички са показани, бутонът изчезва. Проверката брои продуктите след всеки клик и гледа текста и бутона.

Vue

Тест

Провери се

1. Страницата с продуктите е бавна. Кое е първото нещо, което правиш?
2. App подава :products="visible()", където visible е функция, която филтрира. Пишеш в полето на App. Рисува ли се ProductList?
3. Каталогът има 5000 продукта и страницата се отваря бавно. Какво помага най-много?
4. Какво дава мързеливото зареждане на компонент (lazy / defineAsyncComponent) в истинския проект?

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