리액트 블로그 - What We've Been Working On – March 2023

기운찬곰·2023년 9월 23일
post-thumbnail

원문 참고 : https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023

Overview

React Labs 게시물에는 활발한 연구 개발 중인 프로젝트에 대해 글을 씁니다. 지난 업데이트 이후 상당한 진전을 이루었고, 배운 내용을 공유하고자 합니다.


React Server Components

RSC(React Server Component)는 React 팀이 설계한 새로운 애플리케이션 아키텍처입니다.

우리는 먼저 RSC에 대한 연구를 소개 강연RFC에서 공유했습니다. 핵심을 말하자면, 앞서(먼저) 실행되고 자바스크립트 번들에서 제외되는 새로운 종류의 컴포넌트인 서버 컴포넌트를 소개합니다. 서버 컴포넌트는 빌드 중에 실행되어 파일 시스템을 읽거나 정적 컨텐츠를 가져올 수 있습니다. 또한 서버에서 실행할 수 있으므로 API를 구축할 필요 없이 데이터 계층(ex.DB)에 액세스할 수 있습니다. 서버 컴포넌트에서 브라우저의 인터랙티브한 클라이언트 컴포넌트로 데이터를 전달할 수 있습니다.

RSC는 서버 중심의 Multi-Page 앱의 단순한 "request/response" 마인드 모델과 클라이언트 중심의 Single-Page 앱의 원활한 상호작용성을 결합하여 두 가지 장점을 모두 제공합니다.

지난 업데이트 이후, 우리는 제안을 승인하기 위해 리액트 서버 컴포넌트 RFC를 병합했습니다. 우리는 React Server Module Conventions 제안서의 미해결 문제를 해결했으며, 파트너들과 "use client" 협약에 동의했습니다. 이 문서들은 RSC 호환 구현이 지원해야 할 사항에 대한 규격으로도 작용합니다.

가장 큰 변화는 서버 컴포넌트에서 데이터를 가져오는 주요 방법으로 async/await를 도입했다는 것입니다. 우리는 또한 Promises를 unwrap하는 use라는 새로운 hook를 도입하여 클라이언트로부터 데이터 로딩을 지원할 계획입니다. 클라이언트 전용 앱에서 임의의 컴포넌트에서 async/await를 지원할 수는 없지만, RSC 앱을 구성하는 방식과 유사하게 클라이언트 전용 앱을 구성할 때 이에 대한 지원을 추가할 계획입니다.

이제 데이터를 fetching하는 작업이 상당히 잘 이루어졌기 때문에 다른 방향을 모색하고 있습니다. 즉, 데이터베이스 mutations를 실행하고 forms을 구현할 수 있도록 클라이언트에서 서버로 데이터를 전송하는 것입니다. 서버/클라이언트 경계를 넘어 Server Action 기능을 전달하고, 이 기능을 통해 클라이언트가 호출하여 원활한 RPC를 제공할 수 있습니다.

Next.js App Router에 React Server Components가 배포되었습니다. 이것은 RSC를 원시적으로 구매(?)하는 라우터의 깊은 통합을 보여주지만, 이것이 RSC 호환 라우터와 프레임워크를 구축하는 유일한 방법은 아닙니다. RSC 사양 및 구현에 의해 제공되는 기능에 대한 분명한 구분이 있습니다. React Server Components는 호환되는 React 프레임워크에서 작동하는 컴포넌트의 사양으로 사용됩니다.

우리는 일반적으로 기존 프레임워크를 사용하는 것을 권장하지만, 사용자 정의 프레임워크를 직접 구축하는 것도 가능합니다. 자체 RSC 호환 프레임워크를 구축하는 것은 우리가 원하는 만큼 쉽지는 않은데, 주로 깊은 번들러 통합 때문입니다. 현재 세대의 번들러는 클라이언트에서 사용하기에 매우 좋지만, 서버와 클라이언트 사이의 단일 모듈 그래프를 splitting 하기 위한 first-class 으로 설계되지 않았습니다. 이것이 바로 우리가 RSC 내장을 위한 기본사항을 얻기 위해 번들러 개발자들과 직접 협력하는 이유입니다.

