Русский
Русский
English
Статистика
Реклама

Hooks

Перевод 5 типичных ошибок при создании React компонентов (с хуками) в 2020 году

01.07.2020 18:14:54 | Автор: admin
image

Всем привет! Из этой статьи вы узнаете о самых распространенных ошибках при создании React компонентов, а также о том, почему они считаются ошибками, как их избежать или исправить.


Оригинальный материал был написан немецким разработчиком Лоренцом Вайсом для личного блога, а позже собрал много позитивных отзывов на dev.to. Переведено командой Quarkly специально для комьюнити на Хабре.



React


React достаточно давно существует в мире веб-разработки, и его позиции как инструмента для гибкой разработки стремительно укрепились за последние годы. А после анонса и релиза нового хука api/concept создание React-компонентов стало ещё проще.


Несмотря на то, что команда, разработавшая React, и огромное сообщество, которое кодит на этом языке, попытались дать внушительное объяснение концепции React'а, я всё же нашел кое-какие недостатки и типичные ошибки во время работы с ним.


Я составил список всех ошибок, которые обнаружил за последние годы, особенно тех, что связаны с хуками. В этой статье я расскажу о самых распространенных ошибках, постараюсь подробно объяснить, почему я считаю их ошибками, и покажу, как их избежать или исправить.


Дисклеймер


Прежде чем перейти к списку, должен сказать, что на первый взгляд большинство ошибок из списка на самом деле не являются фундаментальными или даже выглядят вполне корректно. Большинство из них вряд ли вообще повлияют на работу или внешний вид приложения. Скорее всего, кроме разработчиков, работающих над продуктом, никто не заметит, что здесь что-то не так. Но я всё равно считаю, что хороший качественный код может помочь улучшить опыт разработки и, следовательно, разработать лучший продукт.


Что касается React-кода, как и по любому другому программному фреймворку или библиотеке, по нему существует миллион различных мнений. Всё, что вы видите здесь, мое личное мнение, а не истина в последней инстанции. Если у вас другое мнение, я с удовольствием выслушаю его.


1. Использование useState, когда нет необходимости в повторном рендере


Одна из основных концепций Reactа это работа с состоянием. Вы можете контролировать весь поток данных и рендеринг через состояние. Каждый раз, когда дерево рендерится снова, оно, скорее всего, привязывается к изменению состояния.


С помощью хука useState вы можете также определять состояние в функциональных компонентах, потому что это действительно чистый и простой способ обработки состояний в Reactе. На примере ниже можно увидеть, как этот хук используется не по назначению.


Теперь больше о примере: предположим, что у нас есть две кнопки, одна кнопка счетчик, а другая отправляет запрос или запускает действие с текущим счетчиком. Однако текущий номер не отображается внутри компонента. Он требуется только для запроса при нажатии второй кнопки.


Так делать нехорошо:


function ClickButton(props) {  const [count, setCount] = useState(0);  const onClickCount = () => {    setCount((c) => c + 1);  };  const onClickRequest = () => {    apiCall(count);  };  return (    <div>      <button onClick={onClickCount}>Counter</button>      <button onClick={onClickRequest}>Submit</button>    </div>  );}

Проблема:


На первый взгляд, вы можете спросить: А в чем, собственно, проблема? Разве не для этого было создано это состояние? И будете правы: всё отлично сработает, и проблемы вряд ли возникнут. Однако в Reactе каждое изменение состояния влияет на компонент и, скорее всего, на его дочерние компоненты, то есть заставляет их выполнить повторный рендеринг.


В приведенном выше примере, так как мы не используем это состояние в нашей части рендера, каждый раз, когда мы будем устанавливать счетчик, это будет заканчиваться ненужным рендерингом. А это может повлиять на работу приложения или привести к неожиданным побочным эффектам.


Решение:


Если внутри вашего компонента вы хотите использовать переменную, которая должна сохранять свое значение между рендерингом, но при этом не вызывать повторный рендеринг, вы можете использовать хук useRef. Он сохранит значение, но не приведет к повторному рендерингу.


function ClickButton(props) {  const count = useRef(0);  const onClickCount = () => {    count.current++;  };  const onClickRequest = () => {    apiCall(count.current);  };  return (    <div>      <button onClick={onClickCount}>Counter</button>      <button onClick={onClickRequest}>Submit</button>    </div>  );}

2. Использование router.push вместо ссылки


