Типы для реактивных примитивов

Solid.js — это современный JavaScript фреймворк для создания пользовательских интерфейсов, который акцентирует внимание на производительности и простоте. Одной из ключевых особенностей Solid является использование реактивности для обновления интерфейса. Важной составляющей реактивности в Solid.js являются реактивные примитивы, такие как Signal, Store и другие, каждый из которых имеет свой тип. Знание этих типов и правильное их использование позволяет достичь наилучшей производительности и избежать ошибок.

Signal

Signal — это один из основных реактивных примитивов в Solid.js, представляющий собой единицу состояния. Он используется для хранения данных, которые могут изменяться и вызывать перерендер компонента при их изменении.

import { createSignal } from "solid-js";

const [count, setCount] = createSignal(0);

setCount(count() + 1);

Здесь createSignal создаёт сигнал с начальным значением, и возвращает пару: сам сигнал (функция для чтения значения) и функцию для обновления значения. Тип этого примитива соответствует значению, которое он хранит. Например, если сигнал хранит число, его тип будет Signal<number>.

import { createSignal } from "solid-js";

const [count, setCount] = createSignal<number>(0);

Тип сигнала будет автоматически выводиться на основе типа начального значения. Однако, для лучшего контроля, можно явно указать тип.

Store

Store в Solid.js представляет собой более сложную структуру данных, которая похожа на реактивный объект. Store позволяет создавать объект с несколькими реактивными полями, которые могут изменяться независимо друг от друга. Для работы с Store в Solid.js используется функция createStore.

import { createStore } from "solid-js/store";

const [state, setState] = createStore({
  user: {
    name: "John",
    age: 30,
  },
});

setState("user", "name", "Jane");

Тип для state будет автоматически определяться в зависимости от структуры объекта, переданного в createStore. Например, если объект имеет свойства name (строка) и age (число), тип хранилища будет следующим:

const [state, setState] = createStore<{ user: { name: string; age: number } }>(...);

Store позволяет обновлять значения на глубоком уровне, что дает большую гибкость, чем Signal. Можно использовать ключи для обновления внутренних значений, как показано в примере, где обновляется только свойство name внутри объекта user.

Computed

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

import { createSignal, createComputed } from "solid-js";

const [count, setCount] = createSignal(0);
const [doubledCount, setDoubledCount] = createSignal(0);

createComputed(() => {
  setDoubledCount(count() * 2);
});

Здесь doubledCount будет иметь тип, аналогичный типу сигнала count, в данном случае number. Тип createComputed выводится на основе выражения, переданного в его аргумент. Если результат выражения — это число, тип будет number, если строка — string и так далее.

Типы реактивных примитивов в Solid.js

  • Signal: Тип сигнала соответствует типу хранимого значения. Для числового сигнала это будет Signal<number>, для строкового — Signal<string>.

  • Store: Тип хранилища соответствует структуре объекта, который хранится в нём. Если это объект с несколькими полями, то тип хранилища будет объектным с соответствующими типами для каждого из полей.

  • Computed: Тип вычисляемого значения зависит от возвращаемого результата. Например, если createComputed вычисляет число, тип будет number.

Пример с использованием всех примитивов

Для иллюстрации использования различных реактивных примитивов в Solid.js можно рассмотреть следующий пример, где комбинируются Signal, Store и Computed.

import { createSignal, createStore, createComputed } from "solid-js";

const [count, setCount] = createSignal(0);

const [state, setState] = createStore({
  user: {
    name: "John",
    age: 30,
  },
});

const [doubledCount, setDoubledCount] = createSignal(0);

createComputed(() => {
  setDoubledCount(count() * 2);
});

setCount(count() + 1);
setState("user", "age", state.user.age + 1);

Здесь:

  1. count — это сигнал типа Signal<number>.
  2. state — это Store, типизированный как { user: { name: string; age: number } }.
  3. doubledCount — ещё один сигнал, который зависит от count и обновляется через createComputed.

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

Обработка типов в TypeScript

Одной из сильных сторон Solid.js является его совместимость с TypeScript. Использование реактивных примитивов позволяет точно контролировать типы данных. TypeScript будет автоматически выводить типы для сигналов и хранилищ, что уменьшает вероятность ошибок и облегчает поддержку кода.

Для получения типов реактивных примитивов можно использовать встроенные утилиты типов, такие как ReturnType. Например, для сигнала можно получить его тип с помощью:

type CountSignal = ReturnType<typeof createSignal>;

Это позволит создать дополнительную абстракцию и обеспечить типовую безопасность при работе с реактивными примитивами в проекте.

Заключение

Работа с реактивными примитивами в Solid.js требует внимательного подхода к типам, особенно в TypeScript. Понимание типов сигналов, хранилищ и вычисляемых значений позволяет не только писать производительный код, но и использовать преимущества статической типизации для предотвращения ошибок.