✍️ RSC가 나온지 꽤 된거 같군요. 하지만 여전히 저는 낯설고 어렵습니다. 결국 핵심은 서버와 클라이언트 두 가지 장점을 결합해서 제공한다는 것이죠. 이를 지원하기 위해 규격과 방법이 추가되었고 아직 더 발전해야할 부분이 있는거 같네요.


Asset Loading

Suspense를 사용하면 컴포넌트의 데이터 또는 코드가 로드되는 동안 화면에 표시할 내용을 지정할 수 있습니다. 이를 통해 페이지가 로드되는 동안은 물론 더 많은 데이터와 코드를 로드하는 라우터 탐색 중에도 사용자는 더 많은 컨텐츠를 계속해서 볼 수 있습니다. 그러나, 사용자의 관점에서, 새로운 콘텐츠의 준비 여부를 고려할 때, 데이터 로딩 및 렌더링이 전체 이야기를 말해주지는 않습니다. 기본적으로 브라우저는 스타일시트, 글꼴 및 이미지를 독립적으로 로드하므로 UI jumps 및 연속적인 layout shifts 이 발생할 수 있습니다.

우리는 Suspense를 스타일시트, 글꼴 및 이미지의 로딩 라이프사이클과 완전히 통합하여, React가 콘텐츠가 표시될 준비가 되었는지 여부를 그들을 고려하여 판단하는 작업을 진행하고 있습니다. React 컴포넌트를 작성하는 방식을 변경하지 않아도 업데이트가 일관성 있고 만족스러운 방식으로 작동할 것입니다. 최적화를 위해 글꼴과 같은 assets을 컴포넌트에서 직접 preload할 수 있는 수동 방식도 제공할 예정입니다.

우리는 현재 이러한 기능들을 구현하고 있으며 곧 더 많은 공유가 있을 것입니다.

✍️ Suspense 좋은 기능이죠. 위에 나와있는대로 부분 Suspense도 가능하고요. 이를 통해 사용자가 더 많은 컨텐츠를 볼 수 있게 해줍니다. 하지만 스타일시트, 글꼴 및 이미지는 독립적으로 로드하므로 이를 완전 통합하여 문제가 없게 하려는 것을 목표로 하고 있군요.


Document Metadata

앱의 페이지와 화면마다 <title> 태그, description 및 화면에 특정된 다른 <meta> 태그와 같은 메타데이터가 다를 수 있습니다. 유지보수 관점에서는 이 정보를 해당 페이지 또는 화면에 대한 React component에 가깝게 유지하는 것이 더 확장성이 있습니다. 그러나 이 메타데이터의 HTML 태그는 일반적으로 앱의 가장 root에 있는 컴포넌트에서 렌더링되는 문서 <head>에 있어야 합니다.

오늘날 사람들은 두 가지 기법 중 하나로 이 문제를 해결한다.

하나의 방법은 <title>, <meta> 및 그 내부의 다른 태그들을 이동시키는 특수한 제3자 컴포넌트를 문서 <head>에 렌더링하는 것입니다. 이것은 주요 브라우저에서 작동하지만 Open Graph parsers와 같이 클라이언트측 자바스크립트를 실행하지 않는 클라이언트들이 많기 때문에 이 기술은 보편적으로 적합하지 않습니다.

또 다른 기술은 서버가 페이지를 두 부분으로 나누어 렌더링하는 것이다. 먼저, 주요 콘텐츠가 렌더링되고 그러한 모든 태그가 수집됩니다. 그런 다음, 이러한 태그를 사용하여 <head>가 렌더링됩니다. 마지막으로 <head>와 주요 콘텐츠가 브라우저로 전송됩니다. 이 방법은 효과가 있지만 <head>를 전송하기 전에 모든 콘텐츠를 렌더링할 때까지 기다려야 하기 때문에 React 18’s Streaming Server Renderer를 이용할 수 없습니다.