Наверное, это очевидно и не имеет никакого отношения к самому Reactу, но я до сих пор довольно часто наблюдаю эту оплошность у людей, создающих React-компоненты.


Допустим, вы создаете кнопку, и пользователь при нажатии на неё должен быть перенаправлен на другую страницу. Так как это SPA, то это действие будет клиентским механизмом маршрутизации. Так что вам понадобится какая-нибудь библиотека. Самая популярная из них в React это react-router, и в следующем примере будет использована именно эта библиотека.


Значит ли это, что добавление слушателя события по клику правильно перенаправит пользователя на нужную страницу?


Так делать нехорошо:


function ClickButton(props) {  const history = useHistory();  const onClick = () => {    history.push('/next-page');  };  return <button onClick={onClick}>Go to next page</button>;}

Проблема:


Хотя для большинства пользователей это вполне рабочее решение, есть одна большая проблема: она появляется, когда дело касается реализации веб-доступности. Кнопка вообще не будет помечена как ссылка на другую страницу, а это значит, что скринридер не сможет ее идентифицировать. Сможете ли вы открыть ее в новой вкладке или окне? Скорее всего, тоже нет.


Решение:


Ссылки на другие страницы при любом взаимодействии с пользователем должны, насколько это возможно, обрабатываться компонентом <Link> или обычным тегом <a>.


function ClickButton(props) {  return (    <Link to="/next-page">      <span>Go to next page</span>    </Link>  );}

Бонусы: это также делает код более читабельным и лаконичным!


3. Обработка действий с помощью useEffect


Один из лучших и продуманных хуков, представленных в Reactе, это useEffect. Он позволяет обрабатывать действия, связанные с изменением prop или state. Несмотря на свою функциональность, этот хук также часто используется там, где не очень-то и нужен.


Представьте себе компонент, который извлекает список элементов и выводит их в DOM. Помимо этого, в случае успешного выполнения запроса мы хотим вызвать функцию onSuccess, которая передается компоненту в качестве свойства.


Так делать нехорошо:


function DataList({ onSuccess }) {  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);  const [data, setData] = useState(null);  const fetchData = useCallback(() => {    setLoading(true);    callApi()      .then((res) => setData(res))      .catch((err) => setError(err))      .finally(() => setLoading(false));  }, []);  useEffect(() => {    fetchData();  }, [fetchData]);  useEffect(() => {    if (!loading && !error && data) {      onSuccess();    }  }, [loading, error, data, onSuccess]);  return <div>Data: {data}</div>;}

Проблема:


Есть два хука useEffect: первый обрабатывает запрос данных к API во время первоначального рендеринга, а второй вызывает функцию onSuccess. То есть, если в состоянии нет загрузки или ошибки, но есть данные, то этот вызов будет успешным. Логично звучит, да?


Конечно, это почти всегда будет срабатывать с первым вызовом. Но вы также потеряете прямую связь между действием и вызываемой функцией. И нет 100% гарантии, что это произойдет только в том случае, если действие fetch будет успешным. А это именно то, что мы, разработчики, так сильно не любим.


Решение:


Самое простое решение установить функцию onSuccess туда, где вызов будет успешным.


function DataList({ onSuccess }) {  const [loading, setLoading] = useState(false);  const [error, setError] = useState(null);  const [data, setData] = useState(null);  const fetchData = useCallback(() => {    setLoading(true);    callApi()      .then((fetchedData) => {        setData(fetchedData);        onSuccess();      })      .catch((err) => setError(err))      .finally(() => setLoading(false));  }, [onSuccess]);  useEffect(() => {    fetchData();  }, [fetchData]);  return <div>{data}</div>;}

Теперь с первого взгляда понятно, что onSuccess вызывается только в случае успешного вызова API.


4. Компоненты с единой ответственностью


Составлять компоненты может быть довольно трудной задачей. Когда делить один компонент на несколько более мелких компонентов? Как структурировать дерево компонентов? Этими вопросами задаются все, кто каждый день работает с компонентным фреймворком. И самой распространенной ошибкой при создании компонентов является объединение двух случаев использования в один компонент.


Возьмем в качестве примера заголовок, который показывает либо кнопку бургерного меню на мобильном устройстве, либо вкладки на десктопе (условие будет обработано волшебной функцией isMobile, которая не является частью этого примера).


Так делать нехорошо:


function Header(props) {  return (    <header>      <HeaderInner menuItems={menuItems} />    </header>  );}function HeaderInner({ menuItems }) {  return isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />;}

