짧은 일주일간 정말 많은 것을 배웠다.
처음엔 백엔드의 부분이라고 생각한 비동기 Part이지만, 배우면 배울수록 그 이유를 납득하게 된다.
처음보는 용어도 정말 많았다. CORS, SOP, Express.. 등
그 중에서 오늘은 React의 코드 흐름의 기반이 되는 배칭(Batching) 이라는
개념을 Zoom 시간에 물흐르듯이 배웠는데 중요하다고 생각이 되어 조금 다뤄볼 예정이다.
동기, 비동기 공부의 연장선이라고 볼 수 있다.
배칭(Batching)은 컴퓨팅에서 많은 작업을 한 번에 처리하기 위해 그룹으로 묶는 기술이다.
불필요한 리렌더링을 줄이기 때문에 작업의 처리량을 향상시키고 시스템 성능을 최적화하는 데 도움을 준다.
React가 더 나은 성능을 위해 여러 개의 state 업데이트를 하나의 리렌더링 (re-render)로 묶는 것이다.
또한 배칭은 비동기 작업 처리에도 적용될 수 있다.
예를 들어, 웹 서버에서 여러 요청을 동시에 처리해야 할 때 배칭을 사용하여 요청을 한 번에 처리할 수 있다.
이 또한, 응답 시간을 단축시키고 서버의 처리 능력을 향상시킨다.
배칭은 작업의 유형과 요구사항에 따라 다양한 방식으로 구현될 수 있다.
예를 들어, 작업을 큐에 넣고 정기적으로 배치 작업을 처리하는 방식이 있을 수 있다.
또는 작업을 동시에 처리하는 스레드 풀을 사용하여 병렬로 작업을 처리할 수도 있다.
아래는 예시 코드이다.
💡 배칭(batching) 참고 코드
import React, { useState, useEffect } from 'react';
import './style.css';
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('batching');
console.log('rendering', { text });
// React의 batching
// 리액트에 지원하는 렌더링 최적화 기술
// batching: 일괄 처리
// 여러 개의 상태변경 함수가 있음
// 이걸 하나씩 호출하는게 렌더링에 좋을까요? 효율적일까요?
// 리액트에서는 여러개의 상태변경함수가 호출될 때 이를 묶어서 한번의 리렌더링으로 처리합니다.
// 개발자가 조꼼 거지같이 코드를 쓰더라도 리액트에서 자체적으로 렌더링 최적화를 함
// 17버전에서는 Promise, setTimeout 같은 비동기 작업의 경우
// batching을 하지 않습니다. useEffect, eventHandler 모두 batching이 적용되지만
// 대신 18버전에서는 auto batching 여기서는 우리가 생각한대로
// 비동기작업도 배칭을 적용하도록 구현되었음.
useEffect(() => {
setText('!');
setText('!!');
setText('!!!');
fetch('https://koreanjson.com/posts/1').then((response) => {
setText('배칭이 안돼요1');
setText('배칭이 안돼요2');
setText('배칭이 안돼요3');
});
console.log('useEffect');
}, []);
const eventHandler = () => {
// 0. 리렌더링 스킵(batching 기능과 상관없음)
// 같은 값으로 상태를 갱신할 경우 2번까지만 리렌더링을
// 진행하고 리-렌더링을 스킵함.
setCount(1);
// 1. 갱신 취소
// 중간에 네트워크 요청하는 코드
// setCount(count + 1);
// setCount(count + 1);
// setCount(count + 1);
// 2.
// setCount(count + 1);
// setCount(count + 2);
// setCount(count + 3);
// 3. 함수형 업데이트
// setCount((prev) => prev + 1);
// setCount((prev) => prev + 1);
// setCount((prev) => prev + 1);
// 4. 함수형 업데이트
// setCount((prev) => prev + 1);
// setCount((prev) => prev + 2);
// setCount((prev) => prev + 3);
};
const setTimeoutHandler = () => {
setTimeout(() => {
setText('배칭이 안돼요1');
setText('배칭이 안돼요2');
setText('배칭이 안돼요3');
setText('배칭이 안돼요4');
}, 300);
};
return (
<React.Fragment>
<h1>{`text : ${text}`}</h1>
<h1>{`count : ${count}`}</h1>
<button onClick={eventHandler}>+</button>
<button onClick={setTimeoutHandler}>setTimeout</button>
</React.Fragment>
);
}
위 사진은 useEffect 일부 코드 부분만 build 한 결과 화면이다.
fetch를 거치기 전, setText는 마지막 텍스트 '!!!'만 출력하는 것을 볼 수 있다.
하지만,fetch로 비동기 작업을 하기 시작하면 순차적으로 텍스트가 3개 모두 출력되는 것을 볼 수 있다.
처음 렌더링 시, console.log('rendering', { text })가 실행되어 'rendering'과 현재 text 상태 값을 출력한다.
그리고 useEffect 훅이 실행된다.
useEffect 내부에서 setText를 통해 text 상태를 '!', '!!', '!!!'로 변경한다.
이때, setText 호출은 배치로 처리되지 않으므로 각 호출마다 컴포넌트가 다시 렌더링된다.
그래서 'rendering'이 3번이 아닌 일정하게 출력되지 않는 것을 볼 수 있다.
이후 fetch 함수가 호출되고 비동기 요청이 시작된다.
fetch 요청은 백그라운드에서 실행되고, 그 다음 코드의 실행을 차단하지 않는다.
따라서 fetch 요청이 완료되기 전까지는 다음 코드가 실행되지 않는다.
fetch 요청이 완료되면, fetch의 응답을 처리하는 콜백 함수가 실행된다.
콜백 함수 내에서 setText를 세 번 호출하여 text 상태를 '배칭이 안돼요1', '배칭이 안돼요2', '배칭이 안돼요3'로 순차적으로 변경한다.
이 때, setText 호출은 한 번의 배치로 처리되므로 컴포넌트는 한 번만 렌더링된다.
따라서, fetch 이전의 setText 호출은 여전히 배치로 처리되지 않고 각 호출마다 컴포넌트가 다시 렌더링되지만,
fetch 이후의 setText 호출은 한 번의 배치로 처리되어 컴포넌트는 한 번만 렌더링된다.
이로 인해 fetch 이후의 텍스트들이 순차적으로 출력되는 것이다.
배치 업데이트
React는 setText 호출과 같은 상태 변경에 대한 배치 업데이트를 수행한다.
배치 업데이트는 동일한 이벤트 루프 내에서 여러 상태 변경을 한 번에 처리하여 효율성을 높인다.
따라서 동일한 이벤트 루프에서 여러 번의 setText 호출이 있을 경우, 실제 렌더링은 단일 배치 업데이트로 처리될 수 있다.
setText 호출에 따른 컴포넌트의 실제 렌더링 횟수는 구현 세부 사항에 의해 결정된다.
일반적으로 setText 호출은 한 번의 배치 업데이트로 처리되므로, 리액트 엔진의 최적화 및 스케줄링
알고리즘에 따라 렌더링 횟수가 달라질 수 있습니다.
그래서 console.log('rendering', { text })가 정확히 두 번 출력되는 것은 보장되지 않는다.
두 번 이상 렌더링될 수도 있고, 더 적은 횟수로 렌더링될 수도 있다.
배칭
fetch를 이용한 비동기 요청은 일반적으로 배칭의 한 예이다.
배칭은 여러 작업을 하나로 묶어서 효율적으로 처리하는 것을 의미한다.
setText 호출은 배치로 처리되지 않고 순차적으로 실행되지만, 이 작업들이 fetch 요청 이후에
순차적으로 일어나므로 여러 작업을 묶어서 효율적으로 처리한다는 점에서 배칭의 개념을 적용할 수 있다.
'배치 업데이트'와 '배칭'의 차이점
React에서의 "배치 업데이트"는 동일한 이벤트 루프에서 여러 상태 변경을 한 번에 처리하는 최적화 기법이다.
이는 여러 번의 setText 호출을 하나의 업데이트로 묶어서 처리하여 성능을 향상시키는 것을 의미한다.
반면에 "배칭"은 일련의 작업들을 묶어서 한 번에 처리하는 개념이다.
여러 작업을 단일 작업으로 묶어서 처리함으로써 성능 향상을 이루는 기법이다.
따라서, setText 호출은 배치 업데이트로 처리될 수 있지만, fetch를 통한 비동기 요청은 단일 배치로 처리되지 않는다.
fetch 요청은 독립적인 비동기 작업이므로, fetch 이후의 setText 호출은 순차적으로 실행되며 컴포넌트가 다시 렌더링된다. 이는 fetch를 통한 비동기 요청이 배칭의 개념에는 적용되지 않는다는 의미이다.
✅ 배치 업데이트
React의 내부 메커니즘으로서 상태 변경을 효율적으로 처리하기 위한 기법이다.
✅ 배칭
여러 개의 API 요청을 한 번에 보내는 것처럼 작업들을 묶어서 처리하는 기법이다.