이것이 바로 컴포넌트 트리의 어느 곳에서나 <title>, <meta> 및 메타데이터 <link>태그를 즉시 렌더링할 수 있도록 내장된 지원을 추가하는 이유입니다. 완전한 client-side code, SSR, 그리고 미래의 RSC를 포함한 모든 환경에서 동일한 방식으로 작동할 것입니다. 이에 대한 자세한 내용은 곧 공유하도록 하겠습니다.

✍️ Next.js에서는 이미 page 마다 title, meta 등을 등록할 수 있게 되어있습니다. React 자체에서도 이를 가능하게 하려는거 같습니다.


React Optimizing Compiler

이전 업데이트 이후로 우리는 리액트의 최적화 컴파일러인 React Forget의 설계를 적극적으로 반복하고 있습니다. 우리는 이전에 그것을 "auto-memoizing compiler"라고 말한 적이 있는데, 그것은 어떤 의미에서는 사실입니다. 그러나 컴파일러를 구축함으로써 리액트의 프로그래밍 모델을 더욱 깊이 이해할 수 있게 되었습니다. React Forget을 이해하는 더 나은 방법은 automatic reactivity compiler 입니다.

리액트의 핵심 아이디어는 개발자들이 자신의 UI를 현재 상태의 함수로 정의한다는 것입니다. 숫자, 문자열, 배열, 객체와 같은 일반 자바스크립트 값으로 작업하고, 컴포넌트 논리를 설명하기 위해 표준 자바스크립트 관용구(if/else 등)를 사용합니다. mental model은 애플리케이션 상태가 변경될 때마다 리액트가 다시 렌더링된다는 것입니다. 우리는 이 단순한 mental model과 자바스크립트 의미론에 근접하는 것이 리액트의 프로그래밍 모델에서 중요한 원리라고 믿습니다.

단점은 리액트가 때때로 너무 반응적일 수 있다는 것입니다: 너무 많이 재렌더링할 수 있다는 것입니다. 예를 들어, 자바스크립트에서는 두 개의 객체나 배열이 동등한지(키와 값이 동일한지) 비교하는 저렴한 방법이 없기 때문에, 각 렌더에서 새로운 객체나 배열을 만드는 것은 리액트가 엄격하게 필요한 것보다 더 많은 작업을 수행하게 할 수 있습니다. 이는 개발자들이 변경사항에 과도하게 반응하지 않도록 컴포넌트를 명시적으로 memoize해야 한다는 것을 의미합니다.

React Forget의 목표는 React 앱이 기본적으로 적절한 양의 반응성을 갖도록 하는 것입니다: 상태 값이 의미 있게 변경될 때만 해당 앱이 다시 render 됩니다. 구현 관점에서는 자동으로 메모하는 것을 의미하지만, reactivity framing(?)이 React과 Forget을 이해하는 더 나은 방법이라고 생각합니다. 이를 생각해 볼 수 있는 한 가지 방법은 현재 리액트는 object identity가 변경되면 리렌더링됩니다. Forget을 사용하면, 시맨틱 값이 변경되면 React이 다시 렌더링되지만 깊이있는 비교에 따른 런타임 비용은 발생하지 않습니다.

구체적인 진전의 관점에서, 지난 업데이트 이후로 우리는 이러한 자동 반응성 접근법과 일치하고 내부적으로 컴파일러를 사용함으로써 얻은 피드백을 통합하기 위해 컴파일러의 설계를 실질적으로 반복해 왔습니다. 작년 말부터 컴파일러에 대한 몇 가지 중요한 리팩터를 거친 후, 우리는 이제 Meta의 제한된 영역에서 컴파일러를 운영에 사용하기 시작했습니다. 생산성이 입증되면 오픈소스화할 계획입니다.

