Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Container/Presentational Pattern -- Separacja logiki od widoku

W Kosmicznej Stacji każdy system ma dwie warstwy: logikę sterowania (centrum dowodzenia) i wyświetlacz (panele informacyjne). W React stosujemy analogiczny podział -- Container/Presentational pattern.

Idea wzorca

Wzorzec dzieli komponenty na dwa typy:

  • Container (Smart Component) -- zarządza stanem, pobiera dane, obsługuje logikę biznesową. Nie zawiera JSX wyświetlającego UI.
  • Presentational (Dumb Component) -- odpowiada wyłącznie za renderowanie UI. Otrzymuje dane przez props, nie wie skąd pochodzą.

Presentational Component

Komponent prezentacyjny jest prosty. Przyjmuje dane i je wyświetla:

1// CrewList.jsx -- komponent prezentacyjny
2function CrewList({ members, onSelect, selectedId }) {
3  return (
4    <ul className="crew-list">
5      {members.map(member => (
6        <li
7          key={member.id}
8          className={member.id === selectedId ? 'active' : ''}
9          onClick={() => onSelect(member.id)}
10        >
11          <span className="name">{member.name}</span>
12          <span className="role">{member.role}</span>
13        </li>
14      ))}
15    </ul>
16  );
17}

Cechy komponentu prezentacyjnego:

  • Nie posiada własnego stanu (lub tylko stan UI jak hover)
  • Otrzymuje dane i callbacki przez props
  • Łatwy do testowania -- wystarczy przekazać props
  • Wielokrotnego użytku w różnych kontekstach

Container Component

Container zarządza stanem i logiką:

1// CrewContainer.jsx -- komponent kontenerowy
2function CrewContainer() {
3  const [members, setMembers] = useState([]);
4  const [selectedId, setSelectedId] = useState(null);
5  const [loading, setLoading] = useState(true);
6
7  useEffect(() => {
8    fetch('/api/crew')
9      .then(res => res.json())
10      .then(data => {
11        setMembers(data);
12        setLoading(false);
13      });
14  }, []);
15
16  const handleSelect = (id) => {
17    setSelectedId(id);
18  };
19
20  if (loading) return <Spinner />;
21
22  return (
23    <CrewList
24      members={members}
25      onSelect={handleSelect}
26      selectedId={selectedId}
27    />
28  );
29}

Ewolucja wzorca -- Custom Hooks

Współcześnie zamiast komponentów kontenerowych często używamy custom hooków:

1// useCrew.js -- hook zamiast container component
2function useCrew() {
3  const [members, setMembers] = useState([]);
4  const [selectedId, setSelectedId] = useState(null);
5  const [loading, setLoading] = useState(true);
6
7  useEffect(() => {
8    fetch('/api/crew')
9      .then(res => res.json())
10      .then(data => {
11        setMembers(data);
12        setLoading(false);
13      });
14  }, []);
15
16  const selectMember = (id) => setSelectedId(id);
17
18  return { members, selectedId, selectMember, loading };
19}
20
21// Użycie -- logika w hooku, UI w komponencie
22function CrewPage() {
23  const { members, selectedId, selectMember, loading } = useCrew();
24
25  if (loading) return <Spinner />;
26
27  return <CrewList members={members} onSelect={selectMember} selectedId={selectedId} />;
28}

Korzyści z separacji

Separacja logiki od widoku daje kilka kluczowych zalet, które sa nieocenione w duzych projektach:

1. Latwe testowanie

Komponenty prezentacyjne mozna testowac bez mockowania API:

1// Test komponentu prezentacyjnego -- zero mocków!
2test('renders crew list', () => {
3  const members = [
4    { id: 1, name: 'Anna', role: 'Pilot' },
5    { id: 2, name: 'Jan', role: 'Engineer' },
6  ];
7
8  render(<CrewList members={members} onSelect={() => {}} selectedId={null} />);
9
10  expect(screen.getByText('Anna')).toBeInTheDocument();
11  expect(screen.getByText('Engineer')).toBeInTheDocument();
12});
13
14// Test hooka -- testujemy logike osobno
15test('useCrew fetches and returns members', async () => {
16  const { result } = renderHook(() => useCrew());
17  await waitFor(() => expect(result.current.members).toHaveLength(5));
18});

2. Wielokrotne uzycie

Komponent

CrewList
mozna uzywac w roznych kontekstach -- na dashboardzie, w modalu, w panelu bocznym -- za kazdym razem z innym zrodlem danych.

3. Niezalezny rozwoj

Projektant moze pracowac nad komponentem prezentacyjnym (wyglad, animacje), a programista nad hookiem (logika, API) -- rownoczesnie i bez konfliktow.

Kiedy stosować ten wzorzec?

  • Zawsze gdy komponent pobiera dane z API i je wyświetla
  • Zawsze gdy ta sama logika jest używana w wielu miejscach
  • Nie dla prostych komponentów z minimalną logiką -- nadmierna abstrakcja to tez problem

Ten wzorzec to fundament dobrej architektury React. Rozdzielenie "co wyswietlic" od "skad wziac dane" sprawia, ze kod jest bardziej przewidywalny, testowalny i elastyczny -- jak oddzielne systemy nawigacji i wyswietlania na statku kosmicznym.

Przejdź do CodeWorlds