Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Wzorce projektowe i reużywalność logiki

W kosmicznej podróży przez świat Reacta, efektywne wykorzystanie zasobów jest kluczowe dla sukcesu misji. Podobnie jak inżynierowie kosmiczni używają sprawdzonych projektów dla różnych modułów statku kosmicznego, tak i my możemy korzystać z wzorców projektowych w React, aby maksymalizować reużywalność kodu i tworzyć bardziej skalowalne aplikacje.

Wzorce renderowania i kompozycji

1. Komponenty wysokiego rzędu (HOC)

Higher-Order Components (HOC) to funkcje, które przyjmują komponent i zwracają nowy, wzbogacony komponent. To wzorzec oparty na zasadzie kompozycji, a nie dziedziczenia.

1// Prosty HOC dodający funkcjonalność śledzenia czasu
2function withTimer(WrappedComponent) {
3  return function WithTimer(props) {
4    const [startTime] = useState(Date.now());
5    const [currentTime, setCurrentTime] = useState(Date.now());
6    
7    useEffect(() => {
8      const timer = setInterval(() => {
9        setCurrentTime(Date.now());
10      }, 1000);
11      
12      return () => clearInterval(timer);
13    }, []);
14    
15    const elapsedTime = (currentTime - startTime) / 1000;
16    
17    return (
18      <WrappedComponent
19        {...props}
20        elapsedTime={elapsedTime}
21      />
22    );
23  };
24}
25
26// Użycie HOC
27function MissionControl({ missionName, elapsedTime }) {
28  return (
29    <div className="mission-control">
30      <h2>Misja: {missionName}</h2>
31      <p>Czas trwania: {elapsedTime.toFixed(0)} sekund</p>
32      {/* reszta komponentu */}
33    </div>
34  );
35}
36
37const MissionControlWithTimer = withTimer(MissionControl);
38
39// Renderowanie wzbogaconego komponentu
40function App() {
41  return <MissionControlWithTimer missionName="Apollo" />;
42}

2. Render Props

Render Props to wzorzec, w którym komponent otrzymuje funkcję, która mówi mu, co ma renderować. Dzięki temu logika może być współdzielona między komponentami.

1function MouseTracker({ render }) {
2  const [position, setPosition] = useState({ x: 0, y: 0 });
3  
4  useEffect(() => {
5    function handleMouseMove(event) {
6      setPosition({
7        x: event.clientX,
8        y: event.clientY
9      });
10    }
11    
12    window.addEventListener('mousemove', handleMouseMove);
13    
14    return () => {
15      window.removeEventListener('mousemove', handleMouseMove);
16    };
17  }, []);
18  
19  return render(position);
20}
21
22// Użycie render props
23function App() {
24  return (
25    <MouseTracker
26      render={({ x, y }) => (
27        <div className="space-map">
28          <h2>Mapa gwiezdna</h2>
29          <p>Współrzędne kursora: ({x}, {y})</p>
30          <div 
31            className="space-ship" 
32            style={{ 
33              position: 'absolute', 
34              left: x, 
35              top: y 
36            }}
37          >
38            🚀
39          </div>
40        </div>
41      )}
42    />
43  );
44}

3. Wzorzec Compound Components

Ten wzorzec pozwala tworzyć komponenty, które współpracują ze sobą, zachowując elastyczność i czytelność kodu.

