Управление состоянием в React: Обход ловушек с помощью пользовательских хуков
Как начинающие разработчики, одним из распространенных камней преткновения, с которыми мы сталкиваемся при разработке React, является эффективное управление состоянием, особенно когда речь идет об обмене состояниями между компонентами. В этой статье мы рассмотрим распространенную проблему, с которой сталкиваются многие разработчики, и найдем решение, которое не только устранит проблему, но и даст ценные знания о правильном управлении состоянием в React.
Выявление проблемы
Представьте, что вы работаете над React-приложением, в котором есть главный компонент, отрисовывающий различные части пользовательского интерфейса на основе определенных условий. У вас также есть пользовательский хук, отвечающий за управление некоторым состоянием вне основного компонента. Кроме того, у вас есть еще один компонент, назовем его EmailVerification
, который условно отображается внутри главного компонента. Этот компонент EmailVerification
содержит кнопку, которая должна обновлять состояние, управляемое пользовательским хуком. Однако, несмотря на обновление состояния, пользовательский интерфейс не отражает изменений, как ожидалось.
Таким образом, у нас получится что-то вроде этого:
export const App = () => {
const { emailVerified, checkEmailVerified } = useEmailVerification();
return (
<div>
{emailVerified && <div>Start contract</div>}
{!emailVerified && (
<EmailVerification />
)}
</div>
);
};
export const EmailVerification = () => {
const { checkEmailVerified } = useEmailVerification();
return <button onClick={checkEmailVerified}>Refresh</button>;
};
export const useEmailVerification = () => {
const [emailVerified, setEmailVerified] = useState<boolean>(false);
const checkEmailVerified = () => {
// here we would have the logic to handle the email verification
const userEmailVerified = true;
setEmailVerified(userEmailVerified);
};
return { emailVerified, checkEmailVerified };
};
Понимание проблемы
Причина проблемы кроется в том, что несколько экземпляров пользовательского хука используются независимо друг от друга в разных компонентах. Каждый экземпляр поддерживает свое собственное состояние, что приводит к несоответствиям при обновлении состояния в одном компоненте, но ожидании отражения изменений в другом.
Когда вы создаете пользовательский хук, вы, по сути, инкапсулируете в него логику с состоянием. Когда этот хук вызывается в компоненте, React создает отдельный экземпляр состояния, связанного с этим хуком, для каждого экземпляра компонента, который его использует. Это означает, что каждый экземпляр компонента, использующий пользовательский хук, будет иметь свое собственное изолированное состояние, не зависящее от других экземпляров компонента.
Такое поведение очень важно для обеспечения того, чтобы каждый компонент поддерживал свое собственное внутреннее состояние и не вмешивался в состояние других компонентов. Это фундаментальный принцип компонентной архитектуры React, в которой компоненты проектируются как самодостаточные и модульные.
Решение
Чтобы решить эту проблему, нам нужно убедиться, что оба компонента имеют одинаковое состояние. Один из подходов заключается в том, чтобы поднять состояние до общего родительского компонента и передать состояние и функцию его обновления в качестве реквизитов дочерним компонентам. Таким образом, мы создаем единый источник истины для состояния, обеспечивая согласованность во всем приложении.
Реализация решения
В нашем случае мы рефакторизовали код, удалив экземпляры пользовательского хука из компонента EmailVerification
, и вместо этого передали состояние и функцию его обновления из главного компонента. Это позволило обоим компонентам совместно использовать одно и то же состояние, гарантируя, что обновления в одном компоненте будут отражены в другом.
Таким образом, новая версия кода будет выглядеть следующим образом:
export const App = () => {
// Custom hook called on the parent component and passed as props to EmailVerification
const { emailVerified, checkEmailVerified } = useEmailVerification();
return (
<div>
{emailVerified && <div>Start contract</div>}
{!emailVerified && (
<EmailVerification
emailVerified={emailVerified}
checkEmailVerified={checkEmailVerified}
/>
)}
</div>
);
};
export const EmailVerification = ({ emailVerified, checkEmailVerified }) => {
// No new instances of the custom hook
const handleContinueButton = async () => {
checkEmailVerified();
if (emailVerified) {
console.log('email verified', emailVerified);
}
};
return <button onClick={handleContinueButton}>Refresh</button>;
};
Заключение
Изначально я полагал, что понимание того, как каждый экземпляр пользовательского хука сохраняет свое собственное состояние, в React довольно простое. Но когда я углубился, то обнаружил, что даже опытные разработчики могут не замечать такого поведения, что приводит к неправильным представлениям об общем состоянии.
Удивительно, что в документации React этой теме посвящен специальный раздел, подчеркивающий её важность!