<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://waynekim92.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://waynekim92.github.io/" rel="alternate" type="text/html" /><updated>2026-10-16T16:55:06+00:00</updated><id>https://waynekim92.github.io/feed.xml</id><title type="html">Wayne’s Dev Blog</title><subtitle>Do you like React, React Native, dogs, games, drawings, etc.? is it so. I write about them.</subtitle><entry><title type="html">Expo 훑어보기 - 2</title><link href="https://waynekim92.github.io/expo/2025/02/08/expo-tutorial-2.html" rel="alternate" type="text/html" title="Expo 훑어보기 - 2" /><published>2025-02-08T13:55:00+00:00</published><updated>2025-02-08T13:55:00+00:00</updated><id>https://waynekim92.github.io/expo/2025/02/08/expo-tutorial-2</id><content type="html" xml:base="https://waynekim92.github.io/expo/2025/02/08/expo-tutorial-2.html"><![CDATA[<h1 id="expo-tutorial-훑어보기---2">Expo Tutorial 훑어보기 - 2</h1>

<h2 id="alert은-또-왜-expo에서-사용할-수-있는-건데">alert은 또 왜 Expo에서 사용할 수 있는 건데?</h2>

<p>global 객체를 건드리는 것은 일반적이지 않은데… global.alert에 알림을 위한 함수가 등록되어 있다.
Expo 레포를 살펴보아도 해당 코드를 못 찾았다 ㄷㄷ
결국은 React-Native에 있는 Alert을 이용했을 것으로 추측된다.</p>

<h2 id="expo-image-picker">expo-image-picker</h2>

<p>5년 전에 Expo가 네이티브 모듈을 커스텀해서 못 쓴다는 욕을 먹고 있을 때에도 Expo SDK는 품질이 좋다는 이야기를 많이들었다.
Expo-image-picker를 쏘보면 꽤 쓸만하다! ( 요즘에는 GPT로 있어서, 쓰는 법 알려주는 건 무의미하니 패스)</p>

<h2 id="react-native-modal">React-Native Modal</h2>

<p>Modal 많이 쓸만해졌네! animationType 지정하여서 원하는 형태로 쓰면 좋을 듯 하다</p>

<h2 id="react-native-gesture-handler">react-native-gesture-handler</h2>

<p>필요에 따라서 제스처 핸들러를 쓰면 좋을 듯 하다. 사용법은 GPT에게~</p>

<h2 id="스크린샷-찍기">스크린샷 찍기!</h2>

<p>오… 처음 보는 패키지가 있어서, 이건 보고 넘어가야겠다.</p>

<p><code class="language-plaintext highlighter-rouge">react-native-view-shot</code>로 View에 있는 요소를 찍고 <code class="language-plaintext highlighter-rouge">expo-media-library</code>로 저장할 수 있다니?!
자세한 사용법은 <a href="https://docs.expo.dev/tutorial/screenshot/">문서</a>를 보자~</p>

<h3 id="그런데-웹은-미지원">그런데 웹은 미지원</h3>

<p><code class="language-plaintext highlighter-rouge">dom-to-image</code>을 사용하여, 웹에서는 스크린샷을 만들어서 처리하구나~
특정 플랫폼 종속 코드에는 Platform 객체를 이용해보자~</p>

<h2 id="상태바-스플레시-스크린-앱-아이콘">상태바, 스플레시 스크린, 앱 아이콘</h2>

<p>상태바 변경은 <code class="language-plaintext highlighter-rouge">expo-status-bar</code></p>

<h3 id="expo-status-bar-는-왜-필요할까-rn-내장-컴포넌트와-api로-충분하지-않나">expo-status-bar 는 왜 필요할까? RN 내장 컴포넌트와 API로 충분하지 않나?</h3>

<p>RN의 StatusBar와 동일한 인터페이스를 가지지만 Expo 환경에 적합하게 동작할 수 있게 <a href="https://github.com/expo/expo/tree/main/packages/expo-status-bar">default 값</a>이 다르다고함.</p>

<h2 id="앱-아이콘">앱 아이콘</h2>

<p>앱 아이콘 변경은 <code class="language-plaintext highlighter-rouge">assets/images/icon.png</code>를 변경</p>

<h2 id="스플래스-스크린">스플래스 스크린</h2>

<p>스플래쉬 스크린 이미지 변경은 <code class="language-plaintext highlighter-rouge">expo-splash-screen</code> 이미 추가 되어 있으니, app.json의 플러그인 설정 변경
명시적으로 splash screen을 숨기라고 하기 전까지 계속 보여준다고함.
필수 API 호출, 글꼴 로드 등 작업을 수행하고 스플래쉬 스크린을 숨김 처리하면 유용하다고함</p>

<h1 id="expo의-추천-리소스">Expo의 추천 리소스</h1>
<p>https://docs.expo.dev/tutorial/follow-up/</p>]]></content><author><name></name></author><category term="expo" /><summary type="html"><![CDATA[Expo Tutorial 훑어보기 - 2]]></summary></entry><entry><title type="html">Expo 훑어보기 - 1</title><link href="https://waynekim92.github.io/expo/2025/02/07/expo-tutorial-1.html" rel="alternate" type="text/html" title="Expo 훑어보기 - 1" /><published>2025-02-07T13:55:00+00:00</published><updated>2025-02-07T13:55:00+00:00</updated><id>https://waynekim92.github.io/expo/2025/02/07/expo-tutorial-1</id><content type="html" xml:base="https://waynekim92.github.io/expo/2025/02/07/expo-tutorial-1.html"><![CDATA[<h1 id="expo">Expo</h1>

<p>5년 전에 RN 개발자에게 실망감을 주었던 Expo가 달라졌어요?</p>

<p>Expo는 프레임워크가 되었다. 파일 기반 라우팅, 네이티브 표준 라이브러리 등을 제공하는 정도로 발전했다. (5년 전에는 둘 다 불가능 했다.)
거기다 EAS(Epo Application Services)를 통해 Expo를 이용하는 개발자에게 빌드 부터 배포에 들였던 시간을 줄여준다.</p>

<h2 id="expo-tutorial">Expo Tutorial</h2>

<p>프로젝트 생성 명령어</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx create-expo-app@latest
</code></pre></div></div>

<p>보일러 플레이트 코드 제거하는 명령어 ( DX 좋고요! )</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run reset-project
</code></pre></div></div>

<h3 id="reset-project-까보자-dx를-위하여">reset-project 까보자~ DX를 위하여~</h3>

<p><a href="https://github.com/expo/expo/blob/1de6bb5632864184d8ea89d4650f70bb42dee4ac/templates/expo-template-default/scripts/reset-project.js#L6">코드</a>를 보면 생각보다 간단하다.</p>

<p>app-example 폴더를 만들고 기존 예제 코드를 옮기고 app 폴더를 만들고 꼭 필요한 파일만 생성하면 끝!</p>

<p>이런 스크립트가 “어떤 폴더 지우고 어떤 파일을 복사-붙여넣기” 하세요 같은 문서 보다 백만배 더 효과적이다.</p>

<h3 id="expo-router">expo router</h3>

<p>expo router는 파일 기반 라우팅을 제공하는 프레임워크이다. ( 요즘에는 ChatGPT로 질문하면, 프레임워크와 라이브러리의 차이를 너무나도 쉽게 할 수 있어서 세상 참 좋아졌다. )</p>

<p>프레임워크이므로 몇가지 사용 규칙을 살펴보자.</p>
<ul>
  <li>app 폴더: 라우터와 레이아웃만 포함하는 폴더, 이 폴더에 포함된 파일은 앱에서 화면이고 웹에서는 페이지가가 된다.</li>
  <li>app/_layout.tsx 파일: 일관성 유지를 위한 해더, 탭 바 같은 공유 UI 요소를 정의하는 곳</li>
  <li>파일 이름 컨벤션
    <ul>
      <li>index 파일은 부모 폴더의 이름과 일치하는 경로를 가지므로 경로 세그먼트 추가 필요 없음</li>
      <li>app/index.tsx 파일은 <code class="language-plaintext highlighter-rouge">/</code> 라우트와 매칭 된다.</li>
    </ul>
  </li>
  <li>라우트 파일은 default 값으로써 React 컴포넌트로 export 된다.
    <ul>
      <li>default로 export 되지 않은 값 expo-router에 의해 렌더링 되지 않는다.</li>
    </ul>
  </li>
  <li>ios, android, web을 네비게이션 탐색 구조를 공유한다.</li>