마지막으로, 많은 사람들이 컴파일러가 어떻게 작동하는지에 대해 관심을 표명했습니다. 컴파일러를 증명하고 오픈 소스를 제공할 때 더 많은 정보를 공유할 수 있기를 기대하고 있습니다. 하지만 지금은 몇 가지 정보를 공유할 수 있습니다:

컴파일러의 코어는 Babel과 거의 완전히 분리되어 있으며, 코어 컴파일러 API는 (대략) 이전 AST 입력이고 새로운 AST 출력입니다 (소스 위치 데이터를 유지하면서). 내부적으로 우리는 낮은 수준의 의미 분석을 수행하기 위해 사용자 정의 코드 표현 및 변환 파이프라인을 사용합니다. 그러나 컴파일러에 대한 주요 공개 인터페이스는 Babel과 다른 빌드 시스템 플러그인을 통해 이루어질 것입니다. 테스트를 쉽게 하기 위해 현재 우리는 컴파일러를 호출하여 각 함수의 새로운 버전을 생성하고 교체하는 매우 얇은 wrapper인 Babel plugin을 가지고 있습니다.

지난 몇 달 동안 컴파일러를 리팩토링하면서 조건, 루프, 재할당, mutation와 같은 복잡성을 처리할 수 있도록 코어 컴파일 모델을 다듬는 데 집중하고자 했습니다. 그러나 자바스크립트는 다음과 같은 기능들을 각각 표현할 수 있는 많은 방법들을 가지고 있다: if/else, ternaries, for, for-in, for-of, etc. 전체 언어를 사전에 지원하려고 하면 핵심 모델을 검증할 수 있는 시점이 지연되었을 것입니다. 대신, 우리는 작지만 대표적인 언어의 부분집합으로 시작했다: let/const, if/else, for loops, objects, arrays, primitives, function calls, and a few other features. 핵심 모델에 대한 자신감을 얻고 내부 추상화를 다듬으면서 지원 언어 하위 집합을 확장했습니다. 또한 아직 지원하지 않는 구문에 대해서도 명시적이며, 지원되지 않는 입력에 대해서는 진단을 기록하고 컴파일을 건너뜁니다. 우리는 메타의 코드베이스에서 컴파일러를 시험해보고 지원되지 않는 기능 중 가장 일반적인 것이 무엇인지 확인할 수 있는 유틸리티가 있어서 다음 우선순위를 정할 수 있습니다. 우리는 언어 전체를 지원하는 방향으로 점진적으로 확대해 나갈 것입니다.

리액트 컴포넌트에서 평이한 자바스크립트를 반응형으로 만드는 것은 코드가 무엇을 하는지 정확히 이해할 수 있도록 의미론에 대한 깊은 이해를 가진 컴파일러가 필요합니다. 이러한 접근 방식을 통해, 우리는 도메인 특정 언어에 국한되지 않고 언어의 완전한 표현성으로 모든 복잡성의 제품 코드를 작성할 수 있는 자바스크립트 내의 반응성을 위한 시스템을 개발하고 있습니다.

✍️ 생각해보니까 이 내용도 어디서 한번 들어본 내용이네요. (참고). 현재는 개발자가 리액트 최적화를 별도로 해줘야 하는 번거로움이 있죠. 이를 위해 useCallback, useMemo, React.memo 등을 사용하기도 하고요. 하지만 앞으로는 React 앱이 기본적으로 적절한 최적화를 자동으로 해줄지도 모르겠네요.


Offscreen Rendering

