Аналіз React Hooks за дапамою атрыбутаў render snapshots і компромісаў.
Высокагарадусны модель мышлення для React і React Native Hooks: атрыбуты render snapshots, effects, refs, мемуізацыя, спецыяльныя Hooks, а таксама як поясніць компромісы пад спытаннях.
Крыніцы: React & React Native Hooks: The Complete Senior Interview Guide
У адміністратыўных спэктараў на React і React Native рэдка канчыцца толькі пытаннем «Што робіць гук?». Пытанне пра useState за хвіліну ператвараецца на разговор пра групаванне і функцыональныя апдэйты; пытанне пра useEffect стае тэмай для дыскусій пра бесканечныя цыклы, застарэлыя клаудзы, анулювання і ситуаціі, калі эфект узагалі не павінен існаваць; useMemo і useCallback прыводзяць да пытання «Чы гэта даслівна прыяўляе швальнасць?». Запамятаваныя вазначэння не вытрымаюць такіх дальнейшых запытанняў. Тое, што застаецца, — это сувязаная модэль, і гід стварае самэў такую: атрыбуты відраслі, пасля чаго — стан, клаудзы, эфекты, рэфы, мемуізацыя, спецыяльныя гукі, швальнасць, а ў канцы — архітэктура, з прыкладамі на React Native там, дзе платформа зменяе ситуацыю.
Модэль атрыбутаў відраслі, ад якой залежыць усё іншае
Пачніце з адной ідеі і трывайце ў яй: калі React атрыбутуе компонент, ён зноў вызывае функцыю компонента з самага пачатку. Выберіце як можно меньшы лічылак.
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Здаецца прыродным думаць, што клік на кантактны элемент прыносіць змяны ў сам компонент і прабывае зменіць зменную count. Але так не адбываецца. Функцыя-зменнік просіць React атрыбуэць компонент занова, і наступны вызыв Counter прыме новую значэнне як аблікованую сталячую вялічыну.
Render 1
count = 0
↓
User clicks
↓
setCount(1)
↓
React schedules another render
↓
Render 2
count = 1
Кожны раз падзець мае свой апвіт своих пропсаў і стану. Нічога ў тым апвіце не зменшваецца пасля таго. Гэты просты факт адказвае на пытанні пра закрыцця над старымі значэннямі, застарелы стан у таймерах, прычыны наявнасці залежнасцэў эфектаў, адносна таго, як паводзяцца калбэкі і адпаведнікі змаганняў, а таксама прычыны таго, чаму мемазія стосуецца ідэнтычнасці. Адпраўальнікі на спытаннях, якія выходзяць за межы API, зазвычай прабуюць з’ясаваць, чы ў вас ёсць такая модэль.
useState: стан, які належыць падзецу
useState дае функцыйнаму компоненту частку стану, якая задае його выходны данны.
const [count, setCount] = useState(0);
Пара, якая вам вяртаецца, мае два разныя ролі: значэнне для текущага падзецу і функцыя, якая запрашвае апдэйт.
count
↓
current state
setCount
↓
request a state update
Выклік сетара не мутыруе нічога на месцы; ён плануе іншы падзец, у яком компонент бачыць новае значэнне.
Як падымаюцца запытанні
Тыповы ўзрост парадоксу выглядае так:
- Базавы: якую проблему рашае
useState? - Середні: чым стан разлічваецца ад звычайнай локальной зменны?
- Професійны: калі правым выборам ёст мэтада апдейта?
- Глыбокі: што на самай працэ ўтварае
setCount(count + 1), павтораны тры разы ў аднам хэндлеры?
Фраза «Ён зберагае стан» адпавядае толькі першай пункцыі. Абодвальнейшая адпаведзь включае ў сябе модэль:
useState зберагае значэнне, якое вплывае на тое, што атрыбутвае компонент. Выклік мэтады апдейта стварае чергу на новую атрыбутвацыю з апдэйтаваным значэннем, таму стан лепша спрыяць як частку кадру гэтай атрыбутвацыі, а не як зменную, яку можна мяніць. Калі наступнае значэнне залежыць ад пярэднего, мэтада апдейта ёст безпечным выборам, таму што React групавае апдэйты, а зафіксаванае значэнне можа вже быць застарелым.
У працоўным разліку, звычная зменна перзначаецца своім пачатковым значэнням ў кожны раз, калі відбываецца рендерынг, і змена яе ніколі не вымагае ад React практыкування рендерынгу знову, таму яна не можа кераваць інтерфейсам.
Чаму важлівы функцыйнальныя апдейты
Ёсць тры способы адбівання значэння, якія выкарыстоўваюць зафіксаванае значэння.
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
Усі тры способы чытаюць адно і то ж count з ныявечнага скэнару, таму пасля агрэгавання результатам являецца адна збільшэння, а не тры. Форма апдейтара выправляе гэта:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
React перадае кожнаму апдейтару найсвежэйшае значэння, якое ўсё ще чакае на обробку, таму тры вызовы працююць правільна. Выбірайце эту форму, калі калькuluецца колька апдейтав разам або ў черге, калі калебэк можа запрацаваць з старым скэнарам, і загалом калі новае значэння выраховваецца з старага.
Корзіна пакупак у аплікацыі React Native — это рэалістычны прыклад. Колькасць прадставляецца на экране, таму яна апавяжана з статусам:
const [quantity, setQuantity] = useState(1);
Функцыі-обрабоўчыкі вылучаюць наступную значэнне з пярэдней, а функцыя змены колькасці обмежвае яе на адзину, тады корзіна ніколі не пакажае нуль чы сяродзе моўнае колькасць.
const increaseQuantity = () => {
setQuantity(prev => prev + 1);
};const decreaseQuantity = () => {
setQuantity(prev => Math.max(1, prev - 1));
};
Нічога не змяніцца межу вебам і натыўным праграмаваннем
Хук ўзімае аднойчыны на обох платформах. У веб-версіі выпадаючы список можа фіксаваць, чы ён адкрыты:
const [isOpen, setIsOpen] = useState(false);
Экран у React Native таксама можа фіксаваць видныцю модальнага вікна:
const [isModalVisible, setModalVisible] = useState(false);
Механізм статусу ўзаецца; разлічаюцься толькі элементы UI, якія ён керуе.
useEffect: сінхронізацыя з зовнішнім светам
Няхайшы элемент не стварае большэйшую колькасць пытак у размове пад час адзінавання. Звычныя апісанні на кшталт „гэты элемент выконвае код пасля адзінавання“ ўсё ж правдывыя, але не выявляюць сэрца проблемы. Кращы спосаб апісання – эфект дапамагае падтрымваць сінхроннасць вашага компонента з системай, якую React не кантролюець.
У вебе такойя системай можа быць DOM-здарэння, API прыгледача, WebSockets, падпісанні, таймеры або запиты да сеті. У React Native частаюць AppState, модуль Keyboard, натыўныя модулі, прыемнікі паведамленняў, стан сеті, API прыгледача, WebSockets і таймеры.
Як развіваюцца пытанні
- Базавыя: для чаго викорыстоўваецца
useEffect? - Середнія: паспрабуйце поясніць, як працюе масіва залежнасцяў.
- Для аднаго з кандыдацоў высокага рангу: што можа змусіць эфект ціклізавацца без канца?
- Для аднаго з кандыдацоў высокага рангу+: у якіх ситуацыях эфект ёсць непадходячым інструментам?
- У рэальных умовах: як заканчыць падпісанне на WebSockets?
Паруйце „useEffect выкорыстоўваецца для вызоваў API“ з гэтым:
useEffect існуе галоўна для таго, каб компонент заставацца сінхронным з зовнішняй системай. Маса залежнасцяў паказвае рэакtyвныя значэнні, якія чытае эфект, і яна павінна быць точной; правіла лінтингу Hooks выяўляюць прычыны ўпускаеў. Калі эфект стварае падпіску, запускае таймер або прыўязвае слухача, ён вяртае функцыю для чысткі, каб стары ресурс быў зваліваны перад тым, як эфект запрацюе зноў, або калі компонент будзе зняты.
Чытанне масы залежнасцяў
За наявнасці залежнасці эфект запрацоўвае пасля першага атрыбутавання і зноў кожны раз, калі гэтае значэнне зміняецца межы атрыбутаваннямаў.
useEffect(() => {
fetchUser(userId);
}, [userId]);
Калі масы залежнасцяў нема, ён запрацоўвае пасля кожнага атрыбутавання:
useEffect(() => {
// ...
});
Калі маса залежнасцяў пустая, ён запрацоўвае пасля таго, як компонент будзе атрыбутаваны:
useEffect(() => {
// ...
}, []);
Будзьце абярожны з фразай «раніцца адна раз». У режыме развіцця Strict Mode спецыяльна запускае, чыстае і знову запускае эфекты, каб выявіць тыя, якія не чыстаюцца належным чынам. Точная формулівацыя такая: эфект з порожнім масавам запускаецца пасля запуску, а режым Strict Mode у режыме развіцця можа дадаць дапаможны цыкл налаштавання і чысткі. Код, який не працуе пасля такога двойнага запуску, зазвычай мае справжнія баги.
Чыстка запобегае вытэканню ресурсоў
Падпіс, які залежыць ад userId, стварае з’ѐеднанне ў тэле эфекту:
useEffect(() => {
const subscription = subscribeToUpdates(userId);
і вяртае функцыю, якая яго закрывае:
return () => {
subscription.unsubscribe();
};
}, [userId]);
Калі userId зменяецца, React запускае паказаную раней чыстку перш чым запусціць новы эфект, таму ніколі не існуе больш адной актыўнай падпісацыі:
Old effect
↓
cleanup old subscription
↓
new effect
↓
subscribe using new userId
Пад час зняцья эфекта запускаецца пасляпэўная чыстка, і нічога не застаецца:
component unmounts
↓
cleanup
↓
subscription removed
Той жа механізм выкорыстоўваецца для абрырання асінхронных задань. Для запыту fetch можна абараніць выконанне запытку за дапамою AbortController у функцыі cleanup, або задаць флаг ignore, каб пазнейшы адпаведзень з старог userId не перазапісваў новейшыя данні.
Прыклад у React Native: реакцыя на AppState
Болей пераканлівы эфект, чым запыт да дадзеных, — гэта той, які прыяўязуе компонент да жыцёвага циклу натыўнай аплікацыі. Шэрыфер падтварае данні, калі аплікацыя вяртаецца на першы план, і знікае пасля таго, як компонент адключаецца.
useEffect(() => {
const subscription = AppState.addEventListener(
"change",
nextState => {
if (nextState === "active") {
refreshData();
}
}
);
return () => {
subscription.remove();
};
}, []);
Это сінхронізацыя з зовнішняй системай у ўсё бол ясной форме, і гэта пакажае спрацоўніку, што вы розумееце, для чаго патрэбны эфекты.
Памылка: сінхронізацыя похадных значэнняў чераз эфект
Падазроўце, што форма зберагае імя і працоўнае апавяданне:
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
Часта анти-патэрна — якія дадзе трэці элемент стану і эфект для його апдэйту:
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Это прыводзіць да зайвага стану і дадзе дапэўнейшую перрадзіцю ў кожны раз, калі зменяецца імя, а пад час аднай перрадзіцы цэлае імя ўстарелае. Паколькі значэнне цалкам выначваецца за дапамогою існуючага стану, яго трэба вырахаваць пад час перрадзіцы:
const fullName = `${firstName} ${lastName}`;
Принцып, які варта сказаць вогулом: якщо значэнне можна вырахаваць сінхронна з пропсоў або стану, то яго трэба вырахаваць пад час перрадзіцы, а не зберагаць і падкоряцца ефекту. Гэтая патэрна дакладней рассматрываецца ў stop syncing state with useEffect.
Памылка: нестабільныя залежнасці аб’ектаў
Ёнклудзіцца стварэнне літералу аб’екта ўнутрь компанента і яго уключэнне як залежнасці.
const filters = {
status: "active",
};
useEffect(() => {
fetchUsers(filters);
}, [filters]);
Кожны раз, калі відбываецца рендерынг, ствараецца новы об’ект filters, а залежнасці порৱняюцца па ссылкі, таму кожны раз эфект бачыць „змену“. Якщо эфект апдейтуе стан, цыкл продовжваецца сам сабой:
Render
↓
new filters object
↓
dependency changed
↓
effect
↓
setState
↓
render
↓
new filters object
↓
effect again
Рэфлексіўны спосаб прынятка — абгорнуць об’ект у мемаізацыю:
useMemo(...)
Перш чым гэта зрабіць, запытайцеся, чы робіцца стварэнне об’екта ў самай компаненте важна. Константа можа быць розмешчаная за межамі компаненты; значэнне, створанае з пропсаў, часта можа быць замененае паляганнем на яе прымітныя поля (на прыклад, status) аб ўтворэннем яе ў самым эфекте. Прынятны спосаб — паспрабаваць выправіць поток дадзеных; дадаўшы мемаізацыю, каб яго схаваць, — гэта не ўсё.
useRef: памяць, якая не рэндаруецца
useRef вяртае змінны контейнер, які застаецца стабільным праз усе рендерынгі, і якога змены ніколі не спрычыняюць новага рендерынгу.
const valueRef = useRef(0);
Вы чытаете і запісвайце ўсё, што ў яму є, через атрыбут current:
valueRef.current
Як толькі прызначаецца новая значэнне, як паказана нижчэй, контейнер апдэюецца, але экран застаецца такім, як і раней, пакуль неякае інша дзія не спрычыніць перрэндаруванне.
valueRef.current = 10;
У спытальніках зазвычай пераходзяць ад пытання «У чым разлік між ref і state?» да пытання «Дайце рэальны прыклад», а потым — да пытання «Чаму тут выбраць ref заместо state?». Хораша адпаведзь:
State — это даны, якія вплываюць на процес рэндарування, таму ўзміненне іх спрычынае перрэндаруванне. Ref заставляе зміннае значэнне застацца актуальным праз усі перрэндарування, не спрычынюючы перрэндарування, калі змінюецца
.current. Ref падходзяць для імператыўных об’ектаў, ID таймераў, пакойшых значэнняў і іншых змінных дадзеных, якія не адображаюцца на інтерфейсе.
У вебе ref дае можлівасць імператыўнага доступу да вузла DOM, напрыклад, для фокусавання пола введэння:
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
У React Native ref-ы таксама ўжываюцца часта, адколі мобільныя інтерфейсы часта патрабуюць імператыўных вызовоў, такіх як прасуванне спіса назад да верху:
const scrollRef = useRef<ScrollView>(null);
scrollRef.current?.scrollTo({
y: 0,
animated: true,
});
Выбір между станам і ref-ам
Рашаючы пытанне простае: чыяжэ зменіць гэтую значэнне, каб змянілася тое, што бачыць корыстнік? Якщо так, тады выберыце
useState
а якщо ні, тады выберыце
useRef
Актыўныя вікні явна вплываюць на інтерфейс, таму гэта ёсць стан:
const [selectedTab, setSelectedTab] = useState("home");
Об’ект таймера існуе толькі для таго, каб пазней моглы яго абанаваць; нічто його не відображае, таму ref-ы є правым выборам:
const timerRef = useRef<number | null>(null);
useMemo: кэшаванне вырахаванага значэння
useMemo зберагае рэзультат вырахунку і перысчытае яго толькі тады, калі змянюецца якая-небудзь залежнасць. Класычным прыкладам є фільтраваны і сортаваны спіс корыстнікаў:
const filteredUsers = useMemo(() => {
return users
.filter(user => user.active)
.sort(sortUsers);
}, [users]);
Калі users застаўляе той самы рэферэнс, фільтрацыя і сортаванне прахоўваюцца пад час наступных атрыбутаў. Зважайце на скрытыя вымаганні: sortUsers таксама павінен быць стабільным, або ж яго павінна быць указана як залежнасц; кроме таго, функцыя sort мутуюць на месцы, што ў даным случае є безпечным толькі таму, што filter вяліка частка ўжо створыў новы масэвак.
Часта задаецца пытанне: «Чы не трэба яго вжываць у всіх месцах?». Адпаведзь, якая паказвае критычнае мненне:
Ня. useMemo не є безкоштовным: янь дадае трэйкінг залежнасцяў, адзначае выкарыстоўванне памяці і коду. Ён стае корыстным, калі дорогі вычысленні выканаюцца занова без прычыны, або калі стабільны рэферэнс мае значэнне для наступных частак коду; перш чым яго дадаць, трэба адмацірацыю.
Надмерны выкарыстоўкой є мемаізаванне спаўнення стрэлкі:
const fullName = useMemo(
() => `${firstName} ${lastName}`,
[firstName, lastName]
);
Робота ў такім случыку простая, таму звычны выраз як быстрей чытаецца, так і прыблізна на той жа скорасці выконваецца:
const fullName = `${firstName} ${lastName}`;
Тепер розглянем прыклад дапрыязду для торгавлі ў React Native, які паказвае тыясячы транзакцый, фільтруюцыхся па символу і прыметку часу, а таксама сортаваных па прыоритэце «найсвежэйшыя першыя».
const visibleTrades = useMemo(() => {
return trades
.filter(trade => trade.symbol === selectedSymbol)
.filter(trade => trade.timestamp >= startTime)
.sort((a, b) => b.timestamp - a.timestamp);
}, [trades, selectedSymbol, startTime]);
Калі такая трансформацыя становіцца значна дорогай, тады мемаўванне стае неабходным. Нават тады трэба паказаць гэта за дапамою інструменту аналізу, а не проста выкладваць гаданні.
useCallback: зберагчы ідэнтычнасць функцыі
useCallback вяртае той самы об’ект функцыі между кожным перзапускам, пакуль не зменяюцца яе залежнасці.
const handlePress = useCallback(() => {
submitOrder();
}, [submitOrder]);
Разлік з useMemo заключаецца толькі ў тым, што кэшуецца. Одна з іх кэшуе цяжыню:
useMemo
→ memoises a value
А іншая — кэшуе ссылку на функцыю:
useCallback
→ memoises a function reference
Фактычна useCallback(fn, deps) паводзіцца як useMemo(() => fn, deps). Адказ «Каб падбаць пра скорасць» ёсць слабы на запитанне «Чаму яго вжываць?». Кращы адказ:
useCallback зберагае стабільнасць ідэнтычнасці функцыі, калі ўсе яе залежнасці застаюцца тымі ж. Яго варта вжываць там, дзе справжня роўнасць канкэрэнтаў мае значэнне, напрыклад, калі функцыя-кантракт перадаецца мемаванаму дзецявому компоненту або калі іншы Hook выказвае яе як залежнасць. Яго не трэба дадаваць за замовчаннем, таму што мемавацыя сама па сабе мае своія затраты і ускладнюе розумэнне коду.
Як яно з’яўляецца у звязку з React.memo
React.memo прыменшае перерасчыткі компонента, калі всі яго пропсы ў поверхневым сэнсе роўныя паканунашням.
const UserRow = React.memo(function UserRow({
user,
onPress,
}) {
return (
// ...
);
});
Якщо родны компонент кожны раз перадае новую функцыю-стрэлку, адна з пропсаў завжды будзе разным:
<UserRow
user={user}
onPress={() => openUser(user.id)}
/>
Паколькі зменяецца рэферэнс, поверхневая парадоражнае не выконваецца, і рэндаруецца запам’ятаваныя строка, што можа таямніча знехтуваць перавагамі React.memo. Стабільны хэндлер выглядае так:
const handlePress = useCallback(() => {
openUser(user.id);
}, [user.id]);
і ён перадаецца заместо функцыі-інлайна:
<UserRow
user={user}
onPress={handlePress}
/>
Памятаце пра адзін важны момант: робіце гэта тады, калі ёсць доказы таго, што рэндараванне дзецячай компаненты ведае большых зусиль, а не як інстынктыўнае рашэнне.
Закрыцція і проблема застарэлых значэнняў
Закрыцція — это функцыя JavaScript, а не Hook, але без яных важка разумець хукі. Закрыцце — это функцыя, якая зберагае доступ да зменных тэрыторіі, дзе яна была створана.
function createLogger(name: string) {
return () => {
console.log(name);
};
}
Внутрэняя функцыя все ўсё бачыць name дзеўяносто пасля таго, як createLogger вярнуўся.
Як у React з’являецца застарэлае закрыцце
Разглядзім компанент з лячыкам:
function Counter() {
const [count, setCount] = useState(0);
і два кантакты: адны, які збяльшвае лічылку, і адны, які після таймауту у адзину секунду фіксае ўмовы лічылкі:
const handleClick = () => {
setTimeout(() => {
console.log(count);
}, 1000);
}; return (
<>
<button onClick={handleClick}>
Log Count
</button> <button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</>
);
}
Падазроўваючы, што працоўнік для фіксацыі даных быў створаны пад час атрыбутавання:
count = 0
Якщо вы нажмёте «Фіксацыя лічылкі», а потым колькаразова збяльшыце ёё за тую ж секунду, таймаут усё раве будзе выведаць даны:
0
нават якія пасляэтапныя атрыбутавання вядуць да іншых дзеянняў:
count = 1
count = 2
count = 3
Калебарас таймауту працюе з мэтаданым count з таго атрыбутавання, якое яго створыла, і гэты моментальны стан ніколі не зменшыцца.
Паспяшнае адказаў у інтэрв’ю
Компаненты на асабліванні працуюць у фрагментах атрыбутаў рэндару: кожны рэндар мае своія значэння і свае замкнутыя функцыі. Калебэк, створаны ў аднам рэндары, зафіксавае значэння таго рэндару, таму асінхронны элементы, такія як таймер, можа спазіраваць стары стан. Рашэння залежыць ад патрабавака: функцыйнае апдэйт, правільныя залежнасці эфектаў або викорыстанне ref, калі даскледзяўна патрэбна найсвежэйшая змінная значэння без спрычынення рэндару.
У гэтым адказе называецца прычына і пропонуецца трое рашэнняў з умовамі для кожнага, што самэ гэта і шукае высокіяй рангу спакоўнік.
Адзіночныя хукі: пакаванне поведэння
Адзіночныя хукі — гэта месца, дзе спалучаюцься паказваныя раней ідэі. Адзіночны хук — это функцыя, назва якой пачынаецца з use і якая вызывае іншыя хукі, чым можна выдзельваць поведэнне з станам і перысвайць яго. Завантажчык прафіля пачынае з адзначэння свайго стану:
function useUserProfile(userId: string) {
const [data, setData] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
Потым выконваецца апыт запрашчэння дадзеных за дапамою эфекта, прыязванага да userId, і вяртаюцься тры значэння:
useEffect(() => {
// fetch user
}, [userId]); return {
data,
loading,
error,
};
}
Экран іх выкарыстоўвае, не ведаючы, як функціонуе процес загрузкі:
function ProfileScreen({ userId }) {
const {
data,
loading,
error,
} = useUserProfile(userId);
і атрыбуе тое, што яму патрэбна, з рэзультата:
// UI
}
Заўважыце, што кожны компонент, які вызывае спецыяльны Hook, мае свой незалежны стан; Hook дзеліцься логікай, а не дадзенням.
Што на самай справе пераканвальнік пераглядае
Запытанне «Чы можаце вы напісаць спецыяльны Hook?» на самай справе стосуецца таго, чы можаце вы адгукнуцца на можлівасць викорыстання повтарнае можна выкарыстоўваць абстракцыі паведання. Хорашы кандыдаты вяршаюць так:
useAuth()
useUserProfile()
useNetworkStatus()
usePagination()
useDebounce()
useKeyboard()
useAppState()
usePermissions()
useInfiniteScroll()
У React Native прыкладам можа служыць Hook для статусу сеті. Ён падпісваецца на змяны супрацэсу падключэння, храніць флаг у стане і вяртае функцию для анпідпісвання як частку процедуры чысткі.
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = subscribeToNetworkChanges(
state => {
setIsConnected(state.isConnected);
}
);
return unsubscribe;
}, []);
return {
isConnected,
};
}
Экран для адправкі платежа потым прымае рашэнне на адной з можлівасцей, якія дае рэзультат:
function CheckoutScreen() {
const { isConnected } = useNetworkStatus();
if (!isConnected) {
return <OfflineMessage />;
}
return <Checkout />;
}
Экран не мае жадзення пра тое, як адрэсаваныя сетевыя падпіскі, і гэта незнаёмасць являе сабою весь сэнс абстракцыі.
Іншыя інструменты з набора Hook
У спэцыяльных інтэрв’ю часта прабуеюць выйсці за межы пяці стандартных Hook, таму трэба знаваць болей шырокі спектар.
useContext
Ён чытае значэнне, якое было заданае вышэй у іерархіі.
const theme = useContext(ThemeContext);
Ён падходзіць для спяльных, межвыпадковых задач: тэмы, аутентыкаціі, локалізаціі і налашчэнняў. Ён не ёсць автаматычна бібліятэкай для керавання станам; кожны корыстувач перарысваецца, калі змянюецца заданае значэнне, што мае значэнне для частай змены дадзеных.
useReducer
Калі калькі станоў змянююцца адночасова, окремыя функціі-задаўшчыкі рассеююць логіку пераходу:
setLoading(...)
setData(...)
setError(...)
Редюсер цэнтрызуе яе:
const [state, dispatch] = useReducer(reducer, initialState);
Компаненты описваюць, што адбылася, а редюсер вяршыць выбіранне наступнага стану:
dispatch({
type: "FETCH_SUCCESS",
payload: data,
});
Моделюванне переходаў як іменаванных дзеянняў дапамагае легчэй прыгадваць і теставаць складныя станы.
useLayoutEffect
Ён мае той самы формат, што і useEffect, але выконваецца сінхронна пасля таго, як React апдэйтуе структуру, і першым, чым браузер атрыбутуе змест, што неабходна тады, калі трэба змерыць лейаут і выконаць належныя корэктыя прычымо да таго, калі корыстнік не пабачыт жадных перашкод. У React Native таксама є ситуацыі, дзе час атрыбутавання лейауту мае значэнне. Це не шырэйшы варіант useEffect; адтуды, што ён блакуе процес атрыбутавання зместу, частае выкарыстоўванне пагоршвае рэакцыю прыстрою на дзеяння.
useId
Ён стварае стабільныя, унікальныя ID, галоўная мета якіх — забезпечыць належную взаімадзею, напрыклад, для павязвання етыкетки з полем вводу. Ён особліва корыстны ў вебе; у React Native, дзе няма браузерскага DOM, сфера яго выкарыстоўвання є вужэйшая.
useImperativeHandle
Ён дазваляе компаненту самам выбраць, што адгукаваны рэф будзе адкрываць, замест таго каб вылівалася асновная вузел. Компанент, які можна перыявіць, могаць адкрываць можлівасць фокусавання:
ref.current?.focus();
альбо прасування да верху:
ref.current?.scrollToTop();
Такая кантролюемая імператыўная паверхні ўжоць спрыяючая для спакульных компанентаў React Native.
useSyncExternalStore
Это падтрымваны спосаб чытаць з хранілні, якая знаходзіцца за межамі React, чым дае аднаковыя кадры нават праз адночасны рэндарынг. Гэта працоўна тэхніка, але яя выклікаецца пад час дыскусый пра архітектуру і яя служыць базай для бібліятэкаў статаусу.
useTransition
Ён пазначае дзеянні як неняхучыя. Канцэптуальна, няхучыя дзеянні падтрымліваюць адказнасць інтерфейсу:
Urgent update
↓
Keep UI responsive
тады як неняхучыя дзеянні могуць быть запланаваныя з нижэйшай прыорітэтам:
Non-urgent update
↓
Can be scheduled with lower priority
Гэта мае значэнне ў большых прыкладах практычнае, дзе дорогі процес перысвечвання інтэрфейса конкуруе з процесамі введення тэксту або нажатыяў.
useDeferredValue
Ён адкладае вычысленне значэння, ўнаследкі чаго спрытныя адносы з корыстнікам застаюцца. Класычны сцэнарый:
User typing
↓
Keep input responsive
↓
Defer expensive filtering
Введаны тэкст апошнаваецца немедленна, тады калі складны процес фільтрацыі працюе з адкладзеным копіям запиту.
Правіла хуков
Два правілы дапамагаюць хукам працаваць, тым што React ідентыфікуе кожны хук па порядку яго вызову.
Вызывайце хукі на верхнім рэвэлі
Ніколы не пакладайце хук у ўмову, цикл або ранніяе вярненне:
if (isLoggedIn) {
useEffect(() => {});
}
Уместа таго пакладзіце ўмову внутрь хука:
useEffect(() => {
if (!isLoggedIn) {
return;
}
і пераказайце значэння, ад каго ён залежыць:
// logic
}, [isLoggedIn]);
Вызывайце хукі толькі з функцый React
Хукі належаць у функцыйныя компоненты і ў спецыяльныя хукі, а не ў дзейскія функцыі-памочнікі або методы класаў.
Працэвае пра скорасць
„Этан экран медленны, што бы вы зрабілі?“ — гэта часта запытанне. Адказ „дадзіце useMemo і useCallback“ паказвае неправы падход. Кращы адказ:
Першы крок — з’ясаваць, дзе на самай працо витыкае час: як часта атрыбутуюцься компоненты, якія вычысленні ўскладненыя, дзе знаходзяцца межы компонентов, як атрыбутуюцься лісты, як распространяюцца змены стану і якая платформа викорыстоўваецца. Потым трэба аналізаваць і усунуць рэальную прычыну спаду скорасці.
Скорасць React Native — гэта не толькі атрыбутуванне
Вартасць мобільнага экрана распадаецца на калькі слоёў:
React rendering
↓
JavaScript execution
↓
Native platform work
↓
Layout
↓
Images
↓
Animations
↓
Lists
↓
Network
Таму, калі экран у React Native працюе з перыявамі, прычыной можа не быць
useState
альбо
useEffect
Гэта можа быць вялікі объём работы JavaScript, занадто частая перрадарыцеўка, вялікія спісы, обробка зображэнняў, завантажэнне анімацый, внутршнія операцыі, затрымкі ў сецыі чы абсалютнае падпісванне статаусу, якое прычыняе актывацыю занадто большага колькасці компонентаў. Знанне таго, дзе шукать сярод усіх гэтых элементаў, і ёст кантраст між высокакваліфікаваным і простым дзеянням у мобільных прыладах.
Вялікі спіс транзакцый
Уявіце дапыт пра фінансы, які перрадаруе тыячы транзакцый. У простай версіи весь процес перасчыляецца занова ў кожны момент перрадарыцеўкі:
const visibleTransactions = transactions
.filter(...)
.sort(...)
.map(...);
Зберагчы результаты фільтрацыі і сортавання ў памяці — гэта адна з разумных мера:
const visibleTransactions = useMemo(() => {
return transactions
.filter(...)
.sort(...);
}, [transactions]);
Гэта рэдкая разва ўсё адпаведна. Таксама неабходныя віртуалізацыя спіса, дешава адрасаванне элементаў, стабільныя ключы, компоненты рэядкаў з мемаізацыяй, строгія падпіска на змест, розумна загрузка адобразаў і адпаведныя взаімадзеянні. У React Native выбор FlatList або FlashList і ўсёякі яго налагоджаны зазвычай мае набліжна большое значэнне, чым адзіны useMemo. Питанне, якое трэба задаць, — гдзе знаходзіцца вузкое месца, а не який Hook дадаць.
Автаналія зместу і архітектура
Context проты бібліятэкі для зберагчыка
На пытанне «Калі вам застосаваць Context заместо Redux або Zustand?» няма ўніверсальнай адпаведзі.
Это залежыць ад стану. Контэкст добра падходзіць для дасягліваа стабільных, спільных для всіх элементаў значэнняя, такіх як тэма чы аднастаўленні. Для складных станоў прыкладнення, якія часта абнавляюцца, патрабуюць селектараў, прыкладнаго пераходу чы дзеяградных падпісаў, зазвычай кращы выбар — спецыяльная бібліятэка станоў.
Важныя паначынкі — это адпаведальнасць за данні і частота ўзнавлення.
Не все патрабуецца ў глобальным хранільніку
Пашыраная архітэктурная памылка — це скупленне всіх значэнняя ў адны хранільнік:
Everything
↓
Global store
Уместна з’ясаваць, хто несе адпаведальнасць за кожны элемент стану. Локальны стан компонента павінен карыстацца тымі деталямі UI, якія існуюць кораткае час:
isModalOpen
inputValue
selectedTab
Стан сервера — это данні, якія запрашваюцца з API; іх часта лепей карыстацца спецыяльной бібліятэкай стану сервера, якая керуе кешаваннем і паўторным запрашэнням:
user profile
products
orders
transactions
Глобальны ў стане кліента — это невялікі набор значэнняў, якія практычна дзеліцца многімі экранамі:
theme
authentication state
application preferences
У Інтэрнете часты ў стане павінен знаходзіцца ў URL, каб ён застаўся пасля перзагрузкі і могаў быць выкарыстоўваны як спялунак:
filters
search
pagination
Правіло, якое трэба памятаць: ў стане павінны знаходзіцца тыя даны, якія маюць сэнс у самай вузкай можлівай сферы.
Як гэта застосаваць на экране з інвестыціямі
Уявіце сабе экран для інвестыцый у React Native, які патрэбуе даных рынку, выбранага символа, даных графіка, даных портфеля, керування AppState, можласці прасування і дорогай трансформацыі графіка. Спрасаваная структура:
InvestmentScreen
│
├── useMarketData()
│
├── usePortfolio()
│
├── useAppState()
│
├── useRef()
│
├── useMemo()
│
└── useCallback()
│
▼
UI
Прыгаея скэч компаненту. Є два моменты, якія трэба зазначыць: фрагмент заканчваецца калькамі прозы і разлініюваннямі, якія не ўтвараюць валіднага коду, таму яго трэба чытаць як псевдакод; а таксама setSymbol з useState вже ў стабільным стане, таму викорыстанне обгорткі useCallback тут є неабяжным, і можна безпосередньа перадаць функцію-зменяльнік.
function InvestmentScreen() {
const [symbol, setSymbol] = useState("AAPL");
const scrollRef = useRef<ScrollView>(null);
const { data: marketData } = useMarketData(symbol);
const { portfolio } = usePortfolio();
const chartData = useMemo(() => {
return transformMarketData(marketData);
}, [marketData]);
const handleSymbolChange = useCallback(
(nextSymbol: string) => {
setSymbol(nextSymbol);
},
[]
);
return (
<ScrollView ref={scrollRef}>
<MarketHeader
symbol={symbol}
onSymbolChange={handleSymbolChange}
/>
<PriceChart data={chartData} />
<PortfolioSummary
portfolio={portfolio}
/>
</ScrollView>
);
}
---
Now each concept has a clear responsibility.
---
Кожны Hook тепер мае адну, зрозумелую задачу:
useState
→ selected symboluseRef
→ imperative ScrollView accessuseMemo
→ expensive chart transformationuseCallback
→ stable handler where usefulCustom Hooks
→ market and portfolio behaviouruseEffect
→ internal synchronization within those Hooks
Эфекты застаюцца ўнутрь самых кастомных Hooks, дзе яны сінхронізуюцца з падачай даных з рынку і жыцёвым циклам прыкладнення, а сам экран практычна выглядае як опис його даных.
Адказванне як у старшага інжынера
Зайсці на адны ўзлёт вышэй за пытанне
Частая памылка — адказваць толькі на тое, што было запытана. Для пытання „Шта такое useMemo?“ адказ „Яе выкарыстоўваюць для зберагчэння значэння“ є правільным, але лёгка забываемым. Болей шырокі адказ:
useMemo зберагае вырахаваную значэнне, ключоўваючы яго за залежнасцямі. Ён не ўключаны як стандартны элемент, таму што спрыяе додатковым витратам ресурсоў і паўхутарыці логіки. Ён становіць сэнс, калі дорогі вырахунак павтараецца некалькі разоў без неабходнасці, або калі важліва стабільная рэферэнцыя, ідеальна пасля таго, як аналіз працы програмы выявіў узгорнуты этап.
За калькі рэчэй вы паказалі знання API, усведамленне пра прыдатнасць, можлівасці компромісаў і здатнасць да правильнага выбору.
Структура адпаведзі на пяць крокаў
Надзеяны шаблон для будзь-яга пытання пра Hook:
1. What is it?
2. Why does it exist?
3. Real example
4. Trade-off / pitfall
5. React Native example when relevant
Застосаванне да useRef:
- Што: стойкое зменнае значэнне, яке не вызывае перзапісу элемента інтэрфейсу.
- Чаму: для дадзеных, якія павінны застацца незмяненымі пасля перзапісу, але не ўтвараюць частку інтэрфейсу.
- Прыклад: ID таймера або ідэнтыфікатор компонента.
- Компроміс: паколькі яго адчыненне не вызывае перзапісу, ён не павінен храніць стан інтэрфейсу.
Тэмы, якія павинны быць розглянуты
Па часткі useState будзьте гатовыя обгаварываць функціі апдэйта і групаванне калькаў, чаму змянэнне звычайнай зменной не паўтарае змяны на экране, што спрычынаў перы-рэндер, дзе павінен знаходзіцца адпаведны элемент стата, і як утрымацца ад викорыстоўвання стата, які на самай працэ ёсьць вырахаваным.
Па часткі useEffect могуць з’явіцца запытанні пра ролю массива залежнасцяў, застарэлыя клаудзы, паўтарныя запускі, для чаго праграмуецца функцыя чысткі, як анулюваць асінхронную работу, ситуацыі, калі эфект ёсьць неправым інструментам, і двойны запуск у режыме Strict Mode пад час разработкі.
Па часткі useRef будзьце гатовыя поясніць, чаму запіс у .current не спрычынае рэндера, у якіх ситуацыях ref є кращым выборам чым стат, як refs прыўязуюцца да DOM або натыўных відзораў, і што значыць імператыўны API.
Для useMemo неабяжна чыстая адразумованасць: чы роўна ён завжды прышвартавае працэс, як ён разлічыцца з useCallback, што значыць рэферэнцыйная аднаковасць, калі правім рашэнням будзе яго адключэнне, і як можна паказаць, што ён дапамогаў.
Для useCallback трэба знать, чаму ідэнтычнасць функцыі можа мяць значэнне, як ёна работае са React.memo, калі ў яе няма жадных практычных выгод, чы ён можа спамітняць працэс, і што павінна быць у яе лісты залежнасцяў.
Занятка з адлагоджэння
У спаканавальных кансультаціях любяць даваць компоненты з адмовамі. Ёсць прыклад з калькама проблем.
function UserList({ users }) {
const [search, setSearch] = useState("");
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.includes(search)
);
}, [users]);
useEffect(() => {
console.log("Search:", search);
}, []);
return (
<List
data={filteredUsers}
renderItem={({ item }) => (
<UserRow
user={item}
onPress={() => selectUser(item.id)}
/>
)}
/>
);
}
Відсутняя залежнасць у useMemo
Мемо сортавана толькі па пользователях:
}, [users]);
але фільтр таксама чытае
search
таму ліст ніколі не апдэйтуецца пад час вводу пользователя. У залежнасцях павінны быць якія-такія з або ўсіх:
}, [users, search]);
Правіло лінтаверкі негаразд заўважыць гэта адразу.
Эфект зашчылены на першай значэнні
У гэтага эфекта ёсць порожній масавы:
useEffect(() => {
console.log(search);
}, []);
таму ён запускаецца толькі пасля маунта і фіксуе первыя данні з search. Якщо мета — фіксавацыя кожных змян, трэба на гэта рэгулювацыя:
useEffect(() => {
console.log(search);
}, [search]);
Інлайн-колбэк, які обрабоўваецца з увагай
Обрабоўчык рэядка
onPress={() => selectUser(item.id)}
являе сабою новую функцыю ў кожны раз, калі відбываецца рендэр. Сама по сабе гэта не ёсць баг. Спачатку трэба выявіць, чы
UserRow
ёсць даскладным і чы справды відбываецца непатрэбны рендэр. Толькі на адной такой падставе можна будзе вжыць
useCallback
альбо пераструктураваць компаненты, напрыклад, пасылаючы id і стабільны обрабоўчык рэядку. Адзін з аспектаў правильнай адпаведзі — гэта стрыманасць.
Хорашыя, сильныя і професійныя адпаведзі
Этапы развіцьба легкія да запамятовання. Хорашы разработчык ведае, што такое useCallback. Кваліфікованы разработчык ведае, калі яго трэба выкарыстоўваць. Досвідчыты разработчык ведае, калі яго не трэба выкарыстоўваць, і можа поясніць наследкі такага выбору. Той самы принцып паслужыць для кожнага Hook. Цэль ніколі не ёст
Maximum Hooks
а радыяй
Correct abstraction
+
Correct data flow
+
Correct performance trade-offs
Одны набор паняў для веба і натыўных прыстроёў
Вам не трэба аднародных знанняяў пра Hook для React і React Native. У вебе прыклады зазвычай стосуюцца
DOM
browser events
Web APIs
WebSockets
browser storage
тады як у обгаворэннях пра мобільныя прыстроі йдзе пра
AppState
Keyboard
native modules
device APIs
push notifications
network state
native event subscriptions
Пры тым основны набор інструментаў у обох варыянтах React адны і тые ж:
useState
useEffect
useRef
useMemo
useCallback
useContext
useReducer
Custom Hooks
closures
dependency arrays
Таму спачатку поясніце концэпцыю React, а потым пакажыце, як яе застосаваць на данай платформе. Лічыцца, што так вы застанетесь надзеянымі як у веб-розработцы, так і ў розработцы для мобільных прыстроёў і гібрыдных рашэнняў.
Карточка швырага апісу
useState
Элемент, які выклікае перрэндер пасля абнаўлення. Тыповыя варыянты выкарыстання:
form values
selected tabs
modal visibility
loading state
local UI state
useEffect
Сінхронізацыя з зовнішнімі системамі. Тыповыя варыянты выкарыстання:
subscriptions
timers
WebSockets
AppState
native events
network operations
Утримайцеся ад яго для значэнняў, якія можна вылучыць пад час перрэндера.
useRef
Стойкое зменнае значэнне, якое не выклікае перрэндер. Тыповыя варыянты выкарыстання:
DOM/native refs
timers
previous values
imperative APIs
mutable non-UI data
useMemo
Значэнне, якое было вырахавана і збераглена ў памяці. Ўжывайце яго, калі
calculation is expensive
or
referential identity matters
спачатку трэба яго вылучыць.
useCallback
Рэферэнс функцыі, якая была збераглена ў памяці. Це карытка, калі
function identity matters
React.memo is involved
another Hook depends on function identity
Не дадзейце яго автаматычна.
useContext
Чытае спакульнае контекстовае дадзеныя, напрыклад:
theme
locale
authentication
configuration
useReducer
Моделюе складныя пераходы. Карытка, калі у вас є
many related state variables
complex transitions
state machine-like behaviour
Custom Hooks
Павтарна выкарыстоўваная стацыонарная логіка, напрыклад:
useAuth()
useNetworkStatus()
useAppState()
usePagination()
useKeyboard()
useUserProfile()
Ментальная модель заместо гласарыю
Замест таго, каб запамятаваць адзін-словяныя значэння для кожнага хука,
useState = state
useEffect = side effect
useRef = reference
useMemo = memoisation
useCallback = callback
падумайце пра тое, якія зв’язкі ўсё гэта мае з процесам выкарыстоўвання інтерфейса: стан UI праходзіць через useState і спрычыняе паўтарны выкарыстоўвання компонентаў, значэнні, якія выраховваюцца, — через useMemo, зменныя даны, якія не адналежаць да UI, зберагаюцца ў useRef, а useEffect спаявае выкарыстоўваны інтерфейс з API, запускамі падзеяў і вучонымі системамі; усё гэта абгружаецца за дапамогою спецыяльных хуків.
React Render
│
┌──────────┼──────────┐
│ │ │
UI State Derived Mutable
│ Values Data
│ │ │
useState useMemo useRef
│
▼
Re-render
│
▼
UI
│
▼
External Systems
│
▼
useEffect
│
▼
APIs / Events / Native
│
▼
Custom Hooks
Пад усім гэтым лежыць калькі фундаментальных прынцыпаў:
Rendering
+
Closures
+
Referential Equality
+
Data Flow
+
Performance
Калі гэтыя прынцыпы будуць чыста зрозумелы, адзінкавыя хуки стануць простымі для викорыстоўвання.
Пытанні, якія трэба ставіць пад час кожнага адзорвання коду
Хукі — это інструменты для керавання разнымі видамі поведзення ў реакtyўных дапытках, і важным навыкам є розумеў, чаму вы викорыстоўваете кожны з іх. Гэтыя пытанні падходзяць як пад час адзорвання коду, так і пад час спэктараў:
- Чы трэба, каб гэты стан існаваў, чы можна яго вырахаваць?
useState?Узагальненне
Hooks становяцца простымі, калі перестаёш адносіцца да іх як да окремых API і бачыш у іх стадіі аднаго прайму:
Rendering
↓
State
↓
Closures
↓
Effects
↓
References
↓
Memoisation
↓
Custom Hooks
↓
Performance
↓
Architecture
Якщо врашчаць гэты праг, перайсці з React для вебу на React Native стае значна менш складным. Калі хтось запытае, чаму вы вжылі useMemo у паводзе конкрэтнага месца, не прыходзіце да стандартнага адзначэння і раскажыце цю історію: проблему, яку вы пазілі, прычыну ёй, чаму самэўпарацыйны функцыя падходзіла, якія компрэсіі вы вазавалі і як вы ацэнілі рынак.
Перад спыткам пераканайцеся, што можете натуральна гаварыць пра:
- Фундаменталы: прычыны рэндара, што такое кадр рэндара, чым стан вядзецца ад звычайнай зменной, клозуры, рэферэнцыйнае равенства і ўзравнаванне.
useContext і useReducer, спецыялізаваныя (useLayoutEffect, useId, useImperativeHandle, useSyncExternalStore, useTransition, useDeferredValue) і спосабы стварэння власных хукіў.React.memo пораўнюе пропсы, вартасць мемуізацыі, аналіз рэндароў, дужа дугія лісты, складныя вычысленні і занадто шырокія падпісы до хранальнікаў.Мета — не запамятаванне адпаведных адказоў на всё, а достатнія знаёмства, каб могчыць разумна распрацаваць незнайомую проблему пад час спытання.