Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Inversion of Control w React -- Elastyczne API komponentow

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.

Czym jest Inversion of Control?

IoC w React oznacza, ze komponent:

  1. NIE koduje na sztywno swojego zachowania i wygladu
  2. Oddaje kontrole konsumentowi przez propsy, sloty, render props lub callback functions
  3. Staje sie elastyczny -- ten sam komponent sluzy do wielu celow

Slot Pattern -- nazwane sloty

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}

Polymorphic Components -- prop
as

Polymorphic 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.

Component Injection

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/>

State Reducer Pattern

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});

Props Collection Pattern

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}

Kiedy uzywac IoC?

| 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 |

Poziomy kontroli

IoC w React mozna stosowac na roznych poziomach:

  1. Props -- najmniej IoC: komponent przyjmuje dane i sam decyduje, jak je wyswietlic
  2. Slots/Children -- wiecej IoC: konsument decyduje o zawartosci
  3. Render props / Component injection -- duzo IoC: konsument decyduje o calym renderowaniu
  4. State reducer -- maximum IoC: konsument modyfikuje nawet logike stanu

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!

Ir a CodeWorlds