Na statku kosmicznym kazdy modul ma standardowe gniazdo -- mozesz do niego podlaczyc silnik plazmowy, napęd jonowy lub eksperymentalny warp drive. Modul nie decyduje, jaki silnik ma miec -- to uzytkownik (inzynier) podejmuje te decyzje. To wlasnie Inversion of Control (IoC) -- komponent oddaje kontrole konsumentowi, zamiast sam decydowac o kazdym szczegole.
IoC w React oznacza, ze komponent:
Zamiast jednego
children, komponent definiuje wiele slotow, ktore konsument wypelnia dowolna trescia:1function SpaceCard({ header, footer, actions, children }) {
2 return (
3 <div className="space-card">
4 {header && <div className="card-header">{header}</div>}
5 <div className="card-body">{children}</div>
6 {actions && <div className="card-actions">{actions}</div>}
7 {footer && <div className="card-footer">{footer}</div>}
8 </div>
9 );
10}
11
12// Konsument decyduje CO wyswietlic w kazdym slocie
13function MissionCard({ mission }) {
14 return (
15 <SpaceCard
16 header={<h3>{mission.name}</h3>}
17 actions={
18 <>
19 <button>Start</button>
20 <button>Cancel</button>
21 </>
22 }
23 footer={<span>Priority: {mission.priority}</span>}
24 >
25 <p>Distance: {mission.distance} ly</p>
26 <p>Status: {mission.status}</p>
27 </SpaceCard>
28 );
29}asPolymorphic components pozwalaja konsumentowi zdecydowac, jakim elementem HTML bedzie komponent:
1function SpaceButton({ as: Component = 'button', variant = 'primary', children, ...props }) {
2 return (
3 <Component className={`space-btn btn-${variant}`} {...props}>
4 {children}
5 </Component>
6 );
7}
8
9// Uzycie -- ten sam komponent, rozne elementy HTML
10<SpaceButton onClick={handleClick}>Kliknij</SpaceButton>
11<SpaceButton as="a" href="/missions">Przejdz do misji</SpaceButton>
12<SpaceButton as={Link} to="/dashboard">Dashboard</SpaceButton>
13<SpaceButton as="div" role="button">Div-button</SpaceButton>Prop
as daje pelna kontrole nad renderowanym elementem. Komponent SpaceButton nie musi wiedziec, czy bedzie <button>, <a>, czy <Link> -- decyduje konsument.Zamiast kodowac na sztywno, jaki komponent renderowac wewnatrz, pozwol konsumentowi wstrzyknac dowolny komponent:
1// Komponent listy z wstrzykiwanym rendererem elementow
2function MissionList({ missions, renderItem, emptyState }) {
3 if (missions.length === 0) {
4 return emptyState || <p>Brak misji</p>;
5 }
6
7 return (
8 <ul>
9 {missions.map((mission, index) => (
10 <li key={mission.id}>
11 {renderItem(mission, index)}
12 </li>
13 ))}
14 </ul>
15 );
16}
17
18// Konsument 1: Proste karty
19<MissionList
20 missions={missions}
21 renderItem={(mission) => <SimpleCard mission={mission} />}
22/>
23
24// Konsument 2: Rozbudowane karty z akcjami
25<MissionList
26 missions={missions}
27 renderItem={(mission, index) => (
28 <DetailedCard
29 mission={mission}
30 position={index + 1}
31 onDelete={() => removeMission(mission.id)}
32 />
33 )}
34 emptyState={<EmptyMissionsIllustration />}
35/>Najbardziej zaawansowana forma IoC -- konsument moze zmodyfikowac logike stanu komponentu:
1function useToggle({ reducer } = {}) {
2 const [isOpen, setIsOpen] = useState(false);
3
4 const dispatch = (action) => {
5 const nextState = reducer
6 ? reducer(isOpen, action) // Konsument decyduje!
7 : defaultReducer(isOpen, action); // Domyslna logika
8 setIsOpen(nextState);
9 };
10
11 const toggle = () => dispatch({ type: 'TOGGLE' });
12 const open = () => dispatch({ type: 'OPEN' });
13 const close = () => dispatch({ type: 'CLOSE' });
14
15 return { isOpen, toggle, open, close };
16}
17
18function defaultReducer(state, action) {
19 switch (action.type) {
20 case 'TOGGLE': return !state;
21 case 'OPEN': return true;
22 case 'CLOSE': return false;
23 default: return state;
24 }
25}
26
27// Uzycie z domyslna logika
28const toggle = useToggle();
29
30// Uzycie z custom logika -- np. blokada zamykania
31const protectedToggle = useToggle({
32 reducer: (state, action) => {
33 if (action.type === 'CLOSE' && !window.confirm('Na pewno?')) {
34 return state; // Nie zamykaj!
35 }
36 return defaultReducer(state, action);
37 },
38});Komponent generuje zestawy propsow, ktore konsument aplikuje do swoich elementow:
1function useSelection(items) {
2 const [selectedIds, setSelectedIds] = useState(new Set());
3
4 const toggleItem = (id) => {
5 setSelectedIds(prev => {
6 const next = new Set(prev);
7 if (next.has(id)) next.delete(id);
8 else next.add(id);
9 return next;
10 });
11 };
12
13 const getItemProps = (item) => ({
14 onClick: () => toggleItem(item.id),
15 'aria-selected': selectedIds.has(item.id),
16 className: selectedIds.has(item.id) ? 'selected' : '',
17 });
18
19 const getSelectAllProps = () => ({
20 onClick: () => {
21 if (selectedIds.size === items.length) setSelectedIds(new Set());
22 else setSelectedIds(new Set(items.map(i => i.id)));
23 },
24 checked: selectedIds.size === items.length,
25 });
26
27 return {
28 selectedIds,
29 selectedItems: items.filter(i => selectedIds.has(i.id)),
30 getItemProps,
31 getSelectAllProps,
32 };
33}| Sytuacja | Podejscie | |----------|-----------| | Rozny wyglad, ta sama struktura | Slot Pattern | | Rozne elementy HTML | Polymorphic (
as) |
| Rozne komponenty wewnetrzne | Component Injection |
| Rozna logika stanu | State Reducer |
| Rozne zestawy propsow | Props Collection |IoC w React mozna stosowac na roznych poziomach:
Im wiecej IoC, tym komponent jest bardziej elastyczny, ale tez trudniejszy w uzyciu. Klucz to znalezienie odpowiedniego balansu -- jak na statku kosmicznym, gdzie zbyt wiele recznych kontrolek spowalnia pilota, ale zbyt maly -- uniemozliwia reakcje na nieprzewidziane sytuacje!