| Vue: реактивность |
|
| Добавил(а) microsin | ||||||||||||||||||||||||
|
Реактивность (Reactivity) Vue означает автоматическое отслеживание изменений данных и обновление интерфейса при их изменении. Это ключевая концепция, которая избавляет разработчика от ручного управления DOM. [Как это работает] 1. Реактивные объекты. Когда вы создаете объект данных в компоненте Vue, Vue превращает его в реактивный прокси: data() { 2. Зависимости и триггеры. Vue автоматически отслеживает зависимости между данными и интерфейсом: < template> < div>{{ message }}< /div> < !-- Зависит от message --> < /template> Когда `message` изменяется, Vue знает, что нужно обновить именно этот < div>. [Методы реализации реактивности] Vue 2 (Object.defineProperty). Использует геттеры и сеттеры для отслеживания: // Упрощенный пример Vue 3 (Proxy API). Более современный и мощный подход: const reactiveData = new Proxy(data, { Вот основные отличия реактивности во Vue 2 и Vue 3, представленные в таблице для наглядного сравнения:
Выводы для разработки. Эти отличия имеют практические последствия: ● Гибкость Vue 3: благодаря Proxy и ref() работа с данными стала проще. Вы можете добавлять свойства в объекты или изменять массивы по индексу, не беспокоясь о потере реактивности. Если вы планируете миграцию с Vue 2 на Vue 3, стоит обратить особое внимание на изменения в работе с массивами и объектами, а также на новый API ref() и reactive(). [Практические примеры] Базовый пример: < template> < div> < p>{{ message }}< /p> < button @click="reverseMessage">Перевернуть< /button> < /div> < /template> Реактивность массивов и объектов: export default { [Особенности Vue 3 с Composition API] < script setup> [Преимущества реактивности Vue] 1. Декларативность: описываете, как должен выглядеть интерфейс, а Vue сам обновляет его. Ограничения и нюансы: 1. Инициализация свойств: все реактивные свойства должны быть объявлены изначально. 2. Прямые изменения массивов: некоторые методы (по индексу) могут не отслеживаться. 3. Асинхронные обновления: Vue объединяет обновления для эффективности. // Несколько изменений = одно обновление DOM Реактивность — это основа Vue, которая позволяет создавать сложные интерфейсы с минимальным количеством кода, сосредотачиваясь на логике приложения, а не на механике обновления интерфейса. [Декларация Reactive State] В Composition API рекомендуемый метод для декларации реактивного состояния - использовать функцию ref(): import { ref } from 'vue' Функция ref() принимает аргумент и возвращает объект-обертку над ним. Доступ к аргументу в этом объекте осуществляется через его свойство .value: const count = ref(0) Чтобы обращаться к ref-фам в template компонента, декларируйте и возвращайте их в функции setup() компонента: import { ref } from 'vue' После этого можно обращаться к значению ref-объекта в шаблоне компонента следующим образом: // Шаблон компонента: < template> .. < div>{{ count }}< /div> .. < /template> Обратите внимание, что в шаблоне не нужно добавлять .value при использовании ref. Для удобства ref-ы автоматически раскрываются, когда используются внутри шаблонов (с некоторыми оговорками). Вы также можете напрямую мутировать (изменять) ref в обработчиках события: < button @click="count++"> {{ count }} < /button> Для более сложной логики мы можем декларировать функции, которые мутируют ref-ы в той же самой области видимости, и публиковать их как методы рядом с состоянием (значением): import { ref } from 'vue' Опубликованные методы затем можно использовать в обработчиках события: < button @click="increment"> {{ count }} < /button> [< script setup>] Публикация вручную состояния (state) и методов через setup() может быть утомительной и слишком громоздкой. К счастью, этого можно избежать при использовании однофайловых компонентов (Single-File Components, SFC). Так что мы можем упростить использование функционала реактивности с помощью < script setup>: < script setup> Импортирования высокого уровня, переменные и функции, декларируемые в < script setup>, становятся автоматически доступными в шаблоне template того же компонента. Думайте о template как о функции JavaScript, декларированной в той же области видимости - она натурально имеет доступ ко всему, что объявлено рядом. [Откуда появился ref] У вас может появиться вопрос, зачем нам нужны эти ref-ы вместе с их .value вместо простых переменных. Чтобы понять это, нужно кратко обсудить, как работает система реактивности Vue. Когда вы используете ref в шаблоне template, и меняете позже его значение, Vue автоматически детектирует это изменение и обновляет DOM соответствующим образом. Это стало возможным благодаря системе отслеживания зависимостей (dependency-tracking), основанной на системе реактивности. При первом рендеринге компонента Vue отслеживает каждый ref, который был использован во время рендеринга. Позже, когда ref мутирован, система реактивности вызовет триггер для повторного рендеринга для компонентов, которые отслеживают этот ref. В стандартном JavaScript нет возможности обнаружить доступ или мутацию обычных переменных. Однако мы можем перехватить операции чтения и установки (get и set) объекта, используя методы getter и setter. Свойство .value дает возможность для Vue определить, был ли доступ к ref на чтение (get) или на мутирование (set). Под капотом Vue выполняет отслеживание в своем getter, и выполняет trigger в своем setter. Концептуально это можно представить себе следующим образом: // Псевдокод для демонстрации принципа, Другой приятный бонус ref-фов заключается в том, что в отличие от обычных переменных вы можете передать ref-ы в функции, сохраняя при этом доступ к последнему значению и соединению реактивности. Это особенно полезно при рефакторинге сложный логики в повторно используемом коде. Более подробно система реактивности обсуждается в [5]. [Глубокая реактивность] Ref-ы могут содержать значение любого типа, включая глубоко вложенные объекты, массивы или встроенные структуры данных JavaScript наподобие Map. Объект ref делает свое значение глубоко реактивным. Это означает, что можно ожидать обнаружение изменений даже при мутации вложенных объектов или массивов: import { ref } from 'vue' Не примитивные значения превращаются в реактивные proxy через reactive(), что будет обсуждаться далее. Можно также отказаться от глубокой реактивности с помощью shallow ref. Для таких ref-ов отслеживается реактивность доступа только для .value. Shallow ref может использоваться для оптимизации производительности, избегая затрат на наблюдение над большими объектами, или в тех случаях, когда внутреннее состояние управляется внешней библиотекой. См. также: Reduce Reactivity Overhead for Large Immutable Structures [Тайминг обновления DOM] Когда вы мутируете реактивное состояние, DOM обновляется автоматически. Однако следует отметить, что обновления DOM не применяются синхронно. Вместо этого Vue буферизирует обновления до "следующего тика" в цикле обновления (update cycle) для гарантии, что каждый компонент обновится только один раз независимо от того, сколько изменений состояния было сделано. Чтобы дождаться завершения обновления DOM после изменения состояния, вы можете использовать глобальную API-функцию nextTick(): import { nextTick } from 'vue' [reactive()] Другой способ объявить реактивное состояние - использовать API-функцию reactive(). В отличие от ref, который оборачивает внутреннее значение в специальных объект, функция reactive() делает реактивным сам объект: import { reactive } from 'vue' Использование в template: < button @click="state.count++"> {{ state.count }} < /button> Реактивные объекты Vue это JavaScript-прокси [6], и они ведут себя как обычные объекты. Разница в том, что Vue способен перехватывать доступ и мутацию всех свойств реактивного объекта для отслеживания реактивности и запуска trigger. Функция reactive() глубоко преобразует объект: вложенные объекты также оборачиваются при доступе к ним через reactive(). Это также приводит к внутреннему вызову ref(), когда значение ref это объект. Подобно shallow ref также есть API-функция shallowReactive(), которая отменяет глубокую реактивность объекта. Reactive Proxy vs. Original. Важно отметить, что возвращаемое значение из reactive() является Proxy [6] исходного объекта, который не равен исходному объекту: const raw = {} При этом реактивен только proxy, так что мутирование оригинального объекта не приведет к срабатыванию обновлений. Поэтому лучшая практика для работы с системой реактивности Vue: эксклюзивное использование proxi-версий вашего состояния. Чтобы обеспечить согласованный доступ к proxy, вызов reactive() на том же объекте всегда возвратит тот же proxy, а вызов reactive() на существующем proxy также возвратит тот же proxy: // Вызов reactive() на том же самом объекте возвратит Это правило применяется также и к вложенным объектам. Из-за глубокой реактивности вложенные объекты внутри reactive-объекта также являются proxy: const proxy = reactive({}) Ограничения reactive(). В применении reactive() действуют несколько ограничений: 1. Ограниченные типы значений: это работает только с типами объектов (object, array и collection-типы, такие как Map и Set). Не получается хранить примитивные типы, такие как string, число или boolean. 2. Нельзя заменить весь объект целиком: поскольку отслеживание Vue reactivity работает только над доступом к свойствам, мы всегда должны сохранять одну и ту же ссылку на reactive-объект. Это значит, что мы не можем легко "заменить" reactive-объект, потому что связь реактивности с первой ссылкой теряется: let state = reactive({ count: 0 }) 3. Not destructure-friendly, т. е. отсутствие поддержки деструкции: когда мы деструктурируем reactive-объекты со свойством примитивного типа в локальные переменные, или когда передаем такое свойство в функцию, мы потеряем reactivity-соединение: const state = reactive({ count: 0 }) Из-за наличия этих ограничений рекомендуется использовать ref() как primary API для декларации reactive state. [Дополнительные подробности разворачивания ref] Reactive Object в качестве свойства. Сам ref автоматически разворачивается (unwrapped), когда к нему осуществляется доступ на чтение или запись как к свойству reactive-объекта. Другими словами, он ведет себя как обычное свойство: const count = ref(0) Если новый ref присвоен свойству, связанному с существующим ref, то это заменит старый ref: const otherCount = ref(2) Ref unwrapping происходит только когда есть вложение внутри deep reactive объекта. Это не применимо, когда происходит доступ к ref как к свойству shallow reactive объекта. Оговорки для массивов и коллекций. В отличие от reactive объектов, не будет происходить unwrapping, когда к ref происходит обращение как к элементу reactive-массива или типу native-коллекции наподобие Map: const books = reactive([ref('Vue 3 Guide')]) Оговорки при unwrapping в template. Ref unwrapping в шаблонах template применимо только если ref это свойство верхнего уровня в контексте рендера template. В следующем примере count и object это свойства верхнего уровня, но object.id таковым не является: const count = ref(0) Таким образом, это выражение в шаблоне работает ожидаемо: {{ count + 1 }} ... однако это выражение НЕ РАБОТАЕТ: {{ object.id + 1 }} Результат рендера будет [object Object]1, потому что object.id не был unwrapped при вычислении выражения, и остался объектом ref. Чтобы это исправить, мы можем деструктурировать id в свойство верхнего уровня: const { id } = object В шаблоне: {{ id + 1 }} Теперь результат рендера будет 2. Еще следует отменить, что ref не становится unwrapped, если он конечное вычисляемое значение текстовой интерполяции (например тег {{ }}), так что следующее приведет к рендеру 1: {{ object.id }} Это просто фича удобства для интерполяции текста, и эквивалентно {{ object.id.value }}. [Ссылки] 1. Vue 3, быстрый старт. |