Ако работите с React ежедневно, рано или късно ще откриете, че Овладяването на useState и useEffect прави цялата разлика между компонент, който „работи периодично“, и солиден, поддържан и лесен за отстраняване на грешки интерфейс. И двата hooks-а изглеждат прости на пръв поглед, но веднага щом започнете да правите HTTP заявки, абонаменти или таймери, възникват съмнения, ефектите се изпълняват в цикли и състоянията стават „остарели“.
В следващите редове ще видим Как правилно да използвате useState и useEffect в React, от основите до напредналите шаблониТози курс интегрира най-добрите практики за производителност, управление на зависимости, почистване на ефекти, използване с външни API и някои трикове за избягване на често срещани грешки. Целта е до края да можете да четете всеки компонент с hooks и да разбирате какво се случва, и най-вече да можете да проектирате свой собствен, без страх да повредите нещо.
Какво точно представляват useState и useEffect и кога трябва да ги използвате?
React въведе Hooks, започвайки с версия 16.8. Да управлява логиката на състоянието и жизнения цикъл в рамките на функционални компоненти, без да използва класове. Целта е да ви позволи да композирате декларативно логика за многократна употреба, използвайки самия език JavaScript (затваряния, чисти функции и др.).
Куката useState Използва се за добавяне локално състояние към функционален компонентВместо да има това състояние y this.setStateВие декларирате двойка [valor, setValor] React ще асоциира реда на извикванията на hook в този компонент с това състояние. Всяко рендериране ще има своя собствена „версия“ на това състояние.
От своя страна, useEffect е куката, предназначена за странични ефекти.код, който се изпълнява след като React е нарисувал в DOM и който взаимодейства с външни системи: заявки за данни, абонаменти за websocket, достъп до localStorage, манипулиране на прозорци, таймери с setInterval o setTimeoutИ др
Въпреки че често се казва, че useEffect е еквивалентен на комбинираните componentDidMount, componentDidUpdate и componentWillUnmountПо-точно е да се смята, че описва „Какво трябва да се синхронизира с екстериора след всяко рендериране“След това, масивът от зависимости дефинира когато Тази синхронизация е извършена.

useState: декларативно управление на локалното състояние
Куката useState Декларира се в горната част на компонента и получава начална стойностВръща масив с две позиции: текущата стойност и функция за нейното актуализиране. Нещо толкова просто като брояч се дефинира по следния начин:
Основен пример за useState:
import { useState } from "react";
функция BasicCounter() {
const[брой, setCount] = useState(0);
константно нарастване = () => {
setCount(брой + 1);
};
връщане (
Натиснали сте бутона {count} пъти
Натиснете ме
);
}
Във всяко рендериране, брой представлява състоянието, съответстващо на това специфично рендиранеПри обаждане setCountReact планира ново рендериране с актуализираната стойност. Ако актуализацията зависи от предишната стойност, е за предпочитане да се използва функционалният подход:
setCount(prev => prev + 1);
Този синтаксис избягва проблеми, когато няколко актуализации са свързани заедно, защото React гарантира, че prev е последната стабилна стойностдори в едновременен режим.
useEffect: Разбиране на жизнения му цикъл и масива от зависимости
Сигнатурата на ефектната кука е много проста: useEffect(efecto, dependencias?)Първият аргумент е функция за настройка, която се изпълнява след рисуване на DOM. По желание, тази функция може да върне друга функция за почистване, която React ще извика преди повторно изпълнение на ефекта или при демонтиране на компонента.
Ключът към ефективното използване на useEffect е разбирането на масива от зависимости.Този масив показва от кои „реактивни“ стойности зависи логиката на ефекта: props, състояния и променливи или функции, дефинирани в компонента. React сравнява текущите зависимости с тези от предишното рендериране, използвайки Object.isАко има някаква промяна, първо изпълнете предишното почистване и след това конфигурирайте новия ефект.
В зависимост от това как пишете този масив, получавате различни поведения:
- Няма втори аргументЕфектът се изпълнява след всяко рендериране.
- Празен масив []Ефектът се изпълнява автоматично веднъж след първото рендериране (режим „componentDidMount“ и „componentWillUnmount“).
- Масив със зависимости [a, b]Ефектът се изпълнява след първото рендериране и всеки път, когато една от тези зависимости се промени.
Много типичен пример: Актуализирайте заглавието на документа с броячТова е съвременният функционален еквивалент на класическия модел на клас с componentDidMount + componentDidUpdate.
Пример за useEffect, синхронизиращ заглавието на документа:
import { useState, useEffect } from "react";
функция БроячСЗаглавие() {
const[брой, setCount] = useState(0);
useEffect(() => {
document.title = `Натиснахте бутона ${count} пъти`;
}, [брой]);
връщане (
Бутонът е бил натиснат {count} пъти
setCount(брой + 1)}>Натисни ме
);
}
В този случай, зависимостта е countи трябва да е в масива, независимо от всичко.защото се използва в ефекта. Ако се опитате да го пропуснете, eslint-plugin-react-hooks Това би ви предупредило; и ако принудите линтера да млъкне, ще се сблъскате с трудно откриваеми грешки, при които заглавието не съответства на действителната стойност на брояча.