Проблема:


При таком подходе компонент HeaderInner пытается вести двойную жизнь. А мы знаем, что тяжело быть Кларком Кентом и Суперменом одновременно. Это также затрудняет процесс тестирования или повторного использования компонента в других местах.


Решение:


Если перенесем условие на один уровень выше, будет проще увидеть, для чего сделаны компоненты, и что они отвечают только за одно: заголовок, вкладку или кнопку бургер-меню, и не стоит пытаться отвечать за несколько вещей одновременно.


function Header(props) {  return (    <header>{isMobile() ? <BurgerButton menuItems={menuItems} /> : <Tabs tabData={menuItems} />}</header>  );}

5. useEffect с единой ответственностью


Помните времена, когда у нас были только методы componentWillReceiveProps или componentDidUpdate для подключения к процессу рендеринга React-компонента? В голову сразу приходят мрачные воспоминания, а ещё осознаешь всю прелесть использования хука useEffect и особенно то, что его можно использовать без ограничений.


Но иногда, когда используешь useEffect для некоторых вещей, мрачные воспоминания приходят вновь. Представьте, например, что у вас есть компонент, который извлекает некоторые данные из бэкэнда и отображает путь (breadcrumbs) в зависимости от текущего местоположения (снова используется react-router для получения текущего местоположения).


Так делать нехорошо:


function Example(props) {  const location = useLocation();  const fetchData = useCallback(() => {    /*  Calling the api */  }, []);  const updateBreadcrumbs = useCallback(() => {    /* Updating the breadcrumbs*/  }, []);  useEffect(() => {    fetchData();    updateBreadcrumbs();  }, [location.pathname, fetchData, updateBreadcrumbs]);  return (    <div>      <BreadCrumbs />    </div>  );}

Проблема:


Существует два варианта использования хука: сбор данных (data-fetching) и отображение пути (displaying breadcrumbs). Оба обновляются с помощью хука useEffect. Этот самый хук useEffect сработает, когда fetchData и updateBreadcrumbs функционируют или меняется location. Основная проблема в том, что теперь мы также вызываем функцию fetchData при изменении location. Это может стать побочным эффектом, о котором мы и не подумали.


Решение:


Если разделить эффект, станет понятно, что функции используются только для одного эффекта, при этом неожиданные побочные эффекты исчезают.


function Example(props) {  const location = useLocation();  const updateBreadcrumbs = useCallback(() => {    /* Updating the breadcrumbs*/  }, []);  useEffect(() => {    updateBreadcrumbs();  }, [location.pathname, updateBreadcrumbs]);  const fetchData = useCallback(() => {    /*  Calling the api */  }, []);  useEffect(() => {    fetchData();  }, [fetchData]);  return (    <div>      <BreadCrumbs />    </div>  );}

Бонусы: случаи использования теперь также логически распределены внутри компонента.


Заключение


При создании React-компонентов натыкаешься на множество подводных камней. Никогда не удается досконально понять весь механизм, и вы обязательно столкнетесь хотя бы с маленьким, а, скорее всего, даже с большим косяком. Однако совершать ошибки тоже нужно, когда изучаешь какой-нибудь фреймворк или язык программирования. Никто не застрахован от них на 100%.


Я думаю, что делиться своим опытом в этой сфере очень полезно для других, это должно помочь другим ребятам не наступать на те же грабли.

Подробнее..

Перевод Мифы о useEffect

07.10.2020 10:05:19 | Автор: admin


Доброго времени суток, друзья!

Представляю вашему вниманию перевод небольшой заметки 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, он сравнил компоненты с атомами, а хуки с электронами. Хуки представляют собой низкоуровневые примитивы, и именно это делает их такими мощными. Красота этих примитивов заключается в абстрагировании стадий жизненного цикла, с которыми мы имели дело раньше. С момента релиза хуков мы наблюдаем взрыв инноваций и появление хороших идей и библиотек на основе этих примитивов, что помогает нам, как разработчикам, создавать более качественные приложения.
Подробнее..

Svelte Redux Redux-saga

07.02.2021 02:13:24 | Автор: admin

Попытка жалкого подобия на хуки useSelector, useDispatch, как в react-redux.

Большинство из нас сталкивались с redux, а те, кто использовал его в ReactJS могли пощупать хуки useSelector, useDispatch, в ином случае через mstp, mdtp + HOC connect. А что со svelte? Можно навернуть, или найти что-то похожее на connect, по типу svelte-redux-connect, описывать огромные конструкции, которые будем отдавать в тот самый connect:

