Optimizuokite savo React aplikacijas su useState. Išmokite pažangių technikų efektyviam būsenos valdymui ir našumo didinimui.
React useState: būsenos valdymo („State Hook“) optimizavimo strategijos
useState „Hook“ yra pagrindinis „React“ elementas, skirtas komponentų būsenai valdyti. Nors jis yra nepaprastai universalus ir lengvai naudojamas, netinkamas jo naudojimas gali sukelti našumo problemų, ypač sudėtingose programose. Šiame išsamiame vadove nagrinėjamos pažangios useState optimizavimo strategijos, siekiant užtikrinti, kad jūsų „React“ programos būtų našios ir lengvai prižiūrimos.
Kaip suprasti useState ir jo poveikį
Prieš pradedant nagrinėti optimizavimo technikas, prisiminkime useState pagrindus. useState „Hook“ leidžia funkciniams komponentams turėti būseną. Jis grąžina būsenos kintamąjį ir funkciją šiam kintamajam atnaujinti. Kiekvieną kartą, kai būsena atnaujinama, komponentas yra pervaizduojamas iš naujo.
Paprastas pavyzdys:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
Count: {count}
);
}
export default Counter;
Šiame paprastame pavyzdyje, paspaudus mygtuką „Increment“, atnaujinama count būsena, o tai sukelia Counter komponento pervaizdavimą. Nors tai puikiai veikia mažuose komponentuose, nekontroliuojami pervaizdavimai didesnėse programose gali smarkiai paveikti našumą.
Kodėl verta optimizuoti useState?
Nereikalingi pervaizdavimai yra pagrindinė našumo problemų priežastis „React“ programose. Kiekvienas pervaizdavimas naudoja resursus ir gali lemti lėtą vartotojo patirtį. Optimizuojant useState galima:
- Sumažinti nereikalingus pervaizdavimus: Užkirsti kelią komponentų pervaizdavimui, kai jų būsena iš tikrųjų nepasikeitė.
- Pagerinti našumą: Padaryti programą greitesnę ir jautresnę.
- Pagerinti prižiūrimumą: Rašyti švaresnį ir efektyvesnį kodą.
1 optimizavimo strategija: funkciniai atnaujinimai
Kai atnaujinate būseną remdamiesi ankstesne būsena, visada naudokite funkcinę setCount formą. Tai apsaugo nuo problemų su pasenusiais uždariniais (stale closures) ir užtikrina, kad dirbate su naujausia būsenos reikšme.
Neteisingai (potencialiai problemiška):
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setTimeout(() => {
setCount(count + 1); // Potencialiai pasenusi 'count' reikšmė
}, 1000);
};
return (
Count: {count}
);
}
Teisingai (funkcinis atnaujinimas):
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setTimeout(() => {
setCount(prevCount => prevCount + 1); // Užtikrina teisingą 'count' reikšmę
}, 1000);
};
return (
Count: {count}
);
}
Naudodami setCount(prevCount => prevCount + 1), jūs perduodate funkciją į setCount. „React“ tuomet įtrauks būsenos atnaujinimą į eilę ir įvykdys funkciją su naujausia būsenos reikšme, taip išvengiant pasenusių uždarinių problemos.
2 optimizavimo strategija: nekintami (Immutable) būsenos atnaujinimai
Dirbant su objektais ar masyvais būsenoje, visada juos atnaujinkite nekintamai (immutably). Tiesioginis būsenos keitimas (mutating) nesukels pervaizdavimo, nes „React“ naudoja nuorodų lygybę (referential equality) pokyčiams aptikti. Vietoj to, sukurkite naują objekto ar masyvo kopiją su norimais pakeitimais.
Neteisingai (būsenos keitimas):
function ShoppingCart() {
const [items, setItems] = useState([{ id: 1, name: 'Apple', quantity: 2 }]);
const updateQuantity = (id, newQuantity) => {
const item = items.find(item => item.id === id);
if (item) {
item.quantity = newQuantity; // Tiesioginis keitimas! Nesukels pervaizdavimo.
setItems(items); // Tai sukels problemų, nes React neaptiks pokyčio.
}
};
return (
{items.map(item => (
{item.name} - Quantity: {item.quantity}
))}
);
}
Teisingai (nekintamas atnaujinimas):
function ShoppingCart() {
const [items, setItems] = useState([{ id: 1, name: 'Apple', quantity: 2 }]);
const updateQuantity = (id, newQuantity) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
)
);
};
return (
{items.map(item => (
{item.name} - Quantity: {item.quantity}
))}
);
}
Pataisytoje versijoje mes naudojame .map(), kad sukurtume naują masyvą su atnaujintu elementu. Išskleidimo operatorius (...item) naudojamas sukurti naują objektą su esamomis savybėmis, o tada mes perrašome quantity savybę nauja reikšme. Tai užtikrina, kad setItems gaus naują masyvą, o tai sukels pervaizdavimą ir atnaujins vartotojo sąsają.
3 optimizavimo strategija: `useMemo` naudojimas siekiant išvengti nereikalingų pervaizdavimų
useMemo „hook“ gali būti naudojamas skaičiavimo rezultatui įsiminti (memoize). Tai naudinga, kai skaičiavimas yra brangus ir priklauso tik nuo tam tikrų būsenos kintamųjų. Jei tie būsenos kintamieji nepasikeitė, useMemo grąžins talpykloje (cache) esantį rezultatą, neleisdamas skaičiavimui vėl vykti ir išvengiant nereikalingų pervaizdavimų.
Pavyzdys:
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ data }) {
const [multiplier, setMultiplier] = useState(2);
// Brangus skaičiavimas, priklausantis tik nuo 'data' ir 'multiplier'
const processedData = useMemo(() => {
console.log('Apdorojami duomenys...');
// Imituoti brangią operaciją
let result = data.map(item => item * multiplier);
return result;
}, [data, multiplier]);
return (
Processed Data: {processedData.join(', ')}
);
}
function App() {
const [data, setData] = useState([1, 2, 3, 4, 5]);
return (
);
}
export default App;
Šiame pavyzdyje processedData yra perskaičiuojamas tik pasikeitus data ar multiplier. Jei pasikeis kitos ExpensiveComponent būsenos dalys, komponentas bus pervaizduotas, tačiau processedData nebus perskaičiuojamas, taip taupant apdorojimo laiką.
4 optimizavimo strategija: `useCallback` naudojimas funkcijoms įsiminti
Panašiai kaip useMemo, useCallback įsimena funkcijas. Tai ypač naudinga perduodant funkcijas kaip `props` į antrinius komponentus. Be useCallback, kiekvieno pervaizdavimo metu sukuriama nauja funkcijos instancija, todėl antrinis komponentas pervaizduojamas iš naujo, net jei jo `props` iš tikrųjų nepasikeitė. Taip yra todėl, kad „React“ tikrina, ar `props` skiriasi, naudodamas griežtą lygybę (===), o nauja funkcija visada skirsis nuo ankstesnės.
Pavyzdys:
import React, { useState, useCallback } from 'react';
const Button = React.memo(({ onClick, children }) => {
console.log('Mygtukas pervaizduotas');
return ;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// Įsiminti inkrementavimo funkciją
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // Tuščias priklausomybių masyvas reiškia, kad ši funkcija sukuriama tik vieną kartą
return (
Count: {count}
);
}
export default ParentComponent;
Šiame pavyzdyje increment funkcija yra įsimenama naudojant useCallback su tuščiu priklausomybių masyvu. Tai reiškia, kad funkcija sukuriama tik vieną kartą, kai komponentas yra prijungiamas. Kadangi Button komponentas yra apgaubtas React.memo, jis bus pervaizduotas tik pasikeitus jo props. Kadangi increment funkcija yra ta pati kiekvieno pervaizdavimo metu, Button komponentas nebus pervaizduojamas be reikalo.
5 optimizavimo strategija: `React.memo` naudojimas funkciniams komponentams
React.memo yra aukštesnės eilės komponentas, kuris įsimena funkcinius komponentus. Jis neleidžia komponentui būti pervaizduotam, jei jo props nepasikeitė. Tai ypač naudinga „gryniems“ (pure) komponentams, kurie priklauso tik nuo savo props.
Pavyzdys:
import React from 'react';
const MyComponent = React.memo(({ name }) => {
console.log('MyComponent pervaizduotas');
return Hello, {name}!
;
});
export default MyComponent;
Norėdami efektyviai naudoti React.memo, įsitikinkite, kad jūsų komponentas yra „grynas“, t. y. visada pateikia tą patį rezultatą su tais pačiais įvesties `props`. Jei jūsų komponentas turi šalutinių poveikių arba priklauso nuo konteksto, kuris gali keistis, React.memo gali būti ne pats geriausias sprendimas.
6 optimizavimo strategija: didelių komponentų skaidymas
Dideli komponentai su sudėtinga būsena gali tapti našumo problemų šaltiniu. Šių komponentų suskaidymas į mažesnes, lengviau valdomas dalis gali pagerinti našumą, izoliuojant pervaizdavimus. Kai pasikeičia viena programos būsenos dalis, reikia pervaizduoti tik atitinkamą subkomponentą, o ne visą didelį komponentą.
Pavyzdys (konceptualus):
Užuot turėjus vieną didelį UserProfile komponentą, kuris tvarko tiek vartotojo informaciją, tiek veiklos srautą, padalinkite jį į du komponentus: UserInfo ir ActivityFeed. Kiekvienas komponentas valdo savo būseną ir pervaizduojamas tik tada, kai pasikeičia jo konkretūs duomenys.
7 optimizavimo strategija: „Reducers“ naudojimas su `useReducer` sudėtingai būsenos logikai
Dirbant su sudėtingais būsenos perėjimais, useReducer gali būti galinga alternatyva useState. Jis suteikia struktūrizuotesnį būdą valdyti būseną ir dažnai gali pagerinti našumą. useReducer „hook“ valdo sudėtingą būsenos logiką, dažnai su keliomis antrinėmis reikšmėmis, kurioms reikalingi detalūs atnaujinimai, pagrįsti veiksmais.
Pavyzdys:
import React, { useReducer } from 'react';
const initialState = { count: 0, theme: 'light' };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'toggleTheme':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
Theme: {state.theme}
);
}
export default Counter;
Šiame pavyzdyje reducer funkcija apdoroja skirtingus veiksmus, kurie atnaujina būseną. useReducer taip pat gali padėti optimizuoti atvaizdavimą, nes galite kontroliuoti, kurios būsenos dalys sukelia komponentų pervaizdavimą naudojant įsiminimą, palyginti su potencialiai platesniais pervaizdavimais, kuriuos sukelia daugybė useState „hook'ų“.
8 optimizavimo strategija: selektyvūs būsenos atnaujinimai
Kartais galite turėti komponentą su keliais būsenos kintamaisiais, tačiau tik kai kurie iš jų sukelia pervaizdavimą, kai pasikeičia. Tokiais atvejais galite selektyviai atnaujinti būseną naudodami kelis useState „hook'us“. Tai leidžia izoliuoti pervaizdavimus tik toms komponento dalims, kurias iš tikrųjų reikia atnaujinti.
Pavyzdys:
import React, { useState } from 'react';
function MyComponent() {
const [name, setName] = useState('John');
const [age, setAge] = useState(30);
const [location, setLocation] = useState('New York');
// Atnaujinti vietovę tik tada, kai ji pasikeičia
const handleLocationChange = (newLocation) => {
setLocation(newLocation);
};
return (
Name: {name}
Age: {age}
Location: {location}
);
}
export default MyComponent;
Šiame pavyzdyje, pakeitus location, bus pervaizduota tik ta komponento dalis, kuri rodo location. Būsenos kintamieji name ir age nesukels komponento pervaizdavimo, nebent jie būtų aiškiai atnaujinti.
9 optimizavimo strategija: būsenos atnaujinimų „Debouncing“ ir „Throttling“
Situacijose, kai būsenos atnaujinimai vykdomi dažnai (pvz., vartotojui įvedant duomenis), „debouncing“ ir „throttling“ gali padėti sumažinti pervaizdavimų skaičių. „Debouncing“ atideda funkcijos iškvietimą tam tikram laikui po paskutinio jos iškvietimo. „Throttling“ apriboja, kiek kartų funkcija gali būti iškviesta per tam tikrą laikotarpį.
Pavyzdys („Debouncing“):
import React, { useState, useCallback } from 'react';
import debounce from 'lodash.debounce'; // Įdiekite lodash: npm install lodash
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSetSearchTerm = useCallback(
debounce((text) => {
setSearchTerm(text);
console.log('Paieškos terminas atnaujintas:', text);
}, 300),
[]
);
const handleInputChange = (event) => {
debouncedSetSearchTerm(event.target.value);
};
return (
Searching for: {searchTerm}
);
}
export default SearchComponent;
Šiame pavyzdyje debounce funkcija iš „Lodash“ bibliotekos naudojama atidėti setSearchTerm funkcijos iškvietimą 300 milisekundžių. Tai neleidžia būsenai atsinaujinti po kiekvieno klavišo paspaudimo, taip sumažinant pervaizdavimų skaičių.
10 optimizavimo strategija: `useTransition` naudojimas neblokuojantiems UI atnaujinimams
Užduotims, kurios gali blokuoti pagrindinę giją ir sukelti vartotojo sąsajos (UI) strigimus, galima naudoti useTransition „hook“, norint pažymėti būsenos atnaujinimus kaip neskubius. „React“ tuomet suteiks pirmenybę kitoms užduotims, pavyzdžiui, vartotojo sąveikoms, prieš apdorodamas neskubius būsenos atnaujinimus. Tai užtikrina sklandesnę vartotojo patirtį, net ir dirbant su skaičiavimams imliomis operacijomis.
Pavyzdys:
import React, { useState, useTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState([]);
const loadData = () => {
startTransition(() => {
// Imituoti duomenų įkėlimą iš API
setTimeout(() => {
setData([1, 2, 3, 4, 5]);
}, 1000);
});
};
return (
{isPending && Loading data...
}
{data.length > 0 && Data: {data.join(', ')}
}
);
}
export default MyComponent;
Šiame pavyzdyje startTransition funkcija naudojama pažymėti setData iškvietimą kaip neskubų. „React“ tuomet suteiks pirmenybę kitoms užduotims, pavyzdžiui, atnaujinti vartotojo sąsają, kad atspindėtų įkėlimo būseną, prieš apdorodamas būsenos atnaujinimą. isPending vėliavėlė nurodo, ar perėjimas (transition) vyksta.
Pažangesni aspektai: kontekstas ir globalus būsenos valdymas
Sudėtingoms programoms su bendrinama būsena apsvarstykite galimybę naudoti „React Context“ arba globalaus būsenos valdymo biblioteką, tokią kaip „Redux“, „Zustand“ ar „Jotai“. Šie sprendimai gali pasiūlyti efektyvesnių būdų valdyti būseną ir išvengti nereikalingų pervaizdavimų, leisdami komponentams prenumeruoti tik tas būsenos dalis, kurių jiems reikia.
Išvada
Optimizuoti useState yra labai svarbu kuriant našias ir lengvai prižiūrimas „React“ programas. Suprasdami būsenos valdymo niuansus ir taikydami šiame vadove aprašytas technikas, galite žymiai pagerinti savo „React“ programų našumą ir reakcijos greitį. Nepamirškite profiliuoti savo programos, kad nustatytumėte našumo problemas, ir pasirinkite tinkamiausias optimizavimo strategijas pagal savo konkrečius poreikius. Neoptimizuokite per anksti, nenustatę realių našumo problemų. Pirmiausia sutelkite dėmesį į švaraus, prižiūrimo kodo rašymą, o tada optimizuokite pagal poreikį. Svarbiausia yra rasti pusiausvyrą tarp našumo ir kodo skaitomumo.