Динамические модели
В данный момент динамические модели все еще находятся на стадии разработки, их API может меняться со временем. Данная статья носит исключительно ознакомительный характер мы настоятельно не рекомендуем использовать в продакшене этот функционал.
НЕ РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ В ПРОДАКШЕНЕ.
На данный момент effector не имеет возможности динамически создавать юниты, юниты должны быть инициализированы статически на уровне модуля. Если же создавать юниты во время рантайма, то произойдет утечка памяти, потому что юниты навсегда останутся в графе. Хотя и можно попробовать использовать withRegion
, createNode
и clearNode
, но это требует определенного навыка разработчика, поскольку это низкоуровневый API и более того придется самому отслеживать жизненный цикл юнитов, что может быть головной болью.
Поэтому для случаев когда нужно было иметь динамику использовались key-value сторы, которые хранили в себе объекты, где ключами были идентификаторы, а значениями состояния, например:
import { createStore, createEvent } from "effector";import { useStoreMap } from "effector-react";
type Item = { id: string; count: number };const $items = createStore<Record<string, Item>>({});
const addItem = createEvent<Item>();const removeItem = createEvent<string>();
$items.on(addItem, (state, item) => ({ ...state, [item.id]: item,}));
$items.on(removeItem, (state, id) => { const copy = { ...state }; delete copy[id]; return copy;});
При этом в UI подписывались с помощью useStoreMap
только на ту часть, что соответствует id
для получения данных:
import { $items, addItem, removeItem } from "./model";import { useStoreMap, useUnit } from "effector-react";
function Counter({ id }: { id: string }) { const item = useStoreMap({ store: $items, keys: [id], fn: (state, [key]) => state[key], });
const [onAddItem, onRemoveItem] = useUnit([addItem, removeItem]);
if (!item) return null;
return ( <div> <span>{item.count}</span> <button onClick={() => onAddItem({ id, count: item.count + 1 })}>+</button> <button onClick={() => onRemoveItem(id)}>Удалить</button> </div> );}
Хотя такой подход работает, это не очень удобно, особенно если структура куда сложнее, чем в этом примере.
Модели приносят новый способ работы с динамическими состояниями, позволяя создавать экземпляры моделей на лету, которые имеют свои собственные состояния и логику.
Установка и работа с моделями
В данный момент модели реализованы в отдельном репозитории и доступны отдельным пакетом:
npm install @effector/model
а также пакет для интеграции с React:
npm install @effector/model-react
В корне репозитория вы сможете найти директорию apps
, где есть примеры использования моделей в приложениях с около-реальным функционалом. В данной статье мы просто ознакомимся с API и что из себя будут представлять динамические модели.
API моделей
Динамические модели приносят ряд новых API для работы:
-
keyval
– оператор который создаёт коллекцию инстансов модели, где каждый элемент идентифицируется ключом. Именно черезkeyval
происходит динамическое создание и удаление экземпляров модели.keyval
также может использоваться внутриkeyval
для вложенных структур. В аргументе ожидает колбэк который вернет объект со свойствами:state
– состояние модели, является объектом со сторами илиkeyval
моделью . Одно из свойств также должно быть ключом моделиkey
– ключ модели, иначе говоря ее уникальный идентификаторapi
– опциональный объект с событиями или эффектами для работы с модельюonMount
– опциональное событие или эффект, которое вызывается при создании инстанса моделиoptional
– опциональный массив строк с необязательными полями модели при создании
Например:
export const restaurantsList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $category = createStore<string[]>([]);
const dishesList = keyval(() => { const $name = createStore(""); const $description = createStore(""); const $price = createStore(0); const $additives = createStore<Additive[]>([]);
return { key: "name", state: { name: $name, description: $description, price: $price, additives: $additives, }, optional: ["additives"], }; });
return { key: "name", state: { name: $name, description: $description, category: $category, dishes: dishesList, }, api: { addDish: dishesList.edit.add, removeDish: dishesList.edit.remove, }, optional: ["category", "dishes"], };});
Теперь используя restaurantsList
мы можем в рантайме добавлять, изменять или удалять экземпляры модели. Все, что описано внутри keyval
будет динамически создано для каждого инстанса.
const addRestaurant = createEvent();
sample({ clock: addRestaurant, fn: () => ({ name: "Starbucks", description: "Американская корпорация и крупнейшая в мире сеть кофеен", }), target: restaurantsList.edit.add,});
lens
– линза необходима нам чтобы погрузиться внутрьkeyval
для работы с данными, например имея вложенныеkeyval
мы можем достучаться с самого верха до самого низа и получить данные или апи для работы с ним:
const menuItemIdLens = lens(orderKeyval).item(orderId).menuItemId;const foodDescLens = lens(restaurantKeyval).item(restId).menu.item(menuItemIdLens).description;
В данный момент API линзы дорабатывается и может отличаться от того, что есть в примерах репозитория.
Помимо основного пакета effector также имеется API для effector-react, чтобы удобно работать с моделями в React:
useEntityList(keyval, View)
– хук, который принимаетkeyval
первым аругментом и компонент вторым. Итерирует по всем ключам коллекции и для каждого создаётEntityProvider
, передавая в негоView
. Проще говоря, это способ отрисовать список и в дальнейшем удобнее работать с остальными хуками без передачиid
.useEntityItem(keyval, key?)
– возвращает сущность поid
в коллекцииkeyval
. Еслиkey
передан явно, ищет элемент по этому ключу, если ключ не передан, пытается получить его из ближайшегоEntityProvider
.useItemApi(keyval, key?)
– возвращает объект API сущности для работы с ней.useEditItemField(keyval, key?)
– возвращает объект с функциями для обновления каждого поля модели. Еслиkey
передан явно, ищет элемент по этому ключу, если ключ не передан, пытается получить его из ближайшегоEntityProvider
.useEditKeyval(keyval)
– возвращает объект методов для модификации модели, например добавить, удалить или обновить.
const { add, map, remove, replaceAll, set, update } = useEditKeyval(ordersList);
Связанные API и статьи
-
API
clearNode
— Метод для уничтожения ноды и ее связейwithRegion
— Метод установки региона для нод
-
Articles
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.