</ul>

<p>react-navigation 으로 부터 탈출!</p>

<h3 id="tabs-폴더">“(tabs)” 폴더</h3>

<p><code class="language-plaintext highlighter-rouge">(tabs)</code> 이름을 가진 폴더는 탭 네비게이션을 정의하는 곳이다. ( 잘 쓰이지 않겠지만 이중삼중으로 tabs 내비게이션을 구성할 수 있다. )
<code class="language-plaintext highlighter-rouge">_layout.tsx</code> 파일에서 탭 네비게이션을 정의하면 된다.</p>

<h2 id="expo-router-">expo router ?!</h2>
<p>expo router에 어떤 발전이 있었는 지 보자.</p>

<ul>
  <li>웹에서 가장 뛰어난 파일 시스템 라우팅 개념을 접목하였다고 한다. ( 아주 <a href="https://docs.expo.dev/router/introduction/#what-are-the-benefits-of-file-based-routing">자화자찬</a>이 …)</li>
  <li>지연 평가를 통하여 최적화도 되어있다고 한다.</li>
  <li>웹 SEO를 위하여 빌드 타임에 정적 렌더링을 지원한다.</li>
</ul>

<p>그리고 몇가지 특이사항도 보고 넘어가자.</p>
<ul>
  <li>React Navigation과 같이 사용할 수 있지만… 당연히 새앱에서는 Expo Router만 사용하기를 권장한다.</li>
  <li>Wix의 React Native Navigation을 Expo Go에서 호환이 안되어서 사용하지 못 한다.</li>
  <li>서버 측 렌더링은 기본적으로 지원하지 않아서 추가 설정을 해야한다. ( 소스코드를 살펴보니, metro를 이용하여 SSR을 지원하려는 흔적이 보인다. )</li>
  <li>웹 번들러로 Metro web을 사용한다.</li>
</ul>

<h3 id="expo-router의-파일-기반-네비게이션-시스템은-어떻게-만들어져있을까">expo router의 파일 기반 네비게이션 시스템은 어떻게 만들어져있을까?</h3>

<p>까보고 싶어서 양이 엄청나게 많다… 단순히 하나의 라이브러리로 만들기에는 규모가 크다.
그래서인지  여러 패키지를 의존하고 있다.</p>

<p>expo-router를 설치할 때 같이 설치하는 의존성 패키지들</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar
</code></pre></div></div>

<p><a href="https://github.com/expo/expo/tree/main/packages/expo-router">package</a>를 까보자…
expo-router의 <a href="https://github.com/expo/expo/blob/main/packages/expo-router/package.json">package.json</a>을 살펴보니, @react-navigation에 의존하고 있는 것으로 확인되었다.
그럼… expo-router의 핵심 부분은 파일 기반 라우터를 처리하는 코드로 구성되어 있을 것으로 추측된다.</p>

<p>코드 파일을 읽고 가공하는 일은 주로 babel로 처리하니 <a href="https://github.com/expo/expo/blob/main/packages/babel-preset-expo/src/expo-router-plugin.ts">expo-router-plugins.ts</a>를 살펴보자.
코드만 보아서는 이해가 되지 않아서, 다른 파일을 찾아보자.</p>

<p>결국은 react-navigation으로 구성되어 있으니, react-navigation을 사용할 때 처럼 App을 등록하는 부분이 있을 거 같아 찾아보니 이 <a href="https://github.com/expo/expo/blob/80b3d8e6f6a659806b6bc06b555e5397090a36db/packages/expo-router/src/qualified-entry.tsx#L20">파일</a>을 찾았다.</p>

<p>Head.Provider는 무시해도 될 거 같아보이고, ExpoRoot를 살펴보니 ctx가 어떻게 만들어지는 지만 알면, 파일 기반 네비게이션을 위한 라우터 정보를 어떻게 만드는 지 알 수 있을 것으로 추측된다.
ctx에 담겨오는 children가 어떻게 만들어지는만 보면 될것으로 추측됨!</p>

<h3 id="_ctxjs"><a href="https://github.com/expo/expo/blob/80b3d8e6f6a659806b6bc06b555e5397090a36db/packages/expo-router/_ctx.js">_ctx.js</a></h3>

<p><a href="https://webpack.js.org/guides/dependency-management/#requirecontext">require.context</a>는 webpack 컴파일러가 지원하는 특수 기능으로, 특정 기본 디렉토리에서 일치하는 모든 모듈을 가져올 수 있습니다.
그런데… webpack은 웹에서만 사용될 터인데… expo의 모바일 번들링 당시에는 메트로를 사용하는 것일텐데, 무언가 잘못 추측하고 있는 거 같다.
음… 중간 과정을 놓친 거 같지만 ctx를 통해서 app 폴더의 트리 구조 정보를 받아올 것으로 추측된다.</p>

<p><a href="packages/expo-router/src/rsc/router/expo-definedRouter.ts">expo의 라우터</a>를 정의하는 파일을 찾았으니, 이 파일을 기반으로 역으로 추적해보자.
문맥을 잃어버림 … ( Expo Router 개발자들은 DX를 위아혀 얼마나 딥다이브 하고 있는 것인지… 이걸 개발하였던 배경지식이 있는 사람만 제대로 이해 할 수 있는 수준처럼 느껴진다. )</p>

<p>도움을 받을 수 있을 지 모르지만 <a href="https://github.com/expo/expo/discussions/34744">Help!</a>을 요청해보자.</p>

<p>아쉽지만 튜토리얼의 다음 단계로 넘어가자.</p>]]></content><author><name></name></author><category term="expo" /><summary type="html"><![CDATA[Expo]]></summary></entry><entry><title type="html">React Native Web 파헤치기 3</title><link href="https://waynekim92.github.io/react-native/react-native-web/2024/12/21/react-native-web-deep-dive-3.html" rel="alternate" type="text/html" title="React Native Web 파헤치기 3" /><published>2024-12-21T10:26:00+00:00</published><updated>2024-12-21T10:26:00+00:00</updated><id>https://waynekim92.github.io/react-native/react-native-web/2024/12/21/react-native-web-deep-dive-3</id><content type="html" xml:base="https://waynekim92.github.io/react-native/react-native-web/2024/12/21/react-native-web-deep-dive-3.html"><![CDATA[<h1 id="batchinator-">Batchinator ?</h1>

<p><a href="https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/vendor/react-native/Batchinator/index.js">Batchinator</a>의 코드는 짧으니, 처음 부터 하나씩 씹어먹어보겠습니다.
InteractionManager를 import 하는군요. InteractionManager는 RN에도 있는 API 입니다.
<a href="https://reactnative.dev/docs/interactionmanager">공식 문서</a>에는 상호작용/애니메이션이 완료 된 후에 오랫동안 실행되는 작업에 대해 예약할 수 있는 API라고 하는 군요.</p>

<p>이 API를 사용해본 적이 없기 때문에… 왜 필요할지 한번 추측/생각 해보고 넘어가봐야할 거 같습니다.
일반적으로 React Native에서 JS Thread는 하나만 사용할 수 있지요?! 우리가 실행하는 작업이 완료되기 까지 오랜 시간이 걸리는 작업이라면,
이 작업이 완료되기 까지 다른 작업을 실행할 수 없게 됩니다. 이런 경우에 InteractionManager를 사용하면, 이점이 있는 것으로 추측해볼 수 있을 거 같습니다.</p>

<p>특히 자바스크립트 애니메이션 작업을 부드럽게 처리할 수 있다는군요?!</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">InteractionManager</span><span class="p">.</span><span class="nx">runAfterInteractions</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// ...long-running synchronous task...</span>
<span class="p">});</span>
</code></pre></div></div>

<p>터치 핸들링 시스템은 터치를 상호작용으로 간주하고 모든 터치가 종료되거나 취소될 때까지 runAfterInteractions의 콜백을 지연한다고 하는군요.
그럼 사용자의 반응이 발생 중 일 때에는 콜백을 실행하기 않기 때문에 애니메이션 작업에 영향을 주지 않는 콜백이 등록 되는 것으로 보면 되겠군요.</p>

<p>InteractionManager에 대한 이해도가 올라갔으므로 Batchinator 코드를 다시 보겠습니다.</p>

