Понимание правила lint React Hooks 'Exustive-deps'
Мне трудно понять правило линта "исчерпывающая глубина".
Я уже читал этот пост и этот пост, но не нашел ответа.
Вот простой компонент React с проблемой ворса:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
useEffect(() => {
onChange(value);
}, [value]);
return (
<input
value={value}
type='text'
onChange={(event) => setValue(event.target.value)}>
</input>
)
}
Мне нужно добавить onChange
к useEffect
массив зависимостей. Но в моем пониманииonChange
никогда не изменится, поэтому его там не должно быть.
Обычно мне это удается так:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
onChange(event.target.value)
}
return (
<input value={value} type='text' onChange={handleChange}></input>
)
}
Почему ворс? Есть ли четкое объяснение правила lint для первого примера?
Или я не должен использовать useEffect
Вот? (Я новичок с крючками)
2 ответа
Причина, по которой правило линтера хочет onChange
войти в useEffect
крючок потому что это возможно для onChange
для переключения между визуализацией, а правило lint предназначено для предотвращения такого рода ссылок на "устаревшие данные".
Например:
const MyParentComponent = () => {
const onChange = (value) => { console.log(value); }
return <MyCustomComponent onChange={onChange} />
}
Каждый рендер MyParentComponent
пройдет другой onChange
функция для MyCustomComponent
.
В вашем конкретном случае вам, вероятно, все равно: вы хотите только позвонить onChange
когда значение меняется, а не когда onChange
функция меняется. Однако это не ясно из того, как вы используетеuseEffect
.
Корень здесь в том, что ваш useEffect
несколько унидиоматичен.
useEffect
лучше всего использовать для побочных эффектов, но здесь вы используете его как своего рода концепцию "подписки", например: "делать X, когда Y изменяется". Функционально это работает, благодаря механикеdeps
массив (хотя в этом случае вы также вызываете onChange
на начальном рендере, что, вероятно, нежелательно), но не по назначению.
Вызов onChange
на самом деле это не побочный эффект, это просто эффект срабатывания onChange
мероприятие для <input>
. Так что я думаю, что ваша вторая версия, которая называет обаonChange
а также setValue
вместе более идиоматичен.
Если бы были другие способы установки значения (например, кнопка очистки), постоянно нужно помнить о вызове onChange
может быть утомительным, поэтому я мог бы написать это как:
const MyCustomComponent = ({onChange}) => {
const [value, _setValue] = useState('');
// Always call onChange when we set the new value
const setValue = (newVal) => {
onChange(newVal);
_setValue(newVal);
}
return (
<input value={value} type='text' onChange={e => setValue(e.target.value)}></input>
<button onClick={() => setValue("")}>Clear</button>
)
}
Но на данный момент это косяк.
Основное назначение exhaustive-deps
предупреждение состоит в том, чтобы не дать разработчикам пропустить зависимости внутри своего эффекта и потерять некоторое поведение.
Дэн Абрамов, разработчик ядра Facebook, настоятельно рекомендует оставить это правило включенным.
В случае передачи функций в качестве зависимостей в React FAQ есть специальная глава:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
tl;dr
Если вам нужно поместить функцию в массив зависимостей:
- Поместите функцию за пределы вашего компонента, чтобы вы были уверены, что ссылка не будет изменяться при каждой визуализации.
- Если можете, вызовите функцию вне вашего эффекта и просто используйте результат как зависимость.
- Если функция должна быть объявлена в области видимости вашего компонента, вы должны запомнить ссылку на функцию с помощью
useCallback
крючок. Ссылка будет изменена только в случае изменения зависимостей функции обратного вызова.