Практычныя прытамулі: Компаненты React павінны быть чыстымі — секрэт StrictMode
Практычныя прыказкі: Компаненты React павінны быть чыстымі — таўзар справы за StrictMode: кантракты, перакрыццяі і слоты для коду для команд, якія викорыстоўваюць гэты патэрн.
Існавайце гэта як перапрацоўку ідэй з артыкула «React Components Must Be Pure — the Secret Behind StrictMode and the Compiler» для аператараў: чыстыя этапы, арганізаваныя блакі коду і прыметкі для вяснавання, якія застаюцца пасля перадачы заданняў. Этап Апглэву лепш працюе, калі яго спрыяваць як мерыемую плошчу. Запісаўце адна ідеальная версія, адзін прыклад неудачы і прыметкі па вяснаванню перад тым, як расшырваць масштаб задання. Спрыяйце гэтым этапам як кантракту межаў уваходу і перакананых выходных рэзультатаў. Даце назвы артыкулам, задаце критэрыя успеху і адмовіцеся ад беззвучнага частковага завершэння.
Што такое чыстая функцыя?
Для стадіі «Што такое чыстая функцыя» неабходна прадзеўжчае апісанне вхідных дадзеных, абяцелі ступеня і крэтарыяў выходу пры перадзеіснаванні коду. Аператары должны магчымае запускать ступень з вядомай точкі перапытку без неабясненняя схованага стану. Запісваюць час выконання і кост токенаў або запыткаў праза функцыйнае рэзультаты. Відкрытыя данні пра косцы запобегаюць неспадзяваным рахункам, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя сераўысы. Стан трэба размешчаць разам з компонентам, які керуе мутацыяй. Размешчэнне всего ў глобальным хранільніку ускладняе выяўленне багоў, зв’язаных з часам выконання.
// ✅ pure function — same input, same output, and it touches nothing outside
function double(x: number): number {
return x * 2;
}
double(3); // 6
double(3); // 6 — six no matter how many times you call it
// 🔴 impure function — it changes the outer variable `total` (a side effect)
let total = 0;
function addToTotal(x: number): number {
total += x; // side effect!
return total; // the result changes on every call
}
addToTotal(3); // 3
addToTotal(3); // 6 — same input, different result
Компонент таксама ёсць чыстая функцыя
Для компаненту A, яка ўособлівае певны ўражак, неабходна перад змянай коду адзначыць вхідныя даны, адпаведальнага за выкананне цього ўражка і крэтырыя завершэння. Аператары должны магчымае перадзеяць выкананне ўражка з вядомага пункта контролю, не прымушаныя здогадвацца пра захаваны стан. Конфігурацыю трэба залічыць паза кодам прыкладнення. Файлы сераўнавання, хранільнікі секрэтных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць, не чытаяўшы весь код. Стан трэба захавваць разам з компанентай, якая керуе мутацыямі. Размешчэнне всего ў глобальным хранільніку ускладняе выяўленне багоў, зв’язаных з часам выканання.
type Props = { name: string };
// ✅ pure — same name, always the same result
function Greeting({ name }: Props) {
return <h1>Hello, {name}!</h1>;
}
Параграф — Не чапаць за зовнішні элементы пад час атрыбутавання
Для стадіі Break the Rule Don неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Неабяжна задокументаваць як шлях успеху, так і шлях вярнення да нормы. Перапрыбуткі, людзкія перакрыцця і обробка некоректных паведамленняў є часткай продукту, а не чымсь, што дадаецца пазней. Стан неабяжна размешчаць разам з компонентам, які керуе мутацыямі. Перанесенне всього у глобальны хранільнік ускладняе выяўленне проблем з часама адпаведнаго выканання. Для стадіі Break the Rule Don неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Спрыяць гэтай стадіі як кантракту межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даць назвы артыфактам, узначыць перакананні на успех і не прабаваць прыймать часткова завершанне без падтверджэння.
let count = 0; // a variable outside the component
// 🔴 not pure — changes an external variable while rendering
function Counter() {
count = count + 1; // side effect!
return <p>{count}</p>;
}
Выпануце самі — чаму паказуецца “2”?
Калі працюяце на этапе “Выпануце самі — чаму?”, спачатку запісайце умовы: неабяжлівыя данні, сигнал успеху і тое, што выканаецца у разы ўзельнага неякшага рэзультата. Такі чарт дапамагае заліцвачваць будучыя змены ў кодзе. Запісвайце час выканання і кост токена або запыту праза функцыйнальнымі рэзультатамі. Відразлівае паказанне коста з’являецца неспадзейчыкавых сум у момент, калі праця пераходзіць з дэмаверсіі ў спакульнаныя сераўы. Спрыятлівайце эфектам як сінхронізацыі з зовнішнім светам, а не як замене вырахаваных значэнняў пад час атрыбутацыі.
import { useState } from 'react';
let count = 0;
function Counter() {
const [, setTick] = useState(0); // a device to trigger re-renders
count = count + 1; // 🔴 changes an external variable on every render
return (
<div>
<p>{count}</p>
<button onClick={() => setTick((n) => n + 1)}>Re-render</button>
</div>
);
}
// ✅ pure — computed from input (props) alone
function Counter({ count }: { count: number }) {
return <p>{count}</p>;
}
Але вы можете зменіць “Рэзультаты, якія ствараюцца пад час атрыбутацыі”
Калі працюеце над стадзіяй «Але вы можетэ зменіць», спачатку запісайце угоду: неабяжлівыя даны, сігнал успеху і тое, што выканаецца пад частым неудачам. Такі список пераконтроўвае чыстасць пазнейшых змян у кодзе. Зберагаюце настройкі праза код аплікацыі. Файлы сераўнавання, хранальнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаюць адбавіць пераказ без чытання всей структуры. Спрэчвайце эфекты як сінхронізацыю з зовнішнім светам, а не як замену вырахаваных значэнняў пад час атрыбутавання.
function ProductList({ products }: { products: Product[] }) {
// ✅ this array was just created in this render — handle it however you like
const sorted = [...products].sort((a, b) => a.price - b.price);
return (
<ul>
{sorted.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
Загадка 1 рашаная — чаму StrictMode выканае задачы два разы
Калі працюеце над стадіяй «Mystery 1 Solved Why», спачатку запісайце контракт: неабяжлівыя даны, сигнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список дапамагае заліцьваты змяны ў кодзе чыстымі. Документавайце як шлях успеху, так і шлях вяснавання проблемы. Перапрыбуткі, людзкія контралі і обработка некоректных паведамленняў ёсць часткай продукту, а не чыставанням пазнейша. Спрыяйце на эфекты як на сінхронізацыю з зовнішнім светам, а не як на замену значэннях, вырашаных пад час адрасавання. Калі працюеце над стадіяй «Mystery 1 Solved Why», спачатку запісайце контракт: неабяжлівыя даны, сигнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список дапамагае заліцьваты змяны ў кодзе чыстымі. Спрыяйце гэтай стадіі як на контракт межа данымі і перакананымі выходнымі даннымі. Даўце назвы элементам, задайце критэрыя успеху і не падтрымвайце тыхі частковыя завершэння.
// main.tsx — in development, the components inside this render twice
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
Тады куда іду пабочныя эфекты?
Работа з падстэйджамі ўсё краща эфективна, калі яе розглядаць як вимерную паверхню. Запісайте адна ідеальная версія, адзін прыклад неудачы і прыметку па поверненню да попярэдней версіі пры розширэнні масштаба. Запісвайце часы виконання і косты токеноў або запытаў разам з функцыйнальнымі рэзультатамі. Візуабельнае прадставлення костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаныя сераўы. Зберагайце вартасць обробкі низкой, а дорогія процесы вырахоўвання пераводзіце пасля мерыявання, толькі з викорыстоўваннем мемаізацыі. Нерэшаная мемаізацыя можа сховаць багі, вызваные застарэлымі даннымі.
// 🔴 side effect during rendering — fires on every render
function ProductPage({ id }: { id: number }) {
logView(id); // a side effect in the render body — not allowed
return <h1>Product {id}</h1>;
}
// ✅ in an event handler — runs only at the moment the user clicks
function BuyButton({ id }: { id: number }) {
return <button onClick={() => logPurchase(id)}>Buy</button>;
}
Загадка 2 рашаная — чаму компілятор React паважае чыстасцю
Загадка 2: чаму стэйдж наікращае працюе, калі яго спрыяваць як вимерную паверхню. Зафіксавайце адны «золаты» транскрыпт, адзін прыклад неудачы і запіс пра вярнэнне да попераднего стану, перш чым расширваць масштабы. Зберагаюце настройкі пазначкай за межамі коду прыемліка. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куда аператары можуць адбавляць контроль, не чытаючы весь граф. Робіце процес рэндараўцы дашчавым, а складныя вырахункі выпрацоўвайце за дапамогою мемаізацыі толькі пасля вимеравання. Неразумная мемаізацыя можа закрыць багі, вызваны застарэлымі дадзеннямі.
// 🔴 not pure → the compiler skips optimization (ESLint points at this component)
let renderCount = 0;
function RenderCounter() {
renderCount++; // mutating an external variable during render — a violation
return <p>{renderCount}</p>;
}
// ✅ pure → the compiler memoizes automatically (reuse the previous result when inputs match)
function Label({ text }: { text: string }) {
return <p>{text}</p>;
}
function App() {
return (
<>
<RenderCounter />
<Label text="Pure Component" />
</>
);
}
export default App;
Працаваць з UI як з дрэвам
Інтэрфейс Seeing UI як сцэна працюе найкраща, калі ёй ставится ролю вимернай паверхні. Зберагчыце адны ідеальны прыклад роботы, адны прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Дакументавайце як шлях успеху, так і шлях вярнэння да нормальнага стану. Перапрыбуткі, людзкі контроль і обработка некоректных паведамленняў є часткай продукту, а не элементамі пазнейшага доўрабатвання. Робіце процес генеравання контэнта дышачым, а складныя вычыслення застаўляйце на пазнейшы момент, толькі пасля вимеры. Нечасавае выкарыстоўвання мемаізацыі можа схаваць багі, зв’язаныя з застарэлымі даннымі. Інтэрфейс Seeing UI як сцэна працюе найкраща, калі ёй ставится ролю вимернай паверхні. Зберагчыце адны ідеальны прыклад роботы, адны прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Ставіцеся да гэтай сцэны як да кантракту межа вхідных дадзеных і перакананых выходных рэзультатаў. Даўайце назвы элементам, задаўце критэрыя успеху і не прабывайце адмаўляць частковае завершэння без паведамлення.
function App() {
return <ProductList products={products} />;
}
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</ul>
);
}
App
└─ ProductList
├─ ProductCard
├─ ProductCard
└─ ProductCard
Заключэнне
У фазе завершэння неабяжна практычна адзінакоўка вхідных дадзенняў, абавесцяванні власніка крока і крэатарыяў выходу пры перадзмене коду. Аператары павінны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць трэба час выконання і кост токенаў аб запытаў па боку функцыйнаых рэзультатаў. Відразлівае паказанне костаў з’являецца перашкодай для неспакою, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя среды. Стан неабяжна размешчаць разам з компонентам, які керуе мутацыямі. Размешчэнне всего ў глобальным хранільніку ускладняе выяўленне багоў, зв’язаных з часам выконання.
Справакі
У стадії «Апавяранні» паказваць неабходныя даны, адпавядаючага за крок адпаведнага элемента і крэтыяры выходу пры перадзеіснаванні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Конфігурацыю трэба знаходзіць за межамі коду прыемленае. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцый належыць у аднам месца, якое аператары можаць пераглядаць, не чытаяўшы весь лянцуг задач.
Чэк-ліст для эксплуатацыі
Пры працэ выканання чэк-ліста для эксплуатацыі спачатку запісваець кантракт: неабходныя даны, сігнал успеху і тое, што выканаецца пры частковай нявыполненасці. Такі чэк-ліст дапамагае заставіць пасляўсія змены коду быць чыстымі.
Лепшыя маленькія, тэставаныя елементы, чым велікія скрыпты. Калі якісь крок не выйшае, адказчыкам трэба быць чыткімі, вказваючы на адзін конкрэтны элемент, а не на заплутаны ланцюг задач.
Эфекты трэба расследжваць як сінхронізацыю з зовнішнім светам, а не як замену значэнням, якія гэтакія вылучаюцца пад процесам рендарування.
Зафіксавайце версіі залежнасцяў і запісуйце хэш-значэнне зображэння, якое было выкарыстоўванае пад час дэманстрацыі. Возможнасць павторэнергіі важлівей, чым традыцыйныя знання.
Спрыятлівайце гэтаму этапу як даговору між вхіднымі данымі і перакананымі выходнымі рэзультатамі. Дайце назвы кожным элементам, задаць критэрыя успеху і не падтрымвайце частковае завершэння без паведамлення.
Эфекты трэба расследжваць як сінхронізацыю з зовнішнім светам, а не як замену значэнням, якія гэтакія вылучаюцца пад процесам рендарування.
Перш чым запускать стак, заморозьце версіі, зафіксавце «золаты» транскрыпты для критичных ланцаў і паказваце крокі для вярнення да пачатковага стану. У спільных середавішчах неабходны ліміты швайнаў, пераказы наявнасці праваў і чысткі власнік для змены секрэтных даных. Лепш выбіраць простую надзею на надзейнасць, чым хітрыя експерыментальныя дэманстрацыі.
Прыметка для d707b555add8: не кладзіце ключы прадаўцаў у репазітарый, задаце верхнюю межу токенав на сесію і зберагачыце транскрыпты празаўседліва з фікстурамі eval, каб пазнейшыя замены модэляў заставаліся порównаннімі.