const mapStateToProps = state => ({  users: state.users,  filters: state.filters});const mapDispatchToProps = dispatch => ({  addUser: (name) => dispatch({    type: 'ADD_USER',    payload: { name }  }),  setFilter: (filter) => dispatch({    type: 'SET_FILTER',    payload: { filter }  }) });

Прямо какие-то страшные флэшбэки до середины 2018, до введения хуков :). Хочу хуки в svelte. Что мы можем из него взять? Хм... store у svelte глобальный, не нужны никакие провайдеры с контекстом (шучу, нужны для разделения контекстов, но пока выкинем). Значит так: мы создаем redux-store, потом попробуем написать наши жалкие хуки для удобства использования.

Итак, наши константы:

//constants.jsexport const GET_USER = '@@user/get'export const FETCHING_USER = '@@user/fetch'export const SET_USER = '@@user/set'

Редюсер:

//user.jsimport {FETCHING_USER, SET_USER} from "./constants";const initialState = {  user: null,  isFetching: false}export default function user(state = initialState, action = {}){  switch (action.type){    case FETCHING_USER:    case SET_USER:      return {        ...state,        ...action.payload      }    default:      return state  }}

Экшены:

//actions.jsimport {FETCHING_USER, GET_USER, SET_USER} from "./constants";export const getUser = () => ({  type: GET_USER})export const setUser = (user) => ({  type: SET_USER,  payload: {    user  }})export const setIsFetchingUser = (isFetching) => ({  type: FETCHING_USER,  payload: {    isFetching  }})

Селекторы. К ним вернемся отдельно:

//selectors.jsimport {createSelector} from "reselect";import path from 'ramda/src/path'export const selectUser = createSelector(  path(['user', 'user']),  user => user)export const selectIsFetchingUser = createSelector(  path(['user', 'isFetching']),  isFetching => isFetching)

И главный combineReducers:

//rootReducer.jsimport {combineReducers} from "redux";import user from "./user/user";export const reducers = combineReducers({  user})

Теперь надо прикрутить redux-saga, а в качестве api у нас будет https://randomuser.me/api/. Во время тестирования всего процесса, эта апи очень быстро работала, а я очень сильно хотел посмотреть на лоадер подольше (у каждого свой мазохизм), поэтому я завернул таймаут в промис на 3 сек.

//saga.jsimport {takeLatest, put, call, cancelled} from 'redux-saga/effects'import {GET_USER} from "./constants";import {setIsFetchingUser, setUser} from "./actions";import axios from "axios";const timeout = () => new Promise(resolve => {  setTimeout(()=>{    resolve()  }, 3000)})function* getUser(){  const cancelToken = axios.CancelToken.source()  try{    yield put(setIsFetchingUser(true))    const response = yield call(axios.get, 'https://randomuser.me/api/', {cancelToken: cancelToken.token})    yield call(timeout)    yield put(setUser(response.data.results[0]))    yield put(setIsFetchingUser(false))  }catch (error){    console.error(error)  }finally {    if(yield cancelled()){      cancelToken.cancel('cancel fetching user')    }    yield put(setIsFetchingUser(false))  }}export default function* userSaga(){  yield takeLatest(GET_USER, getUser)}
//rootSaga.jsimport {all} from 'redux-saga/effects'import userSaga from "./user/saga";export default function* rootSaga(){  yield all([userSaga()])}

И наконец инициализация store:

//store.jsimport {applyMiddleware, createStore} from "redux";import {reducers} from "./rootReducer";import {composeWithDevTools} from 'redux-devtools-extension';import {writable} from "svelte/store";import createSagaMiddleware from 'redux-saga';import rootSaga from "./rootSaga";const sagaMiddleware = createSagaMiddleware()const middleware = applyMiddleware(sagaMiddleware)const store = createStore(reducers, composeWithDevTools(middleware))sagaMiddleware.run(rootSaga)// берем изначальное состояние из storeconst initialState = store.getState()// написали writable store для useSelectorexport const useSelector = writable((selector)=>selector(initialState))// написали writable store для useDispatch, хотя можно было и без этого// но для симметрии использования оставил такexport const useDispatch = writable(() => store.dispatch)// подписываемся на обновление storestore.subscribe(()=>{  const state = store.getState()  // при обновлении store обновляем useSelector, тут нет никакой мемоизации,   // проверки стейтов, обработки ошибок и прочего очень важного для оптимизации  useSelector.set(selector => selector(state))})