<p>Batchinator의 주석을 보니, 더 명확해지는 군요. 낮은 우선순위의 콜백 호출을 일괄 처리하기 위한 클래스라는군요.
예약 된 횟수와 상관 없이 기다렸다가 오직 한번 실행한다는군요.</p>

<p>그럼 Batchinator에 대해서 파악되었군요. 우선 순위 높은 애니메이션 작업 부터 실행 될 수 있게 하여 UX를 높이기 위해 필요한 것이군요.
이제 JS Thread에 의해서 실행되는 작업이 많을 때, InteractionManager를 사용하여 UX를 고려하여 특정 연산을 처리할 수 있을 거 같습니다.</p>

<h1 id="virtualizedlist-코드-이어서-보자">VirtualizedList 코드 이어서 보자</h1>

<p>VirtualizedList의 생성자 코드를 보다가 여기까지 왔는데요. 다시 생성자 코드로 돌아가서 순차적으로 살펴보겠습니다.</p>

<p>FillRateHelper로 List의 상단과 하단 높이를 측정하는 데, 일반적으로 0이므로 크게 신경쓰지 않아도 되겠군요.
Batchinator는 우선순위가 낮은 콜백을 일괄 처리하기 위한 클래스로 이해했으니, 이것도 크게 신경쓰지 않아도 되겠군요.</p>

<p>다시 최적화 렌더링을 이해하기 위한 코드를 찾아나서겠습니다.
viewabilityConfigCallbackPairs prop은 필수 prop이 아니므로 패스해도 될 거 같습니다.</p>

<p>initialRenderRegion은 보아야 될 거 같군요. 우리가 처음 렌더링 될 요소에 대해서 결정하는 부분이니까요.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span>
<span class="p">{</span>
  <span class="c1">//...</span>
  <span class="kd">const</span> <span class="nx">initialRenderRegion</span> <span class="o">=</span> <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_initialRenderRegion</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span>

  <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">cellsAroundViewport</span><span class="p">:</span> <span class="nx">initialRenderRegion</span><span class="p">,</span>
    <span class="na">renderMask</span><span class="p">:</span> <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_createRenderMask</span><span class="p">(</span><span class="nx">props</span><span class="p">,</span> <span class="nx">initialRenderRegion</span><span class="p">),</span>
  <span class="p">};</span>
  <span class="c1">//...</span>
<span class="p">}</span>

<span class="k">static</span> <span class="nx">_initialRenderRegion</span><span class="p">(</span><span class="nx">props</span><span class="p">:</span> <span class="nx">Props</span><span class="p">):</span> <span class="p">{</span><span class="nl">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">}</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">itemCount</span> <span class="o">=</span> <span class="nx">props</span><span class="p">.</span><span class="nx">getItemCount</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">firstCellIndex</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span>
    <span class="mi">0</span><span class="p">,</span>
    <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">itemCount</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">initialScrollIndex</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)),</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">lastCellIndex</span> <span class="o">=</span>
    <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span>
      <span class="nx">itemCount</span><span class="p">,</span>
      <span class="nx">firstCellIndex</span> <span class="o">+</span> <span class="nx">initialNumToRenderOrDefault</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">initialNumToRender</span><span class="p">),</span>
    <span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="na">first</span><span class="p">:</span> <span class="nx">firstCellIndex</span><span class="p">,</span>
    <span class="na">last</span><span class="p">:</span> <span class="nx">lastCellIndex</span><span class="p">,</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>_initialRenderRegion으로 렌더링 될 cell의 범위를 구하는군요. 그 후 VirtualizedList의 state이 초기화 됩니다.
