Найти тему
IT Hardcore

Batching и React

Вместо вступления: в интернетах нет наглядных примеров batching`а, попробуем это исправить.

Начнем с того, что изменение state в компоненте React вызывает его перерендер.

Попробуем изменить несколько state`ов.

const handleClick = () => {
setAge(19);
setName('
Vasiliy');
}

Происходит 1 ререндер.

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

Усложним пример и добавим несколько асинхронных операций:

const handleClick = () => {
setAge(19);
setName('Ivan');
setTimeout(() => {
setAge(20);
setName('Vasiliy');
}, 0)
setTimeout(() => {
setAge(21);
setName('Oleg');
}, 0)
}

2 ререндера. Синхронные операции объединяются отдельно от асинхронных, но есть нюанс. Механизм batching`а асинхронных операций добавлен только в 18 React.

Попробуем изменить время второго таймера.

const handleClick = () => {
setTimeout(() => {
setAge(20);
setName('Vasiliy');
}, 100)
setTimeout(() => {
setAge(21);
setName('Oleg');
}, 200)
}

2 ререндера. Таймеры завершаются в разное время.

Добавим небольшую синхронную задержку (имитируем сложные вычисления) на величину большую, чем срабатывание таймеров.

function sleep(second: number) {
let start = new Date().getTime();
const end = start + second * 1000;
while(start < end) {
start = new Date().getTime();
}
}
const handleClick = () => {
setTimeout(() => {
setName('Vasiliy');
setAge(20);
}, 500)
setTimeout(() => {
setAge(21);
setName('Oleg');
}, 1000)
asyncFunc().then(() => {
sleep(2);
setAge(19);
setName('Ivan');
})
}

2 ререндера. Асинхронная функция вызвает первый ререндер. Оба таймера, успевшие к завершению асинхронной функции, объединяются во второй ререндер.

Теперь изменим время таймеров.

const handleClick = () => {
setTimeout(() => {
setName('Vasiliy');
setAge(20);
console.log('timer 1')
}, 0)
setTimeout(() => {
setAge(21);
setName('Oleg');
console.log('timer 2')
}, 1)
asyncFunc().then(() => {
sleep(1);
setAge(19);
setName('Ivan');
console.log('async')
})
}

Оба таймера завершились до выполнения асинхронной функции. Но теперь каждый из них вызывает свой собственный ререндер. Результат: 3 ререндера.

Последний пример. Возьмем таймер, запускающий асинхронную функцию, простой таймер и асинхронную функцию, запускающую таймер:

const handleClick = () => {
setTimeout(() => {
setName('Vasiliy');
setAge(20);
asyncFunc().then(() => {
sleep(1);
setAge(21);
setName('Dmitriy');
})
}, 500)
setTimeout(() => {
setAge(22);
setName('Oleg');
}, 1500)
asyncFunc().then(() => {
sleep(1);
setAge(19);
setName('Ivan');
setTimeout(() => {
setAge(24);
setName('Vladimir');
}, 1000)
})
}

В порядке очереди Event Loop запускаются

  1. Внешняя асинхронная функция - 1 ререндер.
  2. Первый таймер (500мс) успевает к завершению первого шага. Он запускает внутреннюю асинхронную функцию. Все вместе - 2 ререндер.
  3. В 3 ререндере объединяются оставшиеся таймеры (1500мс и запущенный внутри асинхронной функции)

Надеюсь, что разобрал все возможные комбинации и сделал процесс batching`а более наглядным.

Для большего погружения советую посмотреть это видео.