Интервьюер: «В ответ
setState
Это синхронно или асинхронно? "
я: "Асинхронный,setState
Невозможно получить результат сразу. "
Интервьюер: «В каком сценарии он асинхронный, но может ли он быть синхронным, и в каком сценарии он синхронный?»
Я:"......"
setState
Это действительно асинхронно?
Я только смотрел на это в течение последних двух днейsetState
Часть кода реализации, здесь я выскажу свое личное мнение, может быть больше текста или картинок, нетерпеливые студенты могут пропустить, чтобы увидеть резюме (Исходная версия 16.4.1).
Прежде чем смотреть на это, чтобы облегчить понимание и упростить процесс, мы по умолчанию используем внутренний код реакции на выполнение.performWork
,performWorkOnRoot
,performSyncWork
,performAsyncWork
Когда эти четыре метода есть, React обновляется и воздействует на пользовательский интерфейс.
Синтетическое событиеsetState
Прежде всего, вы должны понимать, что такое синтетические события.Чтобы решить проблемы с кроссплатформенностью и совместимостью, react инкапсулирует набор механизмов событий сам по себе для проксирования нативных событий, таких как вjsx
общий вonClick
,onChange
Это синтетические события.
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
в синтетических событияхsetState
Метод записи более распространен, щелкните событие, чтобы изменитьthis.state.val
государственная стоимостьincrement
Вы можете увидеть стек вызовов, нажав точку останова в событии.Здесь я размещаю блок-схему, которую я нарисовал:
dispatchInteractiveEvent
прибытьcallCallBack
Пока это обработка и выполнение синтетических событий, начиная сsetState
прибытьrequestWork
это вызовthis.setState
Логика здесь в основном выглядитrequestWork
Эта функция (изdispatchEvent
прибытьrequestWork
Стек вызовов принадлежитinteractiveUpdates$1
изtry
кодовый блок, упомянутый ниже).
function requestWork(root, expirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpiration(expirationTime);
}
}
существуетrequestWork
В трех ветвях есть три ветви if и два метода.performWorkOnRoot
а такжеperformSyncWork
, что является нашей функцией обновления по умолчанию, но в синтетическом событии берется вторая ветка if, а во второй ветке два признакаisBatchingUpdates
а такжеisUnbatchingUpdates
Оба начальных значенияfalse
,Но когдаinteractiveUpdates$1
ЦКisBatchingUpdates
установить какtrue
, следующееinteractiveUpdates$1
код:
function interactiveUpdates$1(fn, a, b) {
if (isBatchingInteractiveUpdates) {
return fn(a, b);
}
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
if (!isBatchingUpdates && !isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
// Synchronously flush pending interactive updates.
performWork(lowestPendingInteractiveExpirationTime, false, null);
lowestPendingInteractiveExpirationTime = NoWork;
}
var previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
var previousIsBatchingUpdates = isBatchingUpdates;
isBatchingInteractiveUpdates = true;
isBatchingUpdates = true; // 把requestWork中的isBatchingUpdates标识改为true
try {
return fn(a, b);
} finally {
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
в этом методеisBatchingUpdates
установлен вtrue
, в результате чегоrequestWork
метод,isBatchingUpdates
дляtrue
,ноisUnbatchingUpdates
даfalse
, и был возвращен непосредственно.
Куда уходит логика возврата?Вернулась наконец.interactiveUpdates
Этот метод, присмотритесь, этот метод имеетtry finallyГрамматика, front-end студенты на самом деле используются меньше, короче, они будут выполняться в первую очередьtry
операторы в блоке кода перед выполнениемfinally
код в , покаfn(a, b)
находится в блоке кода попытки, только что упомянутом вrequestWork
Именно эта fn возвращается в (упомянутом выше从dispatchEvent
прибытьrequestWork
весь стек вызовов).
так что когда тыincrement
вызыватьsetState
Когда я позже перехожу к console.log, он принадлежитtry
Выполнение в блоке кода, но поскольку это синтетическое событие, состояние блока кода попытки не обновляется после выполнения блока кода, поэтому введенный вами результат является тем, который был до обновления.state
Значение, это привело к так называемому «асинхронному», но когда выполняется ваш блок Try Code (то есть вашего приращения синтетического события), на этот раз будет выполненоfinally
код вfinally
казнен вperformSyncWork
метод, на этот раз обновит вашstate
и отображается в пользовательском интерфейсе.
Во-вторых, в функции жизненного циклаsetState
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
Стек вызовов setState в функции ловушки:
По сути, это все равно что синтетические события, когдаcomponentDidmount
При выполнении внутри реакции нет обновления, после выполненияcomponentDidmount
иди позжеcommitUpdateQueue
возобновить. Это приводит вас кcomponentDidmount
серединаsetState
После перехода в console.log результатом остается значение до обновления.
3. В исходном событииsetState
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
Нативные события относятся к нереагирующим синтетическим событиям, нативным прослушивателям событий.addEventListener
, или вы можете использовать нативные js, jq напрямуюdocument.querySelector().onclick
Эта форма событий привязки является собственным событием.
requestWork
,существуетrequestWork
из-заexpirationTime === Sync
Причина, только что ушлаperformSyncWork
Обновление не похоже на возврат в синтетическом событии или функции-ловушке, поэтому, когда вы устанавливаетеState в нативном событии, вы можете синхронно получать обновленное значение состояния.
В-четвертых, в setTimeoutsetState
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
существуетsetTimeout
входитьsetState
Это не одна сцена, вам решать, потому что вы можетеsetTimeout
, может быть в функции ловушкиsetTimeout
, также в нативном событииsetTimeout
, но какой бы сценарий это ни был, исходя изevent loopпод модель,setTimeout
идти посерединеsetState
Всегда получайте последнее значение состояния.
Возьмите каштан, как в предыдущем синтетическом событии, так как выsetTimeout(_ => { this.setState()}, 0)
вtry
кодовый блок, когда выtry
блок кода выполняется дляsetTimeout
, кинуть в очередь, и не выполнять, а выполнить сначалаfinally
Кодовый блок и т.д.finally
законченный,isBatchingUpdates
снова сталfalse
, что приводит к окончательному выполнению очередиsetState
когда,requestWork
Ходьба такая же, как родное событиеexpirationTime === Sync
Если ветвь, то производительность будет такой же, как и у собственного события, а самое последнее значение состояния можно будет получить синхронно.
Пятерки,setState
Массовое обновление в
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
Результат выше заканчивается равным 1, вsetState
Когда реакция создастupdateQueue
,пройти черезfirstUpdate
,lastUpdate
,lastUpdate.next
поддерживать очередь обновлений, в финалеperformWork
, тот же ключ будет перезаписан, только последнийsetState
Чтобы обновить, вот часть кода реализации:
function createUpdateQueue(baseState) {
var queue = {
expirationTime: NoWork,
baseState: baseState,
firstUpdate: null,
lastUpdate: null,
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null
};
return queue;
}
function appendUpdateToQueue(queue, update, expirationTime) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
if (queue.expirationTime === NoWork || queue.expirationTime > expirationTime) {
// The incoming update has the earliest expiration of any update in the
// queue. Update the queue's expiration time.
queue.expirationTime = expirationTime;
}
}
взгляните 🌰
class App extends React.Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}
render() {
return <div>{this.state.val}</div>
}
}
В сочетании с приведенным выше анализом в функции ловушкиsetState
Обновленное значение не может быть получено немедленно, поэтому 0 выводится в течение первых двух раз. Когда выполнение достигаетsetTimeout
При входе значения двух предыдущих состояний были обновлены, т.к.setState
стратегия пакетного обновления,this.state.val
Действителен только в последний раз, он равен 1, а вsetTimmout
серединаsetState
Результат обновления можно получить синхронно, поэтомуsetTimeout
Два раза в выводе 2, 3 конечный результат0, 0, 2, 3
.
Суммировать :
setState
"Асинхронный" только в синтетических событиях и хуках, в нативных событиях иsetTimeout
синхронизированы.setState
«Асинхронность» не означает, что внутренняя реализация реализована асинхронным кодом, на самом деле процесс и код, исполняемый сам по себе, являются синхронными, но последовательность вызова синтетических событий и функций-ловушек предшествует обновлению, так что синтетические события а хуки-функции сразу получить нельзя.К обновленному значению формируется так называемая "асинхронность".Конечно, обновленный результат можно получить через callback во втором параметре setState(partialState, callback).setState
Оптимизация пакетного обновления также основана на «асинхронности» (синтетические события, функции ловушек) и не будет обновляться пакетами в собственных событиях и setTimeout.В «асинхронном», если одно и то же значение повторяется несколько разsetState
,setState
Стратегия пакетного обновления перезапишет его, возьмет последнее выполнение, если оно одновременноsetState
Несколько отдельных значений, которые объединяются и обновляются пакетами при обновлении.
Вышеизложенное является моим поверхностным пониманием после прочтения части кода, а анализ деталей исходного кода меньше, в основном, чтобы все понялиsetState
Какой процесс и результаты произошли в разных сценариях и разных методах написания, я надеюсь помочь всем, потому что это личное понимание и мнение, если что-то не так, пожалуйста, укажите и обсудите вместе.
Кроме того, реклама для друга:
Друг организовал волну вступительных постов и разместил их наgithub, желающие могут связаться с cXE3MjcwNDAxNDE=