renderMask는 VirtualizedList._createRenderMask로 만들어지는데요. 렌더링 시에 중요한 역할을 할 것으로 추측됩니다. 한번 살펴보아요!</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="nx">_createRenderMask</span><span class="p">(</span>
  <span class="nx">props</span><span class="p">:</span> <span class="nx">Props</span><span class="p">,</span>
  <span class="nx">cellsAroundViewport</span><span class="p">:</span> <span class="p">{</span><span class="nl">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">},</span>
  <span class="nx">additionalRegions</span><span class="p">?:</span> <span class="p">?</span><span class="nx">$ReadOnlyArray</span><span class="o">&lt;</span><span class="p">{</span><span class="na">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="na">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">}</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">):</span> <span class="nx">CellRenderMask</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">itemCount</span> <span class="o">=</span> <span class="nx">props</span><span class="p">.</span><span class="nx">getItemCount</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>

  <span class="nx">invariant</span><span class="p">(</span>
    <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">first</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span>
    <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">last</span> <span class="o">&gt;=</span> <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">first</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span>
    <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">last</span> <span class="o">&lt;</span> <span class="nx">itemCount</span><span class="p">,</span>
    <span class="s2">`Invalid cells around viewport "[</span><span class="p">${</span><span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">first</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">last</span><span class="p">}</span><span class="s2">]" was passed to VirtualizedList._createRenderMask`</span><span class="p">,</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">renderMask</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CellRenderMask</span><span class="p">(</span><span class="nx">itemCount</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">itemCount</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">allRegions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">cellsAroundViewport</span><span class="p">,</span> <span class="p">...(</span><span class="nx">additionalRegions</span> <span class="o">??</span> <span class="p">[])];</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">region</span> <span class="k">of</span> <span class="nx">allRegions</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">renderMask</span><span class="p">.</span><span class="nx">addCells</span><span class="p">(</span><span class="nx">region</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// The initially rendered cells are retained as part of the</span>
    <span class="c1">// "scroll-to-top" optimization</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">initialScrollIndex</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">props</span><span class="p">.</span><span class="nx">initialScrollIndex</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">initialRegion</span> <span class="o">=</span> <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_initialRenderRegion</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span>
      <span class="nx">renderMask</span><span class="p">.</span><span class="nx">addCells</span><span class="p">(</span><span class="nx">initialRegion</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// The layout coordinates of sticker headers may be off-screen while the</span>
    <span class="c1">// actual header is on-screen. Keep the most recent before the viewport</span>
    <span class="c1">// rendered, even if its layout coordinates are not in viewport.</span>
    <span class="kd">const</span> <span class="nx">stickyIndicesSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">stickyHeaderIndices</span><span class="p">);</span>
    <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_ensureClosestStickyHeader</span><span class="p">(</span>
      <span class="nx">props</span><span class="p">,</span>
      <span class="nx">stickyIndicesSet</span><span class="p">,</span>
      <span class="nx">renderMask</span><span class="p">,</span>
      <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">first</span><span class="p">,</span>
    <span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">renderMask</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>invariant를 통해 cellsAroundViewport가 유효한 값인지 확인하는군요. 이 함수는 scrollToIndex 할 때 에러로 자주 보셨을 거 같네요.
이런 형태의 코드로 에러를 쉽게 파악 할 수 있는 거 같군요. 나중에 한번 써봐야겠다는 생각을 하고 넘어갑니다.</p>

<p>CellRenderMask를 통해서 renderMask라는 걸 인스턴스를 만드는군요. CellRenderMask는 아래에서 자세히 살펴볼 예정인데요.
그 전에 Mask는 무엇을 의미하는 것인지 생각해보고 넘어가야 좋을 거 같습니다.
마스크를 쓰게 되면 보이는 부분과 안 보이는 부분이 아주 명확하게 나뉘어지지요. Mask를 통해서 우리가 보여줄 부분과 안 보여줄 부분을 결정 할 수 있을 거 같네요. 
이러한 마스크의 특징을 이용한다면 마스크에 의해 안 가려진 부분만 렌더링하게 된다면 성능이 향상될 거 같습니다.</p>

<p><img src="/assets/img/robbery-nb.png" alt="robbery-nb.png" /></p>

<p>강도 엔비 사진을 예시로 들면, 가면에 의해 가려져서 눈 부분만 렌더링하면 되기 때문에 다른 부분에 대해서는 렌더링에 대한 부담을 덜 수 있겠습니다.</p>

<h2 id="cellrendermask">CellRenderMask</h2>

<p><a href="https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/vendor/react-native/VirtualizedList/CellRenderMask.js">CellRenderMask 코드</a>를 살펴보겠습니다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">type</span> <span class="nx">CellRegion</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
  <span class="na">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
  <span class="na">isSpacer</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">CellRenderMask</span> <span class="p">{</span>
  <span class="nl">_numCells</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
  <span class="nl">_regions</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">numCells</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">invariant</span><span class="p">(</span>
      <span class="nx">numCells</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">,</span>
      <span class="dl">'</span><span class="s1">CellRenderMask must contain a non-negative number os cells</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">this</span><span class="p">.</span><span class="nx">_numCells</span> <span class="o">=</span> <span class="nx">numCells</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">numCells</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">{</span>
          <span class="na">first</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
          <span class="na">last</span><span class="p">:</span> <span class="nx">numCells</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span>
          <span class="na">isSpacer</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">];</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">enumerateRegions</span><span class="p">():</span> <span class="nx">$ReadOnlyArray</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">addCells</span><span class="p">(</span><span class="nx">cells</span><span class="p">:</span> <span class="p">{</span><span class="nl">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">}):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="p">...</span>
  <span class="p">}</span>

  <span class="nx">numCells</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_numCells</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">equals</span><span class="p">(</span><span class="nx">other</span><span class="p">:</span> <span class="nx">CellRenderMask</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_numCells</span> <span class="o">===</span> <span class="nx">other</span><span class="p">.</span><span class="nx">_numCells</span> <span class="o">&amp;&amp;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="nx">other</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">length</span> <span class="o">&amp;&amp;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">every</span><span class="p">(</span>
        <span class="p">(</span><span class="nx">region</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=&gt;</span>
          <span class="nx">region</span><span class="p">.</span><span class="nx">first</span> <span class="o">===</span> <span class="nx">other</span><span class="p">.</span><span class="nx">_regions</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">first</span> <span class="o">&amp;&amp;</span>
          <span class="nx">region</span><span class="p">.</span><span class="nx">last</span> <span class="o">===</span> <span class="nx">other</span><span class="p">.</span><span class="nx">_regions</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">last</span> <span class="o">&amp;&amp;</span>
          <span class="nx">region</span><span class="p">.</span><span class="nx">isSpacer</span> <span class="o">===</span> <span class="nx">other</span><span class="p">.</span><span class="nx">_regions</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">isSpacer</span><span class="p">,</span>
      <span class="p">)</span>
    <span class="p">);</span>
  <span class="p">}</span>

  <span class="nx">_findRegion</span><span class="p">(</span><span class="nx">cellIdx</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="p">[</span><span class="nx">CellRegion</span><span class="p">,</span> <span class="kr">number</span><span class="p">]</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">firstIdx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">lastIdx</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="nx">firstIdx</span> <span class="o">&lt;=</span> <span class="nx">lastIdx</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">middleIdx</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">((</span><span class="nx">firstIdx</span> <span class="o">+</span> <span class="nx">lastIdx</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
      <span class="kd">const</span> <span class="nx">middleRegion</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">[</span><span class="nx">middleIdx</span><span class="p">];</span>

      <span class="k">if</span> <span class="p">(</span><span class="nx">cellIdx</span> <span class="o">&gt;=</span> <span class="nx">middleRegion</span><span class="p">.</span><span class="nx">first</span> <span class="o">&amp;&amp;</span> <span class="nx">cellIdx</span> <span class="o">&lt;=</span> <span class="nx">middleRegion</span><span class="p">.</span><span class="nx">last</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">[</span><span class="nx">middleRegion</span><span class="p">,</span> <span class="nx">middleIdx</span><span class="p">];</span>
      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">cellIdx</span> <span class="o">&lt;</span> <span class="nx">middleRegion</span><span class="p">.</span><span class="nx">first</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">lastIdx</span> <span class="o">=</span> <span class="nx">middleIdx</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">cellIdx</span> <span class="o">&gt;</span> <span class="nx">middleRegion</span><span class="p">.</span><span class="nx">last</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">firstIdx</span> <span class="o">=</span> <span class="nx">middleIdx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">invariant</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="s2">`A region was not found containing cellIdx </span><span class="p">${</span><span class="nx">cellIdx</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>생성자 내부 코드를 살펴보면, 마스크 역할을 할 Cell들을 만들 거 같습니다. 우선 data prop 만큼의 범위를 가진 마스크를 만드는군요.
isSpacer 필드도 보이는군요. 빈공간을 위한 것이 맞다는 게 점점 확실해지는군요. 
최적화 렌더링을 위하여 초기에 Cell을 취급하는 방식이 재미 있습니다. 렌더링할 Cell을 모두 빈공간으로 취급한 상태로 인스턴스를 생성하는군요.</p>

<p>이어서 addCells 메소드를 보겠습니다. 여기서 부터는 실질적으로 렌더링 할 부분에 대한 처리가 이루어지는 것으로 보입니다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">addCells</span><span class="p">(</span><span class="nx">cells</span><span class="p">:</span> <span class="p">{</span><span class="nl">first</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">last</span><span class="p">:</span> <span class="kr">number</span><span class="p">}):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="nx">invariant</span><span class="p">(</span>
      <span class="nx">cells</span><span class="p">.</span><span class="nx">first</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span>
        <span class="nx">cells</span><span class="p">.</span><span class="nx">first</span> <span class="o">&lt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">_numCells</span> <span class="o">&amp;&amp;</span>
        <span class="nx">cells</span><span class="p">.</span><span class="nx">last</span> <span class="o">&gt;=</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&amp;&amp;</span>
        <span class="nx">cells</span><span class="p">.</span><span class="nx">last</span> <span class="o">&lt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">_numCells</span> <span class="o">&amp;&amp;</span>
        <span class="nx">cells</span><span class="p">.</span><span class="nx">last</span> <span class="o">&gt;=</span> <span class="nx">cells</span><span class="p">.</span><span class="nx">first</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span>
      <span class="dl">'</span><span class="s1">CellRenderMask.addCells called with invalid cell range</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// VirtualizedList uses inclusive ranges, where zero-count states are</span>
    <span class="c1">// possible. E.g. [0, -1] for no cells, starting at 0.</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">cells</span><span class="p">.</span><span class="nx">last</span> <span class="o">&lt;</span> <span class="nx">cells</span><span class="p">.</span><span class="nx">first</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="p">[</span><span class="nx">firstIntersect</span><span class="p">,</span> <span class="nx">firstIntersectIdx</span><span class="p">]</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_findRegion</span><span class="p">(</span><span class="nx">cells</span><span class="p">.</span><span class="nx">first</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">lastIntersect</span><span class="p">,</span> <span class="nx">lastIntersectIdx</span><span class="p">]</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_findRegion</span><span class="p">(</span><span class="nx">cells</span><span class="p">.</span><span class="nx">last</span><span class="p">);</span>

    <span class="c1">// Fast-path if the cells to add are already all present in the mask. We</span>
    <span class="c1">// will otherwise need to do some mutation.</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">firstIntersectIdx</span> <span class="o">===</span> <span class="nx">lastIntersectIdx</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">firstIntersect</span><span class="p">.</span><span class="nx">isSpacer</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// We need to replace the existing covered regions with 1-3 new regions</span>
    <span class="c1">// depending whether we need to split spacers out of overlapping regions.</span>
    <span class="kd">const</span> <span class="na">newLeadRegion</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="kd">const</span> <span class="na">newTailRegion</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="kd">const</span> <span class="na">newMainRegion</span><span class="p">:</span> <span class="nx">CellRegion</span> <span class="o">=</span> <span class="p">{</span>
      <span class="p">...</span><span class="nx">cells</span><span class="p">,</span>
      <span class="na">isSpacer</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">firstIntersect</span><span class="p">.</span><span class="nx">first</span> <span class="o">&lt;</span> <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">first</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">firstIntersect</span><span class="p">.</span><span class="nx">isSpacer</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">newLeadRegion</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
          <span class="na">first</span><span class="p">:</span> <span class="nx">firstIntersect</span><span class="p">.</span><span class="nx">first</span><span class="p">,</span>
          <span class="na">last</span><span class="p">:</span> <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">first</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span>
          <span class="na">isSpacer</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="p">});</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">first</span> <span class="o">=</span> <span class="nx">firstIntersect</span><span class="p">.</span><span class="nx">first</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">lastIntersect</span><span class="p">.</span><span class="nx">last</span> <span class="o">&gt;</span> <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">last</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">lastIntersect</span><span class="p">.</span><span class="nx">isSpacer</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">newTailRegion</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
          <span class="na">first</span><span class="p">:</span> <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">last</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
          <span class="na">last</span><span class="p">:</span> <span class="nx">lastIntersect</span><span class="p">.</span><span class="nx">last</span><span class="p">,</span>
          <span class="na">isSpacer</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="p">});</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">newMainRegion</span><span class="p">.</span><span class="nx">last</span> <span class="o">=</span> <span class="nx">lastIntersect</span><span class="p">.</span><span class="nx">last</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="na">replacementRegions</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[</span>
      <span class="p">...</span><span class="nx">newLeadRegion</span><span class="p">,</span>
      <span class="nx">newMainRegion</span><span class="p">,</span>
      <span class="p">...</span><span class="nx">newTailRegion</span><span class="p">,</span>
    <span class="p">];</span>
    <span class="kd">const</span> <span class="nx">numRegionsToDelete</span> <span class="o">=</span> <span class="nx">lastIntersectIdx</span> <span class="o">-</span> <span class="nx">firstIntersectIdx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span>
      <span class="nx">firstIntersectIdx</span><span class="p">,</span>
      <span class="nx">numRegionsToDelete</span><span class="p">,</span>
      <span class="p">...</span><span class="nx">replacementRegions</span><span class="p">,</span>
    <span class="p">);</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>추가 된 Cell들 범위에서 첫번째와 마지막 cell의 번호를 구하네요.  그 후에는 Head, Tail, Main으로 Region을 나누기 위한 코드가 보입니다.
