SyncModel

SyncModel - Хелпер для синхронизации одной модели в разных местах приложения

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

Типичный пример использования класса, несколько списков содержащих одну модель, но из разных источников (к примеру списки из asyncData и pinia). При обновлении одного из них, необходимо обновлять второй.

  1. Если SyncModel инстанс еще не создан, его нужно создать
export interface IFoo {
  uid: number;
  text: string;
}

export const fooSyncModel = SyncModel.extend<IFoo>({
  eventName: 'foo',
  itemKey: 'uid',
});
  1. Подписаться на изменение модели
import { fooSyncModel, type IFoo } from '.../foo-sync-model';
import { useSyncModelSubscribe } from 'shared-front/lib/composition/sync-model';

export default {
  setup() {
    const foos = ref([] as IFoo[]);

    let unsubFoo = () => {};
    onMounted(() => {
      unsubFoo = fooSyncModel.subsribe({
        get: () => foos.value,
      });
    });
    onUnmounted(unsubFoo);

    // Or
    useSyncModelSubscribe(fooSyncModel, {
      get: () => foos.value,
    });
  },

  // Or
  data: () => ({
    foos: [] as IFoo[],
  }),

  mounted() {
    this.$once(
      'hook:beforeDestroy',
      fooSyncModel.subsribe({
        get: () => this.foos,
      }),
    );
  },
};

1.1 Подписка если модель не совпадает с исходной

import { fooSyncModel } from '.../foo-sync-model';


interface IBar {
  uid: number;
  body: string;
}

export default {
  setup() {
    const bars = ref([] as IBar[]);

    onMounted(() => {
      onUnmounted(fooSyncModel.subsribe({
        get: () => bars.value,
        merge: (oldValue, payload) => {
          const newValue: IBar = {
            ...oldValue,
          };

          if (payload.text) newValue.body = payload.text;

          return newValue;
        },
      }));
    });
  },
};
  1. Обновление модели
import { fooSyncModel } from '.../foo-sync-model';

export default {
  setup() {
    fooSyncModel.update({
      uid: 1,
      text: 'foo'
    });
    fooSyncModel.updateArray([
      {
        uid: 1,
        text: 'foo 1'
      },
      {
        uid: 2,
        text: 'foo 2'
      },
    ]);
  },
}

constructor/extend

Перед использованием необходимо создать модель с нужными параметрами

const syncModel = new SyncModel({
  eventName: 'forum:theme',
});
PropRequiredDefaultDescription
eventNametrue-Уникальное название события по которому будет синхронизироваться модель
itemKeyfalse*'id'Название уникального поля в модели, если в модели нет поля id, itemKey обязательно

Из-за ограничений typescript, для поддержки типов нужно использовать метод extend

interface Theme {
  uuid: string;
  name: string;
}

const syncModel = SyncModel.extend<Theme>()({
  eventName: 'theme',
  itemKey: 'uuid',
});

Подписка на обновления

let theme;

const unSubscribe = syncModel.subscribe({
  get: () => theme,
  set: ({ newValue }) => {
    theme = newValue;
  },
});

Для подписки на обновления нужно указать объект/массив с сущностью и опционально set Возвращается функция для отписки

Если не указан set, для массивов будет использован splice на массиве Для объектов по умолчанию будет применяться lodash/merge

PropRequiredDefaultDescripton
gettrue-Геттер для получения текущего значения сущности, в дальнейшем используется для сравнения с обновленной сущностью(обновляться может сущность с иным id)
setfalseОписано вышеСеттер для сущности, в случае если id из геттера не совпадает/id из геттера не найден в массиве, сеттер не вызывается
sourcefalseundefinedЕсли в подписке указан source и он совпадает с source из update то событие игнорируется для данной подписки
serverfalsefalseПо умолчанию, подписка не происходит на сервере, при установке флага в true, подписка произойдет, Warning для сервера обязательно отписываться от событий при server: true, иначе будет утечка памяти
mergefalse*lodash/mergeФункция для слияния старого и нового значения, Функция принимает 2 аргумента, старое значение (oldValue) и payload. Если в get модель отличается от update, то поле обязательно.

set array

Аргументы сеттера если из геттера вернулся массив и в нем найдена сущность с обновляемым идентификатором

PropDescripton
idИдентификатор обновляемой сущности.
*Название поля не меняется при изменении itemKey
payloadОбновляемая часть, может как целиком перезаписывать сущность так и лишь одно поле, не содержит идентификатор
oldValueПредыдущее значение сущности
newValueНовое значение сущности, результат вызова функции merge
arrayМассив в котором содержится сущность
indexИндекс сущности в array

set object

Аргументы сеттера если из геттера вернулся объект и у сущностей совпадают идентификаторы

PropDescripton
idИдентификатор обновляемой сущности.
*Название поля не меняется при изменении itemKey
payloadОбновляемая часть, может как целиком перезаписывать сущность так и лишь одно поле, не содержит идентификатор
oldValueПредыдущее значение сущности
newValueНовое значение сущности, результат вызова функции merge

merge example

interface IFoo {
  id: number;
  text: string;
}
interface IBar {
  id: number;
  body: string;
}

const fooModel = SyncModel.extend<IFoo>()({
  eventName: 'foo',
});

const bars: IBar[] = [];

const unSubscribe = fooModel.subscribe({
  get: () => bars,
  merge: (oldValue, payload) => {
    const newValue: IBar = {
      ...oldValue,
    };

    if (payload.text) newValue.body = payload.text;

    return newValue;
  },
});

Отписка от обновлений

const unSubscribe = syncModel.subscribe(...);

...

unSubscribe();

Для отписки от обновлений нужно вызвать функцию возращенную из метода subscribe

Обновление сущностей

interface Theme {
  id: number;
  name: string;
  body: string;
}

syncModel.update({
  id: 1,
  name: 'New name',
});
syncModel.updateArray([
  {
    id: 1,
    name: 'New name',
  },
  {
    id: 2,
    name: 'New name',
  }
]);

Обновление сущностей осуществляется через вызов методов update/updateArray, API полностью идентичны, но updateArray принимает массив сущностей для обновления.

Модификаторы

syncModel.update({
  id: 1,
  name: 'New name',
}, { source: 'vuex' });
syncModel.updateArray([
  {
    id: 1,
    name: 'New name',
  },
  {
    id: 2,
    name: 'New name',
  }
], { source: 'vuex' });

Так же у обновлений могут быть модификаторы

NameDescription
sourceФильтр для событий из одного источника, при совпадении источника в update и subscribe, обновление игнорируется