Недавно я узнал, что React визуализирует снимок пользовательского интерфейса до того, как будет вызвана функция set для изменения состояния и будет выполнен повторный рендеринг. Весь JSX работает с предыдущим состоянием. Например:
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState('');
function handleClick() {
setName(prompt('What is your name?'));
alert(`Hello, ${name}!`);
}
return (
<button onClick = {handleClick}>
Greet
</button>
);
}
После указания имени в поле подсказки предупреждение по-прежнему гласит: Hello, !. Это связано с тем, что предупреждение берет имя из первого рендеринга, которое имеет значение null.
Согласно приведенному выше описанию этот код:
import { useState } from 'react';
export default function Form() {
const [to, setTo] = useState('Alice');
const [message, setMessage] = useState('Hello');
function handleSubmit(e) {
e.preventDefault();
alert(`You said ${message} to ${to}`);
}
return (
<form onSubmit = {handleSubmit}>
<label>
To:{' '}
<select
value = {to}
onChange = {e => setTo(e.target.value)}>
<option value = "Alice">Alice</option>
<option value = "Bob">Bob</option>
</select>
</label>
<textarea
placeholder = "Message"
value = {message}
onChange = {e => setMessage(e.target.value)}
/>
<button type = "submit">Send</button>
</form>
);
}
Таким образом, когда срабатывает событие onchange, изменяется состояние как переменной, так и сообщения, и когда форма отправляется, предупреждение должно отображать: You said Hello to Alice, скорее, оно показывает предупреждение, которое включает в себя новые to и новые message.
Где я ошибаюсь?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


