React v17, 18의 변경사항
12.03 [모던 리액트 Deep Dive] 10장 - 리액트 17과 18의 변경 사항 살펴보기
Intro
현재 많은 사이트가 16쓰긴함 그래도 도태되면 안되겠쥬?
10.1 리액트 V17 살펴보기
17의 가장 큰 특징은 새롭게 추가된 기능보다 16에서도 호환되도록 업데이트한 것이 가장 큰 특징이다.
v17부터는 점진적 업그레이드가 가능해졌다.
⇒ 전체트리에는 v17이지만 특정 컴포넌트에 대해서만 v18로 점진적 버전업이 가능하다
(이게 어케되는거지..?)
1. 리액트 점진적 업그레이드 방법
import ThemeContext from './shared/ThemeContext';
const rendererModule = {
status: 'pending',
promise: null,
result: null,
};
// 이전 버전의 루트를 가져오는 코드
// React.lazy를 못쓰는 이유는 컴포넌트를 불러오는작업은 외부 리액트 버전에서 / 렌더링은 내부 리엑트 버전에서 수행해야하기 떄문
export default function lazyLegacyRoot(getLegacyComponent) {
const componentModule = {
status: 'pending',
promise: null,
result: null,
};
return function Wrapper(props) {
// legacy/createLegacyRoot 를 promise 로 layzy 하게 불러온다.
const createLegacyRoot = readModule(rendererModule, () =>
import('../legacy/createLegacyRoot')
).default;
const Component = readModule(componentModule, getLegacyComponent).default;
// 구 리액트를 렌더링할 위치
const containerRef = useRef(null);
// 구 리액트의 루트 컴포넌트
const rootRef = useRef(null);
const theme = useContext(ThemeContext);
const context = useMemo(
() => ({
theme,
}),
[theme]
);
useLayoutEffect(() => {
// 루트 컴포넌트가 없다면
if (!rootRef.current) {
// 루트 컴포넌트를 만든다.
rootRef.current = createLegacyRoot(containerRef.current);
}
const root = rootRef.current;
// cleanUp 시에 unmount
return () => {
root.unmount();
};
}, [createLegacyRoot]);
useLayoutEffect(() => {
if (rootRef.current) {
// 루트 컴포넌트가 존재하면 적절한 props와 context로 렌더링한다.
rootRef.current.render(Component, props, context);
}
}, [Component, props, context]);
return <div style= ref={containerRef} />;
};
}
function readModule(record, importStatement) {
// promise가 없으면 아직 import 하지 못한 것이므로 import 를 실행한다.
if (!record.promise) {
/* eslint-disable */
record.promise = importStatement().then(
value => {
if (record.status === 'pending') {
record.status = 'fulfilled';
record.promise = null;
// 성공시 import 반환 값
record.result = value;
return value
}
},
error => {
if (record.status === 'pending') {
record.status = 'rejected';
record.promise = null;
// 실패시 에러
record.result = error;
}
}
);
}
// 성공 또는 실패시에 결과를 반환한다.
if (record.status === 'fulfilled' || record.status === 'rejected') {
return record.result;
}
throw record.promise;
}
이전 버전 리액트의 루트를 가져와서 현재 버전에 추가 해서 렌더링 시켜주는 Lazy함수를 만들어주고
export default function createLegacyRoot(container) {
return {
// 렌더링
render(Component, props, context) {
ReactDOM.render(
<ThemeContext.Provider value={context.theme}>
<Component {...props} />
</ThemeContext.Provider>,
container
);
},
// 이 컴포넌트의 부모 컴포넌트가 제거될 때 호출될 unmount
unmount() {
ReactDOM.unmountComponentAtNode(container);
},
};
}
// package
{
"private": true,
"name": "react@16 application",
"dependencies": {
"react": "16.8",
"react-dom": "16.8"
}
}
구 버전에서 루트를 만드는 함수를 만들어서 lazyLegacyRoot
로 넘겨주면 구 버전 리액트 코드가 현재 버전에 추가될 수 있다.
export default function App() {
const [theme, setTheme] = useState('slategrey');
function handleToggleClick() {
if (theme === 'slategrey') {
setTheme('hotpink');
} else {
setTheme('slategrey');
}
}
return(
<Suspense fallback={<Spinner />}>
<AboutPage />
</Suspense>
);
}
const Greeting = lazyLegacyRoot(() => import('../legacy/Greeting'));
export default function AboutPage() {
const theme = useContext(ThemeContext);
return (
<>
<h2>src/modern/AboutPage.js</h2>
<h3 style=>
This component is rendered by the outer React ({React.version}).
</h3>
<Clock />
<Greeting /> //구버전 리액트 코드
<br />
</>
);
}
즉,
- 현 버전에서 구 버전 리액트를 lazy로딩함
- 이때 구 버전 리액트 렌더링을 위해서 별도의 루트 요소를 만들고 그 안에 렌더링함
- 렌더링이 완료된 루트요소를 현 버전에 추가함
이렇게 하면서 구, 현 버전이 모두 지원하는 컴포넌트나 훅은 모두 사용가능하고 현버전에서 추가된 훅들도 사용할 수 있다.
(출처 : https://github.dev/wikibook/react-deep-dive-example/tree/main/chapter10/react-gradual-demo)
(일케 했구나~)
2. 이벤트 위임 방식의 변경
p664를 보면
- Dom에 이벤트 리스너 추가 ⇒ 핸들러에 해당 이벤트 리스너 함수 추가
- 리액트에 이벤트 리스너 추가 ⇒ 핸들러에 해당 noop 함수 추가
noop은 no operation으로 아무것도 안함
대신 react는 이벤트 타입(click, change, …)당 하나의 핸들러를 루트에 부착하는 이벤트 위임을 통해 이벤트를 관리함
이벤트의 단계
- 캡처링 : 트리 최상단 요소부터 타깃 요소까지 내려감
- 타깃 : 이벤트 핸들러가 타깃 요소에 도달하는 단계 ⇒ 이때 이벤트가 호출됨
- 버블링 : 이벤트가 발생한 요소에서부터 최상단 요소까지 다시 올라감
이벤트 위임
이벤트 단계를 이용해서 이벤트를 상위 컴포넌트에만 붙이는 것을 의미함
즉, 리액트는 이벤트 핸들러를 각 요소가 아닌 최상단에 연결해서 관리한다
근데 이게 ~v16 까지는 document를 최상단으로 보고 여기에 이벤트를 위임했는데
v17~ 부터는 document가 아닌 react component Root 요소에 달도록 변경되었다.
이것도 결국 호환성을 위함인데 여러버전을 함께 쓸 때 이벤트 버블링으로 인한 혼선을 막기 위함이다.
document로 이벤트 위임이 발생한다면 e.stopPropagation()
를 해줘도 이미 전파가 다 된 상태이기 때문에 의미가 없어져버린다.
이전 예제로 예시를 들어보면
// v17
return (
<>
<h2>src/modern/AboutPage.js</h2>
<h3 style=>
This component is rendered by the outer React ({React.version}).
</h3>
<Clock />
<Greeting /> // v16
<br />
</>
);
<Greeting/>
에 이벤트가 달려있다고 하자
V16에서는 document에 이벤트를 달기 때문에 e.stopPropagation()
를 해줘도 <Clock/>
에 이벤트 전파가 된다.
리액트 버전 뿐만아니라 jQuery같은 다른 라이브러리와 V16이 혼재되어 있는 상황에도 동일한 문제가 발생한다
하지만 V17에서는 최상단 root 로 변경하면서 <Clock/>
으로 이벤트전파를 막을 수 있다.
https://ko.legacy.reactjs.org/blog/2020/10/20/react-v17.html
그래서 v17이상에서 쓸 때는 e.stopPropagation()
을 써도 document에서 안잡힐 가능성이 있으므로 고려해야한다
3. impot React from ‘react’ 노필요 새로운 JSX transform
JSX는 바벨이나 TS를 이용해서 JS로 변환하는 과정이 필요하다
그래서 impot React from ‘react’ 이게 있었어야 하는데
v17부터는 바벨과 협력해 import 를 안해도 되게끔 바꿨다
개발에 편한 것도 있지만 불필요한 Import를 없애서 번들링 크기를 줄였다는 장점도 있다.
이게 가능한 이유는 p671 에서 바벨이 트렌스파일링 한 결과를 비교해보면 바로 알 수 있음
4. 그 밖의 주요 변경사항
이벤트폴링 제거
리액트에서 이벤트를 처리하기 위해서 기본 브라우저 이벤트를 래핑한 syntheticEvent
이라는 이벤트 객체를 사용했다 근데 이벤트가 발생할때마다 새로 만들어야했고 메모리를 많이 먹고 특정 주기적으로 GC를 돌려야 해서 Event fool
이라는 개념을 썼음
약간 합성 이벤트 만의 메모리 풀과 GC느낌이다.
효율적인것 같은데 문제가 있다.
const handleClick = (e) => {
setValue(() => e.target.value);
};
이런 핸들러가 있다면 핸들러가 실행하는 시점에서는 e가 존재하지만 비동기 코드가 실행되는 시점에는 이미 null로 초기화가되어서 e.target.value가 접근이 안된다.
⇒ 이거를방지하기 위해 e를 잠시 저장하기 위해 e.persist()같은 처리가 필요했다
근데 v17부터 필요없어짐. 걍 이벤트 풀링 개념이 사라짐.
useEffect 클린업 함수의 비동기 실행
v16까지는 클린업 함수가 동기적으로 처리되었다. ⇒ 다른 작업을 방해해 성능 저하를 만들었다
v17부터는 클린업 함수가 비동기적으로 → 좀 더 정확히는 커밋 단계가 끝날 때까지 지연된다.
즉, 화면 업데이트가 끝난 이후 클린업 함수가 실행된다.
p676에 비교 캡쳐 있음
컴포넌트의 undefined 반환에 대한 일관적인 처리
컴포넌트에서 반환값이 없으면 에러를 냈는데
v16에서는 forwardRef나 memo에서 undefined 반환하면 걍 넘어갔었음
v17 → 반환값없으면 항상 에러나게 통일
근데
v18 → undefined 반환해도 에러 안나게 통일
(??오잉?)
안 다룬 사항들
react v17버전 : https://github.com/facebook/react/releases/tag/v17.0.0
10.2 리액트 V18 살펴보기
v17이 전진적인 업그레이드 위한 준비였다면 v18은 본격적인 업그레이드였음
가장 큰게 ‘동시성 지원’이다. 이건 리액트의 오래된 숙원이었으며 옛날 부터 예고하긴 했었음
1. 새로 추가된 훅
useId
컴포넌트 별로 유니크한 값을 만들어주는 훅으로 유일하고 SSR환경에서도 동일한 값을 생성해 하이드레이션 에러가 나지 않음(Math.radom() 같은 거로 만들면 에러남)
p682 처럼 생긴값 나옴
useTransition
UI변경을 가로막지 않고 상태를 업데이트 할 수 있는 리액트 훅이다.
⇒ 상태 업데이트로 인한 렌더링 작업을 뒤로 미룰 수 있다.
⇒ 상태업데이트를 저속차선으로 보내버릴 수 있다. (요거는 뒤에서 자세히 설명할게융)
데모 영상과 함께 비교해보자
기존 방식 : https://ajaxlab.github.io/deview2021/blocking/
startTransition을 통한 동시성 방식 : https://ajaxlab.github.io/deview2021/concurrent/
그림으로 이해해보자: https://github.com/reactwg/react-18/discussions/46#discussioncomment-846786
useDeferredValue
useTransition
과 비슷하게 리렌더링을 지연 시켜줄 수 있는 훅으로 디바운스와 비슷한 역할을 수행한다.
디바운스와 다른 점은 첫 번째 렌더링이 완료된 이후 고정된 지연 시간 없이 지연된 렌더링을 수행하며 중간에 중단할 수 있고, 사용자 인터렉션을 차단하지 않는다
useTransition
와의 차이점은
useTransition
⇒ 이벤트 핸들러에서 업데이트(ex. setState)를 트리거할 때 사용 ⇒ setState를 감싸서 사용 (like useCallback)useDeferredValue
⇒ 부모컴포넌트나 동일컴포넌트의 상위 훅에서 새로운 데이터를 받을 때 사용 ⇒ state값을 사용 (like useMemo)
function Typeahead() {
const [query, setQuery] = useState(""); // query는 바로 업데이트
const deferredQuery = useDeferredValue(query); // deferredQuery는 지연 업데이트
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<Suspense fallback="Loading results...">
<SearchSuggestions query={deferredQuery} />
</Suspense>
</>
);
}
근데useDeferredValue
는 값만 연기할 뿐 렌더링에 관여하지 않는다. 즉, 저런식으로짜면 <SearchSuggestions>
얘가 2번 리렌더링 됨 따라서 memo도 해줘야 최적화 가능해짐
...
const suggestions = useMemo(() =>
<SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">
{suggestions}
</Suspense>
</>
);
...
요런식으로?
참고 : https://github.com/reactwg/react-18/discussions/129
useSyncExternalStore
v17에서의 useSubscription
훅이 v18에서 useSyncExternalStore
로 대체되었다.
그 이유로는 위의 useTransitoin
, useDefereedValue
훅들로 인해서 생긴 사이드 이펙트(tearing) 때문인데
좀 더 자세한 설명과 https://github.com/reactwg/react-18/discussions/69
(요약 : 기존에는 동기 렌더링을 해서 treaing 발생 할 일 없었음(노빠꾸였음), 근데 동시성 추가되면서(스탑가능) treaing이 발생할 수 있게 됨 → 그래서 react 내부 훅은 처리했고, 외부 상태는 useSyncExternalStore로 감싸서 처리하셈)
예시 코드를 함께 보시죵 ch10/react-ui-tearing-example
check list
- startTranslation의 trearing 현상
- setState vs startTranslation
- useSyncExternalStore 를 통한 trearing 해소
- useSyncExternalStore 동작방식
다이시 카토의 영상에서 자세한 설명이 있습니당 : https://www.youtube.com/watch?v=oPfSC5bQPR8
useInsertionEffect
CSS-in-js 라이브러리를 위한 훅으로 DOM이 실제로 변경되기 전에 동기적으로 실행된다.
이 훅 내부에 스타일을 삽입하는 코드를 집어넣음으로써 브라우저가 레이아웃을 계산하기 전에 실행될 수 있게씀 해서 스타일 삽입이 가능해졌다.
비슷한 훅인 useInsertionEffect
, useLayoutEffect
, useEffect
가 있는데 실행 순서가
useInsertionEffect
→ useLayoutEffect
→ useEffect
순으로 실행된다. 여기에 DOM 변경 타이밍을 추가하면
useInsertionEffect
→ useLayoutEffect
→ DOM변경작업 → useEffect
이렇게 된다.
하지만 useInsertionEffect
는 실제 애플리케이션 코드 작성할때는 쓸일이 없다ㅎ,, (쓰지말래융)
2. react-dom/client
리액트 트리를 만드는 react-dom이 client, server로 분리되며 이전의 react-dom과 변경된 점이 있다.
createRoot
기존에 react-dom.render
로 만들던 트리를 이제 react-dom.createRoot.render
로 만들어야 한다
hydrateRoot
SSR에서 하이드렉션을 하기위한 메서드이다.
직접 SSR 구현이 필요하다면 이쪽을 수정해야할꺼다
위의 두 API는 새로운 옵션인 onRecoverableError
를 인수로 받는데 이 옵션은 렌더링,하이드렉션 시에 에러가 발생했을 때 실행 시킬 콜백함수이다. (기본값 reportError
, console.error
)
3. react-dom/server
서버에서도 컴포넌트를 생성하는 API의 변경이 있다.
renderToPipeableStream
여기는 책 설명이 너무 빈약합니다… 이렇게 퉁치고 넘어가기엔 핵심적인 내용이 많아서…
리액트 컴포넌트를 HTML로 렌더링 시키는 메서드이다. 근데 스트리밍을 곁들인..
import { renderToPipeableStream } from "react-dom/server";
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/main.js"],
onShellReady() {
response.setHeader("content-type", "text/html");
pipe(response);
},
});
이런식으로 server쪽에서 renderToPipeableStream
을 통해 React 트리를 writable Node.js 스트리밍해 HTML로 렌더링한다 (node 환경에서만 가능)
위에 코드를 해석하면
<App/>
를 root 컴포넌트로 하고 /main.js
script를 포함한 html 을 만드는데
초기 셸(Suspense
바깥부분)이 렌더링 된 직후 onShellReady()
를 실행해라
onShellReady()
에서 pipe를 호출해 HTML 로딩 폴팩을 콘첸츠로 대체하는 인라인 태그와 함께 셸 뒤에 추가 컨텐츠들을 스트리밍 한다.
이렇게 된다…ㅎ,,
import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(document, <App />);
그리고 클라이언트 쪽에서 hydrateRoot를 호출해 서버에서 이미 만든 HTML을 react에 결합하면 끝난다.
각각의 청크 파일로 분리를 하는 기준은 Suspense를 통해서 컴포넌트를 감싸 청크파일들로 만들 수 있다.
즉, HTML을 각각의 청크파일로 만들어서 필요한 부분을 먼저 렌더링 할 수 있다는 뜻이다.
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Posts />
</ProfileLayout>
);
}
예를 들어 위와 같은 페이지가 있고 <Posts />
의 데이터 패칭이 시간이 많이 걸린다고 가정해보자
그럴경우 <Posts />
를 개별 청크로 만들어 스트리밍 HTML을 하도록 수정하려면
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
이렇게 Suspense
만 감싸주면 된다. 이를 통해 청크파일로 분리를 해 스트리밍 할 수 있다.
(이렇게 할 수 있는 이유는 v18때 업데이트 된 Suspense덕분인데 이거는 뒤에서 좀 더 자세히 다루겠습니다.)
공식문서(한국어버전) : https://ko.react.dev/reference/react-dom/server/renderToPipeableStream
덴 아저씨의 좀 더 자세한 설명을보자 : https://github.com/reactwg/react-18/discussions/37 하이디렉션 중 인터렉션이 가능한 이유 : https://github.com/reactwg/react-18/discussions/38
Suspense를 통해 streaming SSR하는 방법 영상 : https://www.youtube.com/watch?v=pj5N-Khihgc
renderToReadableStream
renderToPipeableStream이 Node.js 환경에서의 렌더링을 위해 사용되고
renderToReadableStream는 웹 스트림을 기반으로 작동한다.
서버 환경이 아닌 클라우드플레어나 디노 같은 웹 스트림을 사용하는 엣지 런타임 환경에서 사용되는 메서드이다.
4. 자동배치(Automatic Batching)
자동배치는 여러 상태 업데이트를 하나의 리렌더링에 묶어서 성능을 향상시키는 방법이다.
예를 들어 한번의 클릭으로 두개의 state가 업데이트 된다면 이를 개별적으로 실행시켜 2번 리렌더링하는게 아니라 하나의 배치로 모아 한번에 업데이트 하고 리렌더링 시키는 원리이다.
기존에도 자동 배치가 있긴 했지만 Promise, setTimeout과 같이 비동기 이벤트에 대해서는 적용되지 않았다
이러한 점을 v18에서 보완했다 (루트 컴포넌트를 createRoot로 만들면서 배치 작업이 최적화 할 수 있게 되었다네요ㅎ,,, 원리는 모르겠습니다^^ 갈길이 멀어서…)
v17의 리렌더링과 v18의 자동 배치 이후 리렌더링을 사진으로 비교해보자
p699참조
그리고 자동배치를 막고싶으면 react-dom에서 제공하는 flushSync
api를 통해서 setState를 감싸주면 자동배치를 취소할 수 있다.
5. 엄격모드 고도화
빡빡하게 쓸 수 있다! 패스!
6. Suspense 기능 강화
v16.6때 실험 버전으로 도입된 기능으로 컴포넌트를 동적으로 가져올 수 있게 도와주는 기능이다.
사실 v16때 까지 Suspense는 컴포넌트를 lazy로딩 시키는 정도의 역할 밖에 할 수 없었다
이때 몇가지 문제가 있었는데
- 컴포넌트가 마운트 되기 전에 useEffect가 실행되는 문제 (버그임)
-
서버에서는 suspense사용 못함
v18 이전에는 지연 시킨 컴포넌트를 렌더링 시킬 방법이 없었음 그래서 클라에서 실행되도록 처리해줘야 했음
이러한 문제점을 v18때 해결했는데
- 실제 컴포넌트가 마운트 될 때 useEffect가 실행된다.
- Suspense에 의해 현재 보여지는지 아닌지를 판단해서 useLayoutEffect의 effect를 실행하거나 clearnUp을 실행한다
- Suspense내에 스로틀링이 추가됨
이를 해결한 방법은 Suspense를 제안했던 Sebastian Markbåge의 코드 조각에서 확인 할 수 있다
(세바스찬 ㄱㅅ : https://gist.github.com/sebmarkbage/2c7acb6210266045050632ea611aebee)
그리고 이것을 거슬러 올라가면 Suspense의 정신 모델은 대수적 효과라는 이론에서 나온다
(댄 아저씨 ㄱㅅ: https://overreacted.io/algebraic-effects-for-the-rest-of-us/#how-is-all-of-this-relevant-to-react)
참고 : https://maxkim-j.github.io/posts/suspense-argibraic-effect/
7. IE 지원 중단에 따른 폴리필 필요
v18부터는 Promise, Symbol. Object.assign이 사용가능하다는 가정 하에 배포된다.
근데 우리 IE는 그런거 없어요~ 그래서 각각 폴리필 설치해줘야한다
8. 그 외 변경사항
- 위에서 언급한것 처럼 컴포넌트가 undefined 반환해도 에러 발생 안한다. 그냥 null과 동일하게 처리된다.
- Suspense의 fallback도 동일하다
- renderToNodeStream이 중단되고 renderToPipeableStream을 권장한다
결론
v17은 v18을 위한 빌드업 느낌… 일을 너무 열심히 한다;;
그래도 v18의 핵심은 결국 동시성 렌더링이다
기존에 렌더링은 한번 시작하면 끝날 때까지 멈출 수 없었다.
그 이유는 브라우저 렌더링의 메인 스레드는 하나이기 때문에 여기 들어가는 순간 나올때까지 기다려야된다. 따라서 리액트에 종속된 문제가 아니라 모든 라이브러리가 동일하다
하지만 v18에서는 렌더링을 중지하고 다시 시작하거나 포기하고 다시 시작하거나 여러가지 선택지가 생겼다. 이 와중에 테어링 현상도 잡아서 일관된 UI를 유지한다 (외부 상태는 useSyncExternalStore를 써야하지만)
이런 동시성 렌더링을 위해서 리액트는 트리 전체가 계산이 완료된 이후에 DOM 수정이 완료될 때까지 기다린다.
이 작업은 메인 스레드를 차단하지 않고 백그라운드로 수행되면서 새로운 화면을 만들 작업을 준비한다.
메인스레드를 차단하지 않음으로써 동시성 렌더링을 구현했다.
리액트 팀의 목표는 ‘쉽고 편하게’ 동시성 렌더링을 작동할 수 있도록 하는것이라고 한다. (지금은 전혀 ‘쉽고 편하지’않은것 같다.)
cf. 개인적인 생각
이렇게 고도화 되가는 것 같다. 처음 리액트가 나오고 상태관리로 redux 원툴일때는 보일러플레이트도 많고 코드가 지저분해지지만 그럼에도 ‘전역 상태관리’라는 키를 갖기 위해 사용을 했고 이후 recoil, zustand, jotai, xState 등 여러 상태관리 툴이 나오면서 상태관리가 간편해지고 있다고 느낀다.
기술적으로 ‘동시성 렌더링’은 아직 안정화가 되지 않은 따끈따끈한 응애 단계라고 생각된다. 그래서 여러가지 처리와 생각을 해줘야 하지만 고도화에 있어서 필요한 개념이라고 생각한다. 그래서 위의 예시 처럼 세팅과 관리가 쉬워지는 방향으로 발전해나가지 않을까… 생각이 된다.
프론트엔드에서 가장 중요한 것은 서버에서 받아온 데이터를 유저에게 ‘잘’ 보여주는 것이라고 생각한다
‘잘’ 보여주기 위해서 여러가지 렌더링 기법들이 생겨나고 프론트엔드 생태계도 다양하지고 성숙해지고 있다고 생각한다.
렌더링만해도 SST → CSR → SSR → Streaming SSR → + RSC → PPR … 점점 많은 렌더링 기법 들이 나오고 있다. 은총탄이 없듯이 어떤 렌더링 기법을 쓴다고 모든 문제를 해결해주지 않는다.
하지만 이런 렌더링 기법들을 알고 있는 것으로 어떤 상황에 어떤 기법을 적용하는게 더 UX를 향상시킬지 생각할 수 있고, 나에게 싸울 수 있는 도구가 칼뿐만이 아니라 칼, 도끼, 총, 미사일, 탱크, … 요런식으로 무기고가 늘어나는것이라고 생각한다.
바퀴벌레 잡는데 미사일 쓸 필요는 없지만 좀비가 오면 쏴야지 뭐… 근데 쏠려면 현재 안 쓰더라고 갖고 있어야지… 요런느낌?
까지가 책 내용이고
동시성에대해서 좀 더 탐구하는 시간을 가져보겠습니다.
Concurrent UI Parrern을 위한 인피니티 스톤들(useTransition, Suspense, ErrorBoundary, Stream SSR) + RSC - 미완
‘Next App router에서 RSC와 RCC가 어떤 방식으로 렌더링 되는거지?’ 를 알기 위해 RSC란 무엇인가?
까지가 책 내용이고
동시성에대해서 좀 더 탐구하는 시간을 가져보겠습니다.
Concurrent UI Parrern을 위한 인피니티 스톤들(useTransition, Suspense, ErrorBoundary, Stream SSR) + RSC - 미완
‘Next App router에서 RSC와 RCC가 어떤 방식으로 렌더링 되는거지?’ 를 알기 위해 RSC란 무엇인가?
동시성에대해서 좀 더 탐구하는 시간을 가져보겠습니다.
Concurrent UI Parrern을 위한 인피니티 스톤들(useTransition, Suspense, ErrorBoundary, Stream SSR) + RSC - 미완
‘Next App router에서 RSC와 RCC가 어떤 방식으로 렌더링 되는거지?’ 를 알기 위해 RSC란 무엇인가? ‘Next App router에서 RSC와 RCC가 어떤 방식으로 렌더링 되는거지?’ 를 알기 위해 RSC란 무엇인가?
댓글남기기