1function Spaceship({ children }) {
2  const [power, setPower] = useState(0);
3  const [shields, setShields] = useState(false);
4  
5  const contextValue = {
6    power,
7    setPower,
8    shields,
9    setShields
10  };
11  
12  return (
13    <SpaceshipContext.Provider value={contextValue}>
14      <div className="spaceship">
15        {children}
16      </div>
17    </SpaceshipContext.Provider>
18  );
19}
20
21// Komponenty potomne
22Spaceship.Engine = function Engine() {
23  const { power, setPower } = useContext(SpaceshipContext);
24  
25  return (
26    <div className="engine">
27      <h3>Silnik</h3>
28      <p>Moc: {power}%</p>
29      <input
30        type="range"
31        min="0"
32        max="100"
33        value={power}
34        onChange={e => setPower(Number(e.target.value))}
35      />
36    </div>
37  );
38};
39
40Spaceship.Shields = function Shields() {
41  const { shields, setShields } = useContext(SpaceshipContext);
42  
43  return (
44    <div className="shields">
45      <h3>Tarcze</h3>
46      <button onClick={() => setShields(!shields)}>
47        {shields ? 'Dezaktywuj' : 'Aktywuj'} tarcze
48      </button>
49      <p>Status: {shields ? 'Aktywne' : 'Nieaktywne'}</p>
50    </div>
51  );
52};
53
54// Użycie
55function App() {
56  return (
57    <Spaceship>
58      <h2>Panel kontrolny statku kosmicznego</h2>
59      <Spaceship.Engine />
60      <Spaceship.Shields />
61    </Spaceship>
62  );
63}

Wzorce zarządzania stanem

1. Flux / Redux

Redux implementuje wzorzec Flux, który zakłada jednokierunkowy przepływ danych. Stan aplikacji jest przechowywany w jednym obiekcie (store), a modyfikacje stanu mogą nastąpić tylko przez emisję akcji, które są przetwarzane przez reduktory.

1// Akcje
2const ADD_CREWMEMBER = 'ADD_CREWMEMBER';
3const REMOVE_CREWMEMBER = 'REMOVE_CREWMEMBER';
4
5// Action creators
6function addCrewmember(member) {
7  return {
8    type: ADD_CREWMEMBER,
9    payload: member
10  };
11}
12
13function removeCrewmember(id) {
14  return {
15    type: REMOVE_CREWMEMBER,
16    payload: id
17  };
18}
19
20// Reduktor
21function crewReducer(state = [], action) {
22  switch (action.type) {
23    case ADD_CREWMEMBER:
24      return [...state, action.payload];
25    case REMOVE_CREWMEMBER:
26      return state.filter(member => member.id !== action.payload);
27    default:
28      return state;
29  }
30}
31
32// W komponencie z react-redux
33function CrewManagement() {
34  const crew = useSelector(state => state.crew);
35  const dispatch = useDispatch();
36  
37  function handleAddCrewmember() {
38    const newMember = {
39      id: Date.now(),
40      name: 'Nowy członek załogi',
41      role: 'Kadet'
42    };
43    dispatch(addCrewmember(newMember));
44  }
45  
46  return (
47    <div>
48      <h2>Zarządzanie załogą</h2>
49      <button onClick={handleAddCrewmember}>Dodaj członka załogi</button>
50      <ul>
51        {crew.map(member => (
52          <li key={member.id}>
53            {member.name} - {member.role}
54            <button onClick={() => dispatch(removeCrewmember(member.id))}>
55              Usuń
56            </button>
57          </li>
58        ))}
59      </ul>
60    </div>
61  );
62}

2. Context API

Context API pozwala na przekazywanie danych przez drzewo komponentów bez konieczności przekazywania props na każdym poziomie.

