React 18의 새로운 기능을 사용하기 위해서는 createRoot API를 사용해야 한다.
//이전 버전
import React fron 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>
document.getElementById('root')
);
//리액트18 버전
import React fron 'react';
import ReactDOM from 'react-dom/client';
const rootNode = document.getElementById('root')
ReactDOM.createRoot(rootNode).render(
<React.strictMode>
<App/>
</React.strictMode>
);
기본 버전에서는 render만을 사용해 root를 생성했는데 React 18버전에서는 createRoot라는 새로운 Root API를 통해 root를 생성할 수 있게 되었다. createRoot API를 사용해 React 18버전의 새로운 기능과 API를 사용할 수 있게 된다.
ReactDOM을 불러오는 경로 또한 react-dom에서 react-dom/client
로 변경된 것을 볼 수 있다.
React 18 새로운 기능 키워드
여러 상태 업데이트(setState)를 통합하여 단일 리렌더링으로 처리해 리렌더링의 성능을 개선한 기능이다.
React 18이전엔 useState, setState를 통해 상태를 업데이트하고 업데이트된 결과를 바탕으로 리렌더링을 진행했다. 이때 , state의 개수가 많아지면 그만큼 리렌더링도 많이 발생하기 때문에 불필요한 리렌더링이 많아지고 성능 이슈가 발생할 수 있었다. 그래서 도입된 기능이 배치처리인데 배치처리는 여러 상태 업데이트(setState)가 발생해도 상태를 하나로 통합해 리렌더링을 진행하는 방식이다. React 18이전 버전에서는 이벤트 핸들러와 생명주기 메소드를 사용할 댄 배치처리를 지원하지만 콜백 함수가 포함된 경우 배치 처리를 지원하지 않는다.
//react 17 (리렌더링 두번 유발)
function handleClick() {
fetchSomething().then(() => {
// React 17 이전의 버전에서는 해당 작업을 Batching 처리하지 않는다.
// 왜냐하면 해당 작업은 이벤트가 종료된 이후 (100ms 뒤) 에 실행되기 때문이다.
setCount((c) => c + 1); // 리렌더링 유발
setFlag((f) => !f); // 리렌더링 유발
});
}
//react 18 (리렌더링 한번으로 배칭)
function handleClick() {
fetchSomething().then(() => {
// React 18 이후에서는 해당 작업을 Batching 처리 한다.
setCount((c) => c + 1);
setFlag((f) => !f);
// React 는 해당 작업을 일괄 처리하여 한 번의 리렌더링만 진행한다.
});
}
const changeState = async () => {
if (testRef.current) {
clearTimeout(testRef.current);
testRef.current = null;
}
// 요 안에서 한 개의 update function 은 하나로 batching 됨.
trigger();
trigger();
// 1.5초 후에는 4개의 update function 을 하나로 batching 시킴.
setTimeout(() => {
trigger();
trigger();
}, 1500);
setTimeout(() => {
trigger();
trigger();
}, 1500);
testRef.current = setTimeout(() => {
// 1초 후에는 두 개의 update function 을 하나로 batching 시킴
trigger();
trigger();
}, 1000);
return;
}
changeState 함수는 onClick 이벤트 핸들러를 통해 호출되며, 버튼 클릭 시 두 차례에 걸쳐 리렌더링이 발생한다. 이유는 이벤트 핸들러 내에 위치한 state update 작업은 하나의 queue에 묶여 batching처리되고,
이후 timeout이 끝난 후 실행되는 두 개의 state update 작업은 따로 queue에 묶여 리렌더링을 유발시키기
때문이다. 중요한 것은 위 작업이 하나의 queue에 batching 되지 않는다는 점이다. 이벤트 핸들러에서 실행된 setState와 setTimeout을 기반으로 실행된 setState는 서로 batching 되지 않고 독립적으로 실행된다.
한 가지 신기한 점은 이벤트 핸들러에서 실행되었고 딜레이가 같은 두 개의 setTimeut 내 update funcion도 하나로 batching 되었다는 점이다.
react-dom 라이브러리에 추가된 ReactDOM.flushSync() 메서드는 Auto Batching을 무시하고 즉시 DOM을 렌더링해준다. React에서는 공식적으로 해당 메서드의 사용을 추천하진 않으며 필요한 상황이 있을 경우에만 사용할 것을 강조했다.
import { flushSync } from "react-dom";
function handleClick() {
// React 는 flushSync 메서드가 실행되는 즉시 DOM을 업데이트 한다.
flushSync(() => {
setCounter((c) => c + 1);
});
// React 는 flushSync 메서드가 실행되는 즉시 DOM을 업데이트 한다.
flushSync(() => {
setFlag((f) => !f);
});
// 따라서 해당 함수가 실행될 경우 React는 총 두 번의 리렌더링을 수행한다.
}
Concurrent Feature을 알아보기 전, Concurrent Mode라는 개념에 대해 먼저 알아야한다. Concurrent Mode는 자바스크립트가 싱글 스레드 기반의 언어이기 때문에 여러 작업을 동시에 처리할 수 없는 문제점을 보완하기 위해 나왔다.
React 또한 JS 기반이기 때문에 동일한 문제점이 발생했고 Concurrent Mode(동시성)을 통해 이를 해결하고자 했다. Concurrent Mode는 여러 작업을 작은 단위로 나눠 우선순위를 정해 그에 따라 작업을 번갈아 수행하는 것을 말한다. 동시에 수행되는 것은 아니지만 작업 간 전환이 빠르기 때문에 동시에 수행되는 것처럼 보여 사용자 경험을 UX적으로 향상시킨다.
이러한 배경에서 탄생한 Concurrent Feature은 Concurrent Mode에서 용어가 바뀐 것으로 이번 React 18 업데이트에서 가장 중요한 추가 기능이다. 또한 동시성을 효율적으로 진행할 수 있는 기능인 Suspense와 Transitions를 지원한다. 해당 기능들은 한 번에 여러 동작을 수행해 사용자 경험을 향상시킬 수 있다.
Suspense는 사용자에게 보여주고 싶은 컴포넌트를 먼저 렌더링 할 수 있게 하는 기능이다.
Transition은 setState(상태 업데이트)의 우선순위를 구분한다. Urgent updates는 입력, 클릭, 누르기와 같은 사용자가 직접적인 상호 작용을 하는 기능을 말하고 Transition updates(non-urgent)는 UI 전환과 같은 기능을 말한다.
isPending 상태 값을 가져와 렌더링 결과를 분기 처리하는 기능이다. 여기서 isPending은 state를 업데이트했음에도 리렌더링 하지 않고 UI를 잠시 유지하는 상태를 말한다. isPending은 boolean 값으로 transition 완료를 알려주게 된다.
내부에 존재하는 상태 업데이트는 모두 전환 업데이트일때 우선순위가 높은 상태 업데이트가 발생할 경우 내부에 선언한 상태 업데이트는 중단된다.
const [isPending, startTransition] = useTransition();
const [one, setOne] = useState<number>(0);
const [two, setTwo] = useState<number>(0);
const onClick = () => {
setOne(one + 1);// 긴급 업데이트// startTransition으로 전환 업데이트 선언// 내부에 있는 상태 업데이트는 모두 전환 업데이트
startTransition(() => {
setTwo(two + 2);// 전환 업데이트로 setOne보다 우선순위가 낮음
});
};
return (
<>
<button onClick={onClick} />
<button disabled={isPending} />
<>
);
isPending의 값이 true일 경우 startTransition 내부에 있는 setTow가 우선순위에서 밀리게 된다. setOne의 상태 업데이트 진행시 isPending의 값은 ture이다. setOne의 상태 업데이트가 완료되면 isPending의 값이 false가 되며 setTwo의 상태 업데이트를 시작한다.
서버와 클라이언트에서 사용될 고유 ID를 생성하며 key를 생성하는 것은 아니다.
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}>{100000} </span>
))}
</>
);
function App() {
const [value, setValue] = useState<string>("");
const deferredValue = useDeferredValue(value);// 긴급하지 않은 값으로 지정
const handleClick = (e: any) => {
setValue(e.target.value);
};
return (
<>
<input onChange={handleClick} />
<div>DeferredValue: {deferredValue}</div>
<div>
<SlowUI />
</div>
</>
);
}
export default App;
value가 변경되더라도 deferredValue에는 변경된 값이 아닌 이전 값을 리턴한다. 사용자의 인터랙션이 종료되면 그때 새로운 값을 리턴하게 된다.
https://tech.nexr.kr/e14cd3fa-58be-4983-9e54-a1d157af08c2
https://velog.io/@seungchan__y/React-18-Concurrent-맛보기
https://velog.io/@rookieand/React-18에서-추가된-Auto-Batching-은-무엇인가#️-batching-이란