Дзе useState зберагае свое значэнне: элементы React протыва Fibers
Чаму функцыйная складовая React забывае все між вызваннямі, чаму элементы не можу прытрымліваць стан, і як поле memoizedState файбра падтрымлівае значэнні useState жывымі.
Компонент-функцыя — гэта проста функцыя, а функцыі забываюць свои локальныя зменныкі як толькі вяртаюцца. Аднак useState вяртае апошню актуальную значэнне праз кожна перрадзіцыю, нібы функцыя яго пам’ятала. Што ў гэтым матерыяле — адказ на адну конкрэтную запитанне: дзе фізычна знаходзится гэта значэнне между перрадзіцыямі? У канцы вы зможаце розразліцаваць два об’екты, якія React стварае для кожнага компонента — элемент і файбер, а таксама поясніць, калі саме яны несу стан і чаму.
Загадка: функцыя без памяці
Пачнім з самага вядомага компонента — лічыльніка:
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Адзін раз клікнуце — паказаецца 1, зноў клікнуце — паказаецца 2. Няма нічога неспадзейнага, пакуль не паглядзеце на гэта як на звычны JavaScript. Counter — гэта функцыя. Кожны раз, калі вызываецца функцыя, яе локальныя зменныкі ствараюцца з нічога, а калі яна вяртаецца, яны зникаюць. Наступны вызыв пачынаецца зноў з нуля.
Па гэй логіцы, калі React другі раз вызывае Counter(), рэчы useState(0) павінны зноў даць 0. Але яны даюць 1. Чыго-небудзь за межамі функцыі павінна баціцца цячка з адного вызыву да іншага. Це не можа быть сама тэла функцыі, бо так проста не паводзіцца функцыям. Тады што ж?
Адклэянне элемента
Є явны кандыдат, які адзначаецца як некоректны, і яго адключэнне робіць справжняй адказ яшчэ зрозумелым.
Лінія <button onClick={...}>{count}</button> ўособліва JSX. Браузеры ніколі не выкананяюць JSX; кампайляры, такія як Babel чыстаўказ TypeScript, перапісваюць яе на звычны вызыв функцыі пры падготовке коду да выканання. Канцэптуальна рэзультат такі:
React.createElement("button", { onClick: fn }, count)
Заводзячы аўтаматычны рантайм для JSX у React 17, компільваны вызыв на самае працо ёсць jsx() з пакета react/jsx-runtime, а не React.createElement, але оба выпалююць тое ж самае. Як бы не называлася функцыя, яна вяртае звычны об’ект:
{
type: "button",
props: { onClick: fn, children: 1 },
}
У React гэта называецца элементам. Це простая апіс, а не жывы элемент: маленькі запис, який пазначае, што тут трэба прадстаўіць кнопку з такым обработчыкам кліка, якай паказвае гэты номер. Настоямы элементы маюць ў сабе яшчэ калькі поль, такіх як key, ref і внутраній маркер $typeof, але ніякі з іх не зберагае історыю.
Тепер важлівая заувага. Кожны вызов Counter стварае абсалютна новы элемент. Калі нажмаць на кнопку, запускаецца Counter(), вяртаецца новы об’ект, а пакойшні стае непатрэбным. Якбы стан знаходзіўся на элементе, не было б можлівасці перайсці з адраджэння двух элементаў на адраджэнне адного, таму што об’ект для адраджэння адного больш не існуе, калі пачынаецца адраджэння двух.
Такая можлівасць выкарыстоўвання нарадзілася навмысна. Элементы ўжоць цэнавыя саме таму, што яны перзначаюцца з нуля ў кожны раз, калі відбываецца рендарынг, і ўжо не патрабуецца синхронізаваць іх з чым-небудзь. Гэта таксама значыць, што яны не можу знаходзіцца там, дзе зберагаецца count.
Об’ект, які застаецца: файбер
Паралельна з элементамі, React зберагае ўсё тое ж адны, але дыягональнейшы об’ект для кожнага компонента, які быў падключаны. Ён не перзначаецца ў кожны раз, калі відбываецца рендарынг. Ён ствараецца, калі компонент упершыню падключаецца, а потым застаецца і апдэйтуецца пры тым, калі компонент застаецца на экране. Гэты ёсць файбер.
Волокна частаўка апісваюцца як шта-то таёмнае, але на самай простай лэвелі волакно ёсць звычайны об’ект JavaScript. За ягою няма жадных спецыяльных структураў пад час выканання. Якщо зупініцеся ў дэбагеры ў часе рэкансіляцыі у React і перагледзіце такое волакно, вы пабачыце звычайны об’ект з наборам атрыбутаў, такія ж, якія можна было б стварыць самостайна за дапамогою пары фігурных складакоў. Їх таксама можна паглядзець у браузеры: React прыкладвае внутрашнія ключы да вузлаў DOM, якіе вяду да ўсіх волакен, і самэ гэта дазволяе React DevTools іх знаходзіць.
Уместа таго, каб адразу перыявіць усе поля, карысткавае будуць стварэнне волакна для Counter по аднаму атрыбуту. Адразу пасля маунта мінімальная версія выглядае так:
{
type: Counter,
}
type: чыяе це веданне
type — гэта самая простая польніца. Яна вказывае, які компонент стежыць гэты файбер. type: Counter означае, што об’ект створаны для керавання экземплярам Counter, які зберагае ссылку на саму функцыю.
Элементы-хосты таксама маюць файберы. Кнопка, якую відраслівае Counter, мае свой файбер, а ў яго польніцы type знаходзіцца рошток "button", а не функцыя. Такім чынам файбер <Counter /> мае значэнне { type: Counter }, а файбер <button> — { type: "button" }: адна і тая ж польніца, адна і тая ж мэта, пры чым ён можа вказываць або на ваш компонент, або на вбудованы тэг.
type таксама плейвае ролю ў прыбліжэнні стана. Калі React паруе новы элемент з існуючым файберам у той самы пазіцыі, адпаведны type дазволяе яму перадаць употрэбу файберу і яго стану, тады как разны type вымагае паспрабаваць пазбавіцца старог файбера і створыць новы. Самэй таго змена між двума разнымі компонентамі ў той самы месцы скасоўвае яхні стан.
Аднак сам по сабе type нічога не гаворыць пра тое, як падтрымліваецца стан. Для гэтага патрэбна наступная характерыстыка.
memoizedState: дзе насправды знаходзіцца лік
useState патрэбна адзінка за межамі функцыі, каб зберагаць текущую значэнне, адколі локальныя зменныя функцыі скасоўваюцца праз кожны вызов. эй тая адзінка — характерыстыка на файбере пад назвой memoizedState. „Memoized“ проста значыць запамятаванае: зберагваецца з ранейшага часу, а не перысчыляецца.
Дадзенне яго ў эскіз дае:
{
type: Counter,
memoizedState: { count: 0 },
}
Это спрасцаваны варыянт. У рэальной реалізацыі memoizedState у файбере функцыйнасага компонента паказуе на першы элемент сярод спраўжнага ліста об’ектаў hook, по аднаму на кожны вызов hook, а цячка 0 знаходзіцца ў сабеўскім полі memoizedState гэтага hook, а не ў об’екте { count: 0 }. React не ведае, што вашая зменная называецца count; ён ведае толькі „значэнне першаго hook“. Для разумення таго, дзе знаходзіцца стан, спрасцаваная версія ўсё ж дастатня.
Цякніце на клік. React зноў вызывае Counter(). Калі выконанне дасягае useState(0), хук не вяртае значэння 0 у вашам кодзе. Ён шукае зберажаны стан фібры, знаходзіць тэкучае значэння (якое становіць 1 пасля обробкі кліку) і вяртае яго. Аргумент для useState — цэлая толькі пачаткова значэння: яго викорыстоўваюць пад час першага атрыбутавання элемента, а пасля — ігнаруецца. З таго моменту фібра стае аднаковым джэрелам правды, а не літэралам у тэле функцыі.
Гэта ўсё, што трэба знать для развязкі пачатковай задачы. Колькасць не знаходзіцца ні ў функцыі, якая забывае все межы вызывамі, ні ў элементе, які адкидаецца пасля кожнага атрыбутавання. Яна знаходзіцца ў фібре — окремым об’екте, які React зберагае і апдэйтуе праз кожнае атрыбутавання Counter.
Два практычныя наследкі
Эта модэль адказвае на кальколька праблем, якія інакш часам здаюцца самавольнымі:
- Змена аргумента
useStateпасля запуску компонента не вплывае на збераганыя значэння, таму што React чытае яго толькі першы раз. Якщо вам трэба скасаваць стан на адваротнае з пропсаў, зменіцеkeyкомпонента, ўжо каб React створыў новую волокна. - Паколькі волокна шукаюцца па пазыцыі ў дрэве, стан належыць таму, дзе рэндаруецца компонент, а не да вялічыны функцыі. Два элементы
<Counter />пасярод сабе атрымуюць два волокна і два незалежныя лічыльнікі.
Што ўтримвае волокна паza гэтымі двумя полямі
type і memoizedState дастыць достатак, каб распрацаваць проблему стану, але рэальны файбер несе ў сабе ўжо больш. Ён зберагае кансэкт да вузла DOM, які ён стварыў, паказчыкі на яго абяцелю, першага дзецячага вузла і наступнага братоўскога вузла, каб React мог усё гэта праследаваць, а таксама паказвчыкі на пярэдніяя пропсы, каб іх можна было парабеліць з новымі. Гэтыя поля вядуць, як React вычыляе, што змянілася, і як ён праследвае цэлы дрэво компонентаў, што ёсць аднае пытанне ад таго, дзе знаходзіцца адзінаковая значэнне.
Тут можна чытка адзначыць межу між гэтымі двума об’ектамі, адколі іх злучэнне є прычыной вельмі большых плутанняў ў каштовацьве:
Element Fiber
-------- -----
Created by React.createElement Created internally by React
New object every render Same object, updated in place
Discarded right after Persists for the component's
React reads it entire mounted lifetime
Holds no history Holds memoizedState, the real
remembered value across renders
Describes what should exist Is the thing that actually exists,
with real memory attached to it
Кораткі спосабы запам’ятаваць гэта: элемент — это запит, які вы направляеце да React, а файбер — это сабеўскі запис React пра тое, што зараз існуе. Кожная перрадарацыя стварае новы набор дешавых, без памяці элементоў і передае іх у дрэво файбероў, якое вялічка з паказваннем ранейшай перрадарацыі і храніць усё, што павінна застацца. Адна частка описвае, другая — запам’ятавае.
Адна незаканчаная дзяўжына: файберы прыходзяць па парах
Тэза, што файбер "адчыняецца на месцы", ёсць корыстным першым апрыксамом, але гэта не вся правда. Насправды React храніць два версіі кожнага файбера: адну, якая падходзіць да таго, што зараз апісваецца на экране, і другую, якая готуецца да наступнай адчынення, і перастаўляецца межу ямі. Самэ гэта распаложэнне дазволяе React працаваць над адчыненням без парадзейвання видавальнай часткі UI, і гэта заслуговае на аднальныя поясненні.
Ключовыя выводы
- Локальныя зменныкі функцыйнае-компонента перазьлічваюцца ў кожны раз, калі яна вызываецца, таму сама функцыя не можа зберагаць стан.
- JSX компілюецца у вызывы, які ствараюць элементы: простыя, временныя апісанні, якія перазьлічваюцца ў кожны раз пад час атрыбутавання.
- Fibers — это простыя об’екты, якія застаюцца актуальнымі пакуль компонент монтаваны;
typeвказывае, які компонент стежыць fiber. useStateчытае і запісвае значэнні черезmemoizedStatefiber, які на практыцы вядомае да спісу об’ектаў hook, якія падходзяць за порядкам вызываў.- Пачатковае значэнне, якое пасылаецца да
useState, мае значэнне толькі пад час монтавання; пасля цього джэрелам правды є сам fiber.
Супаўзвязаная літэратура
- Перазьважэнне пра стан React: дзе насправды павінны знаходзіцца вашы даны — У этай стацыі пояснюецца, як зменшыць колькасць багоў у React, пераносячы стан у URL, DOM чы ў вырахаваныя значэння замест таго, каб часта викорыстоўваць useState.
- Як браузер атрымляе зображэнне і дзе паслужыць React — Дазвольце вам дакладна разумець, як працуюць критычны шлях атрымання зображэння, процес супраставлення дадзеных, технологія Fiber і планавальнік задач, якія разам ператвараюць апдэйты React у пікселі на экране.
- Чаму існуюць правілы хуков: Fiber, спісы хуков і дыспэтчэры — Экскурсія ў внутраненасці React: вузлы Fiber, двойная буферазацыя, каналы передачы дадзеных, спісы хуков і тое, як кожная з вбудоўаных груп хуков сховвае свой стан і плануе виконанне задач.