1// Stworzenie kontekstu
2const MissionContext = createContext();
3
4// Provider
5function MissionProvider({ children }) {
6  const [mission, setMission] = useState({
7    name: 'Apollo 11',
8    status: 'W trakcie',
9    crewMembers: [
10      { id: 1, name: 'Neil Armstrong', role: 'Dowódca' },
11      { id: 2, name: 'Buzz Aldrin', role: 'Pilot modułu księżycowego' },
12      { id: 3, name: 'Michael Collins', role: 'Pilot modułu dowodzenia' }
13    ]
14  });
15  
16  function updateMissionStatus(newStatus) {
17    setMission(prev => ({ ...prev, status: newStatus }));
18  }
19  
20  function addCrewMember(member) {
21    setMission(prev => ({
22      ...prev,
23      crewMembers: [...prev.crewMembers, member]
24    }));
25  }
26  
27  const value = {
28    mission,
29    updateMissionStatus,
30    addCrewMember
31  };
32  
33  return (
34    <MissionContext.Provider value={value}>
35      {children}
36    </MissionContext.Provider>
37  );
38}
39
40// Custom hook dla łatwiejszego użycia
41function useMission() {
42  const context = useContext(MissionContext);
43  if (!context) {
44    throw new Error('useMission must be used within a MissionProvider');
45  }
46  return context;
47}
48
49// Użycie w komponentach
50function MissionDashboard() {
51  const { mission } = useMission();
52  
53  return (
54    <div>
55      <h1>Misja: {mission.name}</h1>
56      <p>Status: {mission.status}</p>
57    </div>
58  );
59}
60
61function MissionControl() {
62  const { updateMissionStatus } = useMission();
63  
64  return (
65    <div>
66      <h2>Centrum kontroli misji</h2>
67      <button onClick={() => updateMissionStatus('Zakończona pomyślnie')}>
68        Zakończ misję
69      </button>
70      <button onClick={() => updateMissionStatus('Przerwana')}>
71        Przerwij misję
72      </button>
73    </div>
74  );
75}
76
77function CrewList() {
78  const { mission, addCrewMember } = useMission();
79  
80  return (
81    <div>
82      <h2>Załoga misji</h2>
83      <ul>
84        {mission.crewMembers.map(member => (
85          <li key={member.id}>
86            {member.name} - {member.role}
87          </li>
88        ))}
89      </ul>
90      <button
91        onClick={() => 
92          addCrewMember({
93            id: Date.now(),
94            name: 'Nowy astronauta',
95            role: 'Specjalista misji'
96          })
97        }
98      >
99        Dodaj członka załogi
100      </button>
101    </div>
102  );
103}
104
105// Połączenie wszystkiego
106function App() {
107  return (
108    <MissionProvider>
109      <div className="mission-app">
110        <MissionDashboard />
111        <MissionControl />
112        <CrewList />
113      </div>
114    </MissionProvider>
115  );
116}

3. Custom hooks dla stanu

Jak widzieliśmy w poprzednim module, custom hooki są potężnym narzędziem do enkapsulacji i ponownego wykorzystania logiki związanej ze stanem.

