Comments 19
Мне кажется или 1 и 2 находятся в противоречии друг к другу. Если пофиксить 1 паттерн, то получается исходник ко второму паттерну, если второй пофиксить как предлагается, то получается 1 паттерн.
1-й пункт:
«Не храни в стейте то, что можно вычислить на лету. Лишние рендеры никому не нужны.»
2-й пункт:
«Если данные должны сохраняться между рендерами (например, состояние формы), обычные переменные не подойдут — только useState
или useRef
.»
Где противоречие?
Первый случай — данные уже есть (
props
), просто комбинируем их.Второй случай — данные динамические, и компонент должен их «помнить».
Получается:
Производные значения → вычисляй прямо в компоненте.
Настоящее состояние →
useState
.
Всё, вроде бы, логично, просто контексты разные))
Производные значения → вычисляй прямо в компоненте.
Просто мне непонятно, как можно вычислить someValue без каких то внешних данных. Либо это константа, то ее вынести вовне, либо это вычисляемое из пропсов, что похоже на первый паттерн. Либо вот в стейте хранить.
А, ну тут всё просто на самом деле. Давай на примерах, чтобы стало понятно, где что применять:
1. Константа (вообще не зависит ни от чего)
jsx
const MAX_COUNT = 10; // ← вот это выносим ВНЕ компонента
function Component() {
return <div>Лимит: {MAX_COUNT}</div>; // всегда 10
}
2. Вычисляемое из пропсов (как в 1-м паттерне)
jsx
function User({firstName, lastName}) {
const fullName = `${firstName} ${lastName}`; // ← вычисляем на лету
return <div>{fullName}</div>;
}
3. Состояние (когда нужно "помнить" между рендерами)
jsx
function Counter() {
const [count, setCount] = useState(0); // ← вот это стейт
return (
<button onClick={() => setCount(c => c + 1)}>
Кликнули {count} раз
</button>
);
}
Где твой someValue
?
Если someValue
:
Ниоткуда не берётся (константа) → вариант 1
Считается из пропсов → вариант 2
Меняется внутри компонента и должен сохраняться → вариант 3
Получается: если значение можно получить прямо сейчас из того, что уже есть (пропсы/другие константы) — не пихай его в стейт. Стейт нужен только для того, что компонент должен "помнить" сам.
Надеюсь подробно описал что да как)
Лучше бы я читал эту статью пораньше, когда только начинал. Но все равно освежил память, раскидал в голове все по полкам, за это спасибо)
Полезная статья, особенно для тех, кто только начинает с React. Многие ошибки кажутся мелочами, но потом именно они превращают код в кашу.
Спасибо за статью, хорошо расписано, и расставляет всё по полочкам.
По поводу 17 добавлю, что проблем с мутабельностью начального состояния можно избежать, если его заморозить:
const initialFormState = Object.freeze({
text: '',
error: '',
touched: false,
})
Там, где нужен новый объект на основе этих свойств, можно использовать spread: `{ ...initialFormState, text: 'test text' }`
С другой стороны этот способ несколько противоречит функциональной парадигме Реакта, и также не лишён изъянов. Так что вопрос больше вкусовщины.
Спасибо за хорошее дополнение)
А что, const не достаточно?
const
защищает только переменную от переназначения, но не делает объект неизменяемым: его свойства всё ещё можно изменить.Object.freeze
предотвращает изменение самого объекта и его свойств, что важнее для иммутабельности состояния в React. Поэтому просто const
— недостаточно, если нужна настоящая неизменяемость объекта)
Спасибо за статью, хоть и работаю с реактом уже давно, но всегда полезно проверить сои навыки и освежить память ))
Простите меня конечно, я не хочу Вас обидеть, но такое ощущение, что Джуну дали статью написать. Ваша статья сильно продвигает useCallback, useMemo и других инструментов мемоизации как "обязательные". Но в реальной разработке это приводит к усложнению. В доке реакта даже: мемоизацию стоит применять только для ДОРОГИХ ВЫЧИСЛЕНИЙ или когда проблема производительности подтверждена измерениями (профилированием). Преждевременная оптимизация — это антипаттерн сам по себе, так как добавляет бойлерплейт. Вы не объясняете, когда правила можно нарушать. React — гибкий фреймворк, и многие "антипаттерны" зависят от контекста (размер приложения, производительность, SSR vs CSR). Нет упоминания о React 18+ особенностях (concurrent mode, где эффекты могут запускаться дважды) или инструментах вроде React DevTools для измерения проблем.
С Пунктом 1, 13 и 14 согласен.
Пункт 2. Слишком упрощенный пример. Не ясно мемоизирвоан ли дочерний. Если нет, то проблема вообще надуманная. Новая ссылка на обьеки не вызовет лишних рендеров.
Пункт 3. Вы игнорируете библиотеки. Например, styled-components для компонентов (styled.div) не пересоздаёт классы, и проблема менее актуальна. Статья не уточняет, что пересоздание стилей в небольших компонентах редко влияет на производительность. Без профилирования (React DevTools, Chrome Profiler) рекомендация выглядит как "всегда делай так", что добавляет ненужный boilerplate. А и если стили зависят от пропсов, вынос наружу невозможен без useMemo.
Пункт 4,5 и 6. А что если функция не будет являться зависимостью для useEffect или пропсом мемоизированного компонента? Правильно он бесполезеен и только добавляет кода, опять же в доке: Вам не нужно заключать каждую функцию в useCallback. Это верно в сложных случаях, но ваша статья представляет как универсальное правило, игнорируя, что в простых компонентах это избыточно. Ваши примеры же содержат антипаттерны, слишком минималистичны и не показывают реальные сценарии. Нет альтернативы, глубокого анализа.
Пункт 7. Верно, но не упоминается, что в Strict Mode в React 18+ эффект запустится дважды (mount-unmount-remount) — это нормально, но нужно проектировать эффекты idempotent.
Пункт 8. Верно по правилам хуков, но статья игнорирует ESLint, который уже ловит это, и не объясняет, как справляться с ложными зависимостями (например, функциями внутри эффекта).
Пункт 9. В целом нормальный. Но Пример абстрактен и не показывает реальных последствий. Например, дублирование инициализации может произойти при многократном монтировании компонента в строгом режиме где useEffect вызывается дважды.
Пункт 10. В целом тоже нормальный, но вы подразумеваете, что useCallback всегда создаёт заметный оверхед, но в небольших компонентах он минимален. Это может запутать новичков, заставляя избегать useCallback даже в нужных случаях.
Далее 11 пункт. Вы не поясянете нюанс, что в SSR и динамических импортах это может сломаться. Ваши советы могут привести к memoization hell , не зная нюансов.
Пункт 12. Верно (из-за remount), но не упоминается, когда это полезно (например, для scoped компонентов в Storybook или HOC).
Пункт 15. Идея ясна. Но вы не показываете как if (!shouldRender) return null
может сломать код (к примеру обьявление хуков после return).
Пункт 16. Цитирую: "используйте useReducer вместо множества useState". На практике он добавляет бойлерплейта и в доке рекомендуют его только для deeply nested state или когда next state зависит от previous. Для простых форм несколько стейтов проще и быстрее.
Пункт 17. Это верно для lazy-init и избежания мутаций, но вы преувеличиваете проблему: обычный объект работает нормально, если не мутировать его вручную. Пример подразумевает, что объект всегда мутируется, но это не так.
Пункт 18. Идея верная, но показать более наглядный пример. useRef бывает полезен например для хранения мутабельных объектов или доступа к последнему значению состояния в асинхронных операциях. А при вызове дважды эффекта реф должен быть идемпотентен.
Пункт 19. В принципе соглашусь с вами, но лучше вычислять derived значения напрямую в рендере, без состояния.
Пункт 20. Критика без предложений нормальной альтернативы.
Благодарю за критику!
Но стоит учитывать, что это не учебник, а статья в которой предлагается решение той или иной проблемы (в зависимости от контекста, об этом ниже). Конечно, стоит вставить приписку, что стоит читать доку перед тем как читать это все
И конечно же, нужно понимать, что в целом большинство фреймворков гибкие и стоит смотреть на контекст, прежде чем использовать то или иное решение
А касательно React 18+ и React DevTools. Тут в целом не ведется речь о том, какие особенности и как измерять производительность/нахождение проблем
Также в рамках критики каждого пункта можно заметить, что вы пытаетесь очень глубоко проникнуть в работу фреймворка React. Бесспорно, это правильно, но зачастую, джун или мидл (а статья в целом для данной аудитории) в целом мало вникает во внутренние механизмы, такова суровая реальность. Они порой используют механизмы, которые рассказали Тим/Тех. лиды, старшие товарищи или преподаватели курсов, без особого понимания, что происходит
Стоит отметить, что вы хотите каждый пункт раскрыть с той или иной стороны: "а что если так или так?". Согласен, примеры в части пунктов сильно упрощены и, возможно, полностью не раскрывают необходимость использования тех или иных паттернов
Ну и как вывод: все мы неидеальны и есть чему учиться у всех.
В продакшне так никто не допустит писать. Проблемы надуманы, скорее всего статья написана GPT , и вы видно по ответу не понимаете такое ощущение в чем проблема статьи. Эти проблемы могут возникать только у тех кто первый раз откроет доку или React. У вас примеры некоторые не то что не упрощены, они бесполезны и вводят в заблуждение. Зачем писать статью, претендовавшую на "абсолют" в решении?
Стоит отметить, что данные проблемы не надуманны, если вы же сами указывали, что не хватает по большей части глубины + примеры получше, я выше это подтверждал. Как я уже говорил ранее, мало кто изучает изнутри фреймворки, это большая проблема многих разработчиков и проблемы описанные в статье возникают при разработке
Абсурд в целом называть статьи "асболютом", если по факту, это личные практики и наблюдения. Но спасибо, что попытались ее претендовать на это)
Тогда исправьте в статье все моменты где вы писали "Как стоит делать", если вы не позиционируете так) Мало изучает фреймворки только джуны, или плохие разработчики. Как можно не изучать инструмент, которым ты пользуешься) Даже ваши советы могут привести к утечке или хелу, поэтому стоит задуматься и изучить подробнее тему про которую вы хотите писать
20 частых антипаттернов в React и как их исправить: кратко, понятно, без мифов