[JavaScript, ReactJS, Программирование, Разработка веб-сайтов] Мифы о useEffect (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
07-Окт-2020 12:32


Доброго времени суток, друзья!
Представляю вашему вниманию перевод небольшой заметки 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, #_reactjs, #_programmirovanie (Программирование), #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_react.js, #_reactjs, #_react, #_hooks, #_hook, #_useeffect, #_huki (хуки), #_huk (хук), #_effekt (эффект), #_javascript, #_reactjs, #_programmirovanie (
Программирование
)
, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 05-Окт 10:48
Часовой пояс: UTC + 5