1function useSpaceshipSystems() {
2  const [engines, setEngines] = useState({
3    mainEngine: { power: 0, status: 'offline' },
4    leftThruster: { power: 0, status: 'offline' },
5    rightThruster: { power: 0, status: 'offline' }
6  });
7  
8  const [lifeSupportSystem, setLifeSupportSystem] = useState({
9    oxygen: 100,
10    temperature: 21,
11    pressure: 1
12  });
13  
14  function startEngine(engineName) {
15    setEngines(prev => ({
16      ...prev,
17      [engineName]: { 
18        ...prev[engineName], 
19        status: 'online',
20        power: 10
21      }
22    }));
23  }
24  
25  function setEnginePower(engineName, power) {
26    if (engines[engineName].status === 'offline') {
27      console.warn(`Nie można ustawić mocy - silnik ${engineName} jest wyłączony`);
28      return;
29    }
30    
31    setEngines(prev => ({
32      ...prev,
33      [engineName]: { 
34        ...prev[engineName], 
35        power: Math.max(0, Math.min(100, power))
36      }
37    }));
38  }
39  
40  function stopEngine(engineName) {
41    setEngines(prev => ({
42      ...prev,
43      [engineName]: { 
44        ...prev[engineName], 
45        status: 'offline',
46        power: 0
47      }
48    }));
49  }
50  
51  function adjustLifeSupport({ oxygen, temperature, pressure }) {
52    setLifeSupportSystem(prev => ({
53      ...prev,
54      ...(oxygen !== undefined ? { oxygen } : {}),
55      ...(temperature !== undefined ? { temperature } : {}),
56      ...(pressure !== undefined ? { pressure } : {})
57    }));
58  }
59  
60  return {
61    engines,
62    lifeSupportSystem,
63    startEngine,
64    setEnginePower,
65    stopEngine,
66    adjustLifeSupport
67  };
68}
69
70// Użycie
71function SpaceshipControls() {
72  const {
73    engines,
74    lifeSupportSystem,
75    startEngine,
76    setEnginePower,
77    stopEngine,
78    adjustLifeSupport
79  } = useSpaceshipSystems();
80  
81  return (
82    <div className="spaceship-controls">
83      <h2>Kontrola systemów statku</h2>
84      
85      <div className="engines-section">
86        <h3>Silniki</h3>
87        {Object.entries(engines).map(([name, engine]) => (
88          <div key={name} className="engine-control">
89            <h4>{formatEngineName(name)}</h4>
90            <p>Status: {engine.status}</p>
91            <p>Moc: {engine.power}%</p>
92            
93            {engine.status === 'offline' ? (
94              <button onClick={() => startEngine(name)}>Start</button>
95            ) : (
96              <>
97                <input
98                  type="range"
99                  min="0"
100                  max="100"
101                  value={engine.power}
102                  onChange={e => setEnginePower(name, Number(e.target.value))}
103                />
104                <button onClick={() => stopEngine(name)}>Stop</button>
105              </>
106            )}
107          </div>
108        ))}
109      </div>
110      
111      <div className="life-support">
112        <h3>System podtrzymywania życia</h3>
113        <p>Poziom tlenu: {lifeSupportSystem.oxygen}%</p>
114        <p>Temperatura: {lifeSupportSystem.temperature}°C</p>
115        <p>Ciśnienie: {lifeSupportSystem.pressure} atm</p>
116        
117        <div className="controls">
118          <button 
119            onClick={() => 
120              adjustLifeSupport({ 
121                oxygen: lifeSupportSystem.oxygen + 5 
122              })
123            }
124          >
125            Zwiększ poziom tlenu
126          </button>
127          <button 
128            onClick={() => 
129              adjustLifeSupport({ 
130                temperature: lifeSupportSystem.temperature - 1 
131              })
132            }
133          >
134            Obniż temperaturę
135          </button>
136        </div>
137      </div>
138    </div>
139  );
140}
141
142function formatEngineName(name) {
143  return name
144    .replace(/([A-Z])/g, ' $1')
145    .replace(/^./, str => str.toUpperCase());
146}

Wzorce komponentów UI

1. Container / Presentational Pattern

Ten wzorzec rozdziela logikę biznesową (kontener) od prezentacji (komponent prezentacyjny), co zwiększa reużywalność komponentów i ułatwia testowanie.

1// Komponent kontenerowy - zajmuje się logiką i pobieraniem danych
2function PlanetDataContainer({ planetId, children }) {
3  const [planet, setPlanet] = useState(null);
4  const [loading, setLoading] = useState(true);
5  const [error, setError] = useState(null);
6  
7  useEffect(() => {
8    setLoading(true);
9    
10    fetch(`https://api.planets.com/${planetId}`)
11      .then(response => {
12        if (!response.ok) throw new Error('Nie udało się pobrać danych planety');
13        return response.json();
14      })
15      .then(data => {
16        setPlanet(data);
17        setLoading(false);
18      })
19      .catch(err => {
20        setError(err.message);
21        setLoading(false);
22      });
23  }, [planetId]);
24  
25  // Wywołanie funkcji children z danymi
26  return children({ planet, loading, error });
27}
28
29// Komponent prezentacyjny - zajmuje się tylko wyświetlaniem
30function PlanetInfo({ planet, loading, error }) {
31  if (loading) return <div>Ładowanie danych planety...</div>;
32  if (error) return <div>Błąd: {error}</div>;
33  if (!planet) return null;
34  
35  return (
36    <div className="planet-info">
37      <h2>{planet.name}</h2>
38      <div className="planet-details">
39        <p>Typ: {planet.type}</p>
40        <p>Średnica: {planet.diameter} km</p>
41        <p>Odległość od Słońca: {planet.distanceFromSun} mln km</p>
42        <p>Okres orbitalny: {planet.orbitalPeriod} dni</p>
43        <p>Temperatura powierzchni: {planet.surfaceTemperature}°C</p>
44      </div>
45      
46      {planet.moons && planet.moons.length > 0 && (
47        <div className="moons">
48          <h3>Księżyce ({planet.moons.length}):</h3>
49          <ul>
50            {planet.moons.map(moon => (
51              <li key={moon.id}>{moon.name}</li>
52            ))}
53          </ul>
54        </div>
55      )}
56    </div>
57  );
58}
59
60// Użycie
61function SolarSystemExplorer() {
62  const [selectedPlanet, setSelectedPlanet] = useState('earth');
63  
64  return (
65    <div className="solar-system-explorer">
66      <h1>Eksploracja Układu Słonecznego</h1>
67      
68      <div className="planet-selector">
69        <label>Wybierz planetę: </label>
70        <select 
71          value={selectedPlanet} 
72          onChange={e => setSelectedPlanet(e.target.value)}
73        >
74          <option value="mercury">Merkury</option>
75          <option value="venus">Wenus</option>
76          <option value="earth">Ziemia</option>
77          <option value="mars">Mars</option>
78          <option value="jupiter">Jowisz</option>
79          <option value="saturn">Saturn</option>
80          <option value="uranus">Uran</option>
81          <option value="neptune">Neptun</option>
82        </select>
83      </div>
84      
85      <PlanetDataContainer planetId={selectedPlanet}>
86        {({ planet, loading, error }) => (
87          <PlanetInfo planet={planet} loading={loading} error={error} />
88        )}
89      </PlanetDataContainer>
90    </div>
91  );
92}

2. Composition vs Inheritance

React preferuje kompozycję nad dziedziczeniem. Ten wzorzec pozwala tworzyć elastyczne komponenty poprzez zagnieżdżanie i przekazywanie props.

1// Komponent bazowy - Karta
2function Card({ title, children, footer, className }) {
3  return (
4    <div className={`card ${className || ''}`}>
5      {title && <div className="card-header">{title}</div>}
6      <div className="card-body">{children}</div>
7      {footer && <div className="card-footer">{footer}</div>}
8    </div>
9  );
10}
11
12// Specjalizowane komponenty przy użyciu kompozycji
13function MissionCard({ mission, onComplete }) {
14  return (
15    <Card
16      title={<h3>{mission.name}</h3>}
17      footer={
18        <button onClick={() => onComplete(mission.id)}>
19          Zakończ misję
20        </button>
21      }
22      className="mission-card"
23    >
24      <p><strong>Cel:</strong> {mission.objective}</p>
25      <p><strong>Status:</strong> {mission.status}</p>
26      <p><strong>Postęp:</strong> {mission.progress}%</p>
27      <div className="progress-bar">
28        <div 
29          className="progress" 
30          style={{ width: `${mission.progress}%` }} 
31        />
32      </div>
33    </Card>
34  );
35}
36
37function AstronautCard({ astronaut, onAssign }) {
38  return (
39    <Card
40      title={<h3>{astronaut.name}</h3>}
41      footer={
42        <button onClick={() => onAssign(astronaut.id)}>
43          Przydziel do misji
44        </button>
45      }
46      className="astronaut-card"
47    >
48      <div className="astronaut-info">
49        <img 
50          src={astronaut.avatar} 
51          alt={astronaut.name} 
52          className="astronaut-avatar" 
53        />
54        <div>
55          <p><strong>Ranga:</strong> {astronaut.rank}</p>
56          <p><strong>Specjalizacja:</strong> {astronaut.specialization}</p>
57          <p><strong>Misje:</strong> {astronaut.missionCount}</p>
58        </div>
59      </div>
60    </Card>
61  );
62}
63
64// Użycie
65function Dashboard() {
66  const missions = [
67    { id: 1, name: 'Eksploracja Marsa', objective: 'Zbadać powierzchnię', status: 'W toku', progress: 65 },
68    { id: 2, name: 'Stacja kosmiczna ISS', objective: 'Konserwacja modułów', status: 'Zaplanowana', progress: 0 }
69  ];
70  
71  const astronauts = [
72    { id: 1, name: 'Jan Kowalski', rank: 'Kapitan', specialization: 'Pilot', missionCount: 5, avatar: '/avatars/jk.png' },
73    { id: 2, name: 'Anna Nowak', rank: 'Porucznik', specialization: 'Inżynier', missionCount: 3, avatar: '/avatars/an.png' }
74  ];
75  
76  return (
77    <div className="dashboard">
78      <h1>Panel kontrolny centrum kosmicznego</h1>
79      
80      <section className="missions-section">
81        <h2>Aktywne misje</h2>
82        <div className="missions-grid">
83          {missions.map(mission => (
84            <MissionCard 
85              key={mission.id} 
86              mission={mission} 
87              onComplete={id => console.log(`Zakończono misję ${id}`)} 
88            />
89          ))}
90        </div>
91      </section>
92      
93      <section className="astronauts-section">
94        <h2>Dostępni astronauci</h2>
95        <div className="astronauts-grid">
96          {astronauts.map(astronaut => (
97            <AstronautCard 
98              key={astronaut.id} 
99              astronaut={astronaut} 
100              onAssign={id => console.log(`Przydzielono astronautę ${id}`)} 
101            />
102          ))}
103        </div>
104      </section>
105    </div>
106  );
107}

