Лучший способ работы с хранилищем веб-клиента

Браузеры оснащены опциями хранения, которые помогают вам управлять данными на клиенте, чтобы свести к минимуму вызовы на сервер и даже обеспечить автономную работу в вашем приложении. Некоторые приложения даже изначально загружали данные с сервера и работали без необходимости после этого вызывать сервер.

Дело не в том, нужно ли вам иметь дело с вариантами клиентского хранилища, а в том, когда. Однако, когда вы прыгаете в этот мир, возникают некоторые проблемы:

  • Каждый из них поставляется со своим собственным API, а некоторые из них являются подробными;
  • Каждый из них имеет свои ограничения по типам данных, что означает, что не все типы данных могут быть сохранены;
  • Скорее всего, вам придется вернуться к другим вариантам, потому что не все типы и версии браузеров поддерживают все варианты хранения;
  • Скорее всего, вам потребуется управлять форматом, проверкой и типом данных, чтобы обеспечить согласованное хранение данных и предотвратить проверки данных на уровне приложения.
  • Скорее всего, вам понадобится библиотека, чтобы помочь, но найти такую, которая решит все или большинство ваших проблем, скорее всего, будет непростой задачей.

Входит в пакет «клиент-веб-хранилище»

клиент-веб-хранилище — многообещающее решение, потому что:

  • Он предоставляет единый и простой API для работы со всеми вариантами клиентского хранилища: IndexedDb, WebSQL, LocalStorage и в памяти;
  • Он автоматически переключается на различные параметры, гарантируя, что ваши данные будут правильно храниться на клиенте;
  • Поддерживает длинный список типов данных: Date, Number, String, Boolean, Array, ArrayBuffer, Blob, Float32Array, Float64Array, Int8Array, Int16Array, Int32Array, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array ;
  • Он выполняет базовую проверку данных для вас;
  • Поставляется с поддержкой схемы, которая гарантирует формат данных. Он автоматически создает для вас поля со значениями по умолчанию;
  • Предоставляет перехватчики для связи API вашего сервера, поэтому вы взаимодействуете только с хранилищем данных, в то время как все связи API происходят сзади;
  • Он предлагает невероятно простой API для всего;
  • Позволяет вкладывать схемы для создания сложных хранилищ;

Как это работает?

Он позволяет вам определять небольшие документы с определенным форматом данных через определение схемы. Это ваш способ сообщить ему, как выглядят данные и как они должны выполнять проверку. Позволь мне показать тебе:

Допустим, у меня есть проект TypeScript, поэтому мне нужно определить тип для моих данных:

interface User extends Schema.DefaultValue {
    name: string;
}

interface ToDo extends Schema.DefaultValue {
    name: string;
    description: string;
    complete: boolean;
    user: User;
}

Выше я определяю User и ToDo, которые расширяют Schema.DefaultValue, которые определяют поля id, createdDate и lastUpdatedDate, которые являются важными полями данных, которые обрабатываются автоматически для вас. Он сгенерирует для вас uuid и обновит поля lastUpdatedDate при внесении изменений.

Определить схему

Из этого я могу определить свою схему, которую я также могу добавить к ней:

const userSchema = new Schema<User>("user", {
   name: new SchemaValue(String, true),
});
const todoShema = new Schema<ToDo>("todo", {
   name: new SchemaValue(String, true),
   description: new SchemaValue(String, false, "No Description"),
   complete: new SchemaValue(Boolean),
})

Выше я определяю схему пользователя с обязательным полем имени:

new SchemaValue(type, isRequired, defaultValue)

Затем я определяю схему задач с обязательным полем имени и необязательным полем описания, которое, если значение не указано, будет иметь значение по умолчанию No Description , и, наконец, логическое поле complete, чтобы указать, был ли элемент задачи завершен.

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

const todo1 = todoShema.toValue();
/* todo1
{
  id: "123e4567-e89b-12d3-a456-426614174000",
  name: "",
  description: "No Description",
  complete: false,
  createdDate: "January, 4th 2022",
  lastUpdatedDate: "January, 4th 2022",
}
 */

Он создаст uuid , добавит даты и использует значения по умолчанию для полей, которые я определил в схеме.

Создать хранилище данных

Волшебство происходит, когда вы создаете свой магазин.

const todoStore = new ClientStore<ToDo>("todos", todoSchema)

Это самая простая форма создания хранилища, которое принимает все значения по умолчанию. Это означает, что он обрабатывает версии, тип хранилища, резервные копии, создание, оптимизацию и т. д.

Я также могу предоставить свою конфигурацию, которая состоит из 4 вариантов:

const todoStore = new ClientStore<ToDo>("todos", todoSchema, {
    appName: "My Todo App",
    version: 1, // changes when configuration or schema changes
    type: ClientStore.Type.LOCALSTORAGE,
    description: "manages user todos"
})

Выше я создаю первую версию моего магазина задач, который я определяю как тип LOCALSTORAGE.

Я также могу предоставить список типов, к которым он может вернуться, например:

type: [
  ClientStore.Type.INDEXEDDB,
  ClientStore.Type.LOCALSTORAGE,
  ClientStore.Type.MEMORY_STORAGE
],

Вышеупомянутое означает, попробуйте эти варианты хранения в соответствии с поддержкой браузера, начиная с INDEXEDDB до MEMORY_STORAGE .

Магазин многое делает для вас. Он будет читать хранилище клиентов при загрузке, если они существуют, заполнять все и быть готовым к вашему взаимодействию с ним. Магазин на 100% асинхронный, что очень помогает.

