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

Что выбрать глобальные переменные или useThis?

Привет Хабр!

Как вы знаете при переходе с компонентов классов на функциональные, у нас отняли такую полезную вещь как this, которая указывает на текущий экземпляр компонента. И конечно у нас возник вопрос: а где же тогда хранить timeoutId?. И я видел как люди по разному выкручивались из этой проблемы (Данная статья, является расшифровкой видео)

Например, если timeoutId используется только в рамках одного useEffect можно набросать следующий вариант:

useEffect(() => {  const timeout = setTimeout(() => {    // do some action  }, 3000);    return () => {    clearTimeout(timeout);  }}, [...]);

Данный подход работает, но когда появляется нужда очищать timeout по клику на кнопку, этот подход уже не работает.

Поэтому многие решили, просто создавать переменную вне компонента и хранить в ней id этого тайм-аута, чтобы например по клику иметь возможность его отменить:

let timeout;const Test = () => {  const onClick = () => clearTimeout(timeout);    useEffect(() => {    timeout = setTimeout(() => {      // do some action    }, 3000);  }, [...]);      return (...);}

И это работает в большинстве случаев без каких-либо проблем. Но как всегда есть НО.

Проблема глобальных переменных

Давайте рассмотрим пример. Допустим у нас есть компонент Counter, в котором есть локальный счетчик и глобальный счетчик определенный вне компонента:

let globalCounter = 0;const Counter = () => {  const [stateCounter, setStateCounter] = useState(0);    const onClick = () => {    globalCounter++;    setStateCounter((stateCounter) => stateCounter + 1);  };    return (    <div>      <p>global counter - <b>{globalCounter}</b></p>      <p>state counter - <b>{stateCounter}</b></p>      <button onClick={onClick}>increment</button>    </div>  );}

Компонент достаточно простой. Теперь добавим родительский компонент:

const App = () => {  const [countersNumber, setCountersNumber] = useState(0);    return (    <div>      <button onClick={setCountersNumber((count) => count + 1)}>        add      </button>      <button onClick={setCountersNumber((count) => count - 1)}>        removed      </button>      {[...Array.from(countersNumber).keys()].map((index) => (        <Counter key={index} />      ))}    </div>  );};

Здесь мы храним в state количество счетчиков, и ниже имеем 2 кнопки: для увеличения количества счетчиков и для уменьшения. И собственно вставляем сами счетчики в таком количество, как у нас указано в переменной countersNumber.

Смотрим результат

Перейдем в браузер и выполним следующие действия:

  • Добавим один счетчик;

  • Внутри появившегося счетчика, нажмем "increment" три раза;

  • Добавим второй счетчик.

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

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

Рассмотрим альтернативу

Решением данной проблемы является использование хука useRef(). Именно это и рекомендует React документация:

Они прямо упомянули, что useRef() нужно использовать как аналог this. И более того, для удобства добавили в useRef() возможность передачи начального значения. Поэтому вариант с timeout может выглядеть следующим образом:

const Test = () => {  const timeout = useRef();    const onClick = () => clearTimeout(timeout.current);    useEffect(() => {    timeout.current = setTimeout(() => {      // do some action    }, 3000);  }, [...]);    return (...);}

Возможно в этом решении вас смущает, то что в timeout начинает хранится свойство current, это действительно выглядит немного странно, но у этого есть разумное объяснение, о котором мы рассказывали в предыдущей статье createRef, setRef, useRef и зачем нужен current в ref.

prevProps не исчезли вместе с классами

Использование useRef() для хранения timeout это конечно же очень полезно. Но есть и более интересные способы использования. Например, в компонентах в виде классов есть удобный метод жизненного цикла componentDidUpdate. В качестве первого параметра нам предоставляют prevProps, т.е. props из предыдущей итерации. Это давало нам возможность, сравнивать props из текущей итерации с props из предыдущей. На основании этого выполнять какие-то действия.

componentDidUpdate(prevProps) {  if (this.props.id !== prevProps.id) {    // do some action  }}

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

Давайте напишем хук, который будет возвращать props из предыдущей итерации:

const useGetPrevValue = (value) => {  const prevValueRef = useRef();  useEffect(() => {    prevValueRef.current = value;  });    return prevValueRef.current;};

Здесь мы получаем value из текущей итерации, после создадим ref для хранения данных между итерациями. И в рамках текущей итерации мы вернем текущее значение current равное null. Но перед началом следующей итерации мы обновим current значение, таким образом в следующей итерации в ref у нас будет хранится значение из предыдущей.

И осталось только использовать этот хук:

const CounterView = ({ counter }) => {  const prevCount = useGetPrevValue(counter);    const classes = classNames({    [styles.greenCounter]: counter < prevCounter,    [styles.redCounter] counter > prevCounter,  });    ...}

В итоге, имея значение counter из предыдущей итерации и из текущей итерации, мы можем покрасить в зеленый цвет, если counter уменьшился, или в красный, если counter увеличился.

Расширяйте сознание

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

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

И если вы знаете еще какие-то интересные варианты использования ref обязательно пишите в комментариях

Источник: habr.com
К списку статей
Опубликовано: 16.02.2021 10:21:44
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Reactjs

React

React hooks

Hook

Memoization

Категории

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

  • Имя: Макс
    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