3. Wzorzec Headless Components

Ten wzorzec rozdziela logikę zachowania komponentu od jego wyglądu, pozwalając na maksymalną elastyczność stylistyczną.

1// Komponent bezgłowy (Headless) - zawiera tylko logikę, bez UI
2function useTabControl(initialTab = 0) {
3  const [activeTab, setActiveTab] = useState(initialTab);
4  
5  function activateTab(index) {
6    setActiveTab(index);
7  }
8  
9  return {
10    activeTab,
11    activateTab
12  };
13}
14
15// Użycie z dowolnym UI
16function MissionTabs() {
17  const tabs = [
18    { id: 'overview', label: 'Przegląd' },
19    { id: 'crew', label: 'Załoga' },
20    { id: 'timeline', label: 'Harmonogram' },
21    { id: 'systems', label: 'Systemy' }
22  ];
23  
24  const { activeTab, activateTab } = useTabControl();
25  
26  return (
27    <div className="mission-tabs">
28      <div className="tabs-header">
29        {tabs.map((tab, index) => (
30          <button
31            key={tab.id}
32            className={`tab-button ${activeTab === index ? 'active' : ''}`}
33            onClick={() => activateTab(index)}
34          >
35            {tab.label}
36          </button>
37        ))}
38      </div>
39      
40      <div className="tab-content">
41        {activeTab === 0 && (
42          <div className="overview-tab">
43            <h3>Przegląd misji</h3>
44            <p>Misja Apollo 11 była pierwszą załogową misją na Księżyc...</p>
45          </div>
46        )}
47        
48        {activeTab === 1 && (
49          <div className="crew-tab">
50            <h3>Załoga</h3>
51            <ul>
52              <li>Neil Armstrong - Dowódca</li>
53              <li>Edwin "Buzz" Aldrin - Pilot modułu księżycowego</li>
54              <li>Michael Collins - Pilot modułu dowodzenia</li>
55            </ul>
56          </div>
57        )}
58        
59        {activeTab === 2 && (
60          <div className="timeline-tab">
61            <h3>Harmonogram</h3>
62            <div className="timeline">
63              <div className="timeline-event">
64                <span className="date">16 lipca 1969</span>
65                <span className="description">Start z Przylądka Kennedy'ego</span>
66              </div>
67              <div className="timeline-event">
68                <span className="date">20 lipca 1969</span>
69                <span className="description">Lądowanie na Księżycu</span>
70              </div>
71              <div className="timeline-event">
72                <span className="date">21 lipca 1969</span>
73                <span className="description">Pierwszy krok na Księżycu</span>
74              </div>
75              <div className="timeline-event">
76                <span className="date">24 lipca 1969</span>
77                <span className="description">Powrót na Ziemię</span>
78              </div>
79            </div>
80          </div>
81        )}
82        
83        {activeTab === 3 && (
84          <div className="systems-tab">
85            <h3>Systemy statku</h3>
86            <p>Moduł dowodzenia: Columbia</p>
87            <p>Moduł księżycowy: Eagle</p>
88            <p>Rakieta nośna: Saturn V</p>
89          </div>
90        )}
91      </div>
92    </div>
93  );
94}

