Дженерики в хуках

Библиотека Haunted предоставляет мощный инструмент для создания реактивных веб-компонентов с использованием хуков, вдохновленных React. Одним из ключевых преимуществ является возможность использования дженериков для хуков, что позволяет создавать типобезопасные и гибкие компоненты на TypeScript.


Типизация состояния с useState

Хук useState в Haunted позволяет хранить состояние компонента. В TypeScript его можно параметризовать через дженерик, что обеспечивает строгую типизацию.

import { useState } from 'haunted';

function Counter() {
  const [count, setCount] = useState<number>(0);

  const increment = () => setCount(prev => prev + 1);

  return html`
    <button @click=${increment}>Count: ${count}</button>
  `;
}

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

  • <number> указывает тип состояния.
  • TypeScript предотвращает присвоение значений других типов, например setCount("text") вызовет ошибку компиляции.
  • Дженерик позволяет расширять хуки для сложных структур данных, включая объекты и массивы.
interface User {
  id: number;
  name: string;
}

const [user, setUser] = useState<User | null>(null);

Дженерики в useReducer

Хук useReducer поддерживает полную типизацию действий и состояния через дженерики:

interface State {
  count: number;
}

type Action = { type: 'increment' } | { type: 'decrement' };

const [state, dispatch] = useReducer<State, Action>((state, action) => {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}, { count: 0 });

Преимущества:

  • Типизация State и Action предотвращает ошибки при передаче некорректных действий.
  • Дженерики делают код более предсказуемым и поддерживаемым.
  • Возможность использовать сложные комбинации типов, например объединение нескольких интерфейсов для состояния.

Типизация эффекта с useEffect

Хук useEffect не принимает напрямую дженерики для типа возвращаемого значения, но в Haunted его использование с типизированными состояниями обеспечивает строгую типовую проверку:

const [data, setData] = useState<string[]>([]);

useEffect(() => {
  fetch('/api/items')
    .then(res => res.json())
    .then((items: string[]) => setData(items));
}, []);

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

  • Типизация состояния через дженерик useState<string[]> гарантирует корректность типов внутри эффекта.
  • Можно комбинировать с асинхронными операциями, сохраняя типовую безопасность.

Дженерики и пользовательские хуки

Haunted позволяет создавать кастомные хуки, которые могут быть полностью типизированы:

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then((result: T) => setData(result))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading };
}

interface Post {
  id: number;
  title: string;
}

const { data: posts, loading } = useFetch<Post[]>('/api/posts');

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

  • Универсальность: один хук для любого типа данных.
  • Поддержка строгой типизации при работе с массивами, объектами и сложными структурами.
  • Улучшение автодополнения в редакторах кода.

Ограничения и рекомендации

  • Дженерики нельзя применять для обычных HTML атрибутов компонента — только для состояния и хуков.
  • При работе с объектами и массивами стоит использовать Readonly или Partial для безопасного изменения данных.
  • Для сложных состояний лучше сочетать useReducer с дженериками, чтобы обеспечить строгую проверку действий и состояния.

Практическое применение

  1. Компоненты с типизированным состоянием: хранение форм, настроек, данных пользователя.
  2. Кастомные хуки для API: один универсальный хук для разных эндпоинтов с различными структурами данных.
  3. Сложные редьюсеры: использование дженериков для предотвращения ошибок при обработке действий.

Использование дженериков в хуках Haunted позволяет создавать типобезопасные, масштабируемые и поддерживаемые компоненты, интегрировать сложные состояния и унифицировать работу с данными. Это делает библиотеку удобной для крупных проектов на TypeScript, где строгая типизация критична.