В чем разница между пользовательским хуком и функциональным компонентом?
(распространенная) проблема
У вас есть компонент и вам нужно управлять его состоянием, и он отлично работает:
function BaseExample() {
const [option, setOption] = useState('two');
const handleChange = (el) => {
setOption(el.target.value);
};
return (
<div>
<select
onChange={handleChange}
value={option}
>
{[
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
].map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</select>
<div>{option ? `Selected: ${option}` : 'No selection'}</div>
</div>
);
}
Но что происходит, когда вы пытаетесь его рефакторить?
function RefactoredExample() {
const [option, setOption] = useState('two');
const handleChange = (el) => {
setOption(el.target.value);
};
return (
<div>
{SelectComponent(handleChange, option)}
<div>{option ? `Selected: ${option}` : 'No selection'}</div>
</div>
);
}
function SelectComponent(handleChange, option) {
return (
<select
onChange={handleChange}
value={option}
>
{[
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
].map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</select>
);
}
Теперь у нас есть один компонент, который должен знать слишком много, и другой, который ничего не может сделать самостоятельно.
Вводим пользовательские хуки
По соглашению обычные функциональные компоненты возвращают JSX, а пользовательские хуки могут возвращать что угодно.
Что угодно? Да, даже JSX.
function RefactoredWithHookExample() {
const { option, SelectComponent } = useSelectComponent();
return (
<div>
<SelectComponent />
<div>{option ? `Selected: ${option}` : 'No selection'}</div>
</div>
);
}
function useSelectComponent() {
const [option, setOption] = useState('two');
const handleChange = (el) => {
setOption(el.target.value);
};
const SelectComponent = () => (
<select
onChange={handleChange}
value={option}
>
{[
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
].map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</select>
);
return { option, SelectComponent };
}
Теперь SelectComponent знает все, что ему нужно для управления своим состоянием, а родительский компонент знает только то, что ему нужно.
С замыканиями можно делать все, что угодно!
Такой пример вряд ли можно назвать захватывающим, но помните, что из хука можно вернуть все, что угодно!
Мало того, это может работать как закрытие, так что вы можете получить что-то вроде этого:
function RefactoredWithClosureHookExample() {
const { option, SelectComponent } = useSelectComponent({
options: [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
],
initial: 'two',
});
return (
<div>
<SelectComponent
selectProps={{ style: { color: 'red' } }}
optionProps={{ style: { color: 'green' } }}
/>
<div>{option ? `Selected: ${option}` : 'No selection'}</div>
</div>
);
}
function useSelectComponent({ options, initial }) {
const [option, setOption] = useState(initial);
const handleChange = (el) => {
setOption(el.target.value);
};
const SelectComponent = ({ selectProps, optionProps }) => (
<select
onChange={handleChange}
value={option}
{...selectProps}
>
{options.map((option) => (
<option
key={option.value}
value={option.value}
{...optionProps}
>
{option.label}
</option>
))}
</select>
);
return { option, SelectComponent };
}
Это, конечно, преувеличение. Но, понимая, что возможно, вы будете уверены, что найдете более легкие решения ваших проблем.
Cover Photo by Jamie Matociños on Unsplash