Powrót do strony głównej

Stos okien modalnych React: architektura dla IT

Artykuł opisuje architekturę typowanego stosu okien modalnych dla aplikacji React. Rozwiązuje problemy duplikowania DOM, prop drilling i zależności poprzez slice Redux, hooki overlay i source-wrappers. Przytoczone są przykłady kodu i strategie skalowania.

Typowany stos modalnych w React dla złożonych UI
Advertisement 728x90

Architektura stosu okien modalnych w React dla skalowalnych aplikacji

W złożonych aplikacjach React okna modalne szybko stają się źródłem problemów. Jedno okno modalne z prostym useState działa izolowane, ale wraz ze wzrostem projektu pojawiają się duplikacja komponentów, prop drilling i zależności stanów. Rozwiązaniem jest typowany stos overlays z podziałem logiki według źródeł wywołań.

Rozważmy typowy przypadek: panel administracyjny marketplace z tabelą produktów. Każdy wiersz wymaga okna modalnego do wprowadzania oznaczeń (IMEI, SGTIN), włączając tabelę zamówień, walidację, ładowanie Excel i wieloetapowy proces.

Problemy z lokalnym renderowaniem

Renderowanie okna modalnego IdentificationModal w każdym wierszu tabeli prowadzi do tysięcy ukrytych instancji w DOM. Komponent zawiera tabele, pola wejściowe z maskami, hooki RTK Query, stepper — obciążenie pamięci jest krytyczne.

Google AdInline article slot

Przykład nieudanego podejścia:

const ProductRow = ({ product }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <tr>
      <td>{product.name}</td>
      <td>
        <Button onClick={() => setIsOpen(true)}>Wprowadź oznaczenie</Button>
        <IdentificationModal
          isOpen={isOpen}
          onClose={() => setIsOpen(false)}
          productId={product.id}
        />
      </td>
    </tr>
  );
};

Wniosek: okno modalne powinno być renderowane tylko raz na poziomie strony.

Prop drilling i globalny stan

Przeniesienie okna modalnego na górę rozwiązuje problem DOM, ale wymaga przekazywania callbacków przez 4–5 poziomów komponentów:

Google AdInline article slot
  • ProductsPageProductsTableTableBodyProductRowActionsMenu

Redux z dispatchami openIdent(productId) eliminuje propsy, ale komplikuje się przy wielu kontekstach.

Wielorakie źródła danych

Okno modalne jest używane z trzech miejsc z różnymi API:

| Źródło | Zamówienia | Ładowanie danych | Szablon Excel |

Google AdInline article slot

|--------|------------|------------------|---------------|

| Nagłówek tabeli | Wszystkie zamówienia | ordersApi.getIdentOrders() | ordersApi.getExcel() |

| Wiersz tabeli | Jedno zamówienie | Filtr według orderId | ordersApi.getExcel() |

| Koszyk | Zamówienia koszyka | cartsApi.getCartIdentOrders(cartId) | cartsApi.getExcel() |

Wkładanie logiki do okna modalnego tworzy God Object z if-ami dla każdego API, inwalidacją cache, analityką. Alternatywą jest czysty UI z propsami orders, onSave, ale strona nie zna kontekstu z góry.

Zależności między oknami modalnymi

Otwarcie identyfikacji ukrywa panel działań masowych, zamknięcie przywraca go. Dodanie okna modalnego potwierdzenia («Niezapisane dane») wymaga ręcznego zarządzania flagami:

openConfirmClose: (state) => {
  state.isConfirmOpen = true;
  state.isIdentOpen = false;
},
cancelConfirmClose: (state) => {
  state.isConfirmOpen = false;
  state.isIdentOpen = true;
},

Płaska struktura flag nie odzwierciedla kolejności nakładania i strategii powrotu.

Architektura rozwiązania: typowany stos

System rozwiązuje trzy problemy jednocześnie:

  • Jednolity renderowanie przez Redux-stos z kolejką overlays.
  • Podział kontekstów przez source-opakowania.
  • Automatyczne zarządzanie zależnościami regułami stosu.

Redux-slice dla stosu

export enum EOverlayName {
  BulkActions = 'bulkActions',
  IdentFromHeader = 'identFromHeader',
  IdentFromRow = 'identFromRow',
  IdentFromCart = 'identFromCart',
  EditProduct = 'editProduct',
  // ...
}

type OverlayItem<TData = unknown> = {
  name: EOverlayName;
  data: TData;
  priority?: number;
};

type OverlayStack = OverlayItem[];

Stos przechowuje typowane dane i kolejność. Asynchroniczne działania otwierają overlay z payload.

Overlay-hooki dla triggerów

const useOverlay = () => {
  const dispatch = useDispatch();

  const open = <TData>(name: EOverlayName, data: TData) => {
    dispatch(openOverlay({ name, data }));
  };

  const close = (name: EOverlayName) => {
    dispatch(closeOverlay(name));
  };

  return { open, close };
};

Przycisk w wierszu: open('identFromRow', { productId, orderId }). Okno modalne czyta stos bez prop drilling.

Source-opakowania dla kontekstów

Każde źródło eksportuje adapter:

// identFromRow.adapter.ts
export const useIdentFromRowAdapter = () => {
  const { open } = useOverlay();

  const openIdent = (productId: string, orderId: string) => {
    const orders = ordersApi.useGetIdentOrdersQuery();
    const filtered = orders.data?.filter(o => o.id === orderId);
    open('identFromRow', { orders: filtered, productId });
  };

  return { openIdent };
};

Okno modalne otrzymuje gotowe orders, onSave bez wiedzy o źródłach.

Strategie otwierania i zamykania

Stos wspiera reguły:

  • Zastępowanie: otwarcie ident zamyka bulkActions.
  • Pauza: zamknięcie przywraca poprzednie.
  • Oczyszczanie: potwierdzenie resetuje stos.
const openOverlay = createAsyncThunk(
  'overlay/open',
  async ({ name, data }, { getState }) => {
    const stack = selectStack(getState());
    // Logika zastępowania/priorytetów
    return { name, data };
  }
);

Co jest ważne

  • Stos rozwiązuje prop drilling, duplikację DOM i zależności stanów jedną architekturą.
  • Source-opakowania izolują logikę API, okno modalne pozostaje czystym UI.
  • Typowanie OverlayItem<TData> zapewnia bezpieczeństwo danych.
  • Asynchroniczne strategie automatyzują kolejność otwierania/zamykania.
  • Skaluje się do dziesiątek okien modalnych bez wzrostu złożoności reducera.

— Editorial Team

Advertisement 728x90

Czytaj dalej