Практические замечания: компоненты 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: не храните ключи поставщика в репозитории, установите лимит токенов на одну сессию и сохраняйте отчеты рядом с фикстчерами для оценки, чтобы последующие замены моделей оставались сопоставимыми.