MainRegion이 실질적으로 렌더링 할 Sell들이라서 isSpacer를 false로 설정하는군요.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">newLeadRegion</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">newTailRegion</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="nx">CellRegion</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">newMainRegion</span><span class="p">:</span> <span class="nx">CellRegion</span> <span class="o">=</span> <span class="p">{</span>
  <span class="p">...</span><span class="nx">cells</span><span class="p">,</span>
  <span class="na">isSpacer</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<p>이후 부터는 허허~ Mask 의해 안 가려진 렌더링 할 부분에 대한 처리가 쭈욱 나옵니다.
메소드에 입력 된 cells에 의해서 마스크가 의해 안 가려진 부분이 결정되고 초기에 빈영역으로 지정했던 부분을 splice 함수로 덮어버리는군요.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">_regions</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span>
  <span class="nx">firstIntersectIdx</span><span class="p">,</span>
  <span class="nx">numRegionsToDelete</span><span class="p">,</span>
  <span class="p">...</span><span class="nx">replacementRegions</span><span class="p">,</span>
<span class="p">);</span>
</code></pre></div></div>

<p>다시 VirtualizedList로 돌아가서 renderMask를 만드는 부분을 살펴보겠습니다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">//...</span>

    <span class="kd">const</span> <span class="nx">renderMask</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CellRenderMask</span><span class="p">(</span><span class="nx">itemCount</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">itemCount</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">allRegions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">cellsAroundViewport</span><span class="p">,</span> <span class="p">...(</span><span class="nx">additionalRegions</span> <span class="o">??</span> <span class="p">[])];</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">region</span> <span class="k">of</span> <span class="nx">allRegions</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">renderMask</span><span class="p">.</span><span class="nx">addCells</span><span class="p">(</span><span class="nx">region</span><span class="p">);</span>
      <span class="p">}</span>

      <span class="c1">// The initially rendered cells are retained as part of the</span>
      <span class="c1">// "scroll-to-top" optimization</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">initialScrollIndex</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">props</span><span class="p">.</span><span class="nx">initialScrollIndex</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">initialRegion</span> <span class="o">=</span> <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_initialRenderRegion</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span>
        <span class="nx">renderMask</span><span class="p">.</span><span class="nx">addCells</span><span class="p">(</span><span class="nx">initialRegion</span><span class="p">);</span>
      <span class="p">}</span>

      <span class="c1">// The layout coordinates of sticker headers may be off-screen while the</span>
      <span class="c1">// actual header is on-screen. Keep the most recent before the viewport</span>
      <span class="c1">// rendered, even if its layout coordinates are not in viewport.</span>
      <span class="kd">const</span> <span class="nx">stickyIndicesSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">stickyHeaderIndices</span><span class="p">);</span>
      <span class="nx">VirtualizedList</span><span class="p">.</span><span class="nx">_ensureClosestStickyHeader</span><span class="p">(</span>
        <span class="nx">props</span><span class="p">,</span>
        <span class="nx">stickyIndicesSet</span><span class="p">,</span>
        <span class="nx">renderMask</span><span class="p">,</span>
        <span class="nx">cellsAroundViewport</span><span class="p">.</span><span class="nx">first</span><span class="p">,</span>
      <span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nx">renderMask</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>cellsAroundViewport는 렌더링 초기에는 렌더링하기로 결정된 Cell의 번호를 가지고 있다는 것 기억나십니까? ( 저는 까먹어서 다시 보았습니다. )
initialScrollIndex, initialNumToRenderOrDefault prop과 initialRenderRegion 내부 메소드에 의해서 렌더링하기로 결정한 Cell들의 범위이지요.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">renderMask</span><span class="p">.</span><span class="nx">addCells</span><span class="p">(</span><span class="nx">region</span><span class="p">);</span>
</code></pre></div></div>

<p>renderMask.addCells가 실행되면 이제 초기에 렌더할 cell들의 범위가 정해집니다.
아무리 data prop이 크더라도 렌더링 할 적당한 범위가 정해졌으므로 무시성 렌더링과 비교하면 Big O 차이가 명확해지겠습니다.</p>

<blockquote>
  <p>최적화 렌더링이 있는 컴포넌트와 없는 컴포넌트의 초기 렌더링 비용 Big O로 만들어보자.</p>

  <p>최적화 렌더링이 있는 컴포넌트는 렌더깅하기로 약속했던 상수 크기이므로 만약 10개만 렌더링하리고 약속했다면 O(1)이 되겠지요.</p>

  <p>최적화 렌더링이 없는 컴포넌트의 비용은 데이터 크기에 비례하므로 O(n)이 되겠지요.
O(10) vs O(n)</p>
</blockquote>

<h2 id="react-native-web-파헤치기-2-2을-마치며">“react-native-web 파헤치기 2-2”을 마치며</h2>

<p>대략적으로 VirtualizedList에서 최적화 렌더링 할 수 있게 준비하는 과정을 알게되었습니다. 흥미로군요!
아마 네번째 게시물에서 감동의 눈물을 흘리는 시간이 될 거 같군요.</p>

<p>react-native-web의 VirtualizedList를 살펴보면서 특정 역할을 잘 수행하는 클래스의 정의 구분이 아주 인상적입니다.
FillRateHelper, Batchinator, CellRenderMask 등이 정의 된 것을 보니, 
이러한 클래스를 정의하여 역할을 분리시키는 것이 객체지향에서 이야기하는 어떠한 객체를 만드는 과정의 결과물이라는 것을 느낄 수 있었습니다.
프론트 개발자가 React로  단순히 UI 컴포넌트만 만들다보면 이러한 클래스들을 만들 기회가 많이 없는 거 같습니다.
객체의 역할을 나누고 정의하기 위한 설계를 해볼 기회가 많이 없는 신입 개발자라면 더더욱 오픈소스를 많이 보고 이해하고 따라하는 것이 중요하다는 것을 느꼈습니다.</p>

<p>최근에 “그림 멘토 버트 도드슨의 드로잉 수업” 책을 읽었습니다. 책에는 모사에 대한 이야기가 나왔습니다.
그림에서 “모사”는 그림을 그리는 사람이 다른 사람의 그림을 최대한 똑같이 그려보는 것을 말합니다.
모사 통해 다른 사람의 그림을 그리면서 그림을 그리는 방법을 배울 수 있습니다. 저자는 모사를 많이 하면서 처음에는 뭔가 부끄러움을 느꼈다고 합니다.
자신이 그림을 잘 그린 것이 아니라 다른 사람의 그림을 그린 것이라는 생각이 들었기 때문이라고 합니다.
하지만 모사를 지속하다보니, 그림을 그리는 방법을 많이 알게 되었고 자신만의 그림 스타일이 생겼다고 합니다.
그 후 부터는 모사를 통해서 다른 사람의 그림을 그리는 것이 “자신의 그림을 그리는 방법”을 배우는 것이라고 생각하게 되었다고 합니다.</p>

<p>저 또한 다른 사람의 코드를 보면서 코드를 모사하고 어떠한 것을 만들었을 때, 부끄러운 감정을 느꼈던 것이 기억납니다.
그러다가 몇 년이 지난 후에야 모사를 통해서 다른 사람의 코드를 보고 이해하고 따라하면서 어떠한 것을 만들었을 때, 좋은 코드를 배울 수 있다는 것을 느끼게 되었습니다.</p>

<p>모사는 아주 의미있는 시간이라는 것을 한번 더 강조하여 마무리하겠습니다.</p>]]></content><author><name></name></author><category term="react-native" /><category term="react-native-web" /><summary type="html"><![CDATA[Batchinator ?]]></summary></entry><entry><title type="html">React Native Web 파헤치기 2</title><link href="https://waynekim92.github.io/react-native/react-native-web/2024/12/20/react-native-web-deep-dive-2.html" rel="alternate" type="text/html" title="React Native Web 파헤치기 2" /><published>2024-12-20T16:30:00+00:00</published><updated>2024-12-20T16:30:00+00:00</updated><id>https://waynekim92.github.io/react-native/react-native-web/2024/12/20/react-native-web-deep-dive-2</id><content type="html" xml:base="https://waynekim92.github.io/react-native/react-native-web/2024/12/20/react-native-web-deep-dive-2.html"><![CDATA[<h1 id="알고리즘-책의-1장에-나오는-big-o">알고리즘 책의 1장에 나오는 Big O</h1>
<p>알고리즘 책의 첫장에는 시간복잡도 라는 게 나오는데요. 여기서 <code class="language-plaintext highlighter-rouge">Big O</code> 라는 표기법이 나옵니다. 알고리즘의 최악의 성능을 나타낼 때 쓰이는 표기법이지요.
여기서 재미 있는 부분은 연산 수치에 크게 영향을 주지 않는 것에 대해서는 버림 처리를 한다는 것 입니다.</p>

