Каково использование функционального синтаксиса setState в функциональных компонентах реакции?

речь идет о функциональных компонентах, имеющих useState

скажем

const [age, setAge] = useState(0)

теперь скажем при обновлении age я должен использовать предыдущий age

В документах React упоминается что-то под названием ФУНКЦИОНАЛЬНЫЕ ОБНОВЛЕНИЯ, где вы можете передать функцию, а аргументом для нее будет предыдущее значение состояния, например.

setState((previousAge) => previousAge + 1)

зачем мне это делать, когда я могу просто сделать

setState(previousAge + 1)

в чем преимущества использования функционала setState,

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

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

Randy Casburn 13.12.2020 16:53

Просто для ясности, понятие «функциональные обновления» (функциональное программирование) подразумевает определенные вещи. Вы должны знать, что внутри объекты состояния реакции на самом деле хранятся в виде серии запомненных функций. Таким образом, используя функцию в качестве параметра setState, вы просто добавляете еще одну чистую функцию в стек мемоизированных функций.

Randy Casburn 13.12.2020 17:02

См., например. stackoverflow.com/questions/53024496/… или stackoverflow.com/questions/56782079/react-hooks-stale-state для примеров

Bergi 13.12.2020 19:51
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
3
357
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Потому что, если вы этого не сделаете, вы обнаружите, что в какой-то момент вы получите старое значение для age. Проблема в том, что иногда то, что вы предлагаете, срабатывает. Но иногда этого не будет. Это может не сломать ваш текущий код сегодня, но может сломать другой код, который вы написали несколько недель назад, или ваш текущий код через несколько месяцев.

Симптом действительно странный. Вы можете напечатать значение переменной внутри компонента jsx, используя синтаксис {x}, а затем распечатать ту же переменную, используя console.info, после рендеринга компонента jsx (не раньше) и обнаружить, что значение console.info устарело — console.info, которое происходит после рендеринга. каким-то образом имеют более старое значение, чем рендеринг.

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

Проблемы могут возникнуть в зависимости от того, как быстро/часто вызывается ваш сеттер.

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

Простой пример:

function App() {
    const [counter, setCounter] = useState(0);
    const incWithClosure = () => {
        setCounter(counter + 1);
    };
    const incWithUpdate = () => {
        setCounter(oldCounter => oldCounter + 1);
    };

    return (<>
        <button onClick = {_ => { incWithClosure(); incWithClosure(); }}>
            Increment twice using incWithClosure
        </button>
        <button onClick = {_ => { incWithUpdate(); incWithUpdate(); }}>
            Increment twice using incWithUpdate
        </button>
        <p>{counter}</p>
    </>);
}

Обе кнопки дважды вызывают один из методов увеличения. Но мы наблюдаем:

  • Первая кнопка будет увеличивать счетчик только на 1
  • Вторая кнопка увеличит счетчик на 2, что, вероятно, является желаемым результатом.

Когда это может произойти?

  • Очевидно, что если incWithClosure вызывается несколько раз сразу после друг друга
  • Если задействованы асинхронные задачи, это может легко произойти (см. ниже).
  • Возможно, если у React будет много работы, его алгоритмы планирования могут решить обрабатывать несколько очень быстрых кликов, используя один и тот же обработчик событий.

Пример с асинхронной работой (имитация загрузки ресурса):

function App() {
  const [counter, setCounter] = useState(0);
  const incWithClosureDelayed = () => {
    setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
  };
  const incWithUpdateDelayed = () => {
    setTimeout(() => {
      setCounter((oldCounter) => oldCounter + 1);
    }, 1000);
  };

  return (
    <>
      <button onClick = {(_) => incWithClosureDelayed()}>
        Increment slowly using incWithClosure
      </button>
      <button onClick = {(_) => incWithUpdateDelayed()}>
        Increment slowly using incWithUpdate
      </button>
      <p>{counter}</p>
    </>
  );
}

Нажмите на первую кнопку дважды (в течение одной секунды) и обратите внимание, что счетчик увеличивается только на 1. Вторая кнопка имеет правильное поведение.

Ответ принят как подходящий

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

Почему ломается и когда

Функциональные компоненты React — это просто замыкания, значение состояния, которое у вас есть в замыкании, может быть устаревшим — это означает, что значение внутри замыкания не соответствует значению, которое находится в состоянии React для этого компонента, это может произойти в следующие случаи:

1- асинхронные операции (В этом примере нажмите медленное добавление, а затем несколько раз нажмите кнопку добавления, позже вы увидите, что состояние было сброшено до того, что было внутри замыкания, когда была нажата кнопка медленного добавления)

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick = {() => {
          setCounter(counter + 1);
        }}
      >
        immediately add
      </button>
      <button
        onClick = {() => {
          setTimeout(() => setCounter(counter + 1), 1000);
        }}
      >
        Add
      </button>
    </>
  );
};

2- Когда вы вызываете функцию обновления несколько раз в одном закрытии

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick = {() => {
          setCounter(counter + 1);
          setCounter(counter + 1);
        }}
      >
        Add twice
      </button>
   
    </>
  );
}

спасибо за прикрепление рабочего примера.

ashish singh 13.12.2020 19:42

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

Dorji Tshering 08.07.2022 15:10

Другие вопросы по теме