Automatic Batching, startTransition 과 같은 새로운 API, Suspense 를 지원하는 SSR 등, React 18 의 새로운 기능들을 훑어보자
먼저 React 18의 새로운 기능들을 사용하기 위해서는 createRoot API 를 사용해야한다.
// 이전
import React from 'react';
import ReactDOM from 'react-dom';
// render 사용
ReactDOM.render(
<App />,
document.getElementById('root')
)
// react 18
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<App />,
)
기존 버전에서는 render 만을 사용해 root 를 생성했는데 React 18 버전에서는 creatRoot 라는 새로운 Root API 를 통해 root 를 생성할 수 있게 되었다.
CRA 대신 Vite를 사용해 프로젝트를 생성한 경우 자동으로 createRoot 를 사용하여 프로젝트가 생성된다!
React 18 의 새로운 주요 기능들
Automatic Batching
Concurrent Feature
New Hooks
Automatic Batching 은 여러 상태 업데이트를 통합하여 단일 리렌더링으로 처리하여 리렌더링 성능을 개선한 기능이다.
React 18 이전엔 useState, setState 를 통해 상태를 업데이트하고 업데이트된 결과를 바탕으로 리렌더링을 진행했다. 이때, state 의 개수가 많아지면 그만큼 리렌더링 숫자도 늘어나고, 불필요한 리렌더링의 수도 늘어날 수 있다.
그래서 도입된 기능이 배치처리(batch updating)이다.
배치처리는 여러 상태 업데이트들이 발생해도 상태를 하나로 통합해 리렌더링을 진행하는 방식이다. React 18 이전 버전에서는 이벤트 핸들러와 생명주기 메서드를 사용할 땐 배치처리를 지원하지만 콜백 함수가 포함된 경우 배치처리를 지원하지 않았다.
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
setCount((c) => c + 1); // 아직 리렌더링 안함
setFlag((f) => !f); // 아직 리렌더링 안함
// React는 이 함수가 끝나면 리렌더링 (batching)
};
return (
<div>
<button onClick={handleClick}>Next</button>
<h1>{count}</h1>
</div>
);
React가 자동으로 배칭 하기 떄문에 자동 배칭 기능을 사용하기 위해서 우리가 따로 해야할 작업은 없다. React 18 버전의 createRoot 를 통해, 모든 업데이트들은 어디서 왔는가와 무관하게 자동으로 배칭된다.
이전에는 fetch 등 비동기 함수에서 상태 업데이트 할 때는 배치처리가 안되는 문제가 존재했다.
하지만 createRoot 를 사용하면 비동기 함수 내에서도 배치처리가 제대로 되는 것을 확인할 수 있다.
const [one, setOne] = useState(0);
const [two, setTwo] = useState(0);
const onClick = () => {
fetch('https://jsonplaceholder.typicode.com/posts/1').then((res) => {
setOne(one + 1);
setTwo(two + 1);
});
};
console.log('리렌더링');
return (
<>
<button onClick={onClick}>Fetch</button>
<h1>{one}</h1>
<h1>{two}</h1>
</>
);
하지만 이벤트 핸들러 내에서 일반적으로 상태 업데이트를 하는 것과 콜백 함수에서 상태 업데이트를 하는 것이 중복되면 리렌더링이 상태의 개수만큼 발생하는 것을 확인할 수 있다.
setOne(one + 1); // 일반 상태 업데이트
fetch('https://jsonplaceholder.typicode.com/posts/1').then((res) => {
setTwo(two + 1); // 콜백 함수 내에서 상태 업데이트
});
};
console.log('리렌더링');
return (
<>
<button onClick={onClick}>Fetch</button>
<h1>{one}</h1>
<h1>{two}</h1>
</>
);
만약 Automatic Batching 을 방지하고자 한다면 react-dom 의 flushSync() 함수를 사용하면 된다.
const [one, setOne] = useState(0);
const [two, setTwo] = useState(0);
const onClick = () => {
flushSync(() => {
setOne(one + 1);
});
flushSync(() => {
setTwo(two + 1);
});
// 단, 하나의 flushSync에 여러개의 상태 업데이트를 넣으면 효과가 없음
flushSync(() => {
setOne(one + 1);
setTwo(two + 1);
});
};
Current Mode 는 자바스크립트가 싱글 스레드 기반의 언어이기 때문에 여러 작업을 동시에 처리할 수 없는 문제점을 보안하기 위해 나왔다.
React 또한 JavaScript 기반이기 때문에 동일한 문제점이 발생하였고 Concurrent Mode(동시성)을 통해 이를 해결하고자했다. Concurrent Mode 는 여러 작업을 작은 단위로 나눠 우선순위를 정해 그에 따른 작업을 번갈아 수행하는 것을 말한다. 동시에 수행되는 것은 아니지만 빠르게 전환되기 때문에 동시에 수행되는 것처럼 보여 UX 를 향상시킨다.
이러한 배경에서 탄생한 Concurrent Feature 은 Concurrent Mode 에서 용어가 바뀐 것으로 이번 React 18 버전에서 중요한 기능이다. 동시성을 효율적으로 진행할 수 있는 기능인 Suspense 와 Transitions 를 지원한다.
Suspense 는 사용자에게 보여주고 싶은 컴포넌트를 먼저 렌더링 할 수 있게 하는 기능이다.
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense 로 렌더링을 원하는 컴포넌트를 감싸주면 해당 컴포넌트가 렌더링 되기 전까지 fallback 안의 컴포넌트를 사용자에게 보여준다.
Transition 은 setState(상태 업데이트)의 우선순위를 구분한다. Urgent updates 는 입력, 클릭, 누르기와 같은 사용자가 직접적인 상호 작용을 하는 기능을 말하고, Transtition updates(non-urgent)는 UI 전환과 같은 기능을 말한다.
const [isPending, startTransition] = useTransition();
const [one, setOne] = useState(0);
const [two, setTwo] = useState(0);
const onClick = () => {
setOne(one + 1); // 긴급 업데이트
startTransition(() => {
setTwo(two + 1); // 전환 업데이트로 setOne 보다 우선순위가 낮음
});
};
return (
<>
<button onClick={onClick}>btn1</button>
<button disabled={isPending}>btn2</button>
</>
);
isPending 의 값이 true 일 경우 startTransition 내분에 있는 setTow가 우선순위에서 밀린다. setOne의 상태 업데이트 진행시 isPending 의 값은 true 이다. setOne 의 상태 업데이트가 완료되면 isPending 의 값이 false 가 되며 setTwo 의 상태 업데이트를 진행한다.
서버와 클라이언트에서 사용될 고유 ID 를 생성한다.
function CreateID(){
const id = useId();
return <input id={id} type='text' />;
}
function CreateID(){
const id = useId();
return (
<input id={id + '-firstID'} type='text' />
<input id={id + '-secondID'} type='text' />
)
}
트리에서 긴급하지 않은 부분의 리렌더링을 연기하는 기능 제공
고정된 지연 시간이 없고 긴급한 작업이 완료된 후 즉시 실행하여 사용자의 입력을 차단하지 않는다.
import { useDeferredValue, useState } from 'react';
const SlowUI = () => {
<>
{Array(50000)
.fill(1)
.map((_, index) => {
<span key={index}>{10000}</span>;
})}
</>;
};
function App() {
const [value, setValue] = useState(0);
const deferredValue = useDeferredValue(value); // 긴급하지 않은 값
const handleClick = (e) => {
setValue(e.target.value);
};
return (
<>
<input onChange={handleClick} />
<div>DeferredValue: {deferredValue}</div>
<div>
<SlowUI />
</div>
</>
);
}
export default App;
useTransition과 같은 역할이라고 착각할 수 있지만,useTransition은 함수 실행의 우선순위,useDeferredValue는 값의 업데이트의 우선순위를 지정해준다고 생각하면 된다.
React 18 전에도 배치처리가 가능하긴 했다. 하지만 콜백 함수 내에서 배치처리가 안되는 문제점이 있었다. React 18 버전 업데이트를 통해 Automatic Batching 이 가능해졌고, Concurrent Mode 를 통해 기존 사용하던 Debounce 와 Throttle 을 대체할 수 있게 되었고, Suspence 를 통해 로딩이 오래걸리는 컴포넌트를 렌더링 할 때 fallback 을 통해 로딩 화면으로 대체할 수
React 18 전에도 배치처리가 가능하긴 했다. 하지만 콜백 함수 내에서 배치처리가 안되는 문제점이 있었다. React 18 버전 업데이트를 통해 Automatic Batching 이 가능해졌고, Concurrent Mode 를 통해 기존 사용하던 Debounce 와 Throttle 을 대체할 수 있게 되었고, Suspence 를 통해 로딩이 오래걸리는 컴포넌트를 렌더링 할 때 fallback 을 통해 로딩 화면으로 대체할 수 있기 때문에 사용자 경험을 향상시킬 수 있을 것이다.
전체적으로 React 18 업데이트는 사용자 경험을 향상 시키는 것이 중점으로 보인다.