Wzorce optymalizacyjne

1. Memoizacja

Memoizacja pomaga unikać niepotrzebnych renderowań przez zapamiętywanie rezultatów kosztownych obliczeń.

1// Memoizacja komponentu
2const MemoizedTelemetryChart = React.memo(function TelemetryChart({ data }) {
3  // Symulacja kosztownych obliczeń
4  const processedData = useMemo(() => {
5    console.log('Przetwarzanie danych telemetrycznych...');
6    
7    // Kosztowne przetwarzanie danych
8    return data.map(point => ({
9      x: new Date(point.timestamp).toLocaleTimeString(),
10      y: calculateAverage(point.readings),
11      anomaly: detectAnomaly(point.readings)
12    }));
13  }, [data]); // Recalculate only when data changes
14  
15  return (
16    <div className="telemetry-chart">
17      <h3>Wykres telemetrii</h3>
18      <div className="chart-container">
19        {/* Wykres z przetworzonymi danymi */}
20        {processedData.map((point, index) => (
21          <div 
22            key={index}
23            className={`data-point ${point.anomaly ? 'anomaly' : ''}`}
24            style={{ 
25              height: `${point.y * 2}px`,
26              backgroundColor: point.anomaly ? 'red' : 'blue'
27            }}
28            title={`${point.x}: ${point.y} ${point.anomaly ? '(anomalia)' : ''}`}
29          />
30        ))}
31      </div>
32    </div>
33  );
34}, (prevProps, nextProps) => {
35  // Własna logika porównania props
36  if (prevProps.data.length !== nextProps.data.length) {
37    return false; // renderuj ponownie jeśli zmienił się rozmiar danych
38  }
39  
40  // Porównaj tylko ostatnie 5 punktów danych
41  const prevLastFive = prevProps.data.slice(-5);
42  const nextLastFive = nextProps.data.slice(-5);
43  
44  for (let i = 0; i < 5; i++) {
45    if (!prevLastFive[i] || !nextLastFive[i]) continue;
46    
47    if (
48      prevLastFive[i].timestamp !== nextLastFive[i].timestamp ||
49      !arraysEqual(prevLastFive[i].readings, nextLastFive[i].readings)
50    ) {
51      return false; // renderuj ponownie jeśli dane się różnią
52    }
53  }
54  
55  return true; // nie renderuj ponownie
56});
57
58// Funkcje pomocnicze
59function calculateAverage(readings) {
60  return readings.reduce((sum, val) => sum + val, 0) / readings.length;
61}
62
63function detectAnomaly(readings) {
64  const avg = calculateAverage(readings);
65  const threshold = avg * 1.5;
66  return readings.some(val => val > threshold);
67}
68
69function arraysEqual(a, b) {
70  if (a.length !== b.length) return false;
71  for (let i = 0; i < a.length; i++) {
72    if (a[i] !== b[i]) return false;
73  }
74  return true;
75}

2. Wirtualizacja list

Dla dużych list danych, wirtualizacja pozwala renderować tylko te elementy, które są aktualnie widoczne na ekranie.

