Комментарии 56
Подскажите, пожалуйста, пару интересных вопросов для сеньоров по этой задаче.
Спасибо, что обратили внимание на задачу!
Я бы задал вопросы, связанные с производительностью и оптимизацией, например:
Как организовать параллельную загрузку нескольких профилей и синхронизировать их результат в одном компоненте?
Как выстроить отказоустойчивость при сбоях сети (ретраи, отмена запросов, механизмы кэширования)?
Как решать проблему при работе в среде SSR, где запросы нужно делать ещё до рендера?
Как совместить подход с Suspense и Error Boundaries для более реактивной UX-модели?
Вопрос в общем хороший, у меня больше инетресует вот эта часть: <Profile username="john_doe"> {(user) => (user === null ? <Loading /> : <Badge info={user} />)} </Profile>
Видя такой код я могу предположить что в проекте вашем может быть такого очень много, что имхо есть намек к не очень хорошему проекту )))
Тоже хотел спросить- Это весь код компонента App, или что-то не показано с какой-то целью?
Благодарю за мнение.
Этот пример с функцией в children
демонстрирует, что это обычный React-проп, которому можно передавать функцию как значение.
Сама идея упрощена для собеседования (в статье это указано), чтобы оценить именно понимание жизненного цикла и эффектов - в боевом проекте, конечно, принято грамотно разделять ответственность и не плодить запросы во всех мелких компонентах.
Простите, но запрос данных в эффекте это моветон. Если нужна загрузка данных, то ее надо оформлять хуком и делать функциональную композицию на уровне "умного" компонента(контроллера)
А уже хук будет вызывать внутри useEffect? Давайте угадаю, на выходе хотим три стейта - loading, error и result?
Начиная с React 19, этот подход устарел окончательно, лучше сделать запрос, получить промис и передать его дальше потребителям, используя Suspense и новый метод use. А ошибку ловить в ErrorBoundary.
А почему мы считаем что подход от команды реакт лучше?
Больше возникает вопрос - почему хранение бизнес логики приложения в слое предоставления правильное решение и почему команда React тоже так считает?
Почему это считается правильным только во фронтенд веба ?)
Из документации по реакт:
Only Suspense-enabled data sources will activate the Suspense component. They include:
Data fetching with Suspense-enabled frameworks like Relay and Next.js
Lazy-loading component code with
lazy
Reading the value of a cached Promise with
use
Suspense does not detect when data is fetched inside an Effect or event handler.
The exact way you would load data in the
Albums
component above depends on your framework. If you use a Suspense-enabled framework, you’ll find the details in its data fetching documentation.Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React.
Что имеется в виду в последнем абзаце?
В последнем абзаце речь идёт о том, что полноценная реализация Suspense для произвольных запросов (без специальных фреймворков, типа Relay или Next.js) всё ещё считается нестабильной API. Другими словами, официальной документации и гарантий для общего случая пока нет, и React-команда может менять поведение или интерфейсы в будущем. Поэтому, если вы хотите «чистый» Suspense без дополнительных библиотек, стоит учесть, что это пока не является полностью поддерживаемым сценарием, и ваши решения могут сломаться в следующих версиях React.
О чем-то таком я тоже догадался. Мой вопрос спровоцировало вот это предложение: "Начиная с React 19, этот подход устарел окончательно". На мой взгляд, что бы что-то устарело окончательно должен накопиться опыт, и этот опыт должен перейти в эффективные решения. Данное же предложение из доки подсказывает, что это далеко не так.
И в итоге получаем компонент-мультитул. Тут и загрузка данных, и обработка ошибок, мапинг входных данных, формирование представления. Это подойдёт разве что для пет проекта, а в контексте статьи для проверки на джуна. Поменяйте роут с данными и придется 80% компонента переписать заново. Про тестирование я уже молчу
Хук внутри себя всё равно будет использовать useEffect
для запроса данных, так что это вопрос именно композиции и структурирования кода, а не принципиального отличия в подходе. Пример был упрощен оставляя пространство для вопроса "А как бы вы сделали это?" :)
Не обязательно же. Можно сделать через useSyncExternalStore. Тогда все эти задачи, которые вы проверяете в данном примере с монтирование/размонтирование, состоянием гонки и т.п. будут решены внутри объекта Profile и реакт тут вообще будет как бы ни при чем. Для использования надо только знать спецификацию useSyncExternalStore.
а проброс функции в children не противоречит самому смыслу {children}?
Пожалуй, вы забыли про пару важных моментов: Abort Controller и React Strict Mode.
В статье я специально упомянул, что у нас якобы нет возможности вызвать abort()
, чтобы проверить реакцию кандидата на ситуацию неуправляемого запроса. А Strict Mode помогает увидеть возможные побочные эффекты и дважды вызываемые функции - это тоже важно учитывать для корректной реализации асинхронной логики.
Просто обожаю технические задачи в вакууме на собеседованиях. Ваше идеальное решение не проходит даже базовую проверку на предотвращение рефетча (откройте консоль, и увидите как запрос выполняется дважды при первом маунте).
Меня всегда поражала подобная самоуверенность интервьюеров.
Прикольный вопрос, я бы не принял ни предложенное решение, ни сам факт существования подобного компонента. Весь мир использует tanstack-query в качестве стандарта де-факто, камон.
Спасибо за комент!
"Весь мир использует..." громкое утверджение, сразу хочется попросить пруфы :)
Не все компании применяют именно tanstack-query. К тому же в задаче проверяются общие принципы работы с асинхронностью в React: если человек хорошо понимает эту логику, он без труда освоит и любую абстракцию вроде React Query или SWR.
У всех разные стандарты, это нормально. В моей реальности в 2025 году всё-таки стыдно было бы рисовать спиннеры руками "если данные null", тригерить загрузку в useEffect
, игнорировать серверные компоненты и существование хуков.
"Не все компании применяют именно tanstack-query" - это действительно так. Но всерьёз кандидатов, который не задаёт первым вопросом "а нам точно надо такой велосипед напилить", я на своих интервью не рассматриваю.
Есть callback hell, а это nano-micro-component hell, когда каждый уважающий себя микро-компонент лезет на сервер за данными. Когда их становится много - начинается подобное веселье.
Пример из статьи не призывает каждый микро-компонент делать собственный запрос. Это лишь иллюстрация ключевых вопросов, которые помогают оценить понимание React-хуков и потенциальных ловушек асинхронности. Для боевого применения, конечно, нужно распределять логику так, чтобы не превратить приложение в зоопарк из сотен запросов. Но на собеседовании хочется увидеть, как кандидат работает именно с базовой механикой React - тогда уже понятно, сможет ли он грамотно её применить внутри более продуманной архитектуры.
Представьте, что вы разрабатываете библиотеку компонентов внутри большой компании, и этой библиотекой будут пользоваться другие команды (вполне реальный сценарий в больших IT-компаниях). Задача: написать реализацию компонента
Profile
, чтобы им удобно и ожидаемо могли пользоваться в самых разных контекстах.
Не очень люблю такое, потому что это смахивает на чисто синтетическую задачу или банальное запудривание мозгов соискателю.
Вот вы сами пишете, что уже есть fetchProfile
Тогда зачем нужен вот этот вот компонент? Реэкспортируйте вы этот fetchProfile
и тогда действительно его можно использовать в разных контекстах. И внутри реакт компонентов, и внутри какой-нибудь rtkquery. По сути, вся задача - это прослойка между пользовательским кодом и fetchProfile
, которая уже откидывает часть контекстов в которых будет использована.
Вы действительно проверяете знания реакта, но, на мой взгляд, в неестественных условиях, т.е. создаете дополнительно пространство для стресса и для траты времени на муру.
Спасибо за комментарий!
Понимаю, что такая постановка задачи может показаться искусственной, но для собеседований она довольно типична: нам нужно в ограниченное время проверить, как кандидат мыслит в контексте React, понимает ли он тонкости хуков и асинхронных запросов.
На интервью часто задают подобные микро-задачи, чтобы быстро увидеть, как разработчик работает со стейтом, эффектами и обработкой ошибок на практике - без вдавания в детали полной архитектуры приложения.
Т.е. что это не замена реального проектного кода, а тестовое окружение, где проявляются навыки кандидата.
Так в этом и проблема. Ваша задача быстро проверить навыки. А задача соискателя не ударить в грязь лицом. Если вы даете липовую задачу, действительно, можно не думая начать решать и вроде как показывать навыки. А можно начать рефлексировать и пытаться выяснить, а что тут проверяют?
Создавая искусственные ограничения вы не "упрощаете" задачу, а наоборот создаете реальные препятствия для ее решения. И как выше заметили можете создавать неверное представление о проекте.
fetchProfile
не позволяет отменять запросы. А fetchData1, fetchData2 тоже не позволяют? Как должен ответить соискатель? Решить задачу слепо заткнув дыру в архитектуре или поставить под сомнение реализацию fetchProfile
? В любом случае это игра ва-банк. Ты можешь показать себя либо безынициативным сразу взявшись за работу, либо наоборот излишне инициативным, либо неуверенным, либо болтливым и все это ровно из-за "липовости" тестового примера.
И да, это действительно типично для собеседований, поэтому я и говорю, что такое не очень люблю. Вроде как хотят протестировать навыки (точно хотите навыки протестировать?)), вроде как сами же думают, что речь идет о реальных условиях, но тестируются далеко не только навыки и далеко не в реальных условиях.
Не совсем понятен первый пример с lastUsernameFetched. Разве там условие не будет всегда ложно?
Боже, как же все это противно выглядит.
Я все понимаю и про плюсы реакта и джаваскрипта в целом, у каждого языка есть свои недостатки, но конкретно этот пример показывает насколько же банальная работа с запросами это вязкая, противная, неоднозначная и нагроможденная работа в вебе с джс и реактом конкретно.
Сильно отталкивает от изучения и даже просто наблюдения за, казалось бы, перспективными и популярными разработками в сфере разработки веб приложений
Это просто им движет желание сделать "слишком хорошо" или идеально. По факту же консоль браузера в поисках ошибок обычный пользователь смотреть не будет. И непонятно почему сервер вернет null. Если пользователь отсутствует, то сервер вероятно вернет не 200 код вообще и ошибка будет на уровне запроса.
Частое тыканье в имена пользователей вообще лучше всего решить загрузкой данных для всех отображаемых в данный момент. Ну или Profile создавать не одну инстанцию а на каждый клик отдельно.
В интервью я обязательно спрашиваю: "А что, если проп username может динамически меняться? Например, пользователь кликает по спис
В этих случаях есть стандартное рекомендованное командой реакта решение - использовать ключи. Т.е.:
<Profile key={username} username={username}>
В итоге username внутри компонента меняться не будет. В этом случае нам не надо писать "мусорный" код, вместо этого гарантия корректности будет обеспечена на уровне фреймворка.
Дальше я описываю сценарий: представьте, что в вашем приложении две панели. Слева - список пользователей, справа - . Пользователь начинает быстро кликать то по одному, то по другому пользователю.
Поскольку реакт гарантирует корректность, об этом думать не надо, и ни чего по этому поводу делать не надо. Все будет работать правильно само по себе.
"Да, будет", ведь компонент может быть размонтирован, а асинхронный вызов вернётся. Возникает сценарий, когда React ругается - “Can’t perform a React state update on an unmounted component…”.
Нет, не будет. Это не является какой-либо проблемой. Корректность работы компонента уже гарантирована, и поэтому можно не думать о подобных вещах.
Идеальное решение
А теперь правильное идеальное решение:
const Profile = ({ username, children }) => {
const [user, setUser] = useState(null);
useEffect(() => fetchProfile(username).then(setUser), []);
return children(user);
};
Чтобы обеспечить тот же интерфейс, который требуется изначально, можно объявить дополнительный компонент:
const ProfileWrapper = ({ username, children }) => (
<Profile key={username} username={username}>
{children}
</Profile>
);
Код работает корректно, согласно требованиям, ни каких проблем не имеет.
ЗЫ: а при использовании suspense компонент будет и вовсе выглядеть так:
const Profile = ({ username, children }) => children(use(fetchProfile(username)));
с соответствующей оберткой. И, что характерно - все будет работать как надо. Без ненужных изъебов. KISS.
А теперь правильное идеальное решение:
Супер интересное решение. Браво!
Но в реале наверняка надо в key помещать какое-то Id , так как имена в списке могут совпадать.
Да и что будет если кто-то использует Profile без обёртки ProfileWrapper? - но это конечно, наверное, организационный момент, хотя, согласно "правилу Мерфи" - такое рано или поздно произойдет.
Супер интересное решение.
Это же _стандартное_ решение, которое явно рекомендовано вот тут:
https://react.dev/learn/you-might-not-need-an-effect "Resetting all state when a prop changes"
Но в реале наверняка надо в key помещать какое-то Id , так как имена в списке могут совпадать.
В каком списке? Это один компонент-панелька, который не выводится в массиве.
Да и что будет если кто-то использует Profile без обёртки
ProfileWrapper?
То же самое, что и в том случае, если кто угодно будет использовать любой другой компонент _непредназначенным образом_. В крайнем случае можно импортировать только обертку. Все равно это выглядит гораздо проще. Ну и работает корректнее - тут, как уже отметили, "идеальное" решение автора-то по факту глючное - если быстро протыкать например по 5 разным юзерам, то начнут запросы приходить по очереди и мы увидим как на панельке с задержкой переключается пять юзеров)
Смысл то моего подхода именно в том, чтобы не писать когнитивно сложный код, когда можно его не писать. Тогда и места для ошибки не будет. Нельзя ошибиться в кодле, который не написан) а автор вот решил такой код написать - и посадил ошибку.
Это же _стандартное_ решение, которое явно рекомендовано вот тут:
Это стандартное решение, когда речь идет о коде без эффектов. В данном случае, оно, очевидно, не подходит. По сути, вы заявляете, что у вас есть компонент Profile, который имеет сайдэффекты и зависит от клиентских данных. Но по факту, он от клиентских данных не зависит и любой кто будет его использовать как есть, т.е. просто посылая новые данные в username никакого эффекта не получат. Только изучая документацию или исходный код они узнают, что частью публичного интерфейса является так же и инфраструктурное свойство key.
По сути, вы убираете из реакта функциональность useEffect, и реализуете ее окольными путями.
В документации прямо пишут:
Effects are an escape hatch from the React paradigm. They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM.
Вы же, вместо того, что бы синхронизировать компонент используя встроенные средства, делаете такую синхронизацию средствами предназначенными для другого.
Команда реакт не советует так делать. Эти оптимизации логичны и работают как оптимизации именно когда нет useEffect
Это стандартное решение, когда речь идет о коде без эффектов.
Нет, это стандартное решение, когда надо сбрасывать стейт при изменении пропса. По линку буквально описан кейс из статьи - даже компонент тот же самый с пропсом - Profile и userId.
Только изучая документацию или исходный код они узнают, что частью публичного интерфейса является так же и инфраструктурное свойство key.
Так написано же: "Note that in this example, only the outer ProfilePage
component is exported and visible to other files in the project." (в моем посте это ProfileWrapper). Интерфейс точно такой же, как требуется. Тому, кто использует компонент, ни чего про ключи знать не надо.
По сути, вы убираете из реакта функциональность useEffect, и реализуете ее окольными путями.
useEffect остался там, где был: "useEffect(() => fetchProfile(username).then(setUser), []);
" вместе со всей своей функциональностью.
Вы же, вместо того, что бы синхронизировать компонент используя встроенные средства
Наоборот, я как раз предлагаю использовать встроенные средства, предлагаемые фреймворком, вместо того чтобы писать для синхронизации свои велосипеды. В итоге мой код выше работает правильно (и не может не работать, потому что там негде ошибиться), а код автора с велосипедами - _не работает_ правильно (потому что синхронизация стейта - сложная задача, и даже в таких примитивных кейсах делать ее руками - значить обосраться). Не говоря уже о том, что сам код гораздо проще и лучше поддерживается.
Команда реакт не советует так делать.
Это прямая ложь, я выше дал ссылку, где рассматривается буквально наш кейс и команда реакта четко и однозначно говорит "используйте в этом и похожих случаях ключи".
Оно понятное, но оно основано на том, что у вас в компоненте нет никакого состояния
Если бы состояния не было, то нам не надо было бы его обнулять при помощи key. Состояние то у нас есть, просто нам не надо поддерживать "непрерывность" этого состояния при изменении пропса - наоборот, нам надо состояние сбрасывать. И вот в таких случаях, когда надо сбрасывать состояние при изменении пропсов - и надо использовать ключи.
Возможно, наверное, всё приложение создавать в таком стиле "рубки" - но оно будет какое-то странное для React.
Наоборот, это и есть react-way. Напомню, что реакт - это порт внутреннего php-фреймворка, основа его логики - если мы что-то сделали, то пришел новый ответ с сервера, который заново с нуля и полностью отрендерил всю страницу. Реакт изначально сделан так, чтобы эмулировать такое поведение (и чтобы можно было потом с наименьшими болями портировать код с php на js), это основа его архитектуры.
В React все хороводят вокруг "состояния", а в случае key состояния нет вообще.
Все верно, состояние и работа с ним - это сложно и чревато ошибками. Если есть возможность написать код так, чтобы избежать работы с состоянием или положить работу с ним на плечи самого фреймворка - так и надо делать. Чтобы не обосраться со сложным кодом - надо просто не писать сложный код.
Нет, это стандартное решение, когда надо сбрасывать стейт при изменении пропса. По линку буквально описан кейс из статьи - даже компонент тот же самый с пропсом - Profile и userId.
По ссылке которую вы дали, данный вид оптимизаций может быть выполнен если не используется сайдэффект. Если сайдэффект используется то для этого существует useEffect. О чем прямо написано в первом же абзаце. И пример, который вы упоминаете так же useEffect не использует. Поэтому никакое это не стандартное решение.
Вы взяли частный случай случай оптимизации и заменили им общий случай использования useEffect. Разумеется подобный подход командой реакт не продвигается.
Интерфейс точно такой же, как требуется. Тому, кто использует компонент, ни чего про ключи знать не надо.
Это понятно, что все что угодно можно замаскировать. Но в данном случае, использовать обертку, инфраструктурное свойство только для того, что бы не использовать по назначению useEffect - это антишаблон. Вы просто вынесли и замаскировали зависимость useEffect в шапку компонента. Спрашивается зачем, если для сайдэффектов есть useEffect и возможность задания зависимостей для него.
useEffect остался там, где был: "
useEffect(() => fetchProfile(username).then(setUser), []);
" вместе со всей своей функциональностью.
Вы прочитайте полностью раздел на который ссылаетесь. Он прямо так и называется: "Вам возможно не нужны эффекты". Иными словами, семантику useEffect вы игнорируете. И относитесь к нему как к useMemo например. Так же игнорируете возможность настроить useEffect через массив зависимостей перенося их в шапку компонента. Это и называете проигнорировали функциональность.
Наоборот, я как раз предлагаю использовать встроенные средства, предлагаемые фреймворком, вместо того чтобы писать для синхронизации свои велосипеды.
Какие велосипеды? Ниже человек дал ссылку в которой разработчики Реакт объяснили смысл предупреждения об обновлении стейта на размонтированном компоненте. Проблема действительно есть, но автор статьи использовал пример в котором проблемы нет. Тем не менее, знать о том, что такая проблема существует полезна. И именно для таких вещей придуман useEffect. Если убрать избыточную функциональность для данного примера. То получится ровно тоже самое что и у вас.
const Profile = ({ username, children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchProfile(username)
.then(setUser)
.catch((err) => {
// Здесь можно обсудить дополнительные аспекты обработки ошибок.
// Если интересно, какие именно - пишите вопросы к статье :)
});
}, [username]);
return children(user);
А вот ваше решение:
const Profile = ({ username, children }) => {
const [user, setUser] = useState(null);
useEffect(() => fetchProfile(username).then(setUser), []);
return children(user);
};
Вы просто решили работать с useEffect как с useMemo в соответствии с теми рекомендациями, которые содержатся в приведенном вами разделе документации. Но так делать никто не советовал, потому что useEffect не равно useMemo.
В итоге мой код выше работает правильно (и не может не работать, потому что там негде ошибиться)
Вообще-то нет. Вы так спешили, что неправильно сконфигурировали useEffect. Поэтому как раз ваш код не работает.
а код автора с велосипедами - _не работает_ правильно (потому что синхронизация стейта - сложная задача, и даже в таких примитивных кейсах делать ее руками - значить обосраться).
Он работает правильно. Избыточно в данной ситуации, но все корректно. Никакой сложной задачи в синхронизации стейта нет. Это как раз обычная для реактразработчика задача. Проблема этого кода именно в смысловой нагрузке, а не в технической реализации.
Это прямая ложь, я выше дал ссылку, где рассматривается буквально наш кейс и команда реакта четко и однозначно говорит "используйте в этом и похожих случаях ключи".
Неужели?
Раздел называется "You Might Not Need an Effect". Так, у нас уже не наш случай.
Пример из статьи
export default function ProfilePage({ userId }) {
return (
<Profile
userId={userId}
key={userId}
/>
);
}
function Profile({ userId }) {
// ✅ This and any other state below will reset on key change automatically
const [comment, setComment] = useState('');
// ...
}
Вы видите тут эффекты?
То получится ровно тоже самое что и у вас.
Не получится. Мой код _работает правильно_, а ваш - _не_ работает правильно_. Это весьма принципиальная разница.
Я еще раз повторяю - если вы сами занимаетесь велосипедной синхронизацией, то вы уже обосрались. Потому что вы пишите код, в котором можно ошибиться миллионом разных способов. И вы - ошиблись. А у меня код правильный просто потому, что _ошибиться негде_. Вот и ошибок нет.
Он работает правильно.
Нет, не работает. Если быстро поменять несколько юзеров (быстрее, чем они догружаются), то компонент их все выведет подряд, хотя должен вывести только последнего.
Поэтому как раз ваш код не работает.
Так приведите юзкейс, в котором он не работает. Я выше привел юзкейс, в котором не работает ваш.
Вы видите тут эффекты?
Этот раздел иррелевантен эффектам. Там описывается рекомендованный командой реакта универсальный способ полного сброса стейта компонента.
Если вам надо сбрасывать стейт в общем случае (если нет специфичной для вашего кейса причины, по которой так делать нельзя) - вы используете ключи. Либо пишете говнокод с ошибками.
Я еще раз повторяю - если вы сами занимаетесь велосипедной синхронизацией, то вы уже обосрались. Потому что вы пишите код, в котором можно ошибиться миллионом разных способов. И вы - ошиблись. А у меня код правильный просто потому, что _ошибиться негде_. Вот и ошибок нет.
Я же говорю, вы поспешили. И useEffect сконфигурировали как useMemo, у вас setup функция промис возвращает. Понятно, что легко правится, просто на фоне таких вот выражений выглядит иронично.
Нет, не работает. Если быстро поменять несколько юзеров (быстрее, чем они догружаются), то компонент их все выведет подряд, хотя должен вывести только последнего.
Минуточку. Вы как и автор статьи все скидываете в одну кучу. Но автор объяснил для чего он это делал. Задачи накидывались для того, что бы посмотреть как они решаются сами по себе. А вы выражаетесь так как будто реально будете подобное использовать и у нас тут финальное решение. Но, дело в том, что у нас тут не финальное решение. У нас тут нет списка требований, который объясняет как должно вести себя приложение. Вы, почему то решили, что данное предсказуемое поведение компонента - это проблема. Допустим, но вы предлагаете решить ее запросами к серверу которые будут просто повисать в воздухе. Тогда у меня встречный вопрос - это зачем? Зачем пропускать запросы, которые не планируется показывать на экране? Вы уверены, что проблема в данном случае в данном компоненте, а не в пользовательском опыте?
Так приведите юзкейс, в котором он не работает. Я выше привел юзкейс, в котором не работает ваш.
Если добавить кэшпрофилей, то уже нет никакого смысла работать с ключами и отключать компонент. Еще раз хочу напомнить, что автор в одну кучу свалил все и работу с транспортной системой и конфигурирование компонента. Далее, если у вас внешний источник данных - это какой-нибудь обсервер, тоже нет никакого смысла работать с ключами. Никакого существенного упрощения там не будет.
Вы что оптимизируете? Вот создатели реакта понимали, что это частный случай оптимизации. Что если исключить эффекты у нас не будет никаких промежуточных состояний из которых компонент будет переходить из одного в другое. И тогда да, можно каждый раз отрисовывать актуальное состояние компонента. А когда такие состояния есть сразу возникают вопросы к пользовательскому опыту. Насколько они должны быть плавными? Должны ли быть обработки промежуточных действий? Зачем собственно постоянно все скидывать в нуль?
Этот раздел иррелевантен эффектам. Там описывается рекомендованный командой реакта универсальный способ полного сброса стейта компонента.
Если вам надо сбрасывать стейт в общем случае (если нет специфичной для вашего кейса причины, по которой так делать нельзя) - вы используете ключи. Либо пишете говнокод с ошибками.
Минуточку, сброс ВСЕХ стейтов компонента - это не ровно сбрасывать стейт в общем случае. Разрабы реакта показали, что они имеют в виду. А для того, что бы минимизировать использование подхода, только подходящими случаями они расположили этот раздел в разделе посвященном компонентам без эффектов.
вы предлагаете решить ее запросами к серверу которые будут просто повисать в воздухе. Тогда у меня встречный вопрос - это зачем? Зачем пропускать запросы, которые не планируется показывать на экране?
Незачем. Хотя для сервера это не имеет значение, так как мы не можем передать серверу что запрос уже не нужен. Но это поправимо. - При "размонтировании" компонента запросу посылается Cancel. Это помогает не забивать ограниченное количество запросов к сайту в броузере. Но это не относится к использованию или нет key-техники. И в том и в другом случае это нужно применять.
Если добавить кэшпрофилей, то уже нет никакого смысла работать с ключами и отключать компонент.
Почему? Какая разница есть кеш или нет? Кеш находится вне компонента и использование key-техники никак не затрагивает есть он или нет.
Минуточку, сброс ВСЕХ стейтов компонента - это не ровно сбрасывать стейт в общем случае. Разрабы реакта показали, что они имеют в виду.
Они явно написали, что если вы хотите при каждом изменение пропсов сбрасывать стейт компонента, то используйте key-технику, иначе пишите тонну дополнительного кода в своём компоненте. - выбор что использовать довольно ясен.
После вопроса про кэш уже хочется задать вопрос, вы точно понимаете, что делает key? Вы не забыли про вот эту вот особенность: "... React will recreate the DOM ..."? То, что все useMemo и другие мемоизированные элементы скинутся? Речь идет именно о сбрасывании ВСЕХ стейтов компонента и перерисовку на экране.
Иными словами, вот берете вы данные из кэша и вам не нужно пересоздавать дом. Реакт сам выполнит сверку и найдет дом узлы, которые нужно обновить. А если внутри есть компоненты, которые не зависят от входных пропсов, ну не знаю, какая-нибудь косметика она вообще даже в сверке участвовать не будет. А вот при использовании key она будет вся пересоздана.
Мне вот любопытно, вы все это учитываете? И не забываем, что из-за эффектов могут быть переходные состояния и в вашем случае они всегда будут идти из начального состояния в чем далеко не всегда есть смысл. При этом вы сами пишете, что все эффекты все равно нужно полностью сконфигурировать. Просто вместо того, что бы сконфигурировать еще и зависимости, вы должны так спроектировать, что бы всегда все шло из начального состояния.
Если выкинуть эффекты, оно наверное так и будет. Хотя продумать, что мемные компоненты без зависимостей от входа должны быть вынесены куда-то в другое место иначе они все будут так же пересоздаваться впустую.
После вопроса про кэш уже хочется задать вопрос, вы точно понимаете, что делает key? Вы не забыли про вот эту вот особенность: "... React will recreate the DOM ..."? То, что все useMemo и другие мемоизированные элементы скинутся? Речь идет именно о сбрасывании ВСЕХ стейтов компонента и перерисовку на экране
Это я помню. Сброс всех стейтов - это именно то что нам надо. В данном случае.
Про кэш и useMemo - современная реализация кэша, типа TanStack Query, вполне имеет реализацию, когда кэш находится на самом верхнем уровне (ключом к нему являются обычно символьное значение) и такой кэш никак не волнует сброс состояния в различных компонентах приложения.
Просто вместо того, что бы сконфигурировать еще и зависимости, вы должны так спроектировать, что бы всегда все шло из начального состояния.
Но эта конфигурация есть не более чем действие по освобождению ресурсов при размонтировании компонентов. Не более того. - и тогда не надо думать как сбросить состояние компонента (и возможно нижележащих его children компонентов).
Хотя продумать, что мемные компоненты без зависимостей от входа должны быть вынесены куда-то в другое место иначе они все будут так же пересоздаваться впустую.
Да, кэш будет в другом месте. Это верно. Так и реализован кэш в TanStack Query.
Это я помню. Сброс всех стейтов - это именно то что нам надо. В данном случае.
Если под данным случаем вы понимаете реализацию какого-то профиля, то напоминаю вам использование key не учитывает ни кэш, ни то, что весь компонент со всеми дочерними элементами особенно если они обернуты в memo может вообще не требовать перерисовки. Вы просто каждый раз пересоздаете весь дом, скидываете все состояния. В зависимости от сложности использование key увеличивает время работы скриптов и отрисовки на экране и все это просто для того, что бы не конфигурировать зависимости. Напоминаю, использование кэша не приводит к пересозданию всего дома. Обновляются только конкретные элементы.
Я понимаю, что можно полностью плевать на любые оптимизации реакта. Но такой подход все таки не является общим случаем при использовании реакта. Поэтому сами разработчики предлагают его использовать только в конкретных узких случаях, когда компонент переключается в начальное состояние. Когда используется кэш в начальное состояние никто не переключается, поэтому пересоздавать весь Дом не нужно.
Про кэш и useMemo - современная реализация кэша, типа TanStack Query, вполне имеет реализацию, когда кэш находится на самом верхнем уровне (ключом к нему являются обычно символьное значение) и такой кэш никак не волнует сброс состояния в различных компонентах приложения.
Так об этом и речь. У нас есть кэш, и вместо того, что бы его использовать вы предлагает убирать старый дом, строить новый дом точно такой же как и старый, но с другими данными из кэша. В данном случае вообще состояние компонента обслуживается в другом месте. Зачем сам то компонент скидывать и пересоздавать дом? Почитайте какой кейс описан в документации. Когда у компонента нет вот этих сайдэффектов все сильно упрощается.
Но эта конфигурация есть не более чем действие по освобождению ресурсов при размонтировании компонентов. Не более того. - и тогда не надо думать как сбросить состояние компонента (и возможно нижележащих его children компонентов).
В том то и дело, в вашем случае вы не думаете о том, о чем нужно думать. Вы говорите, что упрощается сбрасывание в начальное состояние вообще всего. Это так. Вопрос, зачем? Вот если у вас есть мемные компоненты зачем их сбрасывать в нуль? Они зависят от своих свойств. Если у нескольких профилей один и тот же набор свойств, то получая данные из кэша эти компоненты даже в сверке участвовать по сути не будут. А вы предлагаете их создавать с нуля по новому. Зачем?
Я просто интереса ради взял пару простеньких компонентов внедрил в них хук, который просто по строке выбирает данные из массива. Получился такой импровизированный кэш. И посмотрел как все это работает. Без key 200 мс, с key 360мс. Разумеется не одна итерация.
Понятное дело мелочь. Но если вот так вот не думать на протяжении всего приложения может что-то существенное и получится.
Да, кэш будет в другом месте. Это верно. Так и реализован кэш в TanStack Query.
мемные компоненты это memo(ComponentName), а кэш - это данные, которые в них передаются.
Если под данным случаем вы понимаете реализацию какого-то профиля, то напоминаю вам использование key не учитывает ни кэш, ни то, что весь компонент со всеми дочерними элементами особенно если они обернуты в memo может вообще не требовать перерисовки
Случай, когда есть список имён слева и, при клике на имя в списке, справа появляется Профиль с этим именем.- то есть всякий раз приходится сбрасывать состояние компонента Профиль, обращаться к бакенду и выводить присланную.с бакенда информацию. - что тут можно то закешировать то? Разве что то что присылает бакенд, но тот кеш не зависит от реализации нашего компонента Профиль.
использование кэша не приводит к пересозданию всего дома. Обновляются только конкретные элементы.
В данном случае (по клике на имя в списке слева) мемоизация (Profile) никак не помогает. memo(Profile) всегда при каждом клике будет терять своё состояние и обращаться к бакенду за новым.
Я понимаю, что можно полностью плевать на любые оптимизации реакта. Но такой подход все таки не является общим случаем при использовании реакта.
Я не знаю что такое "общий случай" в React. - React предлагает много патернов для решения ваших задач. Вы вольны выбрать любой из них, который конкретно подходит в данном случае (слева список, справа Profile, который постоянно меняется при клике по имени в списке.)
Если у нескольких профилей один и тот же набор свойств, то получая данные из кэша эти компоненты даже в сверке участвовать по сути не будут.
У разных Profile разное имя. Что там может быть одинаковое? При смене имени состояние в Profile сбрасывается. Key-техника отлично подходит для данного случая - сброса состояния в Profile.
мемные компоненты это memo(ComponentName), а кэш - это данные, которые в них передаются
Мы разобрали случай, когла слева список имён а справа один(!) Profile с одним именем. Мемоизация Profile в данном случае не нужна вовсе. Ибо пропсы у него всегда будет меняться при клике по имени в списке.
Интересный случай, когда есть список компонентов Profile. К примеру. - в этом случае можно мемоизировать компонет Profile (memo(Profile)) , но раз это список то нужно будет также использовать key (как всегда для списка) - в этом случае при изменении списка memo(Profile) перерендится не будет, если данный профиль останется в списке при изменении (если нет, если исчезает из списка и потом снова появляется в списке , то создание, монтирование и рендер Profile будут производить всегда (независимо от мемоизация).
Так что использовпние memo(Profile) и key-техники в случае списка Profile, так и в случае наличия одного (или несколько) Profile на форме - нормально вполне.
Я думаю можно применять такую тактику разработки:
Использовать первоначально всюду key-технику. И мемоизацию компонент.
И в продакшен.
Если кто-то обнаружит медленную работу на клиенте, то подключать кеш сетевых запросов (типа что есть у TanStack Query) - и в продакшен.
Если снова со аременем кто-то на слабом компьютере обнаружит медленно работу по перерисовке дисплея компьютера, то купить ему новый компьютер только тогда заниматься более тонкой оптимизацией рендера.
Я как-то не вижу особого смысла заранее всюду писать memo или useMemo или useCallback (это необходимо при разработке библиотеки компонентов, но не в приложении). - тут наверное нужно применить принцип "не делай предварительной оптимизации если и так всё работает нормально.
Случай, когда есть список имён слева и, при клике на имя в списке, справа появляется Профиль с этим именем.- то есть всякий раз приходится сбрасывать состояние компонента Профиль, обращаться к бакенду и выводить присланную.с бакенда информацию. - что тут можно то закешировать то? Разве что то что присылает бакенд, но тот кеш не зависит от реализации нашего компонента Профиль.
Все что угодно. С одной стороны весь запрос к беку, а с другой стороны использовать мемные компоненты для различных данных профиля. Кэш, конечно, не зависит, а вот компонент, который использует данные может как учитывать кэш, так и каждый раз все скидывать и рисовать с нуля, хотя, данные получает моментально из кэша.
В данном случае (по клике на имя в списке слева) мемоизация (Profile) никак не помогает. memo(Profile) всегда при каждом клике будет терять своё состояние и обращаться к бакенду за новым.
Все зависит от реализации. Я имел в виду именно дочерние компоненты. Сам же профайл при получении нового id сразу получает данные из кэша, поэтому он ничего не скидывает и открисовывается новыми данными. Реакт запускает сверку и обновляет нужные дом узлы. Он не создает дом по новой как в случае с key
Я не знаю что такое "общий случай" в React.
Можете открыть документацию по реакт. И в обучающем приложении как раз рассказаны базовые возможности реакта. Это и есть общие случаи.
У разных Profile разное имя. Что там может быть одинаковое? При смене имени состояние в Profile сбрасывается. Key-техника отлично подходит для данного случая - сброса состояния в Profile.
Вы забываете суть данной статьи. Она не описывает реальное приложение, а предложена на подумать. Там все что угодно может быть. Например, там может быть название города пользователя, с иконкой и краткой информацией. Что может совпадать у тысяч пользователей. Выделяя эту информацию в мемный компонент, получая данные из кэша и вам уже не нужно перерисовывать на экране эту часть пользователя, если города совпадают. А вот если вы работаете через key эта часть будет полностью пересоздана, даже если у разных пользователей эта часть профиля совпадает.
Мы разобрали случай, когла слева список имён а справа один(!) Profile с одним именем. Мемоизация Profile в данном случае не нужна вовсе. Ибо пропсы у него всегда будет меняться при клике по имени в списке.
Еще раз посмотрите, что и как тут разбирается. А так же для чего.
То что разбирается конкретный небольшой функционал, что бы посмотреть на навыки вовсе не означает, что имеется в виду компонент из одной строки =) А Profile не нужно мемоизировать только если родительский компонент не перерисовывается. Но вообще, я повторяю, я имел в виду дочерние для Profile компоненты.
Я как-то не вижу особого смысла заранее всюду писать memo или useMemo или useCallback (это необходимо при разработке библиотеки компонентов, но не в приложении). - тут наверное нужно применить принцип "не делай предварительной оптимизации если и так всё работает нормально.
Без проблем. Но, что мешает вам тогда убрать вообще реакт. Взять шаблонизатор и всю страницу перерисовывать при каждом чихе? Вы понимаете, что использование реакт уже само по себе есть предварительная оптимизация?
Никто не говорит, что нужно от балды везде пихать всякое. Я говорю о том, что нужно понимать, что и как работает. Если вы понимаете с какими данными работаете, тогда зачем вам например лишний раз их обновлять на экране, когда в этом нет необходимости?
Я понимаю, если на вас насели и вы просто не спроектировали систему и сделали кое как. Но если вы во всем разобрались. Потратили на это время, зачем делать плохо?
Например, там может быть название города пользователя, с иконкой и краткой информацией. Что может совпадать у тысяч пользователей. Выделяя эту информацию в мемный компонент, получая данные из кэша и вам уже не нужно перерисовывать на экране эту часть пользователя, если города совпадают
Ок. Придумать такое конечно можно. При этом это будет работать если город не меняется при кликах по разным профилям. Но обычно и Профиль и Карточка Товара- это всегда с чистого листа выводится. То есть со сброса состояния.
Но, что мешает вам тогда убрать вообще реакт. Взять шаблонизатор и всю страницу перерисовывать при каждом чихе? Вы понимаете, что использование реакт уже само по себе есть предварительная оптимизация?
Мешает что на рынке растёт только количество React разработчиков. А остальных - не растёт или падает. Да и при необходимости оптимизировать React вполне подходит. Да, при необходимости. И только при необходимости.
Я понимаю, если на вас насели и вы просто не спроектировали систему и сделали кое как. Но если вы во всем разобрались. Потратили на это время, зачем делать плохо?
Не считаю key-технику чем-то "плохим". Одна из возможностей. А совершествование и оптимизация? - смотри ниже:
"Из выступления Педро Дуарте на конференции Next.js в 2021 году. Педро — один из создателей Radix UI, и здесь он описывает, сколько времени им потребовалось, чтобы реализовать полностью доступное раскрывающееся меню, которое хорошо работает во всех браузерах и поддерживает все программы чтения с экрана:
Dropdown Menu:
2,000+ hours
6 months
50 reviews
1,000s commits"
Вывод? - совершенству нет предела если есть ресурсы. Если у вас есть 2000 часов на реализацию Dropdown Menu, то оно конечно можно.
P. S. Вспомнил как выпустили iOS v1.0, в которой не было операции "копи-пасте" - ну, не успели. Бывает. Потом, через 6 месяцев, конечно, обновили версию и эта операция появилась. - но требование бизнеса успеть выпустить раньше всё же часто оказывается реальным. А оптимизация - часто это уже потом.
Спасибо за ответы. Беседа была интересная. Удачи! :-)
И вот в таких случаях, когда надо сбрасывать состояние при изменении пропсов - и надо использовать ключи.
Согласен с вами. Надо рассматривать и такую возможность как "рубить" "лист" или даже "сук" дерева компонентов, если это не приводит к потери производительности при смене key.
@Vitaly_js, - @Kergan88 имеет в виду раздел с названием "Resetting all state when a prop changes" в You Might Not Need an Effect.
В этом случае не имеет значение вообще что происходит (используется ли useEffect) в том компоненте, что монтируется в "дерево" с помощью key. - главное просто "сбросить всё состояние" компонента, и неважно как оно было установлено, с помощью useEffect или другим путём. Просто "рубится", создаётся новый "лист" или "сук" с новым состоянием и монтируется в "дерево". И всё. Довольно простое решение. В данном случае.
У автора статьи был усеченный случай, который должен был показывать общее поведение. Поэтому не нужно пытаться сюда впихнуть частные случаи, которые можно по разному оптимизировать.
Тем более, что при работе с запросами сбрасывать все состояния и переключение на нужные состояния это не одно и тоже.
То, что автор использовал setUser(null) вовсе не означает, что нужно все бросать и начинать оптимизацию. Переключение пользователей может быть вообще без фазы сброса, если брать все из кэша. В зависимости от различного поведения в useEffect компонент при изменении пропсов может вести себя по разному. Автор, во всяком случае, хотел проверить умение работать именно в этом случае.
Поэтому имеет значение, что и как происходит в эффектах и оптимизации начинать уже с финальным компонентом.
Или вы в принципе начинаете новый компонент зафигачив что-то в key?
Это же _стандартное_ решение, которое явно рекомендовано вот тут:
Оно понятное, но оно основано на том, что у вас в компоненте нет никакого состояния, как только username меняется, а более точно как только значение key меняется, компонент "отрезается" от дерева компонентов, создаётся заново и "пришивается" к дереву.
Такое поведение возможно, но для React он странное. В React все хороводят вокруг "состояния", а в случае key состояния нет вообще. Оно не нужно тогда. Поменялся key - руби компонент со всем его состоянием и возможно состоянием всех его children, создавай заново и монтируй снова в дерево компонентов.
Возможно, наверное, всё приложение создавать в таком стиле "рубки" - но оно будет какое-то странное для React.
1. Key решает проблему и будет показывать только последнее состояние и не важно что там вызывалось до этого.
2. По "Can’t perform a React state update on an unmounted component…” https://github.com/reactwg/react-18/discussions/82
3. Dependency тоже условный для сценария с key, можно не писать если код стайл позволяет.
Ох уж эти собесы с задачками.
Объясните плиз, как работает идеальное решение, если юзер быстро кликает по рандомным юзернеймам. С useref понял как люди предлагали. В идеальном не понимаю как и зачем тот setUser(null) в начале useeffect
А что думаете насчет варианта с withResolvers?
function Profile({
username,
children,
}: {
username: string;
children: (user: User | null) => ReactNode;
}) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
setUser(null);
const { promise, reject } = fetchProfile(username);
promise.then(setUser);
return () => {
reject();
};
}, [username]);
return children(user);
}
let fetchCount = 1;
function fetchProfile(username: string) {
console.log(`🛜 Fetched ${getPluralizedTimes(fetchCount)}`);
let { promise, resolve, reject } = Promise.withResolvers();
setTimeout(
() => resolve({ name: `Full Name ${username}`, username }),
200 * fetchCount
);
return { promise, reject };
}
Одна React-задача, демонстрирующая ключевые навыки на собеседовании