Всё. Самое интересное начинается с 18 строки. После того, как приходит понятие того, что мы написали, возникает вопрос - если я буду использовать useSelector в 3 разных компонентах с разными данными из store - у меня будут обновляться все компоненты сразу? Нет, обновятся и перерисуются данные, которые мы используем. Даже если логически предположить, что при каждом чихе в store у нас меняется ссылка на функцию, то и обновление компонента по идее должно быть, но его нет. Я честно не до конца разобрался как это работает, но я доберусь до сути, не ругайтесь :)

Хуки готовы, как использовать?

Начнем c useDispatch. Его вообще можно было не заворачивать в svelte-store и сделать просто
export const useDispatch = () => store.dispatch, только по итогу с useSelector мы используем store bindings, а с useDispatch нет - сорян, всё же во мне есть частичка маленького перфекционизма. Используем хук useDispatch в App.svelte:

<!--App.svelte--><script>  import {getUser} from "./store/user/actions";  import {useDispatch} from "./store/store";  import Loader from "./Loader.svelte";  import User from "./User.svelte";  // создаем диспатчер  const dispatch = $useDispatch()  const handleClick = () => {    // тригерим экшен    dispatch(getUser())  }</script><style>    .wrapper {        display: inline-block;        padding: 20px;    }    .button {        padding: 10px;        margin: 20px 0;        border: none;        background: #1d7373;        color: #fff;        border-radius: 8px;        outline: none;        cursor: pointer;    }    .heading {        line-height: 20px;        font-size: 20px;    }</style><div class="wrapper">    <h1 class="heading">Random user</h1>    <button class="button" on:click={handleClick}>Fetch user</button>    <Loader/>    <User/></div>
Кнопока которая тригерит экшенКнопока которая тригерит экшен

Вот такая вот загогулина у меня свёрстана. При нажатии на кнопку Fetch user, тригерим экшен GET_USER. Смотрим в Redux-dev-tools - экшен вызвался, всё хорошо. Смотрим network - запрос к апи выполнен, тоже всё хорошо:

Теперь нужно показать процесс загрузки и полученного нами пользователя. Используем useSelector:

<!--Loader.svelte--><script>    import {useSelector} from "./store/store";    import {selectIsFetchingUser} from "./store/user/selector";// Только в такой конструкции мы можем получить из store данные,     // выглядит не так страшно и не лагает, я проверял :3    $: isFetchingUser = $useSelector(selectIsFetchingUser)</script><style>    @keyframes loading {        0% {            background: #000;            color: #fff;        }        100% {            background: #fff;            color: #000;        }    }    .loader {        background: #fff;        box-shadow: 0px 0px 7px rgba(0,0,0,0.3);        padding: 10px;        border-radius: 8px;        transition: color 0.3s ease-in-out, background 0.3s ease-in-out;        animation: loading 3s ease-in-out forwards;    }</style>{#if isFetchingUser}    <div class="loader">Loading...</div>{/if}

Лоадер рисуется. Данные из store прилетают, теперь надо показать юзера:

<!--User.svelte--><script>    import {useSelector} from "./store/store";    import {selectIsFetchingUser,selectUser} from "./store/user/selector";    $: user = $useSelector(selectUser)    $: isFetchingUser = $useSelector(selectIsFetchingUser)</script><style>    .user {        background: #fff;        box-shadow: 0px 0px 7px rgba(0,0,0,0.3);        display: grid;        padding: 20px;        justify-content: center;        align-items: center;        border-radius: 8px;    }    .user-image {        width: 100px;        height: 100px;        background-position: center;        background-size: contain;        border-radius: 50%;        margin-bottom: 20px;        justify-self: center;    }</style>{#if user && !isFetchingUser}    <div class="user">        <div class="user-image" style={`background-image: url(${user.picture.large});`}></div>        <div>{user.name.title}. {user.name.first} {user.name.last}</div>    </div>{/if}

Пользователя так же получили.

Итог

Запилили какие-никакие подобия на хуки, вроде удобно, но не известно как это отразится в будущем, если сделать из этого mini-app на пару страниц. Саги так же пашут. Через redux devtools можно дебажить redux и прыгать от экшена к экшену, всё хорошо работает.

Подробнее..
Категории: Javascript , Redux , Hooks , Svelte , Redux-saga , Sveltejs

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru