DevGang
Авторизоваться

Что такое useState и почему мы не используем обычный let? 

В настоящее время мы все еще используем хук useState для установки переменной в компоненте React. UseState, представленный как «hooks», записывается так:

const [count, setCount] = React.useState<number>(0);

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

Почему бы нам просто не использовать что-то подобное?

let count = 0;

count++;

Что ж, это всегда работает в нашем первом приложении счетчика с vanilla JavaScript. Почему бы нам тогда не использовать его в React?

TLDR;

React выполняет повторный рендеринг, вызывая функцию компонента, и при каждом вызове функции ваша переменная будет сбрасываться каждый раз.

Отступление

Прежде чем мы перейдем к концепции React Core, давайте вернемся к vanilla JavaScript. Для этой демонстрации мы собираемся создать простое приложение счетчика.

let count = 0;

function add() {
  count++;
  document.getElementById('count').textContent = count;
}

Все просто, правда? Когда срабатывает кнопка add(), которая выполняет функцию прослушивателя кликов, мы добавляем счетчик и обновляем текст, открывая документы.

Если мы присмотримся, мы увидим, что он выполняет 3 действия. Разберем его на отдельные функции

// Объявление
let count = 0;

function mutate() {
  count++;
}

function render() {
  document.getElementById("count").textContent = count;
}

// псевдокод прослушивателя событий
при нажатии кнопки:
  mutate()
  render()

И получаем примерно такое:

Описание видео:

  1. С левой стороны показано, что элемент button имеет атрибут onclick, который запускает mutate() и render().
  2. Каждый раз, когда пользователь нажимает кнопку, число увеличивается на единицу.

3 Действия

Прежде чем мы продолжим, у нас есть три действия, которые мы разбили ранее:

  1. Объявить → инициализировать переменную с помощью let
  2. Мутировать → изменить счетную переменную
  3. Рендеринг → обновить изменения на экране

Давайте разделим кнопку на отдельные функции, чтобы вы могли четко ее видеть.

<h1>Counter</h1>
<p id="count">0</p>
<button onclick="mutate()">Mutate</button>
<button onclick="render()">Render</button>

<script>
  let count = 0;

  function mutate() {
    count++;
    logTime();
    console.log('clicking, count: ', count);
  }

  function render() {
    document.getElementById('count').textContent = count;
  }
</script>

Описание видео:

  1. Когда нажата кнопка изменения, консоль показывает, что счетчик увеличивается. Однако цифра на экране не меняется.
  2. После нажатия кнопки рендеринга число на экране изменится на последнее значение счетчика.

Наблюдаем за React

Благодаря прямому переводу кода JavaScript это то, что мы имеем сейчас.

function Component() {
  let count = 0;

  function mutate() {
    count = count + 1;
    console.log(count);
  }

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={mutate}>Mutate</button>
    </div>
  );
}

Вы видите что-то странное?

Нашли это?

Да, функции рендеринга нет.

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

Функция рендеринга

Что же тогда эквивалентно функции рендеринга в React?

На самом деле это function Component()сам.

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

При вызове функции объект count снова объявляется, функция mutate также повторно объявляется и, наконец, возвращает новый JSX.

Вот демонстрация:

Описание видео:

  1. Мы видим, что есть 2 консольных журнала в строке 13 и 15.
  2. Когда страница перезагружается, логи консоли запускаются. (это нормальное поведение при первоначальном рендере)
  3. Каждый раз, когда нажимается кнопка «Повторный рендеринг», вызываются журналы. Это доказывает, что Component () вызывается при каждом рендеринге.

Что запускает функцию рендеринга?

Если мы запустим код с помощью let on React, изменений не будет. Это потому, что функция рендеринга не вызывается.

React вызовет функцию рендеринга:

  1. Когда значение useState изменяется (с помощью setState)
  2. Когда родительский компонент повторно отрисовывается
  3. Когда переданный реквизит меняется

Второй и третий в основном запускаются из-за setState, но в родительском элементе.

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

Имитация функции рендеринга

Прежде чем преобразовать переменную count в состояние, я хочу продемонстрировать, создав симуляцию функции рендеринга, в которой используется setToggle. Мы можем запустить повторный рендеринг с помощью render.

function Component() {
  //#region  //*=========== Render Fn Simulation ===========
  const [toggle, setToggle] = React.useState<boolean>(false);
  function render() {
    setToggle((t) => !t);
  }
  //#endregion  //*======== Render Fn Simulation ===========

  let count = 0;

  const mutate = () => {
    count = count + 1;
    console.log(`${getTime()}| count: ${count}`);
  };

  return (
    <div>
      <h1>{count}</h1>
      <Button onClick={mutate}>Mutate</Button>
      <Button onClick={render}>Render</Button>
    </div>
  );
}

Посмотрим на это в действии

Описание видео:

  1. Нажата кнопка Mutate, и счетчик увеличивается до 4.
  2. Нажата кнопка рендеринга, но число на экране не меняется, в то время как журнал консоли равен 4.
  3. Функция рендеринга снова нажата, число на экране все еще равно 0, а журнал консоли изменится на 0
  4. После нажатия кнопки mutate он увеличивается, но не с 4, а снова, начиная с 0.

🤯 Почему не работает?

На самом деле это потому, что мы повторно объявляем переменную count.

function Component() {
  //#region  //*=========== Render Fn Simulation ===========
  const [toggle, setToggle] = React.useState<boolean>(false);
  function render() {
    setToggle((t) => !t);
    console.log(`${getTime()} | Render function called at count: ${count}`);
  }
  //#endregion  //*======== Render Fn Simulation ===========

  let count = 0;

  const mutate = () => {
    count = count + 1;
    console.log(`${getTime()}| count: ${count}`);
  };

  return (
    <div>
      <h1>{count}</h1>
      <Button onClick={mutate}>Mutate</Button>
      <Button onClick={render}>Render</Button>
    </div>
  );
}

Каждый раз, когда response вызывает функцию Component, мы повторно объявляем счетчик равным 0. Функция рендеринга все еще работает, и response обновил экран, но он обновился до повторно объявленного счетчика, который по-прежнему равен 0.

Вот почему мы не можем использовать обычную переменную в компоненте React.

Объявление вне компонента

Вы также можете спросить:

Почему бы нам не переместить объявление за пределы функции Component?

Что ж, это имеет смысл, перемещая объявление, мы избегаем повторного объявления count в 0. Давайте попробуем это сделать, чтобы быть уверенными.

let count = 0;

function Component() {
  //#region  //*=========== Render Fn Simulation ===========
  const [toggle, setToggle] = React.useState<boolean>(false);
  function render() {
    setToggle((t) => !t);
    console.log(`${getTime()} | Render function called at count: ${count}`);
  }
  //#endregion  //*======== Render Fn Simulation ===========

  const mutate = () => {
    count = count + 1;
    console.log(`${getTime()}| count: ${count}`);
  };

  return (
    <div>
      <h1>{count}</h1>
      <Button onClick={mutate}>Mutate</Button>
      <Button onClick={render}>Render</Button>
    </div>
  );
}

Описание видео:

  1. Кнопка Mutate нажимается 3 раза, а значение count увеличивается до 3.
  2. Нажата кнопка рендеринга, и число на экране обновляется до 3.
  3. При повторном нажатии кнопки изменения, прирост продолжается от 3 до 5.
  4. При повторном нажатии кнопки рендеринга счетчик обновляется до правильного.

ЭТО РАБОТАЕТ! или это так?

Это действительно сработало, это не было случайностью. Но есть кое-что, что нужно увидеть.

Описание видео:

  1. Текущее количество = 5, это подтверждается нажатием кнопки рендеринга, по-прежнему 5.
  2. Затем мы переходим на другую страницу
  3. Вернуться на страницу счетчика, но счет все еще 5
  4. При нажатии кнопки изменения будет увеличиваться с 5

Да, переменная не очищается.

Это не очень хорошее поведение, потому что мы должны очистить его вручную, иначе оно испортит наше приложение.

Вот почему мы не можем использовать обычную переменную вне компонента React.

Использование useState

Это код, если мы используем useState

function Component() {
  const [count, setCount] = React.useState<number>(0);

  const mutateAndRender = () => {
    setCount((count) => count + 1);
    console.log(`${getTime()} | count: ${count}`);
  };

  return (
    <div>
      <h1>{count}</h1>
      <div className='mt-4 space-x-2'>
        <Button onClick={mutateAndRender} variant='light'>
          Add
        </Button>
      </div>
    </div>
  );
}

А это демо

Описан:ие видео:

Вы можете заметить, что счетчик console.log опаздывает на 1, пока не обращайте на него внимания.

  1. Нажимается кнопка «Добавить», затем счетчик добавляется и одновременно обновляется на экране.
  2. При переходе на другую страницу и обратно счетчик сбрасывается обратно на 0.

Итак, вкратце, useState выполняет 4 вещи:

  1. Объявление, путем объявления с использованием этого синтаксиса
   const [count, setCount] = React.useState<number>(0);
  1. Мутация и рендеринг, изменение значения и автоматический рендеринг изменений с помощьюsetCount
  2. Сохраняет данные при каждом повторном рендеринге → при вызове функции рендеринга useState не будет повторно объявлять значение счетчика.
  3. Сбрасывает значение, когда мы переходим на другую страницу, или обычно вызывается: когда компонент отключается.

Почему счет опаздывает

const mutateAndRender = () => {
  setCount((count) => count + 1);
  console.log(`${getTime()} | count: ${count}`);
};

Это потому, что функция setCount асинхронная.

После вызова функции необходимо время, чтобы обновить значение счетчика. Поэтому, когда мы немедленно вызываем console.log, он все равно возвращает старое значение.

Вы можете переместить console.log за пределы функции, чтобы он запускался при re-render ( Component())

function Component() {
    ...

    const mutateAndRender = () => {
      setCount((count) => count + 1);
    };

    console.log(`${getTime()} | count: ${count}`);

  return ...
}

3 Схемы действий

Вот обновленная диаграмма, теперь вы знаете, что делают useState и setState.

Резюме

Из этого поста мы узнали, что

  1. Мы не можем использовать обычный let, потому что React вызывает саму функцию Component для повторного рендеринга.
  2. Повторный рендеринг приведет к повторному запуску всего кода в функции Component, включая объявление переменной и функции, а также журналы консоли и вызовы функций.
  3. Использование ловушки useState поможет нам обновить переменную и номер на экране, сохраняя при этом данные между повторными рендерингами.

Источник:

#React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Vladimir Shaitan - Видео блог о frontend разработке и не только

Посмотреть