Давайте сначала подумаем над общим вопросом,setState是同步还是异步?
Думай глубже,useState是同步还是异步呢?
Давайте напишем несколько демо, чтобы проверить это.
Первый взгляд на useState
В случае синхронного и асинхронного два состояния использования выполняются последовательно.Пример
function Component() {
const [a, setA] = useState(1)
const [b, setB] = useState('b')
console.log('render')
function handleClickWithPromise() {
Promise.resolve().then(() => {
setA((a) => a + 1)
setB('bb')
})
}
function handleClickWithoutPromise() {
setA((a) => a + 1)
setB('bb')
}
return (
<Fragment>
<button onClick={handleClickWithPromise}>
{a}-{b} 异步执行
</button>
<button onClick={handleClickWithoutPromise}>
{a}-{b} 同步执行
</button>
</Fragment>
)
}
В заключение:
- при нажатии
同步执行
кнопка, только повторно рендерить один раз - при нажатии
异步执行
Когда кнопка нажата, она отображается дважды
В случае синхронного и асинхронного выполнения одно и то же useState дважды подрядПример
function Component() {
const [a, setA] = useState(1)
console.log('a', a)
function handleClickWithPromise() {
Promise.resolve().then(() => {
setA((a) => a + 1)
setA((a) => a + 1)
})
}
function handleClickWithoutPromise() {
setA((a) => a + 1)
setA((a) => a + 1)
}
return (
<Fragment>
<button onClick={handleClickWithPromise}>{a} 异步执行</button>
<button onClick={handleClickWithoutPromise}>{a} 同步执行</button>
</Fragment>
)
}
- при нажатии
同步执行
Когда кнопка нажата, setA выполняется дважды, но объединенный рендеринг выполняется один раз, печатая 3 - при нажатии
异步执行
Когда кнопка нажата, каждый из двух setA рендерится один раз и печатает 2 и 3 соответственно.
Посмотрите еще раз на setState
В случае синхронного и асинхронного выполните два setState последовательноПример
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 1,
b: 'b',
}
}
handleClickWithPromise = () => {
Promise.resolve().then(() => {
this.setState({...this.state, a: 'aa'})
this.setState({...this.state, b: 'bb'})
})
}
handleClickWithoutPromise = () => {
this.setState({...this.state, a: 'aa'})
this.setState({...this.state, b: 'bb'})
}
render() {
console.log('render')
return (
<Fragment>
<button onClick={this.handleClickWithPromise}>异步执行</button>
<button onClick={this.handleClickWithoutPromise}>同步执行</button>
</Fragment>
)
}
}
- при нажатии
同步执行
кнопка, только повторно рендерить один раз - при нажатии
异步执行
Когда кнопка нажата, она отображается дважды
То же, что и результат useState
В случае синхронного и асинхронного выполнения одно и то же setState дважды подрядПример
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
a: 1,
}
}
handleClickWithPromise = () => {
Promise.resolve().then(() => {
this.setState({a: this.state.a + 1})
this.setState({a: this.state.a + 1})
})
}
handleClickWithoutPromise = () => {
this.setState({a: this.state.a + 1})
this.setState({a: this.state.a + 1})
}
render() {
console.log('a', this.state.a)
return (
<Fragment>
<button onClick={this.handleClickWithPromise}>异步执行</button>
<button onClick={this.handleClickWithoutPromise}>同步执行</button>
</Fragment>
)
}
}
- при нажатии
同步执行
Когда кнопка нажата, два setStates объединяются, выполняется только последний раз, и печатается 2 - при нажатии
异步执行
Когда кнопка нажата, каждый из двух setState будет отображаться один раз и печатать 2 и 3 соответственно.
这里跟useState不同,同步执行时useState也会对state进行逐个处理,而setState则只会处理最后一次
Почему между синхронным и асинхронным выполнением получаются разные результаты?
Это включает в себя механизм пакетного обновления реакции, который объединяет обновления.
- Во-первых, зачем вам нужны обновления слияния?
Если обновление не будет объединено, компоненту придется выполнять повторный рендеринг каждый раз, когда выполняется useState, что приведет к недопустимому рендерингу и пустой трате времени (поскольку последний рендеринг перезапишет все предыдущие эффекты рендеринга). Таким образом, react будет собирать некоторые useState/setState, которые можно обновлять вместе, а также объединять и обновлять.
- как слить обновление
Здесь react использует механизм транзакций.
Пакетное обновление в React реализовано через «Транзакцию». В части исходного кода React, посвященной транзакции, роль транзакции объясняется большим абзацем текста и изображением персонажа:
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
В просторечии сегмент логики добавляется до и после фактического useState/setState, чтобы обернуть его. Пока setState в той же транзакции будет объединена (обратите внимание, что useState не будет объединять состояние) обработка.
- Почему setTimeout не может выполнять транзакционные операции
Из-за механизма делегирования событий в реакции событие, выполняемое путем вызова onClick, находится под контролем реакции.
И setTimeout находится вне контроля реакции, и реакция не может добавить логику транзакции до и после кода setTimeout (если только реакция не перезаписывает setTimeout).
Так что при встречеsetTimeout/setInterval/Promise.then(fn)/fetch 回调/xhr 网络回调
Реакция вышла из-под контроля.
Соответствующий исходный код реакции выглядит следующим образом:
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
flushSyncCallbackQueue()
}
executeContext представляет текущую стадию реакции, а NoContext можно понимать как состояние, в котором у реакции нет работы. И flushSyncCallbackQueue будет вызывать наш this.setState синхронно, а это значит, что наше состояние будет обновляться синхронно. Итак, мы знаем, что когда executeContext имеет значение NoContext, наш setState является синхронным.
Суммировать
Подведем итоги вышеописанных экспериментов:
- В обычном потоке событий реакции (например, onClick и т. д.)
- setState и useState выполняются асинхронно (результат состояния не обновляется сразу)
- Выполните setState и useState несколько раз, вызовите повторный рендеринг только один раз.
- Разница в том, что setState объединит состояние, а useState — нет.
- в асинхронных событиях, таких как setTimeout, Promise.then и т. д.
- setState и useState выполняются синхронно (результат немедленного обновления состояния)
- Выполните setState и useState несколько раз, и каждый раз, когда выполняются setState и useState, рендеринг будет вызываться один раз.
Это немного сбивает с толку, просто напишите код и испытайте его на себе ~