"매 setState 매 합쳐서 매 한 번 render". React 18에서 도입된 automatic batching은 모든 update (promise, setTimeout, native event)를 single re-render로 group한다. React 17 이전엔 React event handler 안에서만 batching이었음 — 이제 전부 자동.
매 핵심
매 동작
매 동일 tick 안의 setState calls → React가 모아서 하나의 commit 으로 처리.
결과: less render, less DOM mutation, less component lifecycle invocation.
React 19도 동일 model 유지 + Compiler 가 추가 optimization.
React 17 vs 18+
17: onClick 안 batching O / setTimeout, Promise.then 안 batching X.
18+: 매 모든 context batching O.
Opt-out: flushSync() 로 immediate render 강제.
매 응용
Form state with multiple fields — 매 single render.
Async fetch chain — 매 single render after Promise.
import{useState}from'react';functionForm() {const[name,setName]=useState('');const[email,setEmail]=useState('');const[age,setAge]=useState(0);asyncfunctionhandleSubmit() {constres=awaitfetch('/api/me');constdata=awaitres.json();// 18+: 매 3 setState 매 single render
setName(data.name);setEmail(data.email);setAge(data.age);}return<buttononClick={handleSubmit}>Load</button>;}
flushSync to opt out
import{flushSync}from'react-dom';functionhandleClick() {flushSync(()=>{setCount(c=>c+1);});// 매 DOM 업데이트 매 done — measure 가능
consth=listRef.current.scrollHeight;flushSync(()=>{setHeight(h);});}
Custom hook with batched async
functionuseAsyncForm<T>(){const[data,setData]=useState<T|null>(null);const[loading,setLoading]=useState(false);const[error,setError]=useState<Error|null>(null);asyncfunctionload(fn:()=>Promise<T>){setLoading(true);setError(null);try{constres=awaitfn();// 18+: 매 두 setState 매 single render
setData(res);setLoading(false);}catch(e){setError(easError);setLoading(false);}}return{data,loading,error,load};}
Reducer alternative
const[state,dispatch]=useReducer(reducer,initial);// 매 single dispatch 매 multi-field update — natural batching
dispatch({type:'SUBMIT_OK',payload: data});
Avoid stale closure with functional updates
// 매 sequential update — 매 함수형 사용
setCount(c=>c+1);setCount(c=>c+1);// → +2, not +1