В React.js setName() и другие изменения состояния выполняются не напрямую, а асинхронно. Это приводит к тому, что в вашем коде выводится старое имя. Простым решением было бы:
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState('');
function handleClick() {
const newName = prompt('What is your name?');
setName(newName);
alert(`Hello, ${newName}!`);
}
return (
<button onClick = {handleClick}>
Greet
</button>
);
}
Извините, я не спросил об этом. Я хотел бы узнать, почему не возвращается второй код: вы сказали «Привет» Алисе.
Когда вызывается ваша функция React, DOM генерируется на основе ее текущего состояния. Если где-то в коде активируется перехватчик или создается событие, которое изменит DOM, произойдет повторный рендеринг. Таким образом, каждый раз, когда вы видите в коде «setX()», этот фрагмент кода будет «выполняться» не при текущем рендеринге, а через некоторое время после этого рендеринга и до следующего* рендеринга. Вот почему происходит бесконечный рендеринг, если вы вызываете хук при каждом рендеринге.
Обновлено: * «следующий» рендеринг означает более поздний рендеринг. Технически, если вы видите рендеринг, а затем вызывается перехват, потенциально между «этим» текущим рендерингом и «следующим» рендерингом может быть рендеринг.
Ваше понимание жизненного цикла рендеринга React и управления состоянием не совсем правильное. Событие onchange срабатывает всякий раз, когда входные данные изменяются. Когда это событие срабатывает, оно асинхронно ставит состояние в очередь на обновление через setTo и setMessage. Пока ничто не блокирует цикл событий, он будет обновлять состояние и повторно отображать (если есть что перерисовывать). Состояние на самом деле обновляется в следующем цикле цикла событий, но это будет выполняться в фоновом режиме, прежде чем вы успеете это заметить. Технически он не обновляется в вашей onchange рутине. В вашем втором примере нет ничего, что нужно было бы перерисовывать, поэтому вы не видите изменений в пользовательском интерфейсе. Однако состояние ЕСТЬ обновляется.
Причина, по которой вы видите Hello, ! в первом примере, заключается в том, что метод handleClick блокирует цикл событий до его завершения, что означает, что состояние name фактически не обновится до следующего цикла цикла событий.
Короче говоря, цикл событий выполняет несколько циклов, пока вы манипулируете полями ввода пользовательского интерфейса, давая React время для обновления переменных состояния. К моменту запуска события для отправки формы вы можете быть уверены, что все предыдущие изменения состояния завершены.
Если вы хотите увидеть это с некоторой задержкой, вы можете просто изменить обработчик событий onchange на следующий:
{e => {console.info(to); console.info(message); setMessage(e.target.value)}}
В приведенном выше случае вы увидите предыдущее состояние переменных to и message до их обновления. Если вы внесете несколько изменений для запуска нескольких журналов консоли, вы увидите, что состояние действительно обновляется перед отправкой формы.
Вы «ставите в очередь» изменения состояния, помещая новое событие в очередь событий. JavaScript является однопоточным, поэтому цикл событий удаляет из очереди любое событие, которое находится следующим в очереди. Но если вы просто нажимаете на элементы в DOM, цикл событий наверняка выполняет события в фоновом режиме, потому что было бы крайне неэффективно блокировать, пока вы не нажмете определенную кнопку, чтобы обработать все изменения состояния.
Вот полный пример, который покажет вам, как состояние действительно обновляется при каждом событии onchange.
import { useState } from 'react';
export default function Form() {
const [to, setTo] = useState('Alice');
const [message, setMessage] = useState('Hello');
function handleSubmit(e) {
e.preventDefault();
alert(`You said ${message} to ${to}`);
}
return (
<form onSubmit = {handleSubmit}>
<label>
To:{' '}
<select
value = {to}
onChange = {e => setTo(e.target.value)}>
<option value = "Alice">Alice</option>
<option value = "Bob">Bob</option>
</select>
</label>
<textarea
placeholder = "Message"
value = {message}
onChange = {e => setMessage(e.target.value)}
/>
<h1>To: {to}</h1>
<p>Message: {message}</p>
<button type = "submit">Send</button>
</form>
);
Добавление h1 и p не требуется, поскольку входные данные формы буквально используют текущие значения состояния переменных to и message, но размещение этих значений состояния за пределами входных данных может помочь вам увидеть, что происходит.
Я хотел бы узнать, почему не возвращается второй код: вы сказали «Привет» Алисе.
Потому что к моменту отправки формы изменения состояния уже обработаны. Это связано с тем, что событие onchange обновляет состояние асинхронно, и эти асинхронные события завершатся до того, как будет выполнено событие отправки формы.
До сих пор не могу получить первую часть. Можете ли вы уточнить? не знает о цикле событий.
Цикл событий — это суть вашего замешательства. Я добавил для вас немного больше контекста. Я надеюсь, что это помогает.
Можем ли мы так сказать: когда запускается onChange, компонент перерисовывается. Но этот повторный рендеринг не вызывает функцию handleSubmit, и, следовательно, никаких предупреждений не отображается. В этом случае, когда onChange запускается два раза, компонент визуализируется дважды, каждый раз меняя значение to и message в той последовательности, в которой onChange запускается первым. Теперь, когда срабатывает onSubmit, происходит третий повторный рендеринг с новым состоянием и сообщением, и handleSubmit вызывается с этими двумя новыми значениями. Это правильный путь?
Событие onChange не обновляет состояние напрямую. Он вызывает метод setState, который асинхронно обновляет состояние, и когда состояние компонента React обновляется, компонент выполняет повторную визуализацию (если есть что перерисовывать). Да, повторный рендеринг не приведет к выполнению предупреждения. Единственное, что вызывает alert, — это ваша handleSubmit рутина, которая происходит только тогда, когда вы нажимаете кнопку «Отправить». К тому времени, как вы нажмете эту кнопку, React обработает все инициированные асинхронные изменения состояния.
Да, я имею в виду, что onChange вызывает setState для изменения состояния и повторного рендеринга.
Но верно ли то, что компонент визуализируется трижды перед предупреждением?
Технически onChange запускает повторный рендеринг, потому что обратите внимание, что свойство value ваших входных данных — это значение ваших переменных состояния. Если бы вы не обновили состояние в onChange, вы бы заметили, что независимо от того, что вы делаете с входными данными, их значения остаются прежними. Такое поведение будет раздражать пользователя, но вам было бы полезно попробовать понаблюдать.
Да, это правда, что компонент перерисовывается каждый раз, когда вы меняете входные данные. Это связано с тем, что вы обновляете состояние компонента, и компонент отображает эти переменные состояния как значения для ваших входных данных. Обновления состояния не обязательно означают повторный рендеринг. В целях оптимизации React выполняет повторный рендеринг только тогда, когда следующее состояние рендеринга будет отличаться от предыдущего. Но в вашем случае любое изменение переменных состояния приведет к повторному рендерингу.
Давайте продолжим обсуждение в чате.
Ваш вопрос не совсем ясен, имеет смысл, верно, что показано новое значение? Пожалуйста, включите минимально воспроизводимый пример и поясните реальную проблему.