Skip to content

@virentia/react API

Используйте @virentia/react на границе рендеринга. Доменную логику держите в моделях @virentia/core.

ScopeProvider

Передает scope из ядра в React hooks.

Используйте ScopeProvider один раз на React-дерево, которое должно разделять состояние. Вложенные деревья могут предоставить другой scope, если им нужна изоляция.

tsx
import { scope } from "@virentia/core";
import { ScopeProvider } from "@virentia/react";

const appScope = scope();

export function App() {
  return (
    <ScopeProvider scope={appScope}>
      <Routes />
    </ScopeProvider>
  );
}

useProvidedScope

Читает scope из ближайшего ScopeProvider.

Используйте этот hook, когда компонент должен передать scope в helpers на границе запуска, кеши или внешние адаптеры.

tsx
function SaveButton({ saved }: { saved: EventCallable<void> }) {
  const scope = useProvidedScope();
  const onClick = () => scoped(scope, () => saved());

  return <button onClick={onClick}>Save</button>;
}

Если ScopeProvider отсутствует, hook бросит ошибку.

useUnit

Читает сторы и привязывает вызываемые юниты к scope из ScopeProvider.

Используйте useUnit для простых компонентов, которым нужно несколько сторов, событий или эффектов.

tsx
const countValue = useUnit(count);
const increment = useUnit(incremented);

Объект:

tsx
const model = useUnit({
  count,
  incremented,
  pending: saveFx.pending,
  save: saveFx,
});

Массив:

tsx
const [countValue, increment] = useUnit([count, incremented]);

useModel

Подготавливает модель для рендера.

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

tsx
const model = useModel({
  count,
  incremented,
});

Создание модели из props:

tsx
function createCounterModel({ props }: ModelContext<{ step: number }>) {
  const clicked = event<void>();
  const count = store(0);

  reaction({
    on: clicked,
    run() {
      count.value += props.step;
    },
  });

  return { clicked, count };
}

function Counter(props: { step: number }) {
  const model = useModel(createCounterModel, props);
  const increment = () => model.clicked();

  return <button onClick={increment}>{model.count}</button>;
}

Используйте кеш, когда модель должна пережить размонтирование компонента:

tsx
const model = useModel(createChatModel, props, {
  cache: chatCache,
  key: props.chatId,
});

component

Соединяет фабрику модели и view.

Используйте component, когда модель принадлежит жизненному циклу компонента. Так создание модели, props, события монтирования и размонтирования, а также рендер остаются в одном понятном паттерне.

tsx
export const Counter = component({
  model({ props }: ModelContext<{ step: number }>) {
    const clicked = event<void>();
    const count = store(0);

    reaction({
      on: clicked,
      run() {
        count.value += props.step;
      },
    });

    return { clicked, count };
  },
  view({ model }) {
    const increment = () => model.clicked();

    return <button onClick={increment}>{model.count}</button>;
  },
});

Кешированный компонент:

tsx
export const ChatPanel = component({
  cache: chatCache,
  key: (props: { chatId: string }) => props.chatId,
  model: createChatModel,
  view({ model }) {
    return <div>{model.messages.items.length}</div>;
  },
});

Управляемый компонент:

tsx
export const Parent = component({
  model() {
    const counter = Counter.create({ step: 2 });

    return { counter };
  },

  view({ model }) {
    return <Counter step={2} model={model.counter} />;
  },
});

.create(props) позволяет модели родительского component создать модель дочернего component и владеть ей. Если передать model в дочерний component, этой моделью владеет родитель. Дочерний component обновляет context.props и отправляет lifecycle-юниты пока смонтирован, но не вызывает dispose() для controlled-модели при unmount.

createModelCache

Создает кеш, который учитывает scope и ваш key.

Используйте createModelCache, когда модель должна пережить размонтирование и позже переиспользоваться по key: чаты, вкладки, экраны деталей, медиаплееры, предпросмотры.

ts
const chatCache = createModelCache<string, ChatProps, ChatModel>();

Чтение кешированных моделей:

ts
chatCache.has("support", appScope);
chatCache.get("support", appScope);
chatCache.getInstance("support", appScope);

Очистка кешированных моделей:

ts
chatCache.delete("support", appScope);
chatCache.clear(appScope);

Аргумент scope необязателен у всех методов кеша (has, get, getInstance, delete, clear). Опустите его, чтобы искать ключ по всем scope, которые видел кеш.

ModelContext

Фабрики моделей получают context.

Используйте его, когда логика модели зависит от props, жизненного цикла, текущего scope или ключа кеша.

ts
interface ModelContext<Props, Key = undefined> {
  readonly scope: Scope;
  readonly owner: Owner;
  readonly props: ReactiveWritable<Props>;
  readonly mounted: EventCallable<void>;
  readonly unmounted: EventCallable<void>;
  readonly mounts: StoreWritable<number>;
  readonly key: Key;
}

mounted, unmounted и mounts полезны для логики жизненного цикла внутри модели.