Продвинутая типизация

Haunted — это современная библиотека для создания веб-компонентов на базе Web Components с использованием реактивного подхода, вдохновленного React Hooks. Одним из важных аспектов разработки масштабируемых приложений на Haunted является строгая и продвинутая типизация, особенно при использовании TypeScript. Она обеспечивает безопасность кода, предотвращает ошибки на этапе компиляции и улучшает читаемость проекта.


Типизация компонентов

В Haunted компоненты реализуются как функции, возвращающие шаблоны html. Стандартная сигнатура компонента выглядит так:

import { html } from 'haunted';

function MyComponent(props: { title: string }) {
  return html`<h1>${props.title}</h1>`;
}

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

  • props всегда типизируются как объект. Это позволяет строго контролировать входные данные.
  • Отсутствие обязательного указания типа возвращаемого значения допускается, так как html возвращает тип TemplateResult, но для более строгой типизации рекомендуется явное указание:
import { TemplateResult, html } from 'haunted';

function MyComponent(props: { title: string }): TemplateResult {
  return html`<h1>${props.title}</h1>`;
}

Типизация свойств веб-компонента

Haunted поддерживает декларацию свойств с использованием @property декоратора или через props объекта. Для TypeScript важно явно определять типы:

import { component, html } from 'haunted';
import { property } from 'lit/decorators.js';

class Counter extends HTMLElement {
  @property({ type: Number }) count = 0;
}

customElements.define('my-counter', Counter);

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

  • type указывает, как значение будет конвертироваться при присвоении свойства.
  • TypeScript позволяет гарантировать, что попытка присвоить string вместо number будет ошибкой на этапе компиляции.
  • Использование интерфейсов помогает объединять свойства компонента с дополнительными методами или событиями:
interface CounterProps {
  count: number;
  onIncrement?: () => void;
}

Использование хуков с типами

Haunted использует хуки, аналогичные React, такие как useState, useEffect и useReducer. Типизация этих хуков позволяет избегать неожиданных ошибок:

import { useState } from 'haunted';

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

  return html`
    <button @click=${() => setCount(count + 1)}>Increment: ${count}</button>
  `;
}

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

  • useState<Type>(initialValue) позволяет явно указать тип состояния.
  • useReducer<ReducerType>(reducer, initialState) требует явной типизации как состояния, так и экшенов:
type Action = { type: 'increment' } | { type: 'decrement' };

function reducer(state: number, action: Action) {
  switch (action.type) {
    case 'increment': return state + 1;
    case 'decrement': return state - 1;
  }
}

const [count, dispatch] = useReducer<number, Action>(reducer, 0);

Типизация событий

Haunted позволяет подписываться на события через шаблон. Для строгой типизации событий рекомендуется использовать Generic-типизацию для кастомных событий:

interface MyEventDetail {
  value: string;
}

function InputComponent() {
  const handleChange = (e: CustomEvent<MyEventDetail>) => {
    console.log(e.detail.value);
  };

  return html`<input @change=${(e: CustomEvent<MyEventDetail>) => handleChange(e)} />`;
}

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

  • CustomEvent<T> позволяет TypeScript понимать структуру объекта detail.
  • Это предотвращает ошибки при доступе к несуществующим полям.

Типизация слотов и детей компонентов

Haunted поддерживает передачу детей и слотов. Для строгой типизации удобно использовать children с типом TemplateResult | TemplateResult[]:

interface CardProps {
  title: string;
  children?: TemplateResult | TemplateResult[];
}

function Card({ title, children }: CardProps): TemplateResult {
  return html`
    <div class="card">
      <h2>${title}</h2>
      <div class="content">${children}</div>
    </div>
  `;
}

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

  • children часто используют для вложенных компонентов или динамического контента.
  • Явная типизация помогает избежать ошибок при передаче массива шаблонов или одного элемента.

Типизация кастомных хуков

Haunted позволяет создавать собственные хуки. Их типизация особенно важна для повторного использования:

import { useState, useEffect } from 'haunted';

function useCounter(initial: number) {
  const [count, setCount] = useState<number>(initial);

  useEffect(() => {
    console.log(`Count: ${count}`);
  }, [count]);

  return { count, increment: () => setCount(count + 1) };
}

function CounterComponent() {
  const { count, increment } = useCounter(0);

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

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

  • Возвращаемый объект явно типизируется { count: number; increment: () => void }.
  • TypeScript гарантирует корректное использование методов и свойств.

Совместимость с Lit и TypeScript

Haunted часто используется совместно с Lit для расширенной функциональности. В таком случае строгая типизация становится особенно важной для интеграции с Lit-декораторами, типизированными событиями и TemplateResult. Например:

import { html, TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';

class LitHauntedComponent extends HTMLElement {
  @property({ type: String }) name = '';

  render(): TemplateResult {
    return html`<p>Hello, ${this.name}</p>`;
  }
}

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

  • TypeScript позволяет полностью использовать возможности Lit внутри Haunted-компонентов.
  • Совместная типизация снижает вероятность ошибок при миграции компонентов между библиотеками.

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