Интеграция с внешними хранилищами

FAST Element — это современная библиотека для создания веб-компонентов на JavaScript с реактивной системой обновления свойств и удобным шаблонным синтаксисом. Для построения динамичных приложений часто требуется интеграция с внешними источниками данных, такими как REST API, GraphQL, локальное хранилище или IndexedDB. Рассмотрим ключевые подходы к работе с внешними хранилищами.


Реактивные свойства и синхронизация с данными

В FAST Element свойства компонентов автоматически отслеживаются при изменении, что позволяет синхронизировать данные с интерфейсом. Основные моменты:

import { FASTElement, html, css, observable } from "@microsoft/fast-element";

class UserProfile extends FASTElement {
    @observable user = null;

    async fetchUser(id) {
        const response = await fetch(`https://api.example.com/users/${id}`);
        this.user = await response.json();
    }
}

UserProfile.define({
    name: "user-profile",
    template: html<UserProfile>`
        <div>
            <h2>${x => x.user?.name}</h2>
            <p>Email: ${x => x.user?.email}</p>
        </div>
    `,
    styles: css`
        h2 { color: #333; }
        p { font-size: 14px; }
    `
});

Ключевые моменты:

  • @observable обеспечивает реактивность. Любое изменение свойства автоматически перерисовывает шаблон.
  • Асинхронные операции, такие как fetch, могут напрямую обновлять наблюдаемые свойства.
  • Доступ к данным в шаблоне происходит через стрелочные функции ${x => x.user?.name}, что защищает от ошибок при null или undefined.

Интеграция с REST API

Для REST API важно отделять логику запроса от визуальной части компонента. Использование асинхронных методов и observable свойств позволяет строить реактивный интерфейс:

class TodosList extends FASTElement {
    @observable todos = [];

    async connectedCallback() {
        super.connectedCallback();
        await this.loadTodos();
    }

    async loadTodos() {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos');
        this.todos = await response.json();
    }
}

TodosList.define({
    name: 'todos-list',
    template: html<TodosList>`
        <ul>
            ${x => x.todos.map(todo => html`<li>${todo.title}</li>`)}
        </ul>
    `
});

Особенности:

  • Метод connectedCallback используется для инициализации данных при добавлении компонента в DOM.
  • Массивы и объекты в шаблоне автоматически отслеживаются FAST Element, при изменении массива перерисовываются только измененные элементы.

Работа с локальным хранилищем (localStorage)

Для небольших данных можно синхронизировать observable свойства с localStorage:

class SettingsPanel extends FASTElement {
    @observable theme = 'light';

    connectedCallback() {
        super.connectedCallback();
        this.theme = localStorage.getItem('theme') || 'light';
    }

    changeTheme(newTheme) {
        this.theme = newTheme;
        localStorage.setItem('theme', newTheme);
    }
}

SettingsPanel.define({
    name: 'settings-panel',
    template: html<SettingsPanel>`
        <button @click=${x => x.changeTheme('light')}>Light</button>
        <button @click=${x => x.changeTheme('dark')}>Dark</button>
        <p>Current theme: ${x => x.theme}</p>
    `
});

Особенности синхронизации:

  • Значение observable свойства и localStorage поддерживаются в одном состоянии.
  • Использование стрелочных функций в обработчиках событий позволяет напрямую обновлять данные.

Интеграция с IndexedDB

Для сложных хранилищ подходит IndexedDB, обеспечивающий оффлайн-хранение и работу с большими объемами данных:

function openDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open('AppDB', 1);
        request.onupgradenee ded = () => {
            request.result.createObjectStore('users', { keyPath: 'id' });
        };
        request.onsucc ess = () => resolve(request.result);
        request.oner ror = () => reject(request.error);
    });
}

class IndexedUserList extends FASTElement {
    @observable users = [];

    async connectedCallback() {
        super.connectedCallback();
        await this.loadUsers();
    }

    async loadUsers() {
        const db = await openDatabase();
        const transaction = db.transaction('users', 'readonly');
        const store = transaction.objectStore('users');
        const request = store.getAll();
        request.onsucc ess = () => { this.users = request.result; };
    }
}

IndexedUserList.define({
    name: 'indexed-user-list',
    template: html<IndexedUserList>`
        <ul>
            ${x => x.users.map(user => html`<li>${user.name}</li>`)}
        </ul>
    `
});

Особенности:

  • Все операции IndexedDB асинхронны и требуют работы с onsuccess/onerror.
  • Использование observable свойства позволяет автоматически отобразить данные после их загрузки.
  • Для более сложных операций рекомендуется создавать отдельный слой сервисов для работы с IndexedDB.

Интеграция с GraphQL

GraphQL позволяет получать данные более гибко, чем REST, и часто используется с FAST Element для построения реактивных интерфейсов:

import { request } from 'graphql-request';

class GraphQLUser extends FASTElement {
    @observable user = null;

    async connectedCallback() {
        super.connectedCallback();
        const query = `
            query getUser($id: ID!) {
                user(id: $id) {
                    id
                    name
                    email
                }
            }
        `;
        this.user = await request('https://api.example.com/graphql', query, { id: 1 });
    }
}

GraphQLUser.define({
    name: 'graphql-user',
    template: html<GraphQLUser>`
        <div>
            <h3>${x => x.user?.user.name}</h3>
            <p>${x => x.user?.user.email}</p>
        </div>
    `
});

Особенности:

  • GraphQL-запросы позволяют получать только необходимые поля.
  • Комбинация observable и асинхронных функций обеспечивает автоматическое обновление интерфейса при загрузке данных.

Советы по архитектуре

  1. Отделение слоя данных: Создание сервисов для работы с API или IndexedDB упрощает тестирование и повторное использование компонентов.
  2. Использование @observable и реактивных массивов: Любое изменение данных должно происходить через реактивные свойства для автоматического обновления шаблона.
  3. Асинхронные операции: Методы загрузки данных должны быть асинхронными и безопасно обрабатывать ошибки с помощью try/catch.
  4. Стабильность шаблонов: Использовать безопасный доступ к свойствам (?.) и проверку наличия данных для предотвращения ошибок при первичной отрисовке.

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