Strona główna / Artykuły / Trzy wzorce TypeScript, które poprawiają architekturę aplikacji React

Trzy wzorce TypeScript, które poprawiają architekturę aplikacji React

Dowiedz się, w jaki sposób wzorce Repository, Observer i Builder wykorzystują system typów TypeScript do tworzenia czystszych i łatwiejszych w utrzymaniu baz kodu w React i Next.js.

763 słów

Pisanie w TypeScript nie oznacza jego dobrze wykorzystania

Wielu programistów dodaje adnotacje typowe do swoich zmiennych i uważa to za wystarczające. Jednak prawdziwa siła TypeScript leży gdzie indziej — w wzorcach strukturalnych, które odróżniają kruchą bazę kodu od takiej, która rozwija się stopniowo i sprawnie.

Po pracy nad aplikacjami produkcyjnymi z użyciem React i Next.js, kilka wzorców wyróżnia się jako prawdziwe elementy zmieniające sytuację. Nie są to abstrakcyjne ćwiczenia z podręczników — to rozwiązania problemów, z którymi faktycznie się spotkasz.

1. Wzorzec repozytorium — Oddziel pobieranie danych od wszystkiego innego

Gdy twoje komponenty bezpośrednio wywołują fetch(), stwarzasz sobie trudności przy ponownym pisaniu kodu, gdy tylko API ulegnie zmianie.

Wzorzec repozytorium ukrywa dostęp do danych za czystym interfejsem:

// repositories/userRepository.ts
interface UserRepository {
  getById(id: string): Promise<User>;
  getAll(): Promise<User[]>;
}

export class ApiUserRepository implements UserRepository {
  async getById(id: string): Promise<User> {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  }

  async getAll(): Promise<User[]> {
    const res = await fetch('/api/users');
    return res.json();
  }
}

Dzięki temu twoje komponenty polegają na abstrakcji, a nie na konkretnym wdrożeniu. Oznacza to, że możesz zastąpić ApiUserRepository wersją symulowaną w testach, nie dotykając żadnego kodu interfejsu użytkownika.

2. Wzorzec Obserwatora — reaktywny stan bez zbędnej obciążenia Redux

Redux radzi sobie z tym zadaniem, ale w przypadku stanu niezbyt skomplikowanego może to wyglądać jak użycie młota do małej pracy. Lekka implementacja wzorca Obserwatora oparta na klasach i typowym emiterze zdarzeń oferuje prostszą alternatywę:

// utils/eventBus.ts
type EventMap = {
  'user:loggedIn': { userId: string };
  'cart:updated': { itemCount: number };
};

class TypedEventBus {
  private listeners: Partial<{
    [K in keyof EventMap]: ((payload: EventMap[K]) => void)[]
  }> = {};

  on<K extends keyof EventMap>(event: K, cb: (payload: EventMap[K]) => void) {
    (this.listeners[event] ??= []).push(cb);
  }

  emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
    this.listeners[event]?.forEach(cb => cb(payload));
  }
}

export const eventBus = new TypedEventBus();

Wszystko tutaj jest silnie typowane — nie ma żadnych typów any ukrytych w cieniu. Oznacza to, że nie ma żadnej niejednoznaczności co do tego, jakiego kształtu danych powinien oczekiwać dany słuchacz.

3. Wzorzec Budowniczego — zapewnienie uporządkowania przy tworzeniu złożonych obiektów

Jeśli kiedykolwiek miałeś trudności z tworzeniem obiektów filtrujących, konfiguracji żądań API lub schematów form zawierających zagnieżdżone warunki, wzorzec Builder zapewnia natychmiastową przejrzystość:

// builders/queryBuilder.ts
class QueryBuilder {
  private params: Record<string, string> = {};

  withPage(page: number) {
    this.params['page'] = String(page);
    return this;
  }

  withLimit(limit: number) {
    this.params['limit'] = String(limit);
    return this;
  }

  withSearch(term: string) {
    if (term.trim()) this.params['q'] = term;
    return this;
  }

  build(): string {
    return new URLSearchParams(this.params).toString();
  }
}

// Usage
const query = new QueryBuilder()
  .withPage(1)
  .withLimit(20)
  .withSearch('typescript')
  .build();
// → "page=1&limit=20&q=typescript"

Wynikowy kod jest łatwy do odczytania, jego elementy łączą się ze sobą w sposób uporządkowany, a sama struktura uniemożliwia wywołanie kroków w niewłaściwej kolejności.

Główne wnioski

  • Wzorzec Repository: oddziela warstwę dostępu do danych od logiki interfejsu użytkownika, stosując zasadę inwersji zależności
  • Wzorzec Observer: umożliwia lekki, reaktywny wymianę informacji pomiędzy elementami aplikacji bez konieczności stosowania rozbudowanej bazy kodu
  • Wzorzec Builder: sprawia, że tworzenie złożonych obiektów jest zarówno czytelne, jak i bezpieczne
  • Wszystkie trzy wzorce znacznie korzystają z interfejsów i generyków w TypeScript, dzięki czemu są znacznie bardziej przejrzyste niż ich odpowiedniki w zwykłym JavaScript

„TypeScript to nie tylko dodawanie typów — tworzy on dialog pomiędzy każdą warstwą Twojej aplikacji.” — Dan Vanderkam, autor książki Effective TypeScript

Co spróbować dalej

Wybierz jeden z wzorców przedstawionych w tym artykule i zastosuj go do rzeczywistego modułu w swojej bazie kodu w tym tygodniu. Pracuj nad jednym elementem po drugim i pamiętaj, że te wzorce to narzędzia, a nie dogmaty.

Najlepsze bazy kodu nie są definiowane przez liczbę typów, które zawierają, nawet w projekcie typu TypeScript. Są one definiowane przez celowość — każdy wzorzec znajduje się tam, ponieważ zasłużył na to miejsce.

Literatura pokrewna

  • Kiedy sztuczna inteligencja pisze twoje React App, ale pomija zasady czystego kodu — Poznaj siedem nawyków pisania czystego kodu – DRY, jedna odpowiedzialność, klauzule ochronne i inne – które często są łamane w kodzie React generowanym przez sztuczną inteligencję, oraz sposoby ich naprawy.
  • Zrozumienie React Lanes: Jak bitmaski kodują priorytetaktualizacji — Dowiedz się, jak React używa bitmasek do kodowania kilku priorytetów aktualizacji w jednej liczbie całkowitej, oraz dlaczego operacje bitowe zastępują proste flagi logiczne przy planowaniu.