<p><code class="language-plaintext highlighter-rouge">3n + 10000</code> 연산이 필요한 알고리즘의 big O는 <code class="language-plaintext highlighter-rouge">O(n)</code>이 됩니다. 왜냐하면 <code class="language-plaintext highlighter-rouge">n</code>이 무한이 커지면 상수들은 무시해도 될 만큼 작아지기 때문입니다.</p>

<h1 id="게시물-주제가-react-native-web-파해치기-2-인데-뜬끔-알고리즘">게시물 주제가 “React Native Web 파해치기 2” 인데? 뜬끔 알고리즘???</h1>
<p>react를 사용할 때 useCallback, useMemo, React.memo 등을 사용하면 성능 개선을 할 수 있다는 이야기를 많이 하더라고요.
그런데 이게 얼마나 성능에 긍정적인 영향을 미치는지는 잘 모르겠습니다… 
알고리즘에 나오는 Big O 시간 복잡도 처럼 n에 대한 연산을 줄이는 걸 목표로하는 게 중요하지 않을까는 생각을 자주하게 됩니다.</p>

<p><strong><em>IMHO</em></strong>: 성능저하가 발견되지 않았는데, useCallback, useMemo, React.memo를 남발하는 것은 그렇게 좋지 않은 거 같습니다. 가성비가 떨어지는 거 같아서요.</p>

<h1 id="게임에서-흔히-보이는-렌더링-최적화">게임에서 흔히 보이는 렌더링 최적화</h1>
<p>아래 영상은 게임에서 카메라 외부 요소들이 어떻게 사라지는 지를 보여줍니다.</p>

<p><img src="/assets/img/game-rendering-optimazation-example.gif" alt="game-rendering-optimazation-example.gif" /></p>

<p>아주 그래픽 자원을 아끼기 위한 노력에 저는 눈물을 흘립니다… 웹, 모바일 개발자 여러분들도 지금 같이 눈물을 흘리고 계시나요?</p>

<p><img src="/assets/img/crying-nb.png" width="300px" alt="crying-nb.png" /></p>

<p>그럼 어떻게 눈물나는 렌더링 최적화를 react-native-web에서는 어떻게 하고 있는 지 궁금하지 않습니까?</p>

<h1 id="react-native-web의-virtualizedlist">React Native Web의 VirtualizedList</h1>

<p>VirtualizedList 컴포넌트는 react-native에서 FlatList의 기반이 되는 컴포넌트 입니다.
최적화 렌더링을 위해 Viewport 밖에서 눈물의 쇼를 하는 컴포넌트이지요.</p>

<p>이제 <a href="https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js">react-native-web의 VirtualizedList</a>를 살펴보겠습니다.</p>

<p><img src="/assets/img/react-native-web-virtualized-list-1.png" alt="react-native-web-virtualized-list-1.png" /></p>

<p>우선 상태값 부터 보겠습니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>type State = {
  renderMask: CellRenderMask,
  cellsAroundViewport: {first: number, last: number},
};
</code></pre></div></div>

<p>cellsAroundViewport 라는 게 있군요. 네이밍에서 알 수 있듯이 Viewport 주변에 있는 cell의 번호를 기억하는 용도로 보입니다. 
왜 이런 걸들일 기억해야할지 상상을 해보고 다음 코드를 이어가보겠습니다.</p>

<blockquote>
  <p>상상</p>

  <p>실질적으로 렌더링 되고 있는 cell의 첫번째 번호와 마지막 cell의 번호를 알고 있다면, 
위의 게임 렌더링 영상 처럼 viewport(In Game, 카메라 시아) 주변에 있는 것들 첫번째 항목 부터 마지막 항목까지만 렌더링하게 처리하면 최적화 렌더링할 때 아주 유용할거 같군요?!
사용자들이 볼 수 있는 Viewport와 그 주변에 있는 것들만 렌더링한다면 사용자들은 자신들이 보고 싶은 요소들을 볼 수 있고 개발자 입장에서는 컴퓨터의 연산을 줄이고, 
메모리를 적게 사용할 수 있을 터이니 아주 서로 윈윈 이겠군요. 개꿀?!</p>
</blockquote>

<p>그 다음 부터는 특정 상황에서 쓰기 위한 메소드들이 다수 보입니다. 
scrollToEnd, scrollToIndex, scrollToItem 등등 이런 것들은 최적화 렌더링 보다는 스크롤과 관련 있는 것이기에 가볍게 넘어가줍니다.</p>

<p>constructor는 그래도 유심히 봐주어야 할 거 같군요. FillRateHelper와 Batchinator 그리고 initialRenderRegion가 중요해보입니다.</p>

<p><img src="/assets/img/react-native-web-virtualized-list-2.png" alt="react-native-web-virtualized-list-2.png" /></p>

<p>이것들에 대해서 알아봅시다.</p>

<h2 id="fillratehelper-">FillRateHelper ??</h2>
<p><code class="language-plaintext highlighter-rouge">VirtualizedList</code>의 최대 충족률을 감지하기 위한 헬퍼 클래스라는 군요?!</p>

<p>특정 인덱스의 프레임 지표를 구하기 위한 함수를 받는 생성자를 가졌군요. 
흠?? 여기서 index는 특정 프레임이 존재 유무와  이 프레임의 길이, 오프셋, 레이아웃 내에 있는 지를 지표로 얻을 수 있는 거 같군요?
얻을 수 있는 지표 정보를 보니 성능 측정을 위한 FPS는 아닌 거 같고 어떤 렌더링 요소의 길이, 오프셋, 인덱스, viewport 내에 있는 지를 나타내는 것이 아닐지 추측해보고 넘어가겠습니다.</p>

<p>감지를 활성화하는 activate 메소드와 감지를 비활성화하고 감지를 비활성화하고 데이터를 날리는 deactivateAndFlush 메소드가 있군요.
그리고 그 다음 메소드에는 공백을 계산한느 computeBlankness 메소드가 있네요.</p>

<p>오?!? computeBlankness에 아주 흥미로운 것들이 많습니다.
param에 초기 렌더링에 표시할 항목수를 결정하는 initialNumToRender가 있고 VirtualizedList의 상태값 cellsAroundViewport 그리고 스크롤 지표(측정항목)들이 있군요.</p>

<p>흠… Metrics는 이제 “지표”가 아닌 측정하고 있는 항목들의 집합… 더 간단하게는 상태값으로 해석하는 맞는 거 같네요.</p>

<p>cellsAroundViewport은 어떻게 초기화되는 지 봐야겠습니다.</p>

<p><img src="/assets/img/react-native-web-virtualized-list-3.png" alt="react-native-web-virtualized-list-3" /></p>

