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.
Wzorzec dzieli komponenty na dwa typy:
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:
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}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}Separacja logiki od widoku daje kilka kluczowych zalet, które sa nieocenione w duzych projektach:
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});Komponent
CrewList mozna uzywac w roznych kontekstach -- na dashboardzie, w modalu, w panelu bocznym -- za kazdym razem z innym zrodlem danych.Projektant moze pracowac nad komponentem prezentacyjnym (wyglad, animacje), a programista nad hookiem (logika, API) -- rownoczesnie i bez konfliktow.
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.