Галоўная / Артыкулы / Практычныя прытамулі: Компаненты React павінны быть чыстымі — секрэт StrictMode

Практычныя прытамулі: Компаненты React павінны быть чыстымі — секрэт StrictMode

Практычныя прыказкі: Компаненты React павінны быть чыстымі — таўзар справы за StrictMode: кантракты, перакрыццяі і слоты для коду для команд, якія викорыстоўваюць гэты патэрн.

1954 слоў

Існавайце гэта як перапрацоўку ідэй з артыкула «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аннімі.