과제리스트
- React Lifecycle에 대해 간단히 설명해주세요
- React18에서 업데이트 된 기능에 대해 설명해주세요
- React18에서 추가된 hook들에 대해 설명해주세요
- 요즘 관심있는 주제가 있다면 알려주세요
배치란, 리액트가 더 나은 성능을 위해 여러 개의 상태 업데이트를 한 번의 리렌더링(re-render)으로 묶는 작업이다.
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // 리렌더링 전
setFlag(f => !f); // 리렌더링 전
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행한다. (배치 적용)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
위의 handleClick 이벤트 핸들러 함수에서는 상태 업데이트를 두 번(setCount, setFlag) 수행했지만, 리액트는 배치를 통해 두 번의 업데이트를 한 번의 리렌더링으로 처리한다. 이를 통해 불필요한 렌더링을 방지하고 의도치 않은 버그를 예방할 수 있다.
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// 리액트 17 및 그 이전 버전에서는 배치가 수행되지 않는다. 왜냐하면
// 이 코드들은 이벤트 이후의 콜백에서 실행되기 때문이다.
setCount(c => c + 1); // 리렌더링
setFlag(f => !f); // 리렌더링
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
배치 작업은 React 이벤트 핸들러 내에서만 수행되므로, Promise 내부의 업데이트, setTimeout, 기본 이벤트 핸들러 또는 기타 이벤트에서는 처리되지 않았다.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행한다. (배치 적용)
}, 1000);
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행한다. (배치 적용)
})
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행한다. (배치 적용)
});
하지만, React 18부터 자동 배치(Automatic Batching)라는 것이 추가되었다. 자동 배치는 위와 같이 일반적인 React 이벤트 핸들러 함수 스코프에서 상태 업데이트가 일어나지 않더라도 자동으로 배치를 적용해준다.
자동 배치를 사용하기 위해서는 컴포넌트 트리를 기존의 ReactDOM.render
함수 대신 새로운 ReactDOM.createRoot
함수를 사용해야 한다.
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
}); // 리액트는 즉시 DOM을 업데이트 한다.
flushSync(() => {
setFlag(f => !f);
}); // 리액트는 즉시 DOM을 업데이트 한다.
}
그리고 상태 업데이트에 자동 배치가 적용되지 않았으면 하는 경우에는 새롭게 추가된 ReactDOM.flushSync
함수를 사용할 수 있다.
전환은 긴급 업데이트와 긴급하지 않은 업데이트를 구분하기 위한 React의 새로운 개념이다.
긴급 업데이트는 사용자의 입력에 따라 즉각적으로 업데이트되지 않으면 문제(화면 멈춤, 렉 등)가 있다고 느끼는 영역이다. 반면 전환 업데이트는 화면에 즉시 나타나는 걸 기대하지 않는 영역이다.
React 18 이전까지는 상태 업데이트를 긴급과 전환 업데이트로 명시하는 방법이 없었다. 모든 상태는 긴급 업데이트로 적용하기 때문에, setTimeout
이나 throttle
, debounce
등의 테크닉을 사용해 긴급 업데이트 방해를 우회하는 것이 최선이었다.
하지만, React 18부터는 startTransitionAPI
를 제공함으로써 전환 업데이트를 명시적으로 구분하여 상태 업데이트를 진행할 수 있게 되었다.
import { useTransition } from 'react';
function SearchBar() {
const [isPending, startTransition] = useTransition();
// ...
function handleChange(e) {
const input = e.target.value;
// 긴급 업데이트: 타이핑 결과를 보여준다.
setInputValue(input);
// 이 안의 모든 상태 업데이트는 전환 업데이트가 된다.
startTransition(() => {
// 전환 업데이트: 결과를 보여준다.
setSearchQuery(input);
});
}
// ...
}
startTransition의 경우 크게 두 가지 Use Cases가 있다.
React18에서는 새로운 서버 사이드 렌더링(이하 SSR) 아키텍처가 적용되었다. 새롭게 pipeToNodeWritable API
가 추가 되었고, 이 API를 사용하면 SSR을 통해 <Suspense>
를 사용할 수 있게 되었다.
즉, React.lazy
를 서버 사이드 렌더링에서 사용할 수 있게 되었다.
// lazy 컴포넌트
const OtherComponent = React.lazy(() => import('./OtherComponent'));
React.lazy
는 동적 import를 사용하여 컴포넌트를 렌더링할 수 있게 해주는 함수이다.
이러한 컴포넌트를 lazy 컴포넌트라고 하는데, 이 컴포넌트는 반드시 <Suspense>
컴포넌트 하위에서 렌더링되어야 한다. 지금까지는 이 lazy 컴포넌트와 <Suspense>
를 서버 사이드 렌더링에서 사용할 수 없었다는 것이 문제였다.
하지만 React 18부터는 새로운 렌더링 API인 pipeToNodeWritable
덕분에, <Suspense>
와 함께 lazy 컴포넌트를 사욯할 수 있게 되어 앱을 더 작은 독립적인 유닛으로 만들 수 있다.
현재 리액트 생태계의 주류 환경인 웹팩 기반의 애플리케이션에서, lazy 컴포넌트를 사용하면 코드 스플리팅(Code Splitting)이 적용되어 별도의 자바스크립트 Chunk 파일
로 분리된다. 그리고 이 <Suspense>
컴포넌트 하위 트리의 렌더링 외부 트리의 렌더링 과정을 막지 않고 별도의 과정이 진행된다.
출처: https://doqtqu.tistory.com/348#2.5.%20useInsertionEffect