<p>firstCellIndex는 일반적으로 0이 되지만, 리스트의 렌더링 초기에 처음으로 보여줄 항목을 결정하는 initialScrollIndex prop에 의해서 0이 아닐 수도 있군요.
lastCellIndex은 일반적으로 <code class="language-plaintext highlighter-rouge">(firstCellIndex + 처음에 렌더링하려는 항목의 수)-1</code>이 되는군요.</p>

<p><img src="/assets/img/react-native-web-virtualized-list-4.png" alt="react-native-web-virtualized-list-4.png" /></p>

<p><code class="language-plaintext highlighter-rouge">cellsAroundViewport.last &lt; cellsAroundViewport.first</code> 가 왜 필요한지 어느 정도 추측이 될 거 같습니다.
쳣번째로 렌더링 될 항목이 마지막으로 렌더링 될 항목보다 크다면, cellsAroundViewport.first 이후에 렌더링 될 요소가 없을 거 같군요?!
그래서 공백이 필요 없으니 0?!?!</p>

<p>오… 하나 얻어갑니다. velocity에 1000을 꼽하면 초당 스크롤 px을 알 수 있군요!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec
</code></pre></div></div>

<p>그 다음 부터는 blankTop을 구하는 군요. 상단 공백 크기를 구하는 부분으로 같습니다.
Viewport 외각에서 렌더린 된 첫 번째 항목의 번호를 기반으로 이 항목의 렌더링 정보를 _getFrameMetrics를 이용하여 가져오는군요.
getItemLayout으로 아래 정보들을 가져옵니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(
  data: any,
  index: number,
) =&gt; {length: number, offset: number, index: number}
</code></pre></div></div>

<p>첫번째 렌더링 요소를 찾거나 마지막 셀의 번호 보다 작을 때 까지 순회하는군요.
그러다가 처음 렌더링하는 항목이 0번째 항목이 아닌 경우, 그러니까 initialScrollIndex prop으로 지정한 경우에만 blankTop을 계산하는군요.
기본적으로 blankTop은 0 이 되는군요. 지금 생각해보면 당연한 결과군요…</p>

<p>음… 그렇군요. 너무 힘을 많이 빼버렸군요. 여기 연산은 리스트의 해더의 높이와 푸터의 높이에 대한 공백 크기를 구하기 위한 연산이었군요?
음… 한번 더 상상을 해봐야겠군요.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (firstFrame &amp;&amp; first &gt; 0) {
  blankTop = Math.min(
    visibleLength,
    Math.max(0, firstFrame.offset - offset),
  );
}
</code></pre></div></div>

<blockquote>
  <p>상상 - 처음 렌더링 되는 요소가 만약 10번째 요소이고 0 ~ 9 번째 요소는 렌더링 되어 있지 않다면?</p>

  <p>… 아 상상이 어렵다… visibleLength 값은 무엇을 의미하는 것일까? 
visibleLength은 리스트의 높이를 의미하는 거 같고 offset은 스크롤이 이동한 범위 같다.
blinkTop = Math.min(2000, Math.max(0, 1000 - 1150))
에헤이… 0으로 나오네… 정말 맞는 것일까???!?!?</p>
</blockquote>

<p><img src="/assets/img/react-native-web-virtualized-list-5.png" alt="react-native-web-virtualized-list-5.png" /></p>

<p>찜찜하여서 넘어가지 못 하겠다. Expo로 간단한 샘플 앱을 만들어서 <a href="/assets/video/VirtualizedList-FillRateHelper-blankTop.mp4">확인</a>해보자.</p>

<p><a href="/assets/video/VirtualizedList-FillRateHelper-blankTop.mp4">Expo에서 확인해보기</a></p>

<p>FillRateHelper의 computeBlankness 메소드는 렌더링 초기 및 스크롤 이벤트 발생 시에도 실행되지 않는다.
그렇다는 거는 이 부분은 잠시 미뤄두고 다음 코드를 살펴보면 되겠다.</p>

<h2 id="react-native-web-파헤치기---2을-마치며">“react-native-web 파헤치기 - 2”을 마치며</h2>

<p>상상과 예측과 분석을 통해서 코드를 파헤치는 것은 정말 재미있는 일이다.
이번에는 VirtualizedList의 FillRateHelper를 살펴보았다.
다음에는 Batchinator를 살펴보면서 VirtualizedList의 최적화 렌더링에 대해서 더 자세히 알아보겠다.</p>]]></content><author><name></name></author><category term="react-native" /><category term="react-native-web" /><summary type="html"><![CDATA[알고리즘 책의 1장에 나오는 Big O 알고리즘 책의 첫장에는 시간복잡도 라는 게 나오는데요. 여기서 Big O 라는 표기법이 나옵니다. 알고리즘의 최악의 성능을 나타낼 때 쓰이는 표기법이지요. 여기서 재미 있는 부분은 연산 수치에 크게 영향을 주지 않는 것에 대해서는 버림 처리를 한다는 것 입니다.]]></summary></entry><entry><title type="html">React Native Web 파헤치기 1</title><link href="https://waynekim92.github.io/react-native/react-native-web/2024/12/16/react-native-web-deep-dive.html" rel="alternate" type="text/html" title="React Native Web 파헤치기 1" /><published>2024-12-16T14:20:00+00:00</published><updated>2024-12-16T14:20:00+00:00</updated><id>https://waynekim92.github.io/react-native/react-native-web/2024/12/16/react-native-web-deep-dive</id><content type="html" xml:base="https://waynekim92.github.io/react-native/react-native-web/2024/12/16/react-native-web-deep-dive.html"><![CDATA[<h1 id="리액트-네이티브-웹-">리액트 네이티브 웹 ?</h1>

<p>React Native 개발자라면 React Native Web에 대해 들어본 적이 있을 것입니다.
React Native Web은 <a href="https://reactnative.dev/docs/0.72/components-and-apis">React Native Core 컴포넌트</a>와 <a href="https://reactnative.dev/docs/0.72/usewindowdimensions">Core API</a>를 웹에서도 사용할 수 있게 해주는 라이브러리입니다.</p>

<h1 id="어떻게-">어떻게 ??</h1>

<p>사실상 React Native 코드는 인터페이스에 불과합니다. RN의 인터페이스는 최종적으로 네이티브 코드로 입장하는 관문이지요.</p>

<p><img src="/assets/img/react-native-last-end-point.png" alt="react-native-last-end-point.png" /></p>

<p>React Native Web도 마찬가지로 React Native 코드를 React DOM 코드로 입장시키는 관문 입니다.
우리가 만든 React Native 코드는 <code class="language-plaintext highlighter-rouge">import { ... } from 'react-native'</code>로 시작합니다. 이 코드의 끝에 있는 목적지 모듈의 이름(<code class="language-plaintext highlighter-rouge">react-native</code>)를 <code class="language-plaintext highlighter-rouge">react-native-web</code>으로 수작업으로 바꾸면 대부분의 react-native 코드를 재활용할 수 있습니다.</p>

<p>다음 플로우를 따르면, 어떻게 수작업으로 react-native 코드를 react-native-web 코드로 바꿀 수 있는지 알 수 있습니다.</p>
<ol>
  <li>react-native 프로젝트에서 react-native 컴포넌트를 이용하여 간단하게 HelloWorld를 출력하는 컴포넌트를 만들어보세요.</li>
  <li>react 프로젝트에 <code class="language-plaintext highlighter-rouge">react-native-web</code> 모듈을 설치 합니다. ( <code class="language-plaintext highlighter-rouge">yarn add react-native-web</code> 또는 <code class="language-plaintext highlighter-rouge">npm install react-native-web</code> )</li>
  <li>아까 전에 만들었던 HelloWorld 컴포넌트를 react 프로젝트에 복사 붙여넣기 합니다.</li>
  <li>HelloWorld 컴포넌트에서 <code class="language-plaintext highlighter-rouge">import { ... } from 'react-native'</code>를 <code class="language-plaintext highlighter-rouge">import { ... } from 'react-native-web'</code>로 바꿉니다.</li>
  <li>react 프로젝트를 실행합니다. Tada~! HelloWorld 컴포넌트가 잘 작동하는 것을 볼 수 있습니다.</li>
</ol>

