Композиция и подъём
Иногда поднимать состояние неудобно. В таких случаях помогает композиция — передача готовых компонентов как children или пропсов.
Пример без композиции
function App() {
const [user, setUser] = useState<User | null>(null);
return (
<Layout user={user}>
<Sidebar user={user} />
<Profile user={user} />
</Layout>
);
}
function Layout({ user, children }: { user: User | null; children: React.ReactNode }) {
return (
<div>
<header>{user?.name}</header>
{children}
</div>
);
}
Layout не использует user как данные — он просто передаёт его дальше. Это лишний пропс.
Композиция через children
function App() {
const [user, setUser] = useState<User | null>(null);
return (
<Layout header={<h1>{user?.name}</h1>}>
<Sidebar user={user} />
<Profile user={user} />
</Layout>
);
}
function Layout({ header, children }: {
header: React.ReactNode;
children: React.ReactNode;
}) {
return (
<div>
<header>{header}</header>
{children}
</div>
);
}
Теперь Layout не знает про user — он принимает готовую разметку.
Композиция вместо подъёма
Иногда вместо того чтобы поднимать состояние, проще передать компонент целиком:
// Вместо
<Layout user={user} />
// Лучше
<Layout sidebar={<UserCard user={user} />} />
Layout не знает, что такое user, и не должен знать.
Render props
Компонент принимает функцию, которая возвращает разметку:
function List<T>({ items, renderItem }: {
items: T[];
renderItem: (item: T) => React.ReactNode;
}) {
return <ul>{items.map(renderItem)}</ul>;
}
<List
items={users}
renderItem={(user) => <li key={user.id}>{user.name}</li>}
/>
Компонент List отвечает только за список. Как отрисовать элемент — решает родитель.
Контроль над состоянием
Иногда компонент предоставляет своё состояние родителю через callback:
function ControlledInput({ value, onChange }: {
value: string;
onChange: (v: string) => void;
}) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
Родитель хранит значение, компонент отображает.
Неуправляемый компонент
Если состояние не нужно — компонент сам хранит значение:
function UncontrolledInput() {
const [value, setValue] = useState('');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Родитель не может прочитать или изменить это значение.
Когда что использовать
- Подъём — когда данные нужны нескольким компонентам
- Композиция — когда компонент не должен знать о структуре данных
- Render props — когда нужно гибко управлять рендером
- Контролируемый — когда родитель управляет значением
Итоги
- Композиция уменьшает prop drilling
childrenи пропсы-элементы делают компоненты гибкими- Render props дают контроль над рендером
- Контролируемый компонент — состояние у родителя
- Неуправляемый — состояние внутри