1// Implementacja prostej wirtualizacji (w rzeczywistych aplikacjach 
2// warto użyć bibliotek jak react-window lub react-virtualized)
3function VirtualizedAsteroidList({ asteroids, rowHeight = 50, visibleRows = 10 }) {
4  const [scrollTop, setScrollTop] = useState(0);
5  const containerRef = useRef();
6  
7  const totalHeight = asteroids.length * rowHeight;
8  const startIndex = Math.floor(scrollTop / rowHeight);
9  const endIndex = Math.min(
10    startIndex + visibleRows + 1, 
11    asteroids.length
12  );
13  
14  const visibleAsteroids = useMemo(() => {
15    return asteroids.slice(startIndex, endIndex);
16  }, [asteroids, startIndex, endIndex]);
17  
18  const handleScroll = useCallback(() => {
19    if (containerRef.current) {
20      setScrollTop(containerRef.current.scrollTop);
21    }
22  }, []);
23  
24  return (
25    <div className="asteroids-list">
26      <h2>Katalog asteroid</h2>
27      <p>Liczba asteroid: {asteroids.length}</p>
28      
29      <div 
30        ref={containerRef}
31        className="virtual-list-container"
32        style={{ height: rowHeight * visibleRows, overflow: 'auto' }}
33        onScroll={handleScroll}
34      >
35        <div 
36          className="total-height" 
37          style={{ height: totalHeight }}
38        >
39          <div 
40            className="visible-items"
41            style={{ 
42              transform: `translateY(${startIndex * rowHeight}px)` 
43            }}
44          >
45            {visibleAsteroids.map(asteroid => (
46              <div 
47                key={asteroid.id} 
48                className="asteroid-item"
49                style={{ height: rowHeight }}
50              >
51                <span className="name">{asteroid.name}</span>
52                <span className="diameter">Średnica: {asteroid.diameter} km</span>
53                <span className="distance">
54                  Odległość: {asteroid.distance} mln km
55                </span>
56                <span 
57                  className={`hazard-level ${asteroid.hazardLevel}`}
58                >
59                  {formatHazardLevel(asteroid.hazardLevel)}
60                </span>
61              </div>
62            ))}
63          </div>
64        </div>
65      </div>
66    </div>
67  );
68}
69
70function formatHazardLevel(level) {
71  switch (level) {
72    case 'high': return 'Wysokie zagrożenie';
73    case 'medium': return 'Średnie zagrożenie';
74    case 'low': return 'Niskie zagrożenie';
75    default: return 'Brak zagrożenia';
76  }
77}

3. Lazy loading

Leniwne ładowanie komponentów pozwala odroczyć ładowanie części aplikacji, dopóki nie są potrzebne, co może znacznie przyspieszyć inicjalne ładowanie aplikacji.

1// Lazy loading komponentów
2const LazyMarsExplorer = lazy(() => import('./MarsExplorer'));
3const LazyJupiterMoons = lazy(() => import('./JupiterMoons'));
4const LazySaturnRings = lazy(() => import('./SaturnRings'));
5
6function SolarSystemExplorer() {
7  const [activeSection, setActiveSection] = useState(null);
8  
9  return (
10    <div className="solar-system-explorer">
11      <h1>Eksploracja układu słonecznego</h1>
12      
13      <div className="planet-selector">
14        <button onClick={() => setActiveSection('mars')}>
15          Eksploracja Marsa
16        </button>
17        <button onClick={() => setActiveSection('jupiter')}>
18          Księżyce Jowisza
19        </button>
20        <button onClick={() => setActiveSection('saturn')}>
21          Pierścienie Saturna
22        </button>
23      </div>
24      
25      <Suspense fallback={<div className="loading">Ładowanie modułu eksploracji...</div>}>
26        {activeSection === 'mars' && <LazyMarsExplorer />}
27        {activeSection === 'jupiter' && <LazyJupiterMoons />}
28        {activeSection === 'saturn' && <LazySaturnRings />}
29      </Suspense>
30    </div>
31  );
32}

Podsumowanie

Wzorce projektowe w React dostarczają sprawdzonych rozwiązań dla typowych problemów programistycznych. Pozwalają na tworzenie kodu, który jest:

  1. Reużywalny - komponenty i logikę można łatwo wykorzystać w różnych częściach aplikacji
  2. Testowalny - separacja obowiązków ułatwia pisanie testów
  3. Skalowalny - aplikacja może rosnąć bez drastycznego wzrostu złożoności
  4. Łatwy w utrzymaniu - jasna struktura i organizacja ułatwiają wprowadzanie zmian

W kosmicznej podróży przez React, wzorce te są jak sprawdzone trasy międzygwiezdne, które pozwalają dotrzeć do celu szybciej i bezpieczniej. Eksperymentuj z różnymi wzorcami i wybieraj te, które najlepiej pasują do konkretnych potrzeb Twojej aplikacji.

Ir a CodeWorlds