<p>하지만 이런 단순하고 반복적인 일은 사람이 할 일이 아니지요. 그래서 react-native-web 모듈에는 이 작업을 수행하는 <a href="https://github.com/necolas/react-native-web/tree/master/packages/babel-plugin-react-native-web">babel-plugin</a>이 포함되어 있습니다. 
<a href="https://github.com/necolas/react-native-web/blob/master/packages/babel-plugin-react-native-web/src/index.js">바벨 플러그인의 코드</a>를 한번 살펴보지요.
이름 부터 역할이 아주 확실하지요. <code class="language-plaintext highlighter-rouge">'Rewrite react-native to react-native-web'</code> 그렇습니다. 이 플러그인은 위에서 했던 수작업 <code class="language-plaintext highlighter-rouge">from 'react-native'</code> 의 <code class="language-plaintext highlighter-rouge">from 'react-native-web'</code> 코드로 바꿔주는 처리를 합니다.</p>

<p>FYI. Babel은 자바스크립트/타입스크립트 코드를 읽고 변환하여 AST(Abstract Syntax Tree)로 만듭니다. 개발자는 바벨이 만들어준 AST를 기반으로 변환 규칙을 정의하여 새로운 코드를 만들 수 있지요.</p>

<p><a href="https://astexplorer.net/">AST Explorer</a> 사이트에서 아래 코드의 추상 문법 트리를 자세히 살펴보겠습니다. ( 사이트에 접속하여 아래 코드를 복사-붙여넣이 하세요~)</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Text</span> <span class="k">as</span> <span class="nx">RNText</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-native</span><span class="dl">'</span>

<span class="kd">function</span> <span class="nx">HelloWorld</span><span class="p">(){</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">Text</span><span class="p">&gt;</span>Hello World<span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Program.body &gt; ImportDeclaration.sorce.value 까지 이동해보면 값이 ‘react-native’로 나오는 것을 볼 수 있습니다.
react-native-web babel-plugin 코드로 돌아가서 코드를 보면 import하는 모듈이 ReactNative 모듈이면 작성한 바벨 플러그인 코드에 의해서 react-native-web/dist/exports/Text/index.js로 변환되는 것을 볼 수 있습니다.
react-native-web github 레포에는 dist 폴더가 없으므로 <a href="https://www.npmjs.com/package/react-native-web?activeTab=code">npm의 Code 탭</a>에서 위 경로로 이동하면 해당 컴포넌트가 있는 것을 확인 수 있습니다~</p>

<p>이로써 react-native-web은 react-native 코드를 react-native-web 코드로 입장시키는 관문이라는 것을 알 수 있습니다.</p>

<p>이 바벨 플러그인을 조금 더 살펴보지요. getDistLocation 함수에서 <code class="language-plaintext highlighter-rouge">react-native-web/dist/${...}</code> 형식으로 최종 목적지를 문자열을 반환해주는데요. 
이 목적지에 있는 컴포넌트를 1~2개 정도 보겠습니다.</p>

<h2 id="먼저-view-컴포넌트-부터">먼저 View 컴포넌트 부터</h2>

<p>모듈 설치도 귀찮으니, <a href="https://www.npmjs.com/package/react-native-web?activeTab=code">npm의 Code 탭</a>에서 살펴보겠습니다.
/react-native-web/dist/exports/View/index.js 파일을 먼저 살펴보겠습니다. react 기반 웹 프로젝트에서 코드를 build 하였을 때 코드가 보이는 군요.
어셈블러 장인만 볼 수 있는 코드 수준으로 변환되어 있지는 않지만 그래도 읽기가 불편하니 /react-native-web/src/exports/View/index.js 로 이동하여 코드를 살펴보겠습니다.</p>

<p><img src="/assets/img/react-native-web-view-1.png" alt="react-native-web-view-1.png" />
<img src="/assets/img/react-native-web-view-2.png" alt="react-native-web-view-2.png" />
<img src="/assets/img/react-native-web-view-3.png" alt="react-native-web-view-3.png" /></p>

<p>그렇습니다. 그저 React 코드이지요. JSX로 작성되어 있지 않을 뿐, 그저 React 코드이지요.</p>

<p>갑분사담. Expo 프로젝트의 package.json &gt; scripts에는 기본적으로 웹 플랫폼에서 RN 코드를 실행시킬 수 있는 스크립트를 제공해주시요. Expo를 이용하여 아주 꽤큰 애플리케이션을 만든 후에 웹도 서비스하게 되는 경우가 종종 있는 거 같습니다.
이때 웹 프로젝트를 실행시켜보면 초기 렌더링이 많이 느린 것을 볼 수 있는데요. 번들 사이즈가 크다보니 성능이 좋지않지요. 이런 케이스를 본 개발자들이 꽤 있는 거 같습니다. 제 주변에만 3~4명이 react-native-web 성능이 안 좋다고 이야기하더군요.
이 이야기는 제 머릿속에 물음표를 떠오르게 하는데요. 음… 어떤 기준을 가지고 있는 것이냐에 따라서 달라지기 때문에 이야기가 끝나지 않는 것 같습니다. 그래서 이 이야기는 여기서 끝내겠습니다.</p>

<p>벤치마킹 결과가 궁금하시다면, <a href="https://necolas.github.io/react-native-web/benchmarks/">다음 사이트</a>에서 보실 수 있습니다.</p>

<h2 id="다음은-textinput-컴포넌트">다음은 TextInput 컴포넌트</h2>

<p>npm Code 탭에서 /react-native-web/src/exports/TextInput/index.js 파일로 이동해봅시다!
React Native의 TextInput과 동일하게 웹에서 동작하기 위한 많은 코드들이 보이는군요.</p>

<p><img src="/assets/img/react-native-web-textinput-1.png" alt="react-native-web-textinput-1.png" /></p>

<p>여기서 한번 생각해보면 좋을 부분은 React Native 공식 문서에 있는 “Learn once, write anywhere” 슬로건 입니다.
“한번 배워서 어디서든 코딩 할 수 있다!” 이 슬로건을 위한 노력이 바로 React Native Web 입니다.</p>

<p><a href="https://github.com/microsoft/react-native-macos">macOs</a>, <a href="react-native-windows">windows</a> 등등 다양한 노력들이 있지요.</p>

<h2 id="react-native-web-파헤치기---1을-마치며">“react-native-web 파헤치기 - 1”을 마치며</h2>

<p>React Native 개발자라면 슬로건을 한번 더 생각해보면 좋을 거 같습니다. “한번 배워서 어디서든 코딩 할 수 있다!” 이 슬로건이 얼마나 중요한지요.
우리가 만든 코드가 어디서든 잘 동작 할 수 있다는 것이 얼마나 큰 장점인지요. RN 개발자라면 클린코드를 작성하면서 위 장점을 최대한 누릴 수 있는 스킬이 필요한 거 같습니다.</p>]]></content><author><name></name></author><category term="react-native" /><category term="react-native-web" /><summary type="html"><![CDATA[리액트 네이티브 웹 ?]]></summary></entry><entry><title type="html">Markdown 문법 테스트</title><link href="https://waynekim92.github.io/markdown/2024/12/13/markdown.html" rel="alternate" type="text/html" title="Markdown 문법 테스트" /><published>2024-12-13T12:44:50+00:00</published><updated>2024-12-13T12:44:50+00:00</updated><id>https://waynekim92.github.io/markdown/2024/12/13/markdown</id><content type="html" xml:base="https://waynekim92.github.io/markdown/2024/12/13/markdown.html"><![CDATA[<h1 id="글자-굵음-처리">글자 굵음 처리</h1>

<p>글자 <strong>굵음</strong> 처리</p>

<h1 id="목차">목차</h1>

<ul>
  <li>목차 1</li>
  <li>목차 2</li>
</ul>

<h1 id="인용">인용</h1>

<blockquote>
  <p>인용 문장</p>
</blockquote>

<h1 id="링크">링크</h1>

<p>링크 <a href="https://github.com/WayneKim92">WayneKim92</a></p>

<h2 id="표">표</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>열1</th>
      <th>열1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>행 1</td>
      <td><code class="language-plaintext highlighter-rouge">'Isn't this fun?'</code></td>
      <td>‘Isn’t this fun?’</td>
    </tr>
    <tr>
      <td>행 2</td>
      <td><code class="language-plaintext highlighter-rouge">"Isn't this fun?"</code></td>
      <td>“Isn’t this fun?”</td>
    </tr>
  </tbody>
</table>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let me = go
</code></pre></div></div>]]></content><author><name></name></author><category term="markdown" /><summary type="html"><![CDATA[글자 굵음 처리]]></summary></entry></feed>