Вложенная реактивность

Petite-Vue — облегчённая версия Vue.js, ориентированная на минимализм и простоту интеграции в существующие страницы. Центральная концепция библиотеки — реактивность, позволяющая автоматически обновлять пользовательский интерфейс при изменении данных. В основе лежит наблюдаемая структура данных, которую Petite-Vue отслеживает с помощью Proxy.

Реактивность в Petite-Vue может быть поверхностной и глубокой. Поверхностная отслеживает только верхний уровень объекта, в то время как глубокая реагирует на изменения вложенных объектов. Вложенная реактивность является критически важной для сложных интерфейсов, где состояние представлено многоуровневыми структурами данных.


Создание реактивных вложенных объектов

Реактивный объект создаётся с помощью функции Reactive() внутри createApp():

<div v-scope="app">
  <p>{{ user.name.first }} {{ user.name.last }}</p>
  <input v-model="user.name.first">
  <input v-model="user.name.last">
</div>

<script type="module">
import { createApp } from 'petite-vue';

createApp({
  user: {
    name: {
      first: 'Иван',
      last: 'Петров'
    },
    age: 30
  }
}).mount();
</script>

В этом примере объект user имеет вложенный объект name. Любое изменение user.name.first или user.name.last автоматически отражается в DOM благодаря глубокой реактивности. Petite-Vue рекурсивно оборачивает вложенные объекты, что позволяет отслеживать их изменения без дополнительных усилий.


Реактивность массивов и методов

Массивы также полностью поддерживают вложенную реактивность. Любые операции, изменяющие массив (push, splice, pop), вызывают обновление интерфейса.

Пример:

<div v-scope="app">
  <ul>
    <li v-for="item in tasks">{{ item.name }} - {{ item.completed }}</li>
  </ul>
  <button @click="tasks[0].completed = !tasks[0].completed">Переключить статус первой задачи</button>
</div>

<script type="module">
import { createApp } from 'petite-vue';

createApp({
  tasks: [
    { name: 'Сделать домашку', completed: false },
    { name: 'Прочитать статью', completed: true }
  ]
}).mount();
</script>

При изменении свойства completed первого элемента массива автоматически обновляется соответствующий элемент списка. Важный момент: Petite-Vue отслеживает только изменения существующих объектов, добавление новых вложенных свойств потребует использования reactive или обновления всего объекта.


Обработка динамических вложенных объектов

Если структура данных изменяется динамически (например, добавление новых полей в объект), необходимо использовать реактивное присвоение:

app.user.address = reactive({ city: 'Москва', street: 'Ленина' });

Без функции reactive новые свойства не станут реактивными. Это связано с тем, что Petite-Vue оборачивает только существующие свойства на момент инициализации.


Деструктуризация и реактивность

При деструктуризации вложенных объектов важно учитывать, что реактивность может потеряться, если деструктурировать объект в обычные переменные:

const { name } = app.user; // name больше не реактивен

Для сохранения реактивности следует использовать деструктуризацию внутри объекта scope или обращаться через оригинальный объект: user.name.first.


Вычисляемые свойства и вложенные данные

Petite-Vue поддерживает вычисляемые свойства через функции внутри createApp():

createApp({
  user: {
    name: { first: 'Иван', last: 'Петров' },
    age: 30
  },
  get fullName() {
    return this.user.name.first + ' ' + this.user.name.last;
  }
}).mount();

Вычисляемое свойство fullName автоматически обновляется при изменении вложенных полей user.name.first или user.name.last. Petite-Vue отслеживает все зависимые реактивные поля, обеспечивая синхронизацию данных и интерфейса.


Ограничения вложенной реактивности

  1. Новые свойства объектов: автоматически не становятся реактивными, требуется использование reactive().
  2. Деструктуризация: извлечённые переменные теряют реактивность.
  3. Сложные массивы объектов: изменение вложенного массива внутри массива требует сохранения ссылок на объекты или использования реактивного присвоения.
  4. Глубокие вложенности: слишком глубокие объекты могут снизить производительность, так как каждый уровень оборачивается в Proxy.

Практические рекомендации

  • Для сложных структур данных использовать глубокую инициализацию всех вложенных объектов через createApp().
  • Для динамически добавляемых объектов применять reactive().
  • Сохранять доступ к вложенным объектам через исходный объект, избегая деструктуризации вне scope.
  • Вычисляемые свойства использовать для агрегирования данных из нескольких уровней вложенности.

Эти принципы обеспечивают корректное функционирование вложенной реактивности в Petite-Vue и позволяют строить интерфейсы с многоуровневыми состояниями, минимизируя ручное обновление DOM и сохраняя синхронизацию данных.