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.
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}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}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}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}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}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}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}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}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}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}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}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}Wzorce projektowe w React dostarczają sprawdzonych rozwiązań dla typowych problemów programistycznych. Pozwalają na tworzenie kodu, który jest:
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.