Depois de ver a conferência do @nextjs 13
5분
The Two Reacts
들어가면서
무언가를 배우다보면, 특히 개발과 관련된 것, 저도 모르게 특정한 시야에 갇혀 버리는 경우가 종종 생깁니다. 이미 쉬운 방식 또는 단순한 이해를 분명히 알고 있는데도 불구하고, 그것을 망각하고 더 어렵고 고집스러운 방식으로 이해하려는 경우가 종종 생깁니다.
Dan Abramov가 작성한 The Two Reacts 블로그 글은 이러한 저의 경험을 떠올리게 합니다. 이 글에서 Dan은 RSC(React Server Components)에 대한 이야기를 합니다. 하지만 처음부터 끝까지 RSC에 대한 직접적인 이야기는 없습니다. 대신에, Dan은 애플리케이션이 어떻게 우리에게 나타나는지 차근차근 설명하며 우리가 잠깐 망각했던 부분을 다시 상기시켜 줍니다.
RSC는 아직도 트위터에서는 많은 토론과 반감들이 있다는 것을 Dan이 작성한 트윗1과 트윗2 를 보면 간접적으로 느낄 수 있습니다.
아마 이 글이 커뮤니티 일부에 대한 답변과 같은 역할을 할 수 있을 것 같습니다.
이 글을 옮겨보며 저도 다시 한번 이해하고, 더 나아가서 이해한 것을 다시 상기하고자 합니다.
The Two Reacts
만약 제가 여러분 화면에 어떤 것을 표시하고 싶다고 가정해보겠습니다. 이 블로그 페이지 같은 웹 페이지를 만들고 싶다면 인터렉티브 웹 앱 또는 네이티브 앱, 적어도 2개의 기기가 필요합니다.
여러분의 기기와 제 기기.
제 기기에서 몇 가지 코드와 데이터로 시작됩니다. 예를 들어, 이 블로그 게시물은 제 노트북에서 파일(Mdx)로 편집하고 있습니다. 이 글이 여러분의 화면에 표시된다는 것은 제 기기에서 여러분의 기기로 이미 데이터가 이동되었다는 것을 의미합니다. 제 코드와 데이터가 여러분의 기기에 표시되도록 HTML과 JavaScript로 변환된 것입니다.
이것이 리액트와 관련이 있을까요? 리액트는 컴포넌트(블로그 글, 가입 폼 또는 전체 앱과 같은)라고 불리는 독립적인 단위로 나누고 이를 레고처럼 조합할 수 있는 UI 프로그래밍 패러다임입니다.
컴포넌트는 코드이며, 그 코드는 어딘가에서 실행되어야 합니다. 하지만 잠깐만요, 누구의 컴퓨터에서 실행이 되어야 할까요? 여러분의 컴퓨터에서 실행해야 할까요? 아니면 제 컴퓨터에서 실행해야 할까요?
양쪽 모두에 대해서 예시를 한번 만들면서 이해해보겠습니다.
클라이언트에서 리액트
먼저, 여러분의 컴퓨터에서 컴포넌트가 실행되어야 한다고 가정해보겠습니다.
여기에 인터렉티브를 보여주기 위한 카운터 버튼이 있습니다. 클릭해보세요!
이 컴포넌트의 JavaScript 코드가 로드되었다면, 버튼을 누르는 즉시 숫자가 증가하는 것을 확인할 수 있습니다. 딜레이도 없고, 서버를 기다릴 필요도 없습니다. 또한 추가 데이터를 다운로드할 필요도 없습니다.
이 컴포넌트 코드가 여러분의 컴퓨터에서 실행되었기 때문입니다.
count
는 클라이언트 상태의 일부입니다. 버튼을 누를 때마다 업데이트되는 컴퓨터 메모리에 올라가는 정보입니다.
사용자가 버튼을 몇 번이나 누를지 모르기 때문에 컴퓨터에서 가능한 모든 클릭을 미리 계산해서 준비할 수 없습니다.
제 컴퓨터에서 준비할 수 있는 것은 초기 렌더링 출력("당신은 0번 클릭했습니다.")을 HTML로 전송하는 것 정도입니다. 하지만 그 이후부터는 여러분의 컴퓨터에서 이 코드가 실행되어야 합니다.
하지만 여러분이 여전히 여러분 자신의 컴퓨터에서 이 코드가 실행할 필요가 없다고 얘기할 수도 있습니다. "제 컴퓨터가 아닌 서버에서 대신 실행하면 안되는 건가요?" 버튼을 누를 때마다 여러분의 컴퓨터에서 저의 서버로 다음 렌더링 출력을 요청할 수도 있습니다. 클라이언트 사이드 JavaScript 프레임워크가 등장하기 전에는 웹 사이트가 이러한 방식으로 동작했다는 것을 기억하실 겁니다.
다음 슬라이드를 누른다거나, 탭을 클릭했다거나 등의 경우 새로운 페이지가 서버에서 로드되어 화면이 Flash되는 것을 기억하실 겁니다.
서버에 새로운 UI를 요청하는 방식은 약간의 지연이 예상되는 경우(링크 클릭 시 이동과 같은)에는 효과적입니다. 사용자가 앱에서 다른 화면으로 이동하고 있다는 것을 알고 있다면 기다릴 것입니다. 그러나 슬라이더 드래그, 탭 전환, 카드 스와이프, 메뉴 호버링, 차트 드래그와 같은 직관적인 조작은 즉각적인 피드백이 필요합니다. 이 경우 안정적으로 피드백을 제공하지 않으면 사용자는 불편함을 느낄 것입니다.
이는 기술적이라기 보다는 일상 생활에서 얻은 직관적인 경험입니다. 예를 들어, 엘리베이터 버튼을 누르면 바로 다음 층으로 이동하는 것을 기대하지는 않을 것입니다. 그러나 문 손잡이를 누를 때는 손의 움직임에 따라 손잡이가 바로 움직이기를 기대합니다. 그렇지 않으면 문 손잡이가 고장났다고 생각할 것입니다. 사실 엘리베이터 버튼을 누를때도 즉각적인 피드백을 기대합니다: 손가락 압력에 따라 버튼이 눌렸다는 것을 알 수 있어야 합니다. 그리고 그 압력에 따라 버튼 불이 켜져야 합니다.
UI를 만들 때 네트워크 왕복 없이 짧은 지연 시간을 보장하는 최소한의 인터렉션에 응답할 수 있어야 합니다.
리액트 멘탈 모델을 일종의 방정식으로 설명하는 것을 보셨을 겁니다: UI는 상태(state)의 함수, 즉 UI = f(state)
.
이는 UI 코드가 하나의 상태(state)를 가진 하나의 함수가 되어야한다는 것을 의미하지는 않습니다.
현재 상태(state)가 UI를 결정한다는 의미입니다. 상태(state)가 변경되면 UI는 다시 계산되어야 합니다.
상태(state)는 여러분 컴퓨터에 "존재"하기 때문에 UI를 계산하는 코드(컴포넌트)도 여러분 컴퓨터에서 실행되어야 합니다.
그렇지만, 이것이 반드시 맞는 것일까요?
서버에서 리액트
위와는 반대되는 의견으로 "컴퓨터에서 실행되어야 하는 코드는 제 컴퓨터가 아닌 서버에서 실행되어야 한다"에 대한 것입니다.
다음은 블로그 게시물에 대한 미리보기 카드입니다:
RSC Mental Model
1736 words이 페이지의 컴포넌트는 해당 페이지의 단어 수를 어떻게 알 수 있었을까요?
네트워크 탭을 확인하면 추가로 데이터를 요청하지 않았음을 확인할 수 있습니다. 단어 수를 알기 위해 별도로 해당 블로그 게시물 전체를 다운로드 하는 것도 아닙니다. 이 페이지에 해당 블로그 글을 임베드하는 것도 아닙니다. 단어 수를 계산하기 위해 어떤 API도 호출하지 않습니다.
그래서 이 컴포넌트는 어떻게 동작하는 걸까요?
해당 <PostPreview />
컴포넌트는 제 컴퓨터에서 실행됩니다. 파일을 읽기 위해서 contentlayer
에서 미리
생성된 데이터를 사용하여 파일을 읽습니다. slug에 해당하는 게시물을 찾고 단어 수를 계산합니다. 단어 수를 계산하기
위해 빈칸 정규표현식(/\s+/
)을 사용하여 단어를 분리하고, 빈 문자열을 제거한 후 배열의 길이를 반환합니다.
컴포넌트 코드가 데이터가 위치한 곳에서 바로 실행되기 때문에 네트워크 요청과 같은 추가 수행 작업이 필요 없습니다.
제 블로그의 모든 게시물을 단어 수와 함께 리스트 형태로 보여줄 수도 있습니다.
ChatGPT Prompt Engineering for Developers
10013 wordsFigma cube, skew plugin
217 words디자인 시스템
1393 wordsGithub Code Search
725 wordsGithub Copilot
962 wordsSEONEST
51 words블로그 생성
258 wordsBundle - Module Bundler
1941 wordsESM + TypeScript
1732 wordsJavaScript Eventing Deep dive
1824 wordsJavaScript Object(1) - Prototype
1843 wordsJavaScript Object(2) - Class
1053 wordsJavaScript 동작 원리
1573 wordsTailwind CSS v4.0
1765 wordsYou don't need a build step
2286 words날렸습니다...
692 words동적으로 Image 만들기, edge-function을 곁드린
1001 words모노레포에 관하여
4208 wordsComplex Context APIs
1784 wordsDebug React "Hello world"
713 wordsDelightful React File/Directory Structure
1849 wordsFixing race condition in React
1115 wordsReact 18이 애플리케이션 성능을 향상시키는 방법
2720 wordsNew features in React 18
4150 wordsNext generation(Next.js 13.4)
2517 wordsNext.js 13.1
1325 wordsNext.js 13.2
1981 wordsNext.js 13.3
1426 wordsNext.js 13
1758 wordsNextjs 12.3
577 wordsNextjs Layout RFC: New Routing System
2882 wordsNextjs Layouts RFC Update
2187 wordsNextjs + React Server Component
1415 wordsPlanetScale + Prisma + Next.js
1044 wordsReact 19 Beta!
3350 wordsReact 연대기
1987 wordsReact Compiler
2057 wordsReact Hydration 문제에 대해서
1962 wordsReact RFC: useEvent()
1512 wordsReact Server Components 이해하기(by Josh.W.Comeau)
4029 wordsReact Server Components 이해하기
1778 wordsReact를 좋아하는 이유(by Kent C.Dodds)
845 wordsRemix 맛보기 (1)
3534 wordsRemix 맛보기 (2)
3464 wordsRemix@1.6.5
556 wordsRSC Mental Model
1736 wordsThe End of Front-End Development
1771 wordsThe Two Reacts
1577 wordsTraditional Approaches vs Suspense in React
1529 wordsUnderstanding useMemo and useCallback
2726 wordsWhy React Re-Renders
1835 words리액트 앱에 대한 프로파일링
877 wordsECMAScript 2022
1518 wordsGreat Developer Experience (Vercel)
1304 wordsHTTP... 그것에 대하여
2788 wordsShould I use pixels? or ems/rems?
2327 wordsUse A Reverse Proxy(Nginx)
2032 words알아놓으면 좋은 10가지 모던 웹 아키텍쳐 컨셉
1280 words왜 Vercel은 Edge 렌더링을 다시 Node.js 로 되돌렸을까?
523 words웹 렌더링
2178 words웹 사이트 성능(1) - 지표
1056 words웹 사이트 성능(2) - Core web vitals, LCP
3929 words웹 사이트 성능(3) - Core web vitals, FID
1998 words웹 사이트 성능(4) - Core web vitals, CLS
1494 words간단하게 처리할 수 있습니다. 모든 게시물을 가져와서 <PostPreview />
를 사용하여 렌더링합니다.
이 컴포넌트 코드는 여러분의 컴퓨터에서 실행될 필요가 없습니다. 실제로 여러분 컴퓨터에 제 파일이 존재하지 않기 때문에 실행될 수도 없습니다. 그러면 이 컴포넌트 코드가 언제 실행되었을까요?
Tue Aug 13 2024 14:18:59 GMT+0000 (Coordinated Universal Time)
저는 Next.js를 이용해 SSG(Static Site Generation)을 사용하고 있습니다. 이 페이지는 정적으로 생성되었기 때문에 이 페이지를 빌드할 때 이 코드가 실행됩니다. 이 시간은 제가 이 페이지를 빌드한 시간입니다.
이 시간은 제가 블로그를 정적 웹 호스팅에 마지막으로 배포한 시간이 표시가 됩니다. 정확하게는 이 페이지를 빌드할 때 시간이 표시됩니다.
데이터 소스(source) 가까운 곳에서 컴포넌트를 실행하게 되면 여러분의 기기로 해당 정보를 보내기 전에 자체적으로 데이터를 읽고 미리 처리할 수 있습니다.
여러분이 이 페이지를 로드할 때는 <PostList />
, <PostPreview />
등은 없습니다.
대신 <a>
, <i>
등을 포함한 HTML 코드만 있습니다. 여러분의 기기는 컴포넌트가 UI를 만들기 위해 사용한
전체 데이터(실제 게시물)가 아니라 실제 페이지를 표시하는 UI(렌더링된 게시물 제목, 링크 URL, 단어 수 등)만
수신합니다.
여기서 멘탈 모델은, UI는 서버 데이터의 함수, 즉 UI = f(data)
.
해당 데이터들은 제 기기에서만 존재합니다. 그래서 제 기기에서 실행되어야 합니다.
그러면 어떤 것을 사용해야 하나요?
두 개의 리액트
UI는 컴포넌트로 만듭니다. 그러나 여기서는 매우 다른 두 가지 방법을 말하고 있습니다.
UI = f(state)
, 여기서state
는 클라이언트 사이드이고,f
는 클라이언트에서 실행됩니다. 이 방식은<Counter />
와 같은 즉각적인 인터렉티브 컴포넌트를 만들 수 있습니다. (여기서f
는 서버에서 초기 상태의 HTML을 생성하여 클라이언트로 전송할 수도 있습니다.)UI = f(data)
, 여기서data
는 서버 사이드이고,f
는 서버에서만 실행됩니다. 이 방식은<PostPreview />
와 같은 데이터 처리 컴포넌트를 만들 수 있습니다.(여기서f
는 서버에서만 실행됩니다. 빌드 시간은 "서버"로 계산됩니다.)
더 익숙한 방식을 제쳐두면, 두 가지 방식은 모두 각자의 장점을 살릴 수 있는 매력적인 방식입니다. 불행하게도 이러한 방법은 서로 충돌하는 것처럼 보입니다.
<Counter />
와 같은 즉각적인 상호작용을 위해서는 클라이언트에서 컴포넌트를 실행해야 합니다.
하지만 <PostPreview />
와 같은 컴포넌트는 원칙적으로는 클라이언트에서 실행할 수 없습니다.
서버 전용 API를 사용하기 때문입니다.(이것이 바로 이 컴포넌트의 요점입니다! 그렇지 않다면 클라이언트에서
실행하는 편이 나을지도 모릅니다.)
그러면 모든 컴포넌트를 서버에서 실행하는 것은 어떨까요? 서버에서 <Counter />
와 같은 컴포넌트는
초기 상태만 렌더링할 수 있습니다. 서버는 "현재" 상태를 알지 못하고 서버와 클라이언트 간에 해당 상태를 전달하는 것은
너무 느리고(URL처럼 작은게 아니라면) 항상 가능한 것도 아닙니다.(예: 제 블로그 서버 코드는 배포, 빌드 시점에만
실행되므로 서버에 "전달"할 수 없습니다. 즉, 서버리스로 동작하는 몇 가지 API를 제외하고는 요청을 받을 수 있는
서버 자체가 존재하지 않습니다.)
뭔가 두 개의 React 중 하나를 선택해야 하는 것처럼 보입니다:
- "클라이언트"
UI = f(state)
패러다임으로<Counter />
를 만들 수 있습니다. - "서버"
UI = f(data)
패러다임으로<PostPreview />
를 만들 수 있습니다.
그러나 현실에서 실제 "공식"은 UI = f(data, state)
에 더 가깝습니다. 데이터가 없거나 상태가 없는 경우에는
이러한 경우를 일반화할 수 있습니다. 그러나 대부분 다른 추상화를 선택할 필요 없이 두 가지 방식을 모두 처리할
수 있는 프로그래밍 패러다임을 선호합니다.
그렇다면 해결해야 할 문제는 f
를 서로 다른 두 개의 프로그래밍 환경에서 어떻게 분할할 것인가입니다.
이것이 가능할까요? 여기서 f
는 컴포넌트를 나타내는 실제 함수를 말하는 것이 아니라는 것을 기억하세요.
리액트의 장점은 그대로 유지하면서 컴포넌트를 여러분의 컴퓨터와 제 컴퓨터에서 분리할 수 있는 방법이 있을까요? 서로 다른 두 환경의 컴포넌트를 결합하고 중첩할 수 있을까요? 어떻게 하면 될까요?
어떻게 해야 할까요?
마무리하며
이 글은 RSC에 대한 직접적인 이야기는 없습니다. 하지만 이 글을 읽고 나면 RSC가 왜 필요한지, 리액트가 어떤 문제를 해결하기 위해 이를 만들어졌는지 이해할 수 있을 것입니다.
리액트가 왜 만들어졌는지, 만들어진 방식이 어떤 것인지, 그리고 왜 이러한 방식으로 만들어졌는지에 대한 이해와 멘탈 모델 을 가지게 된다면 좀 더 즐겁게 프로그래밍을 할 수 있을 것 같습니다.
아직까지는 RSC를 product-ready 기능으로, 쉽게 사용할 수 있는 환경이 Next.js가 거의 유일한 환경입니다. 그래서 RSC를 사용하기 위해서 Next.js에 대한 이해가 필요합니다. 이에 대한 비판, 토론도 꽤 있기도 합니다...
그러나 RSC 이해를 통해서 리액트 자체에 대한 이해를 높이는 것은 분명히 도움이 될 것입니다.
마지막으로 Dan이 글을 마무리하며 우리에게 질문을 던진 것과 관련해서 아래 트위터에서 더 많은 토론을 볼 수 있습니다.
⚛️📝 New on Overreacted: The Two Reacts overreacted.io/the-two-reacts/
마지막 업데이트
2/5/2024