Вложенное обновление объектов через update

Idb-keyval — это минималистичная библиотека для работы с IndexedDB в браузере, предоставляющая простой интерфейс для сохранения и получения данных. Одной из ключевых возможностей является метод update, предназначенный для вложенного обновления объектов, уже сохранённых в базе.

Синтаксис метода:

update(key, updater)
  • key — ключ записи в базе IndexedDB.
  • updater — функция, принимающая текущее значение по ключу и возвращающая новое значение.

Метод возвращает промис, который разрешается после завершения обновления. Если значение по ключу отсутствует, updater получит undefined.

Пример базового использования:

import { update } from 'idb-keyval';

update('user', (val) => {
    if (!val) return { name: 'Новый пользователь', settings: {} };
    return { ...val, lastLogin: new Date() };
});

В этом примере обновляется поле lastLogin в существующем объекте user, не затрагивая другие поля.


Вложенные объекты и неизменяемость

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

{
    name: 'Иван',
    settings: {
        theme: 'light',
        notifications: { email: true, sms: false }
    }
}

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

Пример корректного вложенного обновления:

update('user', (val) => {
    if (!val) return { name: 'Иван', settings: { theme: 'dark', notifications: {} } };
    return {
        ...val,
        settings: {
            ...val.settings,
            notifications: {
                ...val.settings.notifications,
                sms: true
            }
        }
    };
});

Здесь мы обновляем только поле sms внутри notifications, не затрагивая остальные настройки.


Использование update с массивами

Если объект содержит массивы, их также можно обновлять вложенно.

Пример добавления элемента в массив tasks:

update('user', (val) => {
    if (!val) return { name: 'Иван', tasks: [] };
    return {
        ...val,
        tasks: [...val.tasks, { id: 3, title: 'Новая задача', done: false }]
    };
});

Использование оператора расширения ... гарантирует, что существующие элементы массива не будут потеряны, а новый объект корректно добавится.


Сочетание нескольких вложенных обновлений

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

update('user', (val) => {
    if (!val) return { name: 'Иван', settings: { theme: 'light', notifications: {} }, stats: { visits: 0 } };
    return {
        ...val,
        settings: {
            ...val.settings,
            theme: 'dark',
            notifications: {
                ...val.settings.notifications,
                email: false
            }
        },
        stats: {
            ...val.stats,
            visits: val.stats.visits + 1
        }
    };
});

Этот подход позволяет обновлять сразу несколько вложенных объектов и массивов, сохраняя неизменяемость всей структуры.


Обработка отсутствующих значений

update автоматически передаст undefined, если ключ отсутствует в IndexedDB. Это позволяет инициализировать новые записи одновременно с обновлением:

update('cart', (val) => {
    if (!val) return { items: [], total: 0 };
    return {
        ...val,
        items: [...val.items, { id: 5, name: 'Товар', price: 100 }],
        total: val.total + 100
    };
});

Таким образом, один метод update справляется и с созданием, и с обновлением данных.


Асинхронные обновления

Функция updater может быть асинхронной. update корректно обработает промис и сохранит результат:

update('user', async (val) => {
    const newSettings = await fetchUserSettingsFromServer();
    return {
        ...val,
        settings: { ...val?.settings, ...newSettings }
    };
});

Это особенно полезно, когда новые значения нужно получить через API или из других асинхронных источников.


Практические советы

  • Использовать оператор расширения ... для сохранения существующих полей.
  • Для глубоких вложенных объектов можно применять вспомогательные функции обновления, чтобы избежать громоздких выражений.
  • Всегда обрабатывать случай undefined, чтобы инициализировать новые записи корректно.
  • Для массивов избегать мутаций (push, splice), вместо этого использовать map, filter или оператор расширения.

Итоговая структура вложенного обновления

  1. Проверка существования значения (undefined).
  2. Создание нового объекта с использованием ... для сохранения неизменяемости.
  3. Обновление вложенных объектов и массивов через расширение и методы неизменяемого обновления.
  4. Возврат нового значения, которое автоматически сохранится в IndexedDB.

Такой подход делает update мощным инструментом для работы с сложными объектами, обеспечивая безопасность данных и простоту поддержки.