Ключы React як ідэнтычнасць компонента: зберагчыце і скасавачыце стан.
Ключы React вялікія рашынку, якія вялікія, яка інстанцыя компонента застаецца пасля рэндара. Як індэксныя ключы праліваюць стан між рядкамі, і як змена ключа навмысна скасоўвае форму.
Крыніцы: React Keys Solve a Much Stranger Problem Than Rendering Lists
Большасць разрабоў React дадаюць key да кожнага вызову .map() без дзеяўгледу, а большасць адпаведзенняў заканчываюцца фразай "React патрабуе яго, каб стежыць за тым, які элемент змяніўся". Што правда, але непачаткова. Асалодны вопыт, які React ставя пад час кожнага адрасавання, — гэта чы рэч, якую ён бачыць зараз, ёсць тая ж самая, якую ён бачыў раней, а key — гэта спосаб, якім вы адпавядаеце на гэты вопыт. Калі вы пачнёте спрыягаць keys як механізм ідэнтыфікацыі, багі з index-key стануць прыведамымі, а вы павінаваліся чыстым, декларатывным спосабам для перзначэння стану компонента за патрэбай.
Рефлексіўная версія
Гэта шаблон, які ўсё больш кодавых базаў. Кожны корыстнік у масе становіць элемент User, а ідэнтыфікатор корыстніка служыць як key:
users.map((user) => (
<User key={user.id} user={user} />
))
У гэтам фрагменте няма нічога падабальнага. Цікавая частка — гэта, што выходзіць з-пад контролю, калі ключ выбіраецца некоректна, і гэта стае видным толькі тады, калі компоненты самі зберагаюць свой стан.
Компонент зі станам дазволяе побачыць проблему
Разглянем компонент-карточку, який зберагае локальны флаг selected. Гэты флаг знаходзіцца внутрь самога компонента, а не ў прыемніку user, таму React павінен вырашыць, яка інстанцыя карточкі володзіць кожным флагам праз рэндарывання:
interface UserCardProps {
user: User;
}
const UserCard = ({ user }: UserCardProps) => {
const [selected, setSelected] = useState(false);
return (
// ...
);
}
Тепер выкажыце троха людзей: Алісу, Боба і ўжо аднаго корыстніка. Калі запісаць іх у відношэннях «індекс-элемент», масіва будзе выглядаць так:
0 → Alice
1 → Bob
2 → Maxime
Што бачыць React з індэкснымі ключамі
Звычная рада — утрымлівацца ад викорыстоўвання індэксаў масівай як ключоў. Прычына стае зрозумелай, якщо паглядзець на элементы, якія на самай працэ React фактычна отрымае, калі викорыстоўваецца індэкс:
<User key={0} user={alice} />
<User key={1} user={bob} />
<User key={2} user={maxime} />
Падзірцем, што спіс падтрымлівае выдаленне, дадзенне новых элементаў або пераранжаванне праз ператасовванне. Якшто адмахнуць Алісу, наступныя відобразэння будуць такімі:
<User key={0} user={bob} />
<User key={1} user={maxime} />
З вашай точкі зору, Аліса зникла, а іншыя двое перасунуліся вышэй. Боб перайшоў з індекса 1 на індэкс 0, а троцьці вядомае з індекса 2 на індэкс 1. З точкі зору React нічога не паведамляе яго пра гэта. Ён бачыць, што элемент з key={0} існаваў раней і яшчэ існуе, з тым самым типам компонента. Той самы тип плюс той самы ключ — гэта саме сігнал, які React выкарыстоўвае, каб адначыць, чыя інстанцыя трэба застаўіць. Таму ён застаўляе інстанцыю, якая раней відобразвала Алісу, і дае ёй пропсы Боба, застаўляе другую інстанцыю і дае ёй пропсы троцьці вядомае, а трэцюя інстанцыя знімаецца.
Наследкі: якщо была выбрана картка Алісы, тады картка Боба становіцца выбранай, таму што статус selected належаў экзантэнцыі пад ключам 0, а не Алісе. Такі ж тип прасачання вплывае на неконтрольваныя данні, фокус, пазіцыю прасування ўнутрь рядка і будзь-які іншы статус, які зв’язаны з экзантэнцыяй.
Чаму такіі багі працяюць
Некоректныя ключы ўражаюча часта зустрэчаюцца ў рэальных прыкладах практычна будзь-яго размеру. Спіс, да якога толькі дадаюцца элементы і які ніколі не зміняе порядку, паводзіцца аднойчы з індэкснымі ключамі та стабільнымі ключамі, таму нічога не выглядае не так. Баг стаўны ўсведамлены пазней, калі хтось дадае функціі выдалення, фільтрацыі, сортавання або перераспаковкі. Инструменты практычнага аналізу дапамагаюць: правіло react/no-array-index-key з eslint-plugin-react пазначае індэксныя ключы, тады рашэнне прыменяецца свядома, а не за звычкай.
Калі элемент практычна не мае стабільнага ідэнтыфікатора, яго трэба стварыць пад час стварэння даных (на прыклад, за дапамою crypto.randomUUID() пад час адгрування), а не пад час відраслівання. Ключ, створанный унутране .map(), зменшаецца ў кожнай відраслі і змушвае React перзапускать кожны рядок, што горша, чым выкарыстоўванне індэкса.
Props описваюць змест, ключы — ідэнтыфікатор
Тут падае корыстны ментальны модэль:
- props адпавядаюць на пытанне "што зараз знаходзіцца ў гэтым элементе?"
- ключ адпавядае на пытанне "гэта які элемент?"
Props могу свабодна змінювацца, пры чым сам компонент застаецца той жа экземпляр з недыскрэтаваным статусам. Якщо рядок падтрымае рэдагаванне ў самым элементе, змена імені корыстніка не павінна прыводзіць да таго, што React адмовіцца ад гэтага рядка і пачне занова; це все той жа корыстнік. Стабільны ключ, такі як user.id, точна выражае гэта.
Цялеспрабавна стварэнняе новага экземпляра
Іноды хочацца працаваць наадварот: хочацца, каб React спрацавляў з компонентам як з абэлкім новым. Ключ таксама працуе паza спискамі:
<Profile key={user.id} user={user} />
Уявіце, што Profile зберагае незашчытаны чернавік у локальным стаце:
interface ProfileProps {
user: User;
}
const Profile = ({ user }: ProfileProps) => {
const [draft, setDraft] = useState({...});
}
Якщо родны элемент перейдзе з аднаго пользователя на іншага, то перанесенне чернавіка пакойшаго пользователя ў наступны профіль стане справжнім багам, а можа і вылетам дадзэйнаў. Без ключа React бачыць той самы тип компонента на той самым месцы і заставае экземпляр, таму чернавік застаецца. З key={user.id} новы ідентыфікатор паведамляе React, што стары Profile ўжо ня існуе. Ён дэмонтуе стары экземпляр, адмахвае яго стац, выкананыя яму эфекты, і монтуюце новы, у якога праграмы ініцыялізацыі useState зноў выкананыя.
Это ідіаматычны альтэрнатыўны спосаб замест таго, калі пісаецца эфект, які стежыць за user і ручна скасоўвае кожны элемент стану. Такі падход да эфекта спачатку адрасавае стары, застарэлы стан, перш чым яго пасправіць, і лёгка забыць пра які-небудзь поль. Чырвонейшае пра тое, чаму сінхронізацыя стану через эфекты часта мае негатыўныя наследкі, можна прачытаць у безпечнейшым патэрне, які заменяе сінхронізацыю на базе useEffect.
Скасаванне формы за дапамою ключа
Той жа трык дапамагае чыста скасаваць форму:
<CreateUserForm key={draft.id} />
Ключ выступае як межа ідэнтыфікацыі. Кожны раз, калі змянюецца draft.id, напрыклад пасля успешнай адправкі, якая стварае новы проект, React стварае новы CreateUserForm без залишковых значэнняў поль, памылак верыфікацыі чы рэгістраў пра тое, што поль было зменена.
Есць два моменты, якія вартае запамятаваць. Працэўка знову прызначае ўсю паддрэву, укладаючы ў тое ж дзецяўскія элементы і все, што яны запрашалі пад час першага прызначэння, таму яе трэба вяршыць для компанентав, дзе патрабуецца цэлы перачыненне. Кроме таго, гэта выклікае цэлы процес знімання і практычнаўкі, што падходзіць для формы, але не ўсё, што трэба вяршыць пасля кожнага натискання клавішы.
Дзе насправды знаходзіцца стан
Усё гэта вынікае з таго, як React сховвае стан. Стан не прыўязаны да функцыі, якую вы напісалі; якбы ён быў прыўязаны, кожны <User /> меў бы аднаковае значэння selected. У замест на гэта React прыўязвае стан да пазіцыі ў вырасленай іерархіі. Пры кожным выраслеўванні ён аналізуе новы выхід і для кожнага элемента вяршыць, чы ён є продактам чагось з пярэдньага выраслеўвання. Тып компонента і пазіцыя ёсць часткай гэтага рашэння, а серед братоў-компонентаў ключ дапамагае яго уточніць. Статыя на дзе useState зберагае свое значэнне детальна розглядае структуру fiber, якая стоіць за гэтым.
Правілле, якое з гэтага выйшла, простае:
- той самы тип, тая самая пазіцыя, той самы ключ: React спрацоўвае яго як той самы компонент і зберагае його стан
Спісы — гэта проста першая сфера, дзе пазіцыя больш не ўсё такая надзейна. Калі элементы можаць перасувацца, з’являцца або зникаць, фраза «другі элемент» больш не означае «тое ж самае, што і раней», таму React патрэбуе, каб вы задалі ідэнтыфікатор. Стабільны ключ дазволяе яму следаваць за элементам, калі спіс абыякш змінюецца; зменены ключ — это явна пазначка пра тое, што на його месцы з’явілася новая рэч.
Галоўныя выводы
- Ключ паведамляе React, які экземпляр є элементам, а не проста який элемент зменіўся.
- Індэксныя ключы прыўязуюць стан да конкрэтнага месца, таму выдаленне або пераранжаванне элементаў прычыняе перасунуць стан на неправілівыя рядкі.
- Іспользуйце ідэнтыфікаторы, якія залишаюцца стабільнымі праз усе перыяды атрыбутаў; ніколі не стварайце ключоў падчас атрыбутаў.
- Цялеспрабавна змена ключа — гэта найчыстэйшы спосаб прызначэння настройкаў компаненту, такаму як форме або рэдагару для кожнай ентытэту.
- Вядзіце прызначэння настройкаў па ключу рэдка: гэта перзаносіць усю паддрэсіну і абмовае ўсё, што знаходзіцца ў яй.