CRUD магазин

Создать предмет очень просто. Мне не нужно указывать все значения. Я могу просто указать значения по умолчанию, а остальные заполняются автоматически:

const todo1 = await todoStore.createItem({
    name: "Go Shopping", // name is required
});

/* Will create item
{
  id: "123e4567-e89b-12d3-a456-426614174000",
  name: "Go shopping",
  description: "No Description",
  complete: false,
  createdDate: "January, 4th 2022",
  lastUpdatedDate: "January, 4th 2022",
}
 */

Он также проверит данные на соответствие типу и типу данных схемы, который я определил. Например, выполнение следующего не удастся:

todoStore.createItem({});
todoStore.createItem({name: 12});
todoStore.createItem({complete: "yes"});

Для меня он проверяет тип и требования к полям. Он также максимально фильтрует и отображает данные для меня.

const todo = fetchSingleTodo("984728374834");
todoStore.createItem(todo);

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

Обновление магазина — аналогичный процесс:

const todo1 = await todoStore.createItem({
    name: "Go Shopping",
});
await todoStore.updateItem(todo1.id, {
  complete: true
})

В общем, он поддерживает только следующие CRUD и ищет асинхронные действия: createItem, updateItem, getItem, getItems, loadItems, removeItem, clear, findItem, findItems .

Магазинные крючки

Можно узнать и перехватить все, что делается в магазине.

Вы можете подписаться на обновления магазина:

const unsub = todoStore.subscribe((eventType, details) => {
    switch (eventType) {
      case ClientStore.EventType.READY: 
        // handle event type here
        break;
      case ClientStore.EventType.CREATED: 
        // handle event type here
        break;
      case ClientStore.EventType.UPDATED: 
        // handle event type here
        break;
      case ClientStore.EventType.ABORTED: 
        // handle event type here
        break;
      case ClientStore.EventType.CLEARED: 
        // handle event type here
        break;
      case ClientStore.EventType.DELETED: 
        // handle event type here
        break;
      case ClientStore.EventType.ERROR: 
        // handle event type here
        break;
      default:
    }
})

unsub() // call to unsubscribe from the store

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

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

const unsub = todoStore.beforeChange(async (eventType, data) => {
    switch (eventType) {
        case ClientStore.EventType.CREATED:
            await todoService.createTodo(data);
            break;
        case ClientStore.EventType.UPDATED:
            await todoService.updateTodo(data.id, data);
            break;
        case ClientStore.EventType.DELETED:
            await todoService.updateTodo(data);
            break;
        case ClientStore.EventType.CLEARED:
            await todoService.deleteAllByIds(data);
            break;
        default:
    };
    
    return true;
});

unsub();

Вы можете вернуть true или false независимо от того, следует ли обновлять хранилище, которое отправит событие ABORT, которое вы можете перехватить в обработчике подписки.

Вы также можете выдавать ошибки или просто позволить вашему API дать сбой, и магазин будет перехватывать и транслировать событие ERROR, которое вы также можете перехватить в обработчике подписки.

Более сложные данные

Отличительной особенностью схемы является то, что вы можете вкладывать их друг в друга для формирования сложных хранилищ.

Например, мы можем вложить схему пользователя в схему задач.

// Schema TS type
interface User extends Schema.DefaultValue {
    name: string;
}

interface ToDo extends Schema.DefaultValue {
    name: string;
    description: string;
    complete: boolean;
    user: User;
}

// denife schemas
const userSchema = new Schema<User>("user");
const todoSchema = new Schema<ToDo>("todo");

userSchema.defineField("name", String, {required: true});

todoShema.defineField("name", String, {required: true});
todoShema.defineField("description", String);
todoShema.defineField("complete", Boolean);
todoShema.defineField("user", userSchema, {required: true});

// create stores
const userStore = new ClientStore<User>("users", userSchema);
const todoStore = new ClientStore<ToDo>("todos", todoSchema);

Тогда создание элемента будет выглядеть так:

// you can create a new on or get it from the store
const AdminUser = await userStore.createItem({
    name: "John Doe"
});

const todo1 = await todoStore.createItem({
    name: "Go Shopping",
    user: AdminUser
});

/* Will create item
{
  id: "123e4567-e89b-12d3-a456-426614174000",
  name: "Go shopping",
  description: "No Description",
  complete: false,
  user: {
    id: 3483748929e82382,
    name: "John Doe",
    createdDate: "January, 4th 2022",
    lastUpdatedDate: "January, 4th 2022",
  } 
  createdDate: "January, 4th 2022",
  lastUpdatedDate: "January, 4th 2022",
}
 */

Хранилище будет глубоко оценивать данные в соответствии с определением схемы, поэтому следующее не удастся:

// missing required user
await todoStore.createItem({name: "Buy shoes"});
// missing required user name
await todoStore.createItem({name: "Buy shoes", user: {}});

Еда на вынос

Это решение избавляет вас от суеты при работе с данными на клиенте. Простой факт, что он обрабатывает множество вариантов хранения с одним и тем же интерфейсом API, является огромным плюсом. Помимо данных, он поддерживает как простые, так и сложные типы данных, формат данных и проверки с удивительными хуками, чтобы действительно извлечь из этого максимальную пользу.

Проверьте это, и дайте мне знать, что вы думаете в комментариях:



Канал YouTube: Before Semicolon
Веб-сайт: beforesemicolon.com