오프스크린 렌더링(Offscreen Rendering)은 추가적인 성능 오버헤드 없이 백그라운드에서 화면을 렌더링하기 위한 React의 다가오는 기능입니다. DOM 요소뿐만 아니라 React 컴포넌트에 대해서도 작동하는 content-visiblity CSS property의 버전이라고 생각하시면 됩니다. 연구 과정에서 다양한 활용 사례를 발견했습니다:

  • 라우터는 사용자가 화면을 탐색할 때 즉시 사용할 수 있도록 백그라운드에서 화면을 미리 렌더링할 수 있습니다.
  • 탭 전환 컴포넌트는 숨겨진 탭들의 상태를 보존할 수 있으므로, 사용자는 그들의 진행을 잃지 않고 탭들 사이를 전환할 수 있다.
  • 가상화된 리스트 컴포넌트는 가시창 위와 아래의 추가 행들을 미리 렌더링할 수 있다.
  • 모달이나 팝업을 열 때 나머지 앱은 '배경' 모드로 전환해 모달을 제외한 모든 것에 대해 이벤트와 업데이트가 비활성화되도록 할 수 있다.

대부분의 리액트 개발자들은 리액트의 offscreen API와 직접적으로 상호작용하지 않을 것이다. 대신 offscreen 렌더링은 라우터와 UI 라이브러리와 같은 것들에 통합될 것이고, 그런 라이브러리들을 사용하는 개발자들은 추가적인 작업 없이 자동으로 이익을 얻을 것이다.

이 아이디어는 컴포넌트를 쓰는 방식을 변경하지 않고도 리액트 트리를 화면 밖으로 렌더링할 수 있어야 한다는 것입니다. 컴포넌트가 화면 밖으로 렌더링되면 해당 컴포넌트가 표시될 때까지 실제로 마운트되지 않습니다. 즉, 효과가 발생하지 않습니다. 예를 들어, 컴포넌트가 useEffect를 사용하여 분석이 처음 표시될 때 로그 분석을 기록하는 경우 사전 렌더링을 수행해도 분석의 정확성이 흐려지지 않습니다. 마찬가지로, 컴포넌트가 화면 밖으로 나갈 때, 그 효과 또한 마운트 해제된다. 오프스크린 렌더링의 주요 특징은 컴포넌트의 상태를 잃지 않고 가시성을 전환할 수 있다는 점이다.

우리는 지난 업데이트 이후 안드로이드와 iOS의 리액트 네이티브 앱에서 내부적으로 메타 사전 렌더링의 실험적 버전을 테스트했으며 긍정적인 성능 결과를 얻었습니다. 또한 화면 밖 렌더링이 Suspense와 함께 작동하는 방식을 개선했습니다. 화면 밖 트리 안에서 일시 정지해도 Suspense의 단점이 발생하지 않습니다. 우리의 남은 작업은 library 개발자들에게 노출되는 기본 요소들을 마무리하는 것이다. 우리는 테스트 및 피드백을 위한 experimental API와 함께 RFC를 올해 말에 발표할 예정입니다.

✍️ 오... 이 기능은 굉장히 좋을거 같네요. CSS의 content-visibility 과 같은 기능을 리액트 컴포넌트에 대해서도 작동하다니... 기대됩니다. 위에처럼 여러가지 이점이 있겠네요.


Transition Tracing

Transition Tracing API를 사용하면 React Transitions이 느려지는 시기를 감지하고 왜 느려질 수 있는지 조사할 수 있습니다. 지난 업데이트에 이어 API의 초기 설계를 완료하고 RFC를 발행했습니다. 기본 능력도 구현됐다. 그 프로젝트는 현재 보류중이다. 우리는 RFC에 대한 피드백을 환영하며, 리액트를 위한 더 나은 성능 측정 도구를 제공하기 위해 개발을 재개하기를 기대한다. 이것은 Next.js App Router와 같이 React Transitions 위에 구축된 라우터에서 특히 유용할 것이다.


저희 팀은 이번 업데이트 외에도 최근 커뮤니티 팟캐스트 및 라이브 스트림에 게스트로 출연하여 업무에 대해 더 많은 이야기를 나누고 질문에 답하고 있습니다.


마치면서

상당히 재미있으면서 어려운 내용이네요. 리액트가 앞으로 어떻게 발전해갈지 대략적인 생각을 알 수 있었던거 같습니다. 하지만 자세한 내용은 세부적으로 더 알아봐야겠네요. 노력하겠습니다.

profile
부계정

0개의 댓글