Несанитизиращи ефекти: логика, която трябва да се изпълни само след рендиране
Има категория ефекти, които те не изискват специално почистванеТова са тези, в които „Правиш нещо и го забравяш“: влизане през конзола, промяна на заглавието на документа, отправяне на еднократна HTTP заявка, измерване на нещо само веднъж и т.н.
В света на социалните класи тази логика беше разпространена сред componentDidMount y componentDidUpdate, често с дублиран код. С куките това дублиране изчезва, защото Един единствен useEffect може да обработва както първоначалното рендериране, така и актуализациите..
Прост пример за ефект без санитария: получаване на местоположението на потребителя чрез геолокационния API на браузъра и го запазете в състоянието:
import { useState, useEffect } from "react";
функция Местоположение на потребителя() {
const [latitude, setLatitude] = useState(0);
const [дължина, setLength] = useState(0);
useEffect(() => {
ако (!navigator.geolocation) {
console.log("Браузърът не поддържа геолокация");
се върнат;
}
navigator.geolocation.getCurrentPosition((pos) => {
задайШирота(поз.координати.широта.доФиксирана(0));
setLength(поз.координати.дължина.toFixed(0));
});
}, []);
В този случай, Искаме да поискаме местоположението само веднъж, когато компонентът е монтиран.Следователно, масивът от зависимости е празен: няма нищо, което би трябвало да задейства ефекта отново. Не е необходимо почистване, защото не сме оставили активни интервали, абонаменти или слушатели.
Друг често срещан пример за ефект без отстраняване е лог или аналитичен тракер, когато определена информация се промениНапример, записване в коя чат стая се намира потребителят или колко артикула има в количката си, стига да не се поддържа жива връзка, свързана с това събитие.
Ефекти от санитарното почистване: абонаменти, интервали и течове на памет
Другата основна категория е ефекти, които се нуждаят от почистванеОбикновено се случва, когато създавате нещо, което би трябвало прекъсване или отмяна когато зависимостите се променят или компонентът се демонтира: API абонаменти, глобални слушатели на събития, таймери, уеб сокети и др.
Моделът е винаги един и същ: В рамките на ефекта инициализирате ресурса и връщате функция, която го отменя.React ще извика тази функция за почистване, преди да изпълни ефекта отново и когато компонентът изчезне от DOM.
Класически пример: брояч, който автоматично се увеличава на интервали.
import { useState, useEffect } from "react";
const AutomaticCounter = () => {
const [брояч, setCounter] = useState(0);
useEffect(() => {
const интервал = setInterval(() => {
console.log("Интервал...");
setCounter((c) => c + 1);
}, 2000);
връщане () => {
console.log("Интервал на изчистване");
изчистиИнтервал(интервал);
};
}, []);
връщане {брояч} ;
};
Тук интервалът се създава, когато компонентът е монтиран, и се изтрива, когато е демонтиран. Ако забравите да извикате clearInterval, Интервалът ще продължи да тече, дори ако компонентът вече не се показва.причинявайки изтичане на памет и актуализации на състоянието на разглобени компоненти.
Друг много често срещан модел е абонамент за глобални събитияКато resize de window o keydown en document:
import { useEffect } from "react";
функция ГлобалниСъбития() {
useEffect(() => {
const handleResize = () => {
console.log("Прозорецът е преоразмерен");
};
const handleKeyDown = (събитие) => {
console.log(«Натиснат клавиш», event.key);
};
window.addEventListener("преоразмеряване", handleResize);
document.addEventListener("клавиатура", handleKeyDown);
връщане () => {
window.removeEventListener("преоразмеряване", handleResize);
document.removeEventListener("клавиатура", манипулатор наКлейДън);
};
}, []);
връщане Слушайки за глобалните събития ;
}
La Почистването оставя системата точно както е била преди инсталирането на компонентаТова огледално отразяване между настройката и почистването е фундаментално, особено като се има предвид, че в режим на строг разработка React може да изпълни допълнителен цикъл от настройка → почистване → настройка, за да открие грешки.
HTTP заявки с useEffect и useState: рецепта стъпка по стъпка
Една от най-честите употреби на useEffect es зареждане на данни от API, когато компонентът е монтиран и съхранявайте резултата в състояние с useStateИма една много практична „рецепта“, която можете да използвате почти всеки път:
- Компонентът стартира в режим на "зареждане" със състояние на типа
isLoading. - Направете заявката вътре в useEffect веднага след като React рисува компонента.
- Запазете отговора в състоянието и марка
isLoadingкатоfalseкогато приключа. - Рендиране условно въртящо се или зареждащо съобщение, докато
isLoadingмореtrueи данните, когато са готови.
Пълен пример: Приложение, което показва произволно кученце, използвайки Dog API.
import { useState, useEffect } from "react";
функция СлучайноДог() {
const[imageUrl, setImageUrl] = useState(null);
const[зарежда се, сетЗарежда се] = useState(true);
const[грешка, setError] = useState(null);
useEffect(() => {
const fetchDog = async () => {
опитвам {
setIsLoading(true);
setError(null);
const отговор = изчакване на fetch(«https://dog.ceo/api/breeds/image/random»);
ако (!response.ok) {
хвърляне на нова грешка(«HTTP грешка» + отговор.статус);
}
const данни = изчакване на отговор.json();
setImageUrl(данни.съобщение);
} улов (грешка) {
setError(съобщениезагрешка);
} накрая {
setIsLoading(false);
}
};
донесиКуче();
}, []);
ако (сеЗарежда) {
връщане Зареждане… ;
}
ако (грешка) {
връщане Грешка: {грешка} ;
}
връщане (
);
}
Обърнете внимание, че е потвърдено response.ok за да се прави разлика между правилен отговор (кодове 200-299) и HTTP грешкиFetch отхвърля обещания само поради мрежови проблеми, така че ако не проверите... ok Може би си мислите „всичко е наред“ дори с 500.
Същият този модел може да бъде адаптиран за заявки, които зависят от props. Например, ако искате да покажете данните на конкретен потребител въз основа на userId което идва отгоре, бихте използвали това свойство като зависимост на ефекта от актуализирайте данните всеки път, когато се променят.
Заявки, зависими от проппорта, и безопасно отменяне по време на демонтаж
Когато URL адресът или идентичността на данните, които ще бъдат заредени, зависи от prop (например, презаредете потребителя, ако се промени props.userId), е важно:
- Посочете правилно свойството като зависимост на ефекта.
- Избягвайте актуализации на състоянието, ако компонентът е премахнат преди да приключи заявката.
Един прост модел за анулиране е използването на вътрешен флаг:
import { useEffect, useState } from "react";
import axios from "axios";
функция Потребител({ потребителскиИдентификатор }) {
const [потребител, setUser] = useState(null);
useEffect(() => {
нека еМоунтед = true;
const fetchData = async () => {
const резултат = изчакване axios(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
ако (еМоунтед) {
setUser(резултат.данни);
}
};
извличане на данни();
връщане () => {
еМоунтед = фалшиво;
};
}, [потребителскиИдентификатор]);
ако (!потребител) {
връщане Зареждане… ;
}
връщане (
{потребителско.име}
Имейл адрес: {user.email}
Телефон: {user.phone}
);
}
Докато еМонтирано море trueРазрешено е да се актуализира състояниетоКогато компонентът бъде премахнат, ефектът на почистване задава този флаг. falseИ дори обещанието да бъде изпълнено по-късно, setUser Вече няма да се изпълнява. Има по-усъвършенствани алтернативи (AbortController, библиотеки с данни и др.), но този илюстративен модел прави идеята ясна.
Логика за повторно използване: множество ефекти в един и същ компонент
Една от големите предимства на куките е, че Не сте ограничени до един единствен useEffect на компонентВсъщност, обикновено е за предпочитане. разделете логиката на няколко специализирани ефекта вместо да смесите всичко в едно огромно.
Помислете за компонент, който има и двете счетоводител, отдалечени данни и може би абонаментВ клас ще се окажете с методи за жизнен цикъл, пълни със смесен код: в componentDidMount Вие стартирахте петицията и се подписахте; през componentDidUpdate ръчно проверихте какво се е променило; в componentWillUnmount Почисти и двете неща.
С кукички можете да използвате един useEffect за всяка „идея“ или отговорност:
import { useState, useEffect } from "react";
const МоятКомпонент = () => {
const[брой, setCount] = useState(0);
const [данни, setData] = useState(null);
useEffect(() => {
console.log(«Мониран компонент»);
}, []);
useEffect(() => {
console.log(`Броят се е променил на ${count}`);
}, [брой]);
useEffect(() => {
console.log("Данните са променени");
}, [данни]);
връщане (
setCount((c) => c + 1)}>Кликнете тук
);
};
По този начин, Всеки ефект се интересува само от това, което го интересува.Кодът става по-четлив и е по-лесно да се добавя или премахва поведение, без да се нарушава останалото. React ще приложи всички ефекти на компонентите в реда, в който ги декларирате.
Оптимизиране на производителността: зависимости и ефекти, които не се повтарят прекомерно
Когато даден ефект извършва скъпа работа (API заявки, тежки изчисления, повторно рендериране от трети страни...), Не искате да се изпълнява при всяка актуализация на компонента.В компонент на клас това често се решаваше с ръчни сравнения. if (prevProps.x !== this.props.x) в componentDidUpdate.
С кукички, Оптимизацията е вградена в самия useEffect подпис. използвайки масива от зависимости. Например, ако искате ефектът да се задейства само когато се промени count, декларирате го като:
useEffect(() => {
console.log("Ha cambiado count");
}, [count]);
React ще сравни стария масив от зависимости [valorAnteriorDeCount] с новото [valorNuevoDeCount]. Ако всички елементи са еднакви, ефектът се пропуска.Ако има някакви разлики, предишният ефект се изчиства (ако е имало почистване) и конфигурацията се изпълнява отново.
По подобен начин, ако искате ефект, който се изпълнява само веднъж, за да инициализира нещо (и по избор да го почисти при дизасемблер), Използвате празен масивНо бъдете внимателни: Проповете и състоянието, които четете в ефекта, ще запазят първоначалните си стойности.Следователно, този модел трябва да се използва, когато сте сигурни, че ефектът ви не зависи от промяна в нищо.
За да избегнете пропуски, силно се препоръчва да активирате правилото exhaustive-deps de eslint-plugin-react-hooksТова правило анализира кода на ефекта и ви предупреждава, ако липсват зависимости, предлагайки подходящо решение. Това е много ефективен начин да се избегнат фини грешки, когато се използват остарели стойности на състоянието или свойства.
Типични проблеми с useEffect и как да ги избегнем
Работата с ефекти е проста на теория, но на практика има редица... повтарящи се грешки което е нещо, което винаги трябва да се има предвид, за да не се изпада в тях отново и отново.
Едно от най-обсъжданите е безкраен цикъл на рендериранеТова се случва, когато вашият ефект актуализира състояние, което е част от неговите зависимости по начин, който не се сближава. Например, ако имате:
useEffect(() => {
setCount(count + 1);
}, [count]);
Във всяко рендериране, както count То се променя, ефектът се изпълнява отново и вие актуализирате състоянието отново и така нататък до безкрай. В тези случаи или сте на грешното място (тази актуализация не трябва да се намира в ефект), или ви е необходимо други видове логика, като например интервал с почистванеили използвайте функционалната форма setCount(c => c + 1) без да се слага count в помещенията.
Друг проблем е усещането, че ефектът „при монтажа се пуска два пъти“В строг режим, React по време на разработка извършва един вид „стрес тест“, като изпълнява setup → cleanup → setup още в началото. Това не се случва в продукцията, но Подчертава лошо проектирани ефекти, които не се почистват правилно.Ако логиката ви за почистване е симетрична на логиката ви за настройка, потребителят не би трябвало да забележи нищо необичайно.
Също така е често срещано да се виждат ефекти, които се повтарят при всяко рендериране, въпреки че имат зависимости. Това обикновено се случва, защото Масивът включва обекти или функции, създадени при всяко рендериранеТъй като референциите винаги се променят, React приема, че зависимостта също се е променила. Решението: декларирайте тези функции или обекти в самия ефект или ги запомнете с useCallback/useMemo ако наистина са необходими навън.
useEffect, външни системи и разширена употреба със събития с ефекти
Причината да бъдеш на useEffect es Поддържайте компонента си синхронизиран с външни системи: сокети, карти, вградени видео плейъри, API на браузъра и др. Моделът винаги се основава на идеята „когато тези зависимости се променят, свържете се отново с новите стойности и се изключете от старите“.
Например, компонент ChatRoom който се свързва с определена стая в зависимост от serverUrl y roomId би трябвало:
- Свържете се по време на каране с първата комбинация от
serverUrlyroomId. - Прекъснете връзката с предишната стая и се свържете с новата, ако потребителят смени стаите или сървърите.
- Изключете напълно, когато разглобявате компонента.
С useEffect и добро почистване можете да постигнете това всяко рендериране има своя кохерентна връзка и че React може да повтори последователността от настройки/почистване, без нищо да остане блокирано.
В по-напреднали сценарии може да се наложи Прочетете последното състояние и свойства в рамките на ефекта, без да задействате повторни изпълненияЕто къде влизат в действие подобни модели. Ефектни събития (като кукички) useEffectEvent в новите препоръки), които ви позволяват да преместите определен нереактивен код от списъка със зависимости. Междувременно, практичен подход е използвайте персонализирани препратки или куки за онези произведения, където класическото реактивно поведение не се вписва добре.
В контекста на рендерирането от страна на сървъра (SSR), имайте предвид, че ефектите се изпълняват само върху клиентаПървоначалният HTML код се рендира на сървъра и след като приложението е хидратирано в браузъра, се задействат операторите useEffect. Ако трябва да покажете нещо различно само от страна на клиента, можете да използвате състояние като didMount това минава false a true в резултат на това [] и оттам да се обуславя рендерирането.
Да доминира useState y useEffect В крайна сметка става въпрос за разбиране, че всяко рендериране е моментна снимка на вашия компонент, че куките (hooks) са обвързани с реда, в който ги декларирате, и че ефектите описват как да се синхронизира тази моментна снимка с външния свят, включително как да се отмени това, което трябва да се отмени. С добро управление на зависимостите, почистване и разделяне на ефектите по отговорности, вашите компоненти ще функционират много по-предсказуемо, ще консумират по-малко ресурси и ще бъде значително по-лесно да се разбере какво може да не е наред, когато нещо не се държи както трябва.