[JavaScript, ReactJS, Программирование, Разработка веб-сайтов] Мифы о useEffect (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Доброго времени суток, друзья!
Представляю вашему вниманию перевод небольшой заметки Kent C. Dodds, в которой он делится своими соображениями относительно правильного использования хука «useEffect».
Я обучил React тысячи разработчиков. Как до, так и после релиза
хуков. Одной из вещей, которые я заметил, является не очень четкое понимание
назначения и механизма работы хука «useEffect». В этой статье я хочу немного об этом рассказать.
Никакого отношения к стадиям жизненного цикла — синхронизация дополнительных эффектов
Разработчики, которые имеют опыт работы с «классовыми» компонентами и стадиями
жизненного цикла, такими как «constructor», «componentDidMount»,
«componentDidUpdate» и «componentWillUnmount», иногда пытаются реализовать
аналогичное поведение в функциональных компонентах с помощью хуков. Это большая
ошибка. Позвольте мне это продемонстрировать. Вот пример забавного
пользовательского интерфейса:
Вот реализация компонента «DogInfo» с помощью классов:
class DogInfo extends React.Component {
controller = null;
state = { dog: null };
fetchDog() {
this.controller?.abort();
this.controller = new AbortController();
getDog(this.props.dogId, { signal: this.controller.signal }).then(
(dog) => {
this.setState({ dog });
},
(error) => {
// обработка ошибок
}
);
}
componentDidMount() {
this.fetchDog();
}
componentDidUpdate(prevProps) {
// обработка изменения dogId
if (prevProps.dogId !== this.props.dogId) {
this.fetchDog();
}
}
componentWillUnmount() {
// отмена запроса
this.controller?.abort();
}
render() {
return <div>{/* рендеринг информации о собаке */}</div>;
}
}
Это стандартный компонент для такого вида интеракции. В нем
используются стадии жизненного цикла «constructor», «componentDidMount»,
«componentDidUpdate» и «componentWillUnmount». Вот что получится, если мы
обернем эти стадии в хуки:
function DogInfo({ dogId }) {
const controllerRef = React.useRef(null);
const [dog, setDog] = React.useState(null);
function fetchDog() {
controllerRef.current?.abort();
controllerRef.current = new AbortController();
getDog(dogId, { signal: controllerRef.current.signal }).then(
(d) => setDog(d),
(er) => {
// обработка ошибок
}
);
}
// didMount
React.useEffect(() => {
fetchDog();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// didUpdate
const prevDogId = usePrevious(dogId);
useUpdate(() => {
if (prevDogId !== dogId) {
fetchDog();
}
});
// willUnmount
React.useEffect(() => {
return () => {
controllerRef.current?.abort();
};
}, []);
return <div>{/* рендеринг информации о собаке */}</div>;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Здесь имеется некоторая несогласованность между хуками. Если бы они
предназначались для использования таким образом, я тоже был бы их противником.
Но правда в том, что useEffect — это не стадия жизненного цикла. Это механизм
синхронизации дополнительных эффектов с состоянием приложения. В
приведенном примере наша задача состоит в том, чтобы запрашивать информацию о
собаке при изменении dogId. Учитывая это, useEffect становится намного проще:
function DogInfo({ dogId }) {
const [dog, setDog] = useState(null);
useEffect(() => {
const controller = new AbortController();
getDog(dogId, { signal: controller.signal }).then(
(d) => setDog(d),
(er) => {
// обработка ошибок
}
);
return () => controller.abort();
}, [dogId]);
return <div>{/* рендеринг информации о собаке */}</div>;
}
Это выглядит намного лучше, не так ли? Когда команда React представила хуки, их
целью являлось не упрощение использования стадий жизненного цикла в
функциональных компонентах, а улучшение ментальной модели относительно дополнительных эффектов приложения. И они этого достигли.
Запомните высказывание Ryan Florence:
«Вопрос не в том, когда запускать эффект, вопрос в том, с каким состоянием
он должен синхронизироваться»
useEffect(fn) // все состояния
useEffect(fn, []) // отсутствие состояний
useEffect(fn, [these, states]) // указанные состояния
Могу я игнорировать eslint-plugin-react-hooks/exhaustive-deps?
Ну, технически да. И иногда для этого существуют хорошие причины. Однако, в
большинстве случаев это является плохой идеей, игнорирование этого правила почти наверняка приведет к ошибкам. Обычно, люди после этого говорят: «Но я только
хотел, чтобы это запускалось после рендеринга!». Вот опять. Думать о хуках в
категориях жизненного цикла неправильно. Если ваш колбэк в useEffect имеет
зависимость, необходимо убедиться, что он выполняется при каждом изменении
этой зависимости. В противном случае, эффект не будет синхронизирован с
состоянием приложения. Короче говоря, у вас будут проблемы. Не игнорируйте
данное правило.
Один большой useEffect
Честно говоря, я давно такого не видел. Но такое порой все-таки случается. То,
что мне действительно нравится в useEffect, так это возможность разделения задачи на любое количество подзадач. Вот простой пример:
Вот некоторый всевдокод для этого демо:
class ChatFeed extends React.Component {
componentDidMount() {
this.subscribeToFeed();
this.setDocumentTitle();
this.subscribeToOnlineStatus();
this.subscribeToGeoLocation();
}
componentWillUnmount() {
this.unsubscribeFromFeed();
this.restoreDocumentTitle();
this.unsubscribeFromOnlineStatus();
this.unsubscribeFromGeoLocation();
}
componentDidUpdate(prevProps, prevState) {
// ... сранение пропсов, повторной подписки и т.д.
}
render() {
return <div>{/* интерфейс чата */}</div>;
}
}
Видите эти 4 задачи? Они смешаны. Что, если вы захотите поделиться частью функционала? Я имею ввиду, что пропсы рендеринга — отличная вещь, но хуки
все же лучше. Я видел, как некоторые люди создают огромный useEffect, отвечающий
за все:
function ChatFeed() {
React.useEffect(() => {
// подписка на ленту
// установка заголовка документа
// перевод статуса в онлайн
// определения местоположения
return () => {
// отписка от ленты
// восстановление заголовка
// перевод статуса в офлайн
// отключение определения местоположения
};
});
return <div>{/* интерфейс чата */}</div>;
}
Но это делает колбэк очень сложным. Предлагаю другой подход. Не забывайте, что
вы можете разделять логику между разными хуками:
function ChatFeed() {
React.useEffect(() => {
// подписка на ленту
return () => {
// отписка от ленты
};
});
React.useEffect(() => {
// установка заголовка документа
return () => {
// восстановление заголовка
};
});
React.useEffect(() => {
// перевод статуса в онлайн
return () => {
// перевод статуса в офлайн
};
});
React.useEffect(() => {
// определения местоположения
return () => {
// отключение определения местоположения
};
});
return <div>{/* интерфейс чата */}</div>;
}
Самодостаточность хуков дает массу преимуществ.
Внешние функции
Я видел такое несколько раз. Позвольте мне просто привести код до и после:
// до. Не делайте так
function DogInfo({ dogId }) {
const [dog, setDog] = React.useState(null);
const controllerRef = React.useRef(null);
const fetchDog = React.useCallback((dogId) => {
controllerRef.current?.abort();
controllerRef.current = new AbortController();
return getDog(dogId, { signal: controller.signal }).then(
(d) => setDog(d),
(error) => {
// обработка ошибок
}
);
}, []);
React.useEffect(() => {
fetchDog(dogId);
return () => controller.current?.abort();
}, [dogId, fetchDog]);
return <div>{/* рендеринг информации о собаках */}</div>;
}
Мы уже видели, как можно упростить этот код, но давайте взглянем на него еще раз:
function DogInfo({ dogId }) {
const [dog, setDog] = React.useState(null);
React.useEffect(() => {
const controller = new AbortController();
getDog(dogId, { signal: controller.signal }).then(
(d) => setDog(d),
(error) => {
// обработка ошибок
}
);
return () => controller.abort();
}, [dogId]);
return <div>{/* рендеринг информации о собаках */}</div>;
}
Я пытаюсь сказать, что определение функции за пределами колбэка useEffect — это плохая идея. Поскольку такая функция является внешней по отношению к useEffect, нам приходится добавлять ее в список зависимостей. Также нам приходится кэшировать ее во избежание бесконечного цикла. Кроме того, мы вынуждены создавать ref для контроллера.
Запомните: при необходимости определения функции, вызываемой в эффекте, определяйте ее внутри колбэка данного эффекта, а не за его пределами.
Заключение
Когда Dan Abramov представил хуки, такие как «useEffect», он сравнил компоненты с атомами, а хуки с электронами. Хуки представляют собой низкоуровневые примитивы, и именно это делает их такими мощными. Красота этих примитивов заключается в абстрагировании стадий жизненного цикла, с которыми мы имели дело раньше. С момента релиза хуков мы наблюдаем взрыв инноваций и появление хороших идей и библиотек на основе этих примитивов, что помогает нам, как разработчикам, создавать более качественные приложения.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Kent C. Dodds
===========Похожие новости:
- [Разработка веб-сайтов, JavaScript, Социальные сети и сообщества] Front End Meetup от Facebook Developer Circle: Moscow
- [JavaScript, C++, WebAssembly] Как скрестить Clion, Emscripten и Cmake
- [Веб-дизайн, Разработка веб-сайтов, Повышение конверсии] Услышал интересное мнение по поводу лэндингов. Согласны или нет?
- [Разработка веб-сайтов, Клиентская оптимизация, Серверная оптимизация, Браузеры] А ваш CDN умеет так?
- [Программирование, Изучение языков] Hi Programming Language: начинаем конструировать
- [Программирование] Реализация расширения Active Patterns для языка OCaml
- [Конференции] Новая неделя YouTube-стримов: от Vue.js до SIMD-инструкций
- [Программирование, Разработка систем связи, Стандарты связи] SDR DVB-T2 receiver на C++
- [Программирование, Проектирование и рефакторинг, Алгоритмы, Терминология IT] Как генерируются UUID (перевод)
- [Разработка веб-сайтов, Python, Django] Отношение один к одному: связывание модели пользователя с кастомной моделью профиля в Django (перевод)
Теги для поиска: #_javascript, #_reactjs, #_programmirovanie (Программирование), #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_react.js, #_reactjs, #_react, #_hooks, #_hook, #_useeffect, #_huki (хуки), #_huk (хук), #_effekt (эффект), #_javascript, #_reactjs, #_programmirovanie (
Программирование
), #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Доброго времени суток, друзья! Представляю вашему вниманию перевод небольшой заметки Kent C. Dodds, в которой он делится своими соображениями относительно правильного использования хука «useEffect». Я обучил React тысячи разработчиков. Как до, так и после релиза хуков. Одной из вещей, которые я заметил, является не очень четкое понимание назначения и механизма работы хука «useEffect». В этой статье я хочу немного об этом рассказать. Никакого отношения к стадиям жизненного цикла — синхронизация дополнительных эффектов Разработчики, которые имеют опыт работы с «классовыми» компонентами и стадиями жизненного цикла, такими как «constructor», «componentDidMount», «componentDidUpdate» и «componentWillUnmount», иногда пытаются реализовать аналогичное поведение в функциональных компонентах с помощью хуков. Это большая ошибка. Позвольте мне это продемонстрировать. Вот пример забавного пользовательского интерфейса: Вот реализация компонента «DogInfo» с помощью классов: class DogInfo extends React.Component {
controller = null; state = { dog: null }; fetchDog() { this.controller?.abort(); this.controller = new AbortController(); getDog(this.props.dogId, { signal: this.controller.signal }).then( (dog) => { this.setState({ dog }); }, (error) => { // обработка ошибок } ); } componentDidMount() { this.fetchDog(); } componentDidUpdate(prevProps) { // обработка изменения dogId if (prevProps.dogId !== this.props.dogId) { this.fetchDog(); } } componentWillUnmount() { // отмена запроса this.controller?.abort(); } render() { return <div>{/* рендеринг информации о собаке */}</div>; } } Это стандартный компонент для такого вида интеракции. В нем используются стадии жизненного цикла «constructor», «componentDidMount», «componentDidUpdate» и «componentWillUnmount». Вот что получится, если мы обернем эти стадии в хуки: function DogInfo({ dogId }) {
const controllerRef = React.useRef(null); const [dog, setDog] = React.useState(null); function fetchDog() { controllerRef.current?.abort(); controllerRef.current = new AbortController(); getDog(dogId, { signal: controllerRef.current.signal }).then( (d) => setDog(d), (er) => { // обработка ошибок } ); } // didMount React.useEffect(() => { fetchDog(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // didUpdate const prevDogId = usePrevious(dogId); useUpdate(() => { if (prevDogId !== dogId) { fetchDog(); } }); // willUnmount React.useEffect(() => { return () => { controllerRef.current?.abort(); }; }, []); return <div>{/* рендеринг информации о собаке */}</div>; } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } Здесь имеется некоторая несогласованность между хуками. Если бы они предназначались для использования таким образом, я тоже был бы их противником. Но правда в том, что useEffect — это не стадия жизненного цикла. Это механизм синхронизации дополнительных эффектов с состоянием приложения. В приведенном примере наша задача состоит в том, чтобы запрашивать информацию о собаке при изменении dogId. Учитывая это, useEffect становится намного проще: function DogInfo({ dogId }) {
const [dog, setDog] = useState(null); useEffect(() => { const controller = new AbortController(); getDog(dogId, { signal: controller.signal }).then( (d) => setDog(d), (er) => { // обработка ошибок } ); return () => controller.abort(); }, [dogId]); return <div>{/* рендеринг информации о собаке */}</div>; } Это выглядит намного лучше, не так ли? Когда команда React представила хуки, их целью являлось не упрощение использования стадий жизненного цикла в функциональных компонентах, а улучшение ментальной модели относительно дополнительных эффектов приложения. И они этого достигли. Запомните высказывание Ryan Florence: «Вопрос не в том, когда запускать эффект, вопрос в том, с каким состоянием он должен синхронизироваться» useEffect(fn) // все состояния useEffect(fn, []) // отсутствие состояний useEffect(fn, [these, states]) // указанные состояния Могу я игнорировать eslint-plugin-react-hooks/exhaustive-deps? Ну, технически да. И иногда для этого существуют хорошие причины. Однако, в большинстве случаев это является плохой идеей, игнорирование этого правила почти наверняка приведет к ошибкам. Обычно, люди после этого говорят: «Но я только хотел, чтобы это запускалось после рендеринга!». Вот опять. Думать о хуках в категориях жизненного цикла неправильно. Если ваш колбэк в useEffect имеет зависимость, необходимо убедиться, что он выполняется при каждом изменении этой зависимости. В противном случае, эффект не будет синхронизирован с состоянием приложения. Короче говоря, у вас будут проблемы. Не игнорируйте данное правило. Один большой useEffect Честно говоря, я давно такого не видел. Но такое порой все-таки случается. То, что мне действительно нравится в useEffect, так это возможность разделения задачи на любое количество подзадач. Вот простой пример: Вот некоторый всевдокод для этого демо: class ChatFeed extends React.Component {
componentDidMount() { this.subscribeToFeed(); this.setDocumentTitle(); this.subscribeToOnlineStatus(); this.subscribeToGeoLocation(); } componentWillUnmount() { this.unsubscribeFromFeed(); this.restoreDocumentTitle(); this.unsubscribeFromOnlineStatus(); this.unsubscribeFromGeoLocation(); } componentDidUpdate(prevProps, prevState) { // ... сранение пропсов, повторной подписки и т.д. } render() { return <div>{/* интерфейс чата */}</div>; } } Видите эти 4 задачи? Они смешаны. Что, если вы захотите поделиться частью функционала? Я имею ввиду, что пропсы рендеринга — отличная вещь, но хуки все же лучше. Я видел, как некоторые люди создают огромный useEffect, отвечающий за все: function ChatFeed() {
React.useEffect(() => { // подписка на ленту // установка заголовка документа // перевод статуса в онлайн // определения местоположения return () => { // отписка от ленты // восстановление заголовка // перевод статуса в офлайн // отключение определения местоположения }; }); return <div>{/* интерфейс чата */}</div>; } Но это делает колбэк очень сложным. Предлагаю другой подход. Не забывайте, что вы можете разделять логику между разными хуками: function ChatFeed() {
React.useEffect(() => { // подписка на ленту return () => { // отписка от ленты }; }); React.useEffect(() => { // установка заголовка документа return () => { // восстановление заголовка }; }); React.useEffect(() => { // перевод статуса в онлайн return () => { // перевод статуса в офлайн }; }); React.useEffect(() => { // определения местоположения return () => { // отключение определения местоположения }; }); return <div>{/* интерфейс чата */}</div>; } Самодостаточность хуков дает массу преимуществ. Внешние функции Я видел такое несколько раз. Позвольте мне просто привести код до и после: // до. Не делайте так
function DogInfo({ dogId }) { const [dog, setDog] = React.useState(null); const controllerRef = React.useRef(null); const fetchDog = React.useCallback((dogId) => { controllerRef.current?.abort(); controllerRef.current = new AbortController(); return getDog(dogId, { signal: controller.signal }).then( (d) => setDog(d), (error) => { // обработка ошибок } ); }, []); React.useEffect(() => { fetchDog(dogId); return () => controller.current?.abort(); }, [dogId, fetchDog]); return <div>{/* рендеринг информации о собаках */}</div>; } Мы уже видели, как можно упростить этот код, но давайте взглянем на него еще раз: function DogInfo({ dogId }) {
const [dog, setDog] = React.useState(null); React.useEffect(() => { const controller = new AbortController(); getDog(dogId, { signal: controller.signal }).then( (d) => setDog(d), (error) => { // обработка ошибок } ); return () => controller.abort(); }, [dogId]); return <div>{/* рендеринг информации о собаках */}</div>; } Я пытаюсь сказать, что определение функции за пределами колбэка useEffect — это плохая идея. Поскольку такая функция является внешней по отношению к useEffect, нам приходится добавлять ее в список зависимостей. Также нам приходится кэшировать ее во избежание бесконечного цикла. Кроме того, мы вынуждены создавать ref для контроллера. Запомните: при необходимости определения функции, вызываемой в эффекте, определяйте ее внутри колбэка данного эффекта, а не за его пределами. Заключение Когда Dan Abramov представил хуки, такие как «useEffect», он сравнил компоненты с атомами, а хуки с электронами. Хуки представляют собой низкоуровневые примитивы, и именно это делает их такими мощными. Красота этих примитивов заключается в абстрагировании стадий жизненного цикла, с которыми мы имели дело раньше. С момента релиза хуков мы наблюдаем взрыв инноваций и появление хороших идей и библиотек на основе этих примитивов, что помогает нам, как разработчикам, создавать более качественные приложения. =========== Источник: habr.com =========== =========== Автор оригинала: Kent C. Dodds ===========Похожие новости:
Программирование ), #_razrabotka_vebsajtov ( Разработка веб-сайтов ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:21
Часовой пояс: UTC + 5