10.1 리액트 17 버전 살펴보기
10.1.1 리액트의 점진적인 업그레이드
리액트 17은 애플리케이션이 너무 커서 한 번에 업그레이드하기 어려운 상황에서도 유용합니다. 버전이 서로 다른 리액트가 공존할 수 있도록 지원해 점진적인 업그레이드를 가능하게 합니다. 이는 애플리케이션의 부분적인 업그레이드를 통해 점진적으로 최신 버전의 기능을 활용할 수 있게 해줍니다.
10.1.2 이벤트 위임 방식의 변경
리액트 16에서는 이벤트 핸들러가 document 레벨에서 실행되었습니다. 그러나 리액트 17부터는 루트 요소로 변경되었습니다. 이는 각 이벤트가 리액트 컴포넌트 트리 수준에서 격리되므로, 이벤트 버블링으로 인한 혼선을 방지할 수 있습니다.
10.1.3 import React from 'react'가 더 이상 필요 없다
리액트 17부터는 JSX를 변환하는 방식이 변경되었습니다. 이전에는 JSX를 사용하기 위해 import React from 'react'가 필요했지만, 리액트 17부터는 자동으로 JSX 변환을 지원합니다.
리액트 16
import React from 'react';
function Greet() {
return <h1\>Hello, world!</h1\>;
}
리액트 17
function Greet() {
return \_jsx('h1', { children: 'Hello, world!' });
}
10.1.4 그 밖의 주요 변경 사항
이벤트 폴링 제거
리액트 16의 SyntheticEvent는 이벤트 객체를 래핑하여 사용했습니다. 그러나 이벤트 폴링 방식이 성능 향상에 큰 도움이 되지 않는다는 점에서 리액트 17에서는 이 방식을 제거하였습니다. 이제 비동기 코드에서 e.persist를 사용하지 않아도 됩니다.
useEffect 클린업 함수의 비동기 실행
리액트 16에서는 클린업 함수가 동기적으로 실행되어 다른 작업을 방해했습니다. 리액트 17부터는 클린업 함수가 컴포넌트 커밋 단계가 완료된 후 비동기적으로 실행됩니다.
컴포넌트의 undefined 반환에 대한 일관적인 처리
리액트 16에서는 forwardRef나 memo에서 undefined를 반환해도 에러가 발생하지 않았지만, 리액트 17부터는 에러가 발생합니다. 이는 코드의 일관성과 안정성을 높이는 변경입니다.
10.2 리액트 18 버전 살펴보기
10.2.1 새로 추가된 훅 살펴보기
useId
서버 사이드와 클라이언트 간에 동일한 유니크한 값을 생성하여 하이드레이션 이슈를 방지합니다.
예시 useId를 사용하여 폼 요소에 고유한 ID를 할당하는 간단한 예시
import React, { useId } from 'react';
function MyForm() {
const id = useId();
return (
<form\>
<label htmlFor\={id}\>Name:</label\>
<input id\={id} type\="text" />
</form\>
);
}
export default MyForm;
useTransition
상태 업데이트를 긴급하지 않은 것으로 간주하여 UI 변경을 가로막지 않고 상태를 업데이트할 수 있습니다. 이는 긴 렌더링 작업을 미루거나 로딩 화면을 보여줄 수 있게 해줍니다.
const \[isPending, startTransition\] = useTransition();
startTransition(() => {
// 상태 업데이트 로직
});
- isPending: 현재 상태 업데이트가 진행 중인지 여부를 나타내는 boolean 값입니다. 이를 통해 UI에서 로딩 스피너를 표시하거나, 상태 업데이트 중임을 사용자에게 알릴 수 있습니다.
useDeferredValue
리렌더링이 급하지 않은 부분을 지연하여 사용자의 인터랙션을 차단하지 않습니다. 상태를 직접 업데이트할 수 없는 경우에 유용합니다.
const deferredValue = useDeferredValue(value);
차이점
- 접근 가능성: useTransition은 상태 업데이트 로직에 직접 접근할 수 있는 경우에 유용합니다. startTransition 함수를 통해 긴급하지 않은 상태 업데이트를 비동기적으로 처리할 수 있습니다.
- 사용 상황: useDeferredValue는 업데이트에 직접 관여할 수 없는 경우, 즉 props처럼 값만 받아야 하는 상황에서 유용합니다. 이는 상태를 직접 업데이트할 수 없는 상황에서 지연된 렌더링을 구현할 수 있게 해줍니다.
useSyncExternalStore
useSyncExternalStore는 외부 스토어와의 동기화를 용이하게 하기 위한 훅입니다. 이 훅은 외부 데이터 소스(예: document.body, window.innerWidth)와의 동기화를 안정적으로 처리하기 위해 설계되었습니다. 기존의 useSubscription 훅이 useSyncExternalStore로 대체되었습니다.
리액트 18에서는 렌더링을 일시 중지하거나 뒤로 미루는 등의 최적화가 가능해지면서 하나의 변수에 대해서 서로 다른 컴포넌트 형태가 나타날 수 있습니다. useTransition, useDeferredValue는 내부적으로 이러한 처리를 해두었지만, 리액트에서 관리할 수 없는 외부 데이터 소스(document.body, window.innerWidth 등)에서는 이러한 최적화로 인해 테어링(불일치) 현상이 발생할 수 있습니다.
useSyncExternalStore는 이러한 외부 데이터 소스와의 동기화를 처리하기 위한 적절한 솔루션을 제공합니다.
import { useSyncExternalStore } from 'react';
function useWindowWidth() {
return useSyncExternalStore(
(subscribe) => {
const onResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
},
() => window.innerWidth
);
}
function WindowWidthComponent() {
const width = useWindowWidth();
return <div\>Window width is: {width}px</div\>;
}
위 예제에서는 useSyncExternalStore를 사용하여 윈도우의 너비를 추적합니다. 이 훅은 윈도우가 리사이즈될 때마다 window.innerWidth 값을 업데이트하며, 외부 데이터 소스와의 일관성을 유지합니다. 이를 통해 렌더링 최적화와 외부 데이터 동기화를 안정적으로 관리할 수 있습니다
useInsertionEffect
CSS-in-JS 라이브러리를 위한 훅으로, CSS가 브라우저에서 렌더링하기 전에 실행됩니다. 이는 성능을 최적화합니다.
10.2.2 react-dom/client
createRoot
리액트 18로 업그레이드하려면 reactDom.render 대신 createRoot를 사용해야 합니다.
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
hydrateRoot
서버 사이드 렌더링을 구현할 때는 hydrateRoot를 사용해야 합니다.
10.2.3 react-dom/server
renderToPipeableStream
스트림을 지원하며, HTML을 점진적으로 렌더링합니다. 기존의 renderToNodeStream은 렌더링 순서에 따라 성능 문제가 있었지만, renderToPipeableStream은 순서와 상관없이 빠르게 렌더링합니다.
10.2.4 자동 배치(Automatic Batching)
여러 상태 업데이트를 하나의 리렌더링으로 묶어서 성능을 향상시킵니다. 리액트 17 이하에서는 이벤트 핸들러 내부에서만 자동 배치가 이루어졌지만, 리액트 18부터는 비동기 이벤트에서도 자동 배치가 이루어집니다.
10.2.5 더욱 엄격해진 엄격 모드
리액트의 엄격 모드는 잠재적인 버그를 찾기 위해 다양한 검사와 경고를 제공합니다.
사용할 수 없는 특정 생명주기 메서드에 대한 경고
- componentWillMount, componentWillReceiveProps, componentWillUpdate 등의 메서드는 더 이상 사용할 수 없습니다.
문자열 ref 사용 금지
- 문자열 ref는 충돌 가능성이 높고, 리액트가 성능 이슈를 일으킬 수 있습니다.
문자열 ref 사용 금지
- 문자열 ref는 충돌 가능성이 높고, 리액트가 성능 이슈를 일으킬 수 있습니다.
문자열 ref 예시
class MyComponent extends React.Component {
render() {
return <div ref\="myDiv"\>Hello, world!</div\>;
}
componentDidMount() {
console.log(this.refs.myDiv); // 문자열 ref 사용 예시
}
}
findDOMNode에 대한 경고
- findDOMNode는 정적인 컴포넌트에서만 정상적으로 작동합니다.
10.2.6 Suspense 기능 강화
리액트 18에서는 서버 사이드 렌더링에서도 Suspense를 사용할 수 있습니다. 이는 초기 렌더링 속도를 향상시키고, 너무 자주 업데이트되는 것을 방지하기 위해 스로틀링을 추가했습니다.
10.2.7 인터넷 익스플로러 지원 중단에 따른 추가 폴리필 필요
Promise, Symbol, Object.assign 등을 지원하지 않는 브라우저에서는 폴리필을 추가해야 합니다.
10.2.8 그 밖에 알아두면 좋은 변경사항
- 컴포넌트에서 undefined를 반환해도 에러가 발생하지 않습니다.
- renderToNodeStream의 지원이 중단되었으며, renderToPipeableStream을 사용하는 것이 권장됩니다.
10.2.9 정리
리액트 17은 다음 버전으로의 업그레이드를 쉽게 하는 데 초점을 맞췄다면, 리액트 18은 동시성 렌더링과 같은 중요한 기능을 대거 추가했습니다. 이는 UI가 일관되게 표시되고, 사용자의 반응성을 높이는 데 큰 도움이 됩니다. 동시성 모드를 지원하려면 외부 라이브러리도 이에 맞춰 업데이트가 필요하므로, 사용하는 라이브러리가 이를 지원하는지 반드시 검토해야 합니다.
'책정리 > 모던 리액트 Deep Dive' 카테고리의 다른 글
12장 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표 (0) | 2024.06.28 |
---|---|
3장 리액트 훅 깊게 살펴보기 (0) | 2024.05.15 |
8장 좋은 리액트 코드 작성을 위한 환경 구축하기 (0) | 2024.05.15 |
모던 리액트 Deep Dive 2.1 - 2.2 :: JSX와 리액트 파이버 (0) | 2024.02.27 |
자바스크립트의 동등 비교 (==, ===, Object.is) (0) | 2024.02.12 |