<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jiwoo-include.log</title>
        <link>https://velog.io/</link>
        <description>FE Developer as Efficiency Maker</description>
        <lastBuildDate>Mon, 12 Jun 2023 04:37:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jiwoo-include.log</title>
            <url>https://velog.velcdn.com/images/jt_include_rw/profile/9ffb84f9-7c58-4b63-a282-2f38295fe843/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jiwoo-include.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jt_include_rw" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Next.js 13] Parallel Routes Hard Navigation Intercepting Routes]]></title>
            <link>https://velog.io/@jt_include_rw/Next.js-13-Parallel-Routes-Hard-Navigation-Intercepting-Routes</link>
            <guid>https://velog.io/@jt_include_rw/Next.js-13-Parallel-Routes-Hard-Navigation-Intercepting-Routes</guid>
            <pubDate>Mon, 12 Jun 2023 04:37:24 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<ul>
<li>Next.js 13에서 제공하는 <a href="https://nextjs.org/docs/app/building-your-application/routing/parallel-routes">Parallel Routes</a>를 사용하여 모달을 구현할 때, Hard Navigation(새로고침과 URL 접근)을 통한 path 접근 시 <code>404 에러</code>가 발생한다.
  <img src="https://velog.velcdn.com/images/jt_include_rw/post/2e9c7f99-05d3-41d3-9006-3c4108ef8994/image.png" alt="Next.js 13 404">    </li>
</ul>
<ul>
<li>Parallel Routes와 <a href="https://nextjs.org/docs/app/building-your-application/routing/route-groups">Route Groups</a>를 함께 사용하면 이미지와 같이 <code>Error: NEXT_NOT_FOUND</code> 가 발생한다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/5123823f-b91c-4b57-aef2-c4dc8dfd0c09/image.png" alt="Next.js 13 NEXT_NOT_FOUND"></li>
</ul>
<h2 id="parallel-routes">Parallel Routes</h2>
<ul>
<li>같은 layout에 여러개의 페이지를 렌더할 수 있도록 함</li>
<li>이를 활용하여 <code>Absolute Dim</code>을 만들어서 <code>modal</code>을 구현.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/a960b6fa-836f-4484-9fc7-1ee581d36123/image.png" alt="Next.js 13 Parallel Routes">
<img src="https://velog.velcdn.com/images/jt_include_rw/post/5a424e33-d942-444b-8f4b-ac50a9844f42/image.png" alt="Next.js 13 Parallel Routes"></li>
</ul>
<h1 id="해결">해결</h1>
<h2 id="parallel-routes-navigation">Parallel Routes Navigation</h2>
<ul>
<li><p><code>Parallel Routes</code>에 <code>Soft Navigation</code>과 <code>Hard Navigation</code>이 있다.</p>
<ul>
<li><code>Soft Navigation</code>: 어플리케이션이 활성화 된 상태에서 <code>Link</code>, <code>Router</code> 등을 통해 페이지를 Navigate 하는 경우</li>
<li><code>Hard Navigation</code>: 전체 페이지가 새로고침 되는 경우. (ex. URL을 통한 접속, 새로고침 등)  <code>default.tsx</code>를 먼저 렌더 시도하고, 불가능하면 404를 렌더한다.<h3 id="soft-navigation--hard-navigation">Soft Navigation / Hard Navigation</h3>
</li>
</ul>
</li>
<li><p><code>Soft Navigation</code>하는 경우 <code>Router</code>의 변화를 <code>Intercept</code>하여 <code>Parallel Routing</code>을 하기 때문에 <code>Hard Navigation</code>으로 접근할 수 있는 페이지를 만들어 해결하려고 했다.</p>
<ol>
<li><code>default.tsx</code> 를 구현하여 해결할 수 있을 것 같았다.</li>
<li>직접 해당 path에 page를 만들어주는 것으로 해결할 수 있을 것 같았다. <h2 id="intercepting-routes">Intercepting Routes</h2>
</li>
</ol>
</li>
<li><p>2번으로 시도</p>
<ul>
<li><p>기존 Parallel Route path: <code>(without-nav-bar)/half-self/@modal/find-expert</code></p>
</li>
<li><p>따라서 <code>(without-nav-bar)/half-self/find-expert</code> 디렉터리 하위에 page 생성</p>
<p>  ⇒ ❗️ 문제: 모든 Navigation이 Intercept되지 않고 <code>(without-nav-bar)/half-self/find-expert</code> page로 이동함</p>
</li>
<li><p>✅ 해결: <a href="https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#convention">Intercepting Routes Convention</a>을 활용</p>
<ul>
<li>기존 Parallel Route path(<code>(without-nav-bar)/half-self/@modal/find-expert</code>)를 <code>(without-nav-bar)/half-self/@modal/(.)find-expert</code> 로 수정하여 한 레벨 상위에서 Navigation을 Intercept하도록 구현</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/1f160267-2c88-445c-a568-1a43981c61db/image.png" alt="Next.js 13 Interceping Routes"></p>
<h1 id="회고">회고</h1>
<ul>
<li><code>default.tsx</code> 를 통해 구현도 해 볼 수 있을 것 같다.<ul>
<li>You can define a <code>default.js</code> file to render as a fallback when Next.js cannot recover a slot&#39;s active state based on the current URL. [<a href="https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#defaultjs">Next.js 공식문서 발췌</a>]</li>
<li>활성화 된 상태가 아닐 때 현재 URL에 대해 fallback으로 활용 가능하기 때문</li>
</ul>
</li>
<li>Next.js 13버전에서 새롭게 추가된 것들에 대한 적응이 아직 부족한 것 같다.</li>
<li>그럼에도 불구하고 파일명 matching을 통해 Fallback만 구현하면 되는 부분에서 편리함은 높은 것 같다.</li>
<li><a href="https://github.com/vercel/next.js/issues/49569">Next.js Github Issue</a>에도 비슷한 상황을 겪은 개발자가 있어서 도움이 될까 싶어 <a href="https://github.com/vercel/next.js/issues/49569#issuecomment-1582303584">내 의견</a>을 남겼다!
<img src="https://velog.velcdn.com/images/jt_include_rw/post/aaef8947-9d9b-4340-8ce8-abf64fb8cd8b/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 13 Elastic Beanstalk 자동 배포]]></title>
            <link>https://velog.io/@jt_include_rw/Next.js-13-Elastic-Beanstalk-%EC%9E%90%EB%8F%99-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@jt_include_rw/Next.js-13-Elastic-Beanstalk-%EC%9E%90%EB%8F%99-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Sat, 10 Jun 2023 09:10:26 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<blockquote>
<ul>
<li>Next.js을 Elastic Beanstalk Node.js 환경에 Github Action을 통해 자동 배포해요!</li>
<li>yarn berry의 PnP zero-install을 적용한 Next.js 어플리케이션을 배포해요!</li>
</ul>
</blockquote>
<h1 id="왜-자동배포를-하였는가">왜 자동배포를 하였는가?</h1>
<ul>
<li>사내 QA를 위해 stage 서버에서 어플리케이션을 배포하는 상황에서 매 번 <code>수정 ➡️ 배포</code> 하는 과정을 자동화하여 생산성을 향상 시키고 싶었어요.
  ➡️ <code>release/*</code> branch에 commit이 push되면 stage 서버 자동 배포 </li>
<li>production 배포 시에도 QA가 완료된 버전을 github branch merge를 통해 자동 배포하여 생산성을 향상 시키고 싶었어요.
  ➡️ <code>main</code> branch에 commit이 push되면 production 서버 자동 배포 </li>
</ul>
<p>❗️아래 글은 stage 서버 자동 배포에 관한 github action입니다.</p>
<h1 id="구현">구현</h1>
<h2 id="branch-배포-전략">branch 배포 전략</h2>
<ul>
<li><p><code>release/*</code> 브랜치에 commit이 push되면 해당 action을 실행!</p>
<pre><code class="language-yaml">  on:
    push:
      branches:
        - &#39;release/**&#39;</code></pre>
</li>
</ul>
<h2 id="배포-실행">배포 실행</h2>
<h3 id="jobsset-eb">jobs/set-eb</h3>
<ul>
<li><p>Elastic Beastalk를 사용하기 위해 <code>aws-cli</code>를 <code>github action</code> <code>Node</code> 환경에 세팅</p>
</li>
<li><p><code>Node</code> 환경을 <code>18.x</code>로 세팅</p>
<pre><code class="language-yaml">       - name: Set up Node ${{ matrix.node-version }}
          uses: actions/setup-node@v3
          with:
            node-version: ${{ matrix.node-version }}
            registry-url: https://registry.npmjs.org/</code></pre>
</li>
<li><p>aws-zip file을 캐싱
➡️ static한 파일이기 때문에 캐싱처리를 하면 action 속도를 개선할 수 있을 것 같았어요!</p>
</li>
</ul>
<pre><code class="language-yaml">           - name: Cache AWS CLI
            uses: actions/cache@v3
            with:
              path: /usr/local/aws-cli
              key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles(&#39;**/awscliv2.zip&#39;) }}

          - name: Install AWS CLI 2
            run: |
              curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot;
              unzip awscliv2.zip
              which aws
              sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update


- AWS configuration을 `github action secret`을 통해 진행
➡️ 가장 중요한 건 보안!!

   ```yaml
         - name: Configure AWS credentials
            uses: aws-actions/configure-aws-credentials@v1
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: ${{ secrets.AWS_REGION }}


### jobs/build

- 실제 Next.js App 빌드 및 EB deploy
- `Node` 환경을 18.x로 세팅.
➡️ 16.14 이후 버전부터 yarn berry를 지원하기 때문에 넉넉하게 세팅했어요!
➡️ yarn을 통해 node package managing
➡️ `cache: ‘yarn’` 를 통해 매 번 npm install -g yarn을 하지 않도록 캐싱하여 `action` 속도를 개선해요!

   ```yaml
           - name: Set up Node ${{ matrix.node-version }}
            uses: actions/setup-node@v3
            with:
              node-version: ${{ matrix.node-version }}
              registry-url: https://registry.npmjs.org/
                        cache: &#39;yarn&#39;

- yarn berry 세팅
    ➡️ `corepack`을 통해 yarn을 사용할 수 있도록해요!
    ➡️ `corepack`은 `node` `v16.9.0`, `v14.19.0`부터 기본 포함된 실험적 기능으로 `yarn`, `pnpm` 같은 package manager를 프로젝트별로 지정하여 사용할 수 있게 해요.
    ➡️ `yarn set version berry` 를 통해 yarn berry를 세팅해요.

    ```yaml
          - name: Set yarn berry
            run: |
              corepack enable
              yarn set version berry
              yarn install
    ```

- `.env`를 `github action secret`을 통해 생성.
    - `.env` 파일에 추가되는 환경 변수는 `github action secret`과 `github action yml 파일`에도 추가 해야해요

    ```yaml
          - name: Generate .env
            run: |
              echo &quot;NEXT_PUBLIC_ENV=$NEXT_PUBLIC_ENV&quot; &gt;&gt; .env
              echo &quot;NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL=$NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL&quot; &gt;&gt; .env
              echo &quot;NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=$NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN&quot; &gt;&gt; .env
            env:
              NEXT_PUBLIC_ENV: ${{ secrets.NEXT_PUBLIC_ENV_STAGE }}
              NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL: ${{ secrets.NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL }}
              NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ${{ secrets.NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN_STAGE }}
    ```

- `build`한 어플리케이션을 Elastic Beanstalk 배포를 위해 zip 압축.

    ```yaml
          - name: Build STAGE
            run: |
              yarn build:stage // package.json script에 존재하는 stage build command

          - name: Generate deployment package
            run: zip ./deploy.zip -r * .[^.]*
    ```

- Elastic Beanstalk에 배포
    - `version_label`이 unique해야해서 timestamp로 우선 지정했어요!

    ```yaml
          - name: Current timestamp
            id: timestamp
            run: echo &quot;::set-output name=date::$(date +&#39;%Y-%m-%dT%H-%M-%S-%3NZ&#39;)&quot;

          - name: Deploy to EB
            uses: einaregilsson/beanstalk-deploy@v18
            with:
              aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              application_name: ${{ secrets.APPLICATION_NAME }}
              environment_name: ${{ secrets.STAGE_ENVIRONMENT_NAME }}
              region: ${{ secrets.AWS_REGION }}
              version_label: ${{ steps.timestamp.outputs.date }}
              deployment_package: deploy.zip
    ```

- 결과를 Slack webhook으로 전송

    ```yaml
          - name: Send result to slack
            uses: 8398a7/action-slack@v3
            with:
              status: ${{ job.status }}
              author_name: IMCEO - STAGE
              fields: repo,commit,message,author 
              mention: here
              if_mention: failure,cancelled
            env:
              SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
            if: always()
    ```


### 전체 코드

```yaml
name: Auto Deploy - STAGE

on:
  push:
    branches:
      - &#39;release/**&#39;

jobs:
  set-eb:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [ 18.x ]
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Node ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          registry-url: https://registry.npmjs.org/

      - name: Cache AWS CLI
        uses: actions/cache@v3
        with:
          path: /usr/local/aws-cli
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles(&#39;**/awscliv2.zip&#39;) }}

      - name: Install AWS CLI 2
        run: |
          curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot;
          unzip awscliv2.zip
          which aws
          sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [ 18.x ]
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Node ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          registry-url: https://registry.npmjs.org/
          cache: &#39;yarn&#39;

      - name: Set yarn berry
        run: |
          corepack enable
          yarn set version berry
          yarn install

      - name: Remove .next
        run: |
          rm -rf .next

      - name: Generate .env
        run: |
          echo &quot;NEXT_PUBLIC_ENV=$NEXT_PUBLIC_ENV&quot; &gt;&gt; .env
          echo &quot;NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL=$NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL&quot; &gt;&gt; .env
          echo &quot;NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=$NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN&quot; &gt;&gt; .env
        env:
          NEXT_PUBLIC_ENV: ${{ secrets.NEXT_PUBLIC_ENV_STAGE }}
          NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL: ${{ secrets.NEXT_PUBLIC_INQUIRY_SLACK_HOOK_URL }}
          NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ${{ secrets.NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN_STAGE }}

      - name: Build STAGE
        run: |
          yarn build:stage

      - name: Generate deployment package
        run: zip ./deploy.zip -r * .[^.]*

      - name: Current timestamp
        id: timestamp
        run: echo &quot;::set-output name=date::$(date +&#39;%Y-%m-%dT%H-%M-%S-%3NZ&#39;)&quot;

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v18
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: ${{ secrets.APPLICATION_NAME }}
          environment_name: ${{ secrets.STAGE_ENVIRONMENT_NAME }}
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{ steps.timestamp.outputs.date }}
          deployment_package: deploy.zip

      - name: Send result to slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          author_name: IMCEO - STAGE
          fields: repo,commit,message,author # action,eventName,ref,workflow,job,took 추가할 수 있음
          mention: here
          if_mention: failure,cancelled
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()</code></pre>
<h1 id="속도-개선">속도 개선</h1>
<ul>
<li>사실 처음부터 <code>yarn</code>과 <code>aws-cli</code>를 캐싱하지는 않았어요!</li>
<li>그래서 아래의 3가지를 통해 <code>action</code> 속도를 개선했어요!</li>
</ul>
<ol>
<li><strong>cache yarn</strong><ul>
<li><code>uses: actions/setup-node@v3</code> 에서 <code>cache:&#39;yarn&#39;</code> 을 통해서 yarn 설치 시간 줄임</li>
</ul>
</li>
<li><strong>yarn berry zero install</strong><ul>
<li>pnp zero install을 통해 github에서 매 번 모듈을 설치하는 과정 개선</li>
</ul>
</li>
<li><strong><code>set-eb</code> 와 <code>build</code> job 분리를 통한 불필요한 동기처리 개선</strong></li>
</ol>
<h3 id="개선-전-평균-4m-20s">개선 전 (평균 4m 20s)</h3>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/145e6e0e-a7dd-4848-95e2-c3790a0bb6e9/image.png" alt=""></p>
<h3 id="개선-후평균-2m-30s">개선 후(평균 2m 30s)</h3>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/caab15c2-9e1a-47c9-8d16-e1b376d7d5e3/image.png" alt=""></p>
<h3 id="➡️-평균-1m-50s-속도-개선">➡️ 평균 1m 50s 속도 개선</h3>
<h1 id="후기">후기</h1>
<ul>
<li>실제 사내에서 <code>main</code>, <code>release/*</code> branch에 따라 <code>production</code>, <code>stage</code> 자동 배포를 사용하고 있어요 ! </li>
<li>stage에 매번 배포하는 과정을 자동화하여 생산성이 향상 되어서 QA 배포 과정이 단순화 되었어요!</li>
<li>production에 배포할 때, github 모바일 앱을 사용해도 되어서 어디서나 해당 버전을 production 출시할 수 있어서 좋았어요!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Native] iOS 위젯 - 구현]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native-iOS-%EC%9C%84%EC%A0%AF-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jt_include_rw/React-Native-iOS-%EC%9C%84%EC%A0%AF-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sat, 08 Apr 2023 13:38:40 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p>시리즈의 설계편에 이어 구현에 대해서만 포스팅합니다.
간단한 Todo 앱을 예시로 설명합니다🥰
위젯은 앱 내 Todo의 값이 변하면 새로운 Timeline을 불러오도록 구현합니다.
저도 아직 모든 개념이 머리에 있지는 않아 100% 정확하지 않을 수 있어요. 양해 부탁드립니다.🙏
<a href="https://github.com/jiwooIncludeJeong/react-native-ios-widget">Github Repository - jiwooIncludeJeong/react-native-ios-widget</a></p>
<h1 id="1-create-react-native-app">1. Create React Native App</h1>
<p>우선 React Native App 생성!</p>
<pre><code>npx react-native@latest init ReactNativeIOSWidget</code></pre><h1 id="2-간단한-todo-앱-작성">2. 간단한 Todo 앱 작성</h1>
<p>아주 간단한 Todo 앱을 작성해보았습니다! 복사 붙여넣기도 좋아요 왜냐하면 이 포스팅은 위젯이 주요한 포스팅이니까요! </p>
<pre><code class="language-ts">//App.tsx
import React, {useEffect, useState} from &#39;react&#39;;
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  Text,
  TouchableOpacity,
  useColorScheme,
  View,
} from &#39;react-native&#39;;
import {Colors} from &#39;react-native/Libraries/NewAppScreen&#39;;

type Todo = {
  id: number;
  isCompleted: boolean;
  text: string;
};

type TodoProps = Todo &amp; {
  onPress: (id: number) =&gt; void;
};

function Todo({isCompleted, text, id, onPress}: TodoProps): JSX.Element {
  const isDarkMode = useColorScheme() === &#39;dark&#39;;
  return (
    &lt;TouchableOpacity onPress={() =&gt; onPress(id)}&gt;
      &lt;View
        style={{
          width: &#39;100%&#39;,
          paddingHorizontal: 20,
          paddingVertical: 12,
          display: &#39;flex&#39;,
          flexDirection: &#39;row&#39;,
          alignItems: &#39;center&#39;,
        }}&gt;
        &lt;View
          style={{
            width: 20,
            height: 20,
            borderWidth: 2,
            borderColor: &#39;black&#39;,
            backgroundColor: isCompleted ? &#39;black&#39; : Colors.lighter,
          }}
        /&gt;

        &lt;Text
          style={{
            color: isDarkMode ? Colors.white : Colors.black,
            fontSize: 24,
            fontWeight: &#39;600&#39;,
            marginLeft: 8,
          }}&gt;
          {text}
        &lt;/Text&gt;
      &lt;/View&gt;
    &lt;/TouchableOpacity&gt;
  );
}

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === &#39;dark&#39;;

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
    paddingTop: 40,
    flex: 1,
  };

  const [todos, setTodos] = useState&lt;Todo[]&gt;([
    {
      id: 1,
      text: &#39;First Todo&#39;,
      isCompleted: false,
    },
    {
      id: 2,
      text: &#39;Second Todo&#39;,
      isCompleted: false,
    },
    {
      id: 3,
      text: &#39;Third Todo&#39;,
      isCompleted: false,
    },
    {
      id: 4,
      text: &#39;Fourth Todo&#39;,
      isCompleted: false,
    },
  ]);

  const handlePress = (id: number) =&gt; {
    setTodos(prev =&gt;
      prev.map(i =&gt; (i.id === id ? {...i, isCompleted: !i.isCompleted} : i)),
    );
  };

  return (
    &lt;SafeAreaView style={backgroundStyle}&gt;
      &lt;StatusBar
        barStyle={isDarkMode ? &#39;light-content&#39; : &#39;dark-content&#39;}
        backgroundColor={backgroundStyle.backgroundColor}
      /&gt;
      &lt;ScrollView
        contentInsetAdjustmentBehavior=&quot;automatic&quot;
        style={backgroundStyle}&gt;
        {todos.map(t =&gt; (
          &lt;Todo key={t.id} {...t} onPress={handlePress} /&gt;
        ))}
      &lt;/ScrollView&gt;
    &lt;/SafeAreaView&gt;
  );
}

export default App;</code></pre>
<p>그럼 다음과 같은 화면이 그려집니다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/1dcca003-b654-4595-b4d2-1f8a9bdee3fa/image.png" alt=""></p>
<h1 id="3-위젯-만들기">3. 위젯 만들기</h1>
<h2 id="3-1-widget-target-만들기">3-1. Widget Target 만들기</h2>
<p><code>File</code> &gt; <code>New</code> &gt; <code>Target</code> 을 통해 <code>Widget Extension</code>을 추가해주세요 !
<img src="https://velog.velcdn.com/images/jt_include_rw/post/b3035f4d-24a2-4db5-8bca-55c667b5c16e/image.png" alt="">
그리고 <code>Product Name</code>을 추가하고 <code>Include Live Activity</code>와 <code>Include Configuration Intent</code>를 해제하고 <code>Finish</code> 클릭. 저는 <code>Product Name: Example</code>로 작성하였어요!
<img src="https://velog.velcdn.com/images/jt_include_rw/post/50169c7a-52c4-4b83-85da-3629189ac2b2/image.png" alt=""></p>
<blockquote>
<p><code>Include Live Activity</code>은 iOS 16부터 생긴 다이나믹 아일랜드에 대응하기 위한 것으로 보입니다.
<code>Include Configuration Intent</code>는 유저가 위젯을 편집하기 해줄 기능을 포함할 것이냐인데, 현재 예제에서는 포함하지 않을 것이라 해제합니다.</p>
</blockquote>
<p>그럼 아래와 같이 ExampleExtension이라는 WidgetExtension이 추가되었습니다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/58ff22b6-3d7c-434c-80ab-e00aabc959bd/image.png" alt=""></p>
<h2 id="3-2-데이터를-공유할-userdefaults-group-만들기">3-2. 데이터를 공유할 UserDefaults group 만들기</h2>
<p>앱에서 쓴 데이터인 UserDefaults를 위젯에서도 사용할 수 있도록 App group을 만들어서 해당 App group이 등록되어있는 앱에서만 데이터를 공유할 수 있도록 합니다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/5d31148f-abd7-4758-a19d-08e0dd533ac1/image.png" alt="">
<code>가장 왼쪽의 ReactNativeIOSWIdget</code> &gt; <code>TARGETS에서 ReactNativeIOSWidget</code> &gt; <code>Signing &amp; Capabilities</code> &gt; <code>+ Capability</code> &gt; <code>App groups 추가</code> &gt; Signing 아래에 <code>App Groups</code> 영역이 추가 &gt; <code>App Groups</code>의 <code>+</code>버튼을 눌러 그룹명 추가.
저는 <code>group.react.native.widget.example</code>로 대-충 지어봤어요ㅎㅎ 😋
같은 과정을 <code>TARGETS에서 ExampleExtension</code> &gt; <code>Signing &amp; Capabilities</code> &gt; <code>+ Capability</code> &gt; <code>App groups 추가</code> &gt; Signing 아래에 <code>App Groups</code> 영역이 추가 &gt; <code>App Groups</code>의 <code>+</code>버튼을 눌러 그룹명 추가합니다. ❗️위에서 입력한 같은 그룹명을 입력해주세요!</p>
<h2 id="3-3-userdefaults를-write할-수-있는-react-native의-native-module-만들기">3-3. UserDefaults를 Write할 수 있는 React Native의 Native Module 만들기</h2>
<p><code>File</code> &gt; <code>New</code> &gt; <code>File</code>을 통해 <code>Header File</code>과 <code>Objective-C File</code>을 만들어줍니다. 둘의 네이밍은 통일하는 것이 좋고, 저는 각각 <code>SharedDefaults.h</code>와 <code>SharedDefaults.m</code>으로 생성하였습니다. 
참고: <a href="https://reactnative.dev/docs/native-modules-ios">React Native iOS Native Modules</a></p>
<pre><code class="language-swift">//SharedDefaults.h
#if __has_include(&quot;RCTBridgeModule.h&quot;)
#import &quot;RCTBridgeModule.h&quot;
#else
#import &lt;React/RCTBridgeModule.h&gt;
#endif

@interface SharedDefaults : NSObject&lt;RCTBridgeModule&gt;

@end</code></pre>
<pre><code class="language-swift">//SharedDefaults.m
#import &lt;Foundation/Foundation.h&gt;
#import &quot;SharedDefaults.h&quot;

@implementation SharedDefaults

-(dispatch_queue_t)methodQueue {
  return dispatch_get_main_queue();
}

RCT_EXPORT_MODULE(SharedDefaults);

RCT_EXPORT_METHOD(set:(NSString *)data
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  @try{
    NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:@&quot;group.react.native.widget.example&quot;]; //App Group명
    [shared setObject:data forKey:@&quot;data&quot;]; // data를 저장할 key 값
    [shared synchronize];
    resolve(@&quot;true&quot;);
  }@catch(NSException *exception){
    reject(@&quot;get_error&quot;,exception.reason, nil);
  }

}

@end</code></pre>
<p>❗️주의할 점: App Group명인 <code>group.react.native.widget.example</code>와 data를 저장할 key 값인 <code>data</code>를 잘 확인해주세요❗️
이제 React Native에서 SharedDefaults라는 NativeModule을 사용할 수 있습니다!
SharedDefaults는 <code>set()</code>이라는 method를 가지며 NSString type을 가집니다. resolver와 rejecter는 각각 성공, 실패에 대한 callback, fallback 함수를 받는 것입니다.</p>
<h1 id="4-react-native에서-userdefaults-write하기">4. React Native에서 UserDefaults Write하기</h1>
<p>SharedDefaults라는 class를 만들었습니다.</p>
<pre><code class="language-ts">//SharedDefaults.ts
import {NativeModules} from &#39;react-native&#39;;

const NativeSharedDefaults = NativeModules.SharedDefaults;

class SharedDefaults {
  public async set(obj: Record&lt;string, any&gt;) {
    try {
      //UserDefaults는 NSString을 받기 때문에 JSON.stringify()하여 Write
      const res: boolean = await NativeSharedDefaults.set(JSON.stringify(obj));
      return res;
    } catch (e) {
      console.warn(&#39;[SHARED DEFAULTS]&#39;, e);
      return false;
    }
  }
}

export default new SharedDefaults();
</code></pre>
<p>그리고 <code>App.tsx</code>에서 <code>useEffect</code>를 통해 <code>todos</code> 상태가 변할 때마다 <code>SharedDefaults.set(todos)</code>를 호출합니다.</p>
<pre><code class="language-ts">//App.tsx
...
function App():JSX.Element {
  ...

  useEffect(() =&gt; {
      SharedDefaults.set(todos);
  }, [todos]);

  ...
}
...</code></pre>
<h1 id="5-widet에서-userdefaults-read하기">5. Widet에서 UserDefaults Read하기</h1>
<p>Xcode에서 아까 생성한 WidgetExtension인 Example 디렉터리의 <code>Example.swift</code> 파일을 엽니다.
우리가 Widget에서 사용할 데이터 타입(모델)을 먼저 정의했습니다.</p>
<pre><code class="language-swift">public struct TodoModel:Codable {
  let id:Int, isCompleted: Bool, text: String;
}</code></pre>
<p>그리고 우리가 위젯에서 실질적으로 그릴 TimelineEntry를 정의하였습니다.
TodoModel 배열을 받아서 반복문으로 그려줄 것이기 때문에 todos는 배열로, date는 TimelineEntry 타입의 required라서 다음과 같이 정의합니다.</p>
<pre><code class="language-swift">struct SimpleEntry: TimelineEntry {
  let date: Date, todos: [TodoModel]
}</code></pre>
<p>전체 코드 github은 올려드릴 것이지만 대부분이 UI, Timeline 관련입니다. 
UserDefaults를 Read하는 부분에 대해서 설명드리겠습니다.</p>
<pre><code class="language-swift">struct Provider: TimelineProvider {
    ...
     func getTimeline(in context: Context, completion: @escaping (Timeline&lt;Entry&gt;) -&gt; ()) {
        var entries: [SimpleEntry] = []

        let userDefaults = UserDefaults(suiteName: &quot;group.react.native.widget.example&quot;)
        let jsonText = userDefaults?.string(forKey: &quot;data&quot;)

        var todos : [TodoModel] = []

        do {
          if jsonText != nil {
            let jsonData = Data(jsonText?.utf8 ?? &quot;&quot;.utf8)
            let valueData = try JSONDecoder().decode([TodoModel].self, from: jsonData)

            todos = valueData
          }
        } catch {
          print(error)
        }
        let currentDate = Date()
        for hourOffset in 0 ..&lt; 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, todos: todos)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}
</code></pre>
<pre><code class="language-swift">let userDefaults = UserDefaults(suiteName: &quot;group.react.native.widget.example&quot;)
let jsonText = userDefaults?.string(forKey: &quot;data&quot;)</code></pre>
<p>를 통해 만들어진 Timeline 기준으로 UserDefaults에 있는 값을 가지고 옵니다. 여기서 App Group명을 적어야 우리가 Write한 데이터를 가져올 수 있습니다. 그리고 우리가 Write한 key값인 <code>data</code>를 통해 value를 가져옵니다. 당연히 NSString이었으니 swift에서도 String이라 <code>JSON parsing</code>이 필요합니다. </p>
<pre><code class="language-swift"> var todos : [TodoModel] = []

do {
     if jsonText != nil {
     let jsonData = Data(jsonText?.utf8 ?? &quot;&quot;.utf8)
     let valueData = try JSONDecoder().decode([TodoModel].self, from: jsonData)

     todos = valueData
    }
  } catch {
    print(error)
  }</code></pre>
<p><code>JSONDecoder()</code>로 <code>JSON parsing</code>한 값을 통해 <code>SimpleEntry</code> 객체를 생성하여<code>entries.append(entry)</code>를 통해 Timeline을 새롭게 만듭니다. </p>
<p>이렇게 따라 작성하면 다음과 같은 결과를 얻을 수 있습니다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/a2081afc-c29b-4571-bfb4-b71b979641b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/jt_include_rw/post/fc0c01ca-9a9c-43b7-88eb-12fbbcdb864c/image.png" alt="">
<img src="https://velog.velcdn.com/images/jt_include_rw/post/7332a9f4-5f3e-4230-92a5-fe0e3bbd6aab/image.png" alt="">
<img src="https://velog.velcdn.com/images/jt_include_rw/post/232925d5-6d1c-4f03-ab7f-7b6218d5a85a/image.gif" alt=""></p>
<h1 id="github-repository">Github Repository</h1>
<p><a href="https://github.com/jiwooIncludeJeong/react-native-ios-widget">jiwooIncludeJeong/react-native-ios-widget</a></p>
<h1 id="느낀-점">느낀 점</h1>
<ul>
<li>정말 안되는 건 없다라는 걸 느꼈던 것 같아요. 처음엔 막막했고, 설계에 따라 자체적으로 버저닝도 해가며 개발했습니다. 첫번째, 두번째 설계가 적합하지 못하다는 것을 깨닫고 어떻게 해야하나라는 생각에도 잠시 빠졌던 것 같아요. 내가 아니라 다른 분이 구현했으면 더 높은 퀄리티의 코드와 더 빠른 속도로 짤 수 있었을까라는 상실감에도 잠시 빠졌었는데요..😭 금방 극복해서 그래도 배포까지 할 수 있었던 것 같습니다!</li>
<li>React Native에서 iOS 위젯을 개발하기 위해 편의성을 제공하는 패키지는 아직 없는 것 같아요. 꾸준히 해당 repository를 발전시켜서 reloadAlltimes()와 같이 WidgetKit에서 제공하는 Method를 RN단에서 호출 할 수 있게 만들 생각이고, 더 나아가 RN이 그린 UI를 SwiftUI로 변환하는 것을 개발할 수 있다면 RN에서 사용하는 간단한 flex와 View 개념들로 SwiftUI를 그려 편리한 위젯 오픈 소스를 만들어보고 싶다는 생각도 들었어요!</li>
<li>물론 부정확한 개념이 있을 수 있고, 모든 개념이 저에게 들어오지는 않았다고 생각해요. 하지만 이 글이 누군가에게는 도움이 되길 바랍니다 🫶</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Native] iOS 위젯 - 설계]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native-iOS-%EC%9C%84%EC%A0%AF-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@jt_include_rw/React-Native-iOS-%EC%9C%84%EC%A0%AF-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 08 Apr 2023 12:23:58 GMT</pubDate>
            <description><![CDATA[<h1 id="react-native에서-위젯을-개발해야한다">React Native에서 위젯을 개발해야한다</h1>
<blockquote>
<p>사내 React Native(이하 RN)으로 개발된 앱에서 위젯 개발 feature가 생겼다. 나는 내심 iOS Native 개발을 해보고 싶던 터라 1순위로 해당 feature를 개발을 희망했고, 다른 개발자분들도 1순위로 원해서 사다리 타기를 통해 해당 feature를 내가 개발할 수 있게 되었다.
원했던 것이지만 RN의 Native Module도 한 번 만들어 본 적 없던 나에게 큰 챌린지가 될 것 같았고 꼭 해내고 싶었다.</p>
</blockquote>
<blockquote>
<p>해당 포스트는 사내 앱 위젯 개발 경험을 예시로, 간단한 React Native에서의 iOS위젯 개발을 소개하는 글입니다.</p>
</blockquote>
<ol>
<li>설계</li>
<li>구현
으로 나누어 포스팅합니다.</li>
</ol>
<h1 id="설계">설계</h1>
<h2 id="큰-틀">큰 틀</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/1aebe05c-5df8-40e2-9584-1b3a5a754eff/image.png" alt="iOS Widget 설계"></p>
<ol>
<li>React Native에서는 Native에서 작성된 함수를 실행하기 위해서 <code>Native Module</code>을 Objective-C로 개발하여 RCTBridgeModule을 통해 <code>React Native에서 호출</code> 할 수 있다. (<a href="https://reactnative.dev/docs/native-modules-ios">iOS Native Modules RN 공식문서</a>)</li>
<li>iOS에서 <code>UserDefaults</code>라는 저장소가 있는데, 이것을 통해 앱 어디에서나 데이터 CRUD가 가능하다.</li>
<li>1번과 2번을 종합하여 <code>UserDefaults를 CRUD하는 Native Module을 작성하여 React Native에서 iOS의 Native단에 존재하는 UserDefaults 저장소에 CRUD</code> 한다.</li>
<li><code>UserDefaults</code>는 어디서나 쓸 수 있기 때문에 해당 저장소의 값을 위젯에서 사용하여 data를 그려주면 될 것이다.</li>
</ol>
<h3 id="❗️하지만">❗️하지만</h3>
<blockquote>
<p>어떻게 위젯의 값을 refresh 할 것인가?</p>
</blockquote>
<p>위 질문에 나에게 던져졌다. 나에게 떠오른 3가지의 방법</p>
<h2 id="1-ios-위젯에-있는-timeline을-사용">1. iOS 위젯에 있는 Timeline을 사용</h2>
<ul>
<li><p>iOS 위젯은 <code>TimelineProvider</code>를 통해 언제 위젯을 업데이트하고 싶은지 제공할 수 있다.</p>
<ul>
<li>특정 시간에 <code>WidgetKit</code>에서 <code>TimelineProvider</code>의 method를 호출해 새로운 Timeline을 요구하고 이 때 method에서 받아오는 데이터로 위젯을 render한다</li>
</ul>
</li>
<li><p><code>WidgetKit</code>은 다음과 같은 생애주기를 가진다</p>
<ul>
<li>사용자가 위젯을  추가할 때는 <code>getSnapshot request</code>를 통해 snapshot 데이터를 보여준다</li>
<li>사용자가 위젯을  추가한 이후로는 <code>timeline request</code>를 통해 실제 데이터를 보여준다</li>
<li><code>Timeline</code>을 만들 때 각각 <code>TimelineReloadPolicy</code>를 지정해주어야한다.(<a href="https://developer.apple.com/documentation/widgetkit/timelinereloadpolicy">Apple 공식문서</a>) 
<img src="https://velog.velcdn.com/images/jt_include_rw/post/9a9527c9-a68b-4e38-b055-78fe1f54eec8/image.png" alt="">
간단하게 설명하면 다음과 같다<blockquote>
<p><code>.atEnd</code> : 모든 timeline의 요청이 끝나고 요청 (default) 
<code>.after(Date)</code> : 첫 after policy timeline 요청으로부터 next timeline 요청까지의 date를 정할 수 있음, 이후의 timeline은 버려짐.
<code>.never</code> : 요청 안함 (static한 위젯에 필요할 것 같음)</p>
</blockquote>
</li>
</ul>
<p>자세한 설명은 <a href="https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date">Apple 공식문서</a>에 그림과 함께 나와 있어요 !</p>
</li>
</ul>
<h3 id="❗️그런데-말입니다">❗️그런데 말입니다...</h3>
<p>우리는 RN에서 상태값이 변할 때마다 위젯의 새로운 Timeline을 만들고 싶어! 시간을 예측할 수 없어!
그래서 해당 방법은 탈락 ❌
<img src="https://velog.velcdn.com/images/jt_include_rw/post/f5fcaa05-65ad-49c3-9572-6bc7de65c61c/image.png" alt="ios 위젯"></p>
<h2 id="2-observable-객체를-만들고-해당-객체를-위젯에서-구독">2. Observable 객체를 만들고 해당 객체를 위젯에서 구독</h2>
<ul>
<li>RXJS에서 Observable을 사용해보았기 때문에 위젯에서 Observable 객체를 구독하고, RN에서 또 하나의 Native Module을 만들어서 Observable 객체의 값을 변경해주면 구독하고 있던 위젯의 Timeline이 새롭게 만들어지지 않을까?<h3 id="❗️그런데-말입니다-1">❗️그런데 말입니다...</h3>
위젯에서 Observable을 구독해도 의미가 없다라는 글을 읽었다 😳 
그리고 굳이 2개의 객체를 같은 동작을 위해 만들면 유지보수 측면에서도 나중에 다른 개발자가 Observable은 왜 필요하지?라고 생각할 것 같아서 탈락 ❌
<img src="https://velog.velcdn.com/images/jt_include_rw/post/276d0837-4eb1-419b-8b8b-464f6ea2188b/image.png" alt="iOS Widget"></li>
</ul>
<h2 id="3-widgetcentersharedreloadalltimelines-사용">3. WidgetCenter.shared.reloadAllTimelines() 사용</h2>
<p><a href="https://developer.apple.com/documentation/widgetkit/widgetcenter#Requesting-a-Reload-of-Your-Widgets-Timeline">Apple 공식문서</a> 발췌</p>
<h3 id="requesting-a-reload-of-your-widgets-timeline">Requesting a Reload of Your Widget’s Timeline</h3>
<p>Changes in your app’s state may affect a widget’s timeline. When this happens, you can tell WidgetKit to reload the timeline for either a specific kind of widget or all widgets. For example, your app might register for push notifications based on the widgets the user has configured. When your app receives a push notification that changes the state for one or more of your widgets, requesting a reload of their timelines updates their display.</p>
<p>If you only need to reload a certain kind of widget, you can request a reload for only that kind. For example, in response to a push notification about a change in a game’s status, you could request a reload for only the game status widgets:</p>
<pre><code>WidgetCenter.shared.reloadTimelines(ofKind: &quot;com.mygame.gamestatus&quot;)</code></pre><p>To request a reload for all of your widgets:</p>
<pre><code>WidgetCenter.shared.reloadAllTimelines()</code></pre><h3 id="번역">(번역)</h3>
<h3 id="위젯의-타임라인-다시-로드-요청">위젯의 타임라인 다시 로드 요청</h3>
<p>앱의 상태가 변경되면 위젯의 Timeline에 영향을 줄 수 있습니다. 이 경우 WidgetKit에 특정 종류의 위젯 또는 모든 위젯에 대한 Timeline을 다시 로드하도록 지시할 수 있습니다. 예를 들어 사용자가 구성한 위젯에 따라 앱이 푸시 알림을 등록할 수 있습니다. 앱이 하나 이상의 위젯 상태를 변경하는 푸시 알림을 수신하면 위젯 timeline을 다시 로드하도록 요청하면 위젯의 값이 업데이트됩니다.</p>
<p>특정 종류의 위젯만 다시 로드해야 하는 경우 해당 종류에 대해서만 다시 로드를 요청할 수 있습니다. 예를 들어 게임 상태 변경에 대한 푸시 알림에 응답하여 게임 상태 위젯에 대해서만 다시 로드를 요청할 수 있습니다:</p>
<pre><code>WidgetCenter.shared.reloadTimelines(ofKind: &quot;com.mygame.gamestatus&quot;)</code></pre><p>모든 위젯에 대해 다시 로드를 요청하는 방법:</p>
<pre><code>WidgetCenter.shared.reloadAllTimelines()</code></pre><p>우선 우리 앱의 위젯은 하나 뿐이라 <code>reloadAllTimelines()</code>를 RN Native Module에서 UserDefaults를 Write 할 때 함께 호출하여 값이 변하면 새로운 Timeline을 가져올 수 있도록 하였다!
<img src="https://velog.velcdn.com/images/jt_include_rw/post/94f1d901-f0b6-4024-8d0c-fb4145eedc15/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Native / iOS] TextInput 커서 이동 입력 시 이슈 해결]]></title>
            <link>https://velog.io/@jt_include_rw/React-NativeiOS-TextInput-%EC%BB%A4%EC%84%9C-%EC%9D%B4%EB%8F%99-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@jt_include_rw/React-NativeiOS-TextInput-%EC%BB%A4%EC%84%9C-%EC%9D%B4%EB%8F%99-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sat, 18 Mar 2023 07:00:00 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<blockquote>
<p>React Native <a href="https://github.com/facebook/react-native/releases/tag/v0.67.3">v0.67.3</a> 이전 버전에서 한글을 포함한 cjk(chinese japanese korean) 폰트에서 TextInput 사용할 때, 작성 중간에 커서를 이동하여 작성을 하면 글자가 깨지는 이슈가 있다. (동영상 참고)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/57c87604-5acd-4c2a-8cea-52a9edbf4ebb/image.gif" alt=""></p>
<h1 id="방법">방법</h1>
<ol>
<li><p>가장 간단한 해결책은 React Native의 버전을 올리는 것이었다.
하지만 많은 프로젝트들이 그러하듯, 이미 사용하고 있는 React Native 버전과 의존성을 가지는 패키지들이 많을 것..😭 최대한 외부 의존성을 줄이며 개발한다고 해도 쉽지 않다..그래서 <code>해당 이슈도 노운 이슈로 남겨져 있었다..</code></p>
</li>
<li><p>그렇다면 해당 이슈를 해결한 PR을 찾아서 React Native의 해당 부분만 수정하여 <code>patch-package</code>를 통해 <code>npm install</code> / <code>yarn install</code> 시 업데이트 되지 않도록 하자!</p>
</li>
</ol>
<h1 id="해결">해결</h1>
<p>관련 이슈를 구글링하다보니 어렵지 않게 <a href="https://github.com/facebook/react-native/issues/32503">머지된 PR</a>을 찾을 수 있었다! 더더욱 반가웠던 건 contributor가 한국분!! 펄-럭 🇰🇷 
친절하게도 patch-package를 위해 PR을 바로 찾아갈 수 있도록 이슈 코멘트를 남겨두셨다! @kmsbernard 버나드님 감사합니다! </p>
<blockquote>
<p>삭제된 코드의 이유를 묻는 코멘트에 대한 버나드님의 답변<img src="https://velog.velcdn.com/images/jt_include_rw/post/e0129d85-0208-432b-b2d8-b1fdf2b423d4/image.png" alt="">삭제된 코드에서는 문자열 비교하는 메소드 호출을 피하는 동작이 있다. 한글도 다른 중국어, 일본어 처럼 비교하는 동작이 필요하다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/a7cf3245-6ad5-4a96-a444-343e6bbf32e4/image.gif" alt=""></p>
<h1 id="마무리">마무리</h1>
<blockquote>
<p>개발 중인 프레임워크, 라이브러리의 버전을 올리기 위해서 함께 버전을 올려야 할 의존성을 가진 패키지가 너무 많다면 patch-package를 이용해 버전을 올려보는 것도 괜찮은 해결책인 것 같다 🥰</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native Android static image 이슈]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native-Android-static-image-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@jt_include_rw/React-Native-Android-static-image-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Sun, 12 Mar 2023 14:22:59 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<blockquote>
<p>프로젝트 내에서 static image를 import 하여 사용하는 경우가 많다. 
어느 날부터 회사 내에서 안드로이드 기기에서 이상한 이미지가 보여지고 있다는 버그 리포트가 생겼다.</p>
</blockquote>
<h2 id="문제의-이미지">문제의 이미지</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/63baef94-7646-4659-99e3-b8032a81a5c2/image.png" alt=""></p>
<h2 id="원래-보여져야한-이미지">원래 보여져야한 이미지</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/eb477a57-c553-4460-9575-c9de6aaf5258/image.png" alt=""></p>
<h2 id="재현-경로">재현 경로</h2>
<blockquote>
<p>해당 문제는 React Native로 구현된 앱을 Android에서 업데이트 설치 시 발생하였다.
조금 찾아보니 facebook의 fresco 2.6.0 버전에서 이상하게 static image를 mapping하는 이슈가 있다고 하였다.</p>
</blockquote>
<p><a href="https://github.com/facebook/fresco/issues/2667">관련 이슈</a></p>
<h1 id="해결">해결</h1>
<h2 id="해결책-1">해결책 1</h2>
<p>위 이슈 링크를 보면 알 수 있는 것 처럼, fresco 2.6.0 버전으로 올리며 발생한 문제이다. 따라서 fresco를 2.5.0 버전으로 다운그레이드하면 해결 될 것이라고 생각했다.</p>
<h2 id="해결책-2">해결책 2</h2>
<p>s3에 이미지를 올리고 static image가 아닌 remote image를 <code>source:{uri:[이미지 URL]}</code>로 수정하면 해결될 것이라고 생각했다.</p>
<h2 id="따라서">따라서</h2>
<p>처음에 <code>해결책 1</code>로 해결하려고 했다. 단순히 버전만 다운그레이드하여서 새로운 빌드로 확인해보았다. 하지만 이는 해결책이 되지 못했다. 예상컨데 다른 react-native android 관련 라이브러리에서 fresco 2.6.0 버전을 사용하는 것 같다. 
배포 빌드가 몇 시간 남지 않아서 <code>해결책 2</code>를 통해 임시로 해결하고 빌드를 올렸다.</p>
<blockquote>
<p>하지만 이렇게 찜찜하게 빌드를 올리니 찜찜했고, 모든 static image에 리스크를 두면서 해당 이미지만 해결하기는 싫었다 😭</p>
</blockquote>
<h3 id="그래서">그래서</h3>
<p>조금 찾아보니 <code>build.gradle</code>에 dependencies를 직접 커스터마이징할 수 있는 문법이 있었다! 그래서 바로 적용하였다 !
<a href="https://docs.gradle.org/current/userguide/resolution_rules.html">참고</a> </p>
<pre><code>android/app/build.gradle

...
configurations.all {
        resolutionStrategy {
            force &quot;com.facebook.fresco:fresco:2.5.0&quot;
            force &quot;com.facebook.fresco:animated-gif:2.5.0&quot;
    }
}

dependencies {
    implementation fileTree(dir: &quot;libs&quot;, include: [&quot;*.jar&quot;])
    //noinspection GradleDynamicVersion
    ...
</code></pre><p>회사 프로젝트에서는 static image 중 gif 확장자도 사용하고 있기 때문에  fresco의 animated-gif 라이브러리도 사용하고 있다. 그래서 fresco의 fresco와 animated-gif의 버전을 강제로 2.5.0으로 맞춰주었다!</p>
<p>그랬더 재현경로에서도 해결 완료 !!! 🥳</p>
<h1 id="왜">왜?</h1>
<p>이슈를 올린 분의 commit은 다음과 같다</p>
<p><code>imagepipeline/src/main/java/com/facebook/imagepipeline/request/ImageRequest.java</code></p>
<pre><code class="language-java">As-is

    mRequestPriority = builder.getRequestPriority();
    mLowestPermittedRequestLevel = builder.getLowestPermittedRequestLevel();
    mCachesDisabled = builder.getCachesDisabled(); // 삭제
    mIsDiskCacheEnabled = builder.isDiskCacheEnabled();
    mIsMemoryCacheEnabled = builder.isMemoryCacheEnabled();
    mDecodePrefetches = builder.shouldDecodePrefetches();</code></pre>
<pre><code class="language-java">To-be

    mRequestPriority = builder.getRequestPriority();
    mLowestPermittedRequestLevel = builder.getLowestPermittedRequestLevel();

    mIsDiskCacheEnabled = builder.isDiskCacheEnabled();

    int cachesDisabledFlags = builder.getCachesDisabled(); // 추가
    if (!mIsDiskCacheEnabled) {
      // If disk cache is disabled we must make sure mCachesDisabled reflects it
      cachesDisabledFlags |= CachesLocationsMasks.DISK_READ | CachesLocationsMasks.DISK_WRITE;
    } //분기 추가
    mCachesDisabled = cachesDisabledFlags; //추가

    mIsMemoryCacheEnabled = builder.isMemoryCacheEnabled();
    mDecodePrefetches = builder.shouldDecodePrefetches();</code></pre>
<p>해당 commit의 주석에 따르면 disk cache가 불가능한 상태이면 mCachesDisabled에도 반영해야하는데 이전에는 반영하고 있지 않았던 것 같다.</p>
<h1 id="하지만">하지만..</h1>
<p>하지만 facebook은 fresco repository를 더 이상 관리하지 않는지 해당 개발자가 해결하여 PR을 올렸고 main에 머지가 되었지만 2.6.1 버전을 아직 배포해주지 않고 있어서, 현재로서는 2.5.0 fresco version을 사용하는 것이 유일한 해결책인 것 같다 ..</p>
<p>그래도 임시 방편으로 해결함에 그치지 않고 서칭을 통해서 근본적인 원인을 찾아서 해결하여 회사 내의 버그를 해결한 좋은 경험이었다 🥰</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Remix + supabase]]></title>
            <link>https://velog.io/@jt_include_rw/Remix-supabase</link>
            <guid>https://velog.io/@jt_include_rw/Remix-supabase</guid>
            <pubDate>Sat, 04 Mar 2023 11:56:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/2a529e88-37a6-4f64-ab26-a108fe2beaef/image.png" alt=""></p>
<h1 id="supabase">Supabase</h1>
<blockquote>
<p>Supabase is an open source Firebase alternative providing all the backend features you need to build a product. Learn more about Supabase, follow a quickstart for an overview, or dive straight into the different products and APIs.</p>
</blockquote>
<p>팀원 중 한 분이 나에게 Supabase라는 것을 소개시켜주었다. Firebase와 비슷하게 Storage, DB, Auth 등을 제공하는데 <a href="https://supabase.com/docs">공식문서</a>를 확인해보니 </p>
<ol>
<li>NoSQL DB를 제공하는 Firebase와는 다르게 postgresql DB를 제공해주고 DB Table도 GUI로 제공하는 것이 편리하게 느껴졌다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/3a18b7a0-9ea6-49b2-8dc3-44b8473b600f/image.png" alt=""></li>
<li>API_KEY로 init하면 바로 메소드를 사용할 수 있는 것이 간편하다고 느껴졌다.</li>
<li>서버 없이 혼자 개발할 사이드프로젝트이고 Remix에서 제공하는 loader에서 DB에 직접 접근하여 Server Side에서 데이터 fetch를 하고 Client Side에서는 렌더만 하기에 용이하다고 생각했다.</li>
</ol>
<h2 id="하지만">하지만</h2>
<p>공식문서에는 Remix와 관련된 문서를 제공하지 않는다. 그래서 NextJS 문서를 참고하며 개발하였다.</p>
<h1 id="처음엔">처음엔..</h1>
<p>처음에는 <a href="https://www.prisma.io/">Prisma</a>를 이용하여 Supabase에서 제공하는 DB에 접근하여 ORM을 통해 데이터를 받아올까 했었다. 
몇차례 시도 후 이는 필요 없다는 판단을 하였다. 이유는 supabaseClient 객체에서 DB에서 데이터에 접근할 수 있는 CRUD를 제공하고 있기 때문이다. </p>
<h1 id="supabase-db-데이터-접근-method">Supabase DB 데이터 접근 method</h1>
<p><a href="https://supabase.com/docs/reference/javascript/insert">공식문서의 Database Functions</a></p>
<ul>
<li>Fetch<pre><code class="language-ts">const { data, error } = await supabase.from(&#39;[테이블명]&#39;).select();</code></pre>
테이블에 해당하는 데이터를 가져올 수 있고, error 상황에 대해서도 핸들링할 수 있다.</li>
<li>Insert<pre><code class="language-ts">const { data, error } = await supabase.from(&#39;[테이블명]&#39;).insert({title:&#39;타이틀명&#39;});</code></pre>
테이블에 해당하는 row를 생성한다. supabase에서 테이블을 생성할 때 아래 그림과 같이 자동 생성될 column을 지정할 수 있다. (<strong><em>Is Identity 체크</em></strong>)
추가로 Primary Key, Unique 값 설정 등도 간단하게 GUI로 할 수 있다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/54a962ca-4f23-4ef1-8bc2-47abe70ef9a3/image.png" alt=""></li>
</ul>
<p>bulk create</p>
<pre><code class="language-ts">const { data, error } = await supabase.
                            from(&#39;[테이블명]&#39;).
                            insert([
                                 {title:&#39;타이틀명1&#39;},
                                 {title:&#39;타이틀명2&#39;} ...
                               ]);</code></pre>
<p>create and return </p>
<pre><code class="language-ts">const { data, error } = await supabase.from(&#39;[테이블명]&#39;).insert({title:&#39;타이틀명&#39;}).select();</code></pre>
<ul>
<li>Update</li>
<li>Upsert(Update + Insert =&gt; 중복되는 값이 있으면 Update, 없으면 Create)</li>
<li>Delete</li>
</ul>
<p>등등 메소드도 지원한다.</p>
<h1 id="supabase--remix-">Supabase + Remix ?</h1>
<blockquote>
<p>그래서 Remix에서 위에서 상세설명한 두 가지 메소드에 대해 어떻게 구현하였는지 설명한다.</p>
</blockquote>
<h2 id="fetch">Fetch</h2>
<ul>
<li>Fetch를 통해 데이터를 가져오는데 이것을 ServerSide에서 하여 브라우저에 이미 데이터가 존재하는 html을 다운로드 받게 하고 싶었다. 그래서 Remix에서 제공하는 Server Side function인 loader에서 데이터를 fetch하여 Client Side로 전달했다.</li>
</ul>
<pre><code class="language-ts">...
export const loader = async () =&gt; {
    const { data, error } = await supabase.from(&#39;[테이블명]&#39;).select(); // serverside의 supabase 객체
      return json({
        data
    });
}
...</code></pre>
<h2 id="insert-upsert-update-delete">Insert, Upsert, Update, Delete</h2>
<ul>
<li><p>DB에 데이터를 추가하거나 수정하는 상황에서는 Remix에서 제공하는 Server Side function인 action에서 데이터를 supabase의 DB로 데이터를 추가/수정하였다. 또한 onClick 함수를 구현하는 것이 아닌 Remix가 의도한 <code>&lt;form/&gt;</code>을 이용하여 데이터를 Server Side로 넘겨줍니다. 저는 form의 post method를 이용하여 action 함수를 호출하였습니다.</p>
<pre><code class="language-ts">...
export const action = async ({request}) =&gt; {
const { data } = Object.fromEntries(await request.formData());
const { error } = await supabase
  .from(&#39;[테이블명]&#39;)
  .insert({ body: String(data) });

if (error) {
  console.log(error);
  return json( { success: false; } );
}
return json( { success: true; } );
};
</code></pre>
</li>
</ul>
<p>...</p>
  <Form method={'post'}>
          <input type={'text'} name={'data'} />
          <button type={'submit'}>보내기</button>
  </Form>

<p>```</p>
<p>Remix와 Supabase를 통해 Supabase에서 제공하는 DB CRUD하는 방법에 대해 간단히 알아보았습니다.
아직 해쳐나가야할 관문이 많을 것 같아 또 돌아오겠습니다..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Remix 시작기 (NextJS와 비교하며)]]></title>
            <link>https://velog.io/@jt_include_rw/Remix-%EC%8B%9C%EC%9E%91%EA%B8%B0</link>
            <guid>https://velog.io/@jt_include_rw/Remix-%EC%8B%9C%EC%9E%91%EA%B8%B0</guid>
            <pubDate>Sun, 19 Feb 2023 12:44:23 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<ul>
<li>Remix에 대한 소개글은 너무 많기에, 실제 사용하면서 느낀 점을 작성하였습니다.</li>
<li>기존에 사용하던 NextJS와 비교가 다수 있습니다.</li>
<li><a href="https://remix.run/">Remix 공식 홈페이지</a></li>
</ul>
<h1 id="왜-remix인가">왜 Remix인가?</h1>
<p>개발을 혼자서 할 사이드 프로젝트를 진행함에 있어서 두 가지 선택지가 있었다.</p>
<blockquote>
<ol>
<li>기존에 사용하던 NextJS를 사용할까?</li>
<li>새로운 React 프레임워크인 Remix를 사용할까?</li>
</ol>
</blockquote>
<p>Remix를 선택하였다. </p>
<blockquote>
<ol>
<li>Remix는 DB에 직접 접근하여 Server side에서 데이터를 가져올 수 있다.</li>
<li>기존에는 NextJS를 사용하였으나 SSR을 제대로 활용하지 못하는 것 같다는 고민이 있었다.</li>
<li>하던 것만 하기 보다는 새로운 것을 사용하여 개발하고 싶었다.</li>
</ol>
</blockquote>
<h1 id="시작">시작</h1>
<h2 id="눈에-띈-것">눈에 띈 것</h2>
<h3 id="디렉터리-구조">디렉터리 구조</h3>
<ul>
<li>프로젝트를 init한 Remix 프로젝트는 디렉터리 구조가 다음과 같았다.<pre><code>Remix
</code></pre></li>
</ul>
<p>app
 ⌙root.tsx
 ⌙entry.client.tsx
 ⌙entry.server.tsx
 ⌙routes
     ⌙index.tsx </p>
<p>NextJS
src
 ⌙pages
    ⌙index.tsx
    ⌙_app.tsx
    ⌙_document.tsx</p>
<pre><code>- 개인적으로 느낀 점은 NextJS의 `_app.tsx` + `_document.tsx`와 Remix의 `root.tsx`와 비슷하다는 것이다. 이유는 `root.tsx`에서 `&lt;meta/&gt;`, `&lt;link/&gt;`, `&lt;script /&gt;` 관련 코드를 입력하기 때문이다. 또한, 아래와 같이 `root.tsx`에 작성하면 Remix에서 제공하는 `&lt;Meta /&gt;`, `&lt;Links /&gt;`, `&lt;Scripts&gt;`를 통해 html에 입력된다.</code></pre><p>export const links: LinksFunction = () =&gt; {
  return [{ rel: &quot;stylesheet&quot;, href: globalStylesheetUrl }];
};</p>
<p>export const meta: MetaFunction = () =&gt; ({
  charset: &quot;utf-8&quot;,
  title: &quot;My Amazing App&quot;,
  viewport: &quot;width=device-width,initial-scale=1&quot;,
});</p>
<pre><code>

- `entry.client.tsx`는 브라우저의 엔트리포인트를 제공하고 여기서 ReactDOM.hydrate()를 통해 이미 render 되어 있는 html에 대해 이벤트 리스너를 추가하여 서버사이드에서 받은 값만 넣어준다. (참고: [ReactDOM.hydrate()](https://reactjs.org/docs/react-dom.html#hydrate))
- `entry.server.tsx`는 서버 사이드에서 렌더링할 때 HTTP의 응답을 생성할 때 사용한다.

[위 두 가지에 대한 이해는 부족합니다..]
- `routes` 디렉터리는 NextJS의 `pages`와 같다고 느꼈고 동적 라우팅 역시 지원한다. 아래처럼 디렉터리 구조가 되어 있다면 생성된 route(page)는 다음과 같다.(도메인 네임은 임시로 _**temp.com**_)</code></pre><p>app
 ⌙routes
     ⌙index.tsx 
     ⌙my-page
         ⌙index.tsx
        ⌙edit
            ⌙name.tsx
            ⌙nickname.tsx</p>
<pre><code>1. temp.com
2. temp.com/my-page
3. temp.com/my-page/edit/name
4. temp.com/my-page/edit/nickname

### .env를 통해 환경변수를 사용할 때, NODE_ENV의 위치
- NextJS를 사용할 때, `.env` 파일을 통해 설정한 NODE 환경 변수를 next.config.js를 통해 클라이언트 사이드(브라우저)에서도 사용할 수 있었다. 하지만 아직 remix.config.js에서는 그런 configuration을 지원하지 않는 듯하다..(혹시라도 아신다면 알려주세요 !)
- 브라우저에서 사용해야할 외부 패키지를 init하기 위해서는 환경변수에서 API_KEY를 가져와야했다. Remix 공식 홈페이지에서는 다음과 같이 소개한다. ([Remix#browser-environment-variables](https://remix.run/docs/en/v1/guides/envvars#browser-environment-variables))

파파고로 번역한 것을 조금 수정하였습니다.
&gt;
#### 브라우저 환경 변수
몇몇 사람들은 Remix가 환경 변수를 클라이언트 사이드(브라우저 번들)에 넣을 수 있는지 묻습니다. 이것은 빌드가 무거운 프레임워크에서 일반적인 전략입니다. 그러나 이 접근법은 몇 가지 이유로 문제가 될 수 있습니다:
&gt;
1. 이것은 실제 환경 변수가 아닙니다. 빌드하는 시점에 어떠한 서버에 배포하는지 당신은 알아야만 합니다.
2. 다시 빌드하고 다시 배포하지 않으면 환경변수의 값을 변경할 수 없습니다.
3. 공개적으로 접근 가능한 파일에 실수로 중요한 정보(암호)를 노출하기 쉽습니다.
&gt;
대신, 모든 환경 변수(모든 서버 암호와 브라우저의 JavaScript에 필요한 정보)를 서버에 보관하고, window.ENV를 통해서만 브라우저 코드에 노출하는 것이 좋습니다. 항상 서버가 있고 브라우저 번들에 이 모든 정보가 필요하지 않으므로, 서버가 Remix가 제공하는 loader에서 클라이언트 사이드로 환경 변수를 제공할 수 있습니다.
</code></pre><p>export async function loader() {
  return json({
    ENV: {
      STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY,
    },
  });
}</p>
<p>export function Root() {
  const data = useLoaderData<typeof loader>();
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        &lt;script
          dangerouslySetInnerHTML={{
            __html: <code>window.ENV = ${JSON.stringify(
              data.ENV
            )}</code>,
          }}
        /&gt;
        <Scripts />
      </body>
    </html>
  );
}</p>
<pre><code>

# 개발 진행 중 ..
확실히 개발 진행 중에 느낀 점은 NextJS에 비해 서버 사이드와 클라이언트 사이드가 분리되어 있다는 것입니다. NextJS를 통해 서버 사이드와 클라이언트 사이드에 대해 조금 이해하셨다면 Remix도 충분히 재밌게 개발해 볼 수 있지 않을까라는 개인적인 의견입니다.

사이드프로젝트는 계속 될 것이라 앞으로도 느리지만 꾸준히 업로드하겠습니다 ! 🥰
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS/NPM] private npm registry 사용한 웹 배포기]]></title>
            <link>https://velog.io/@jt_include_rw/NextJSNPM-private-npm-registry-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9B%B9-%EB%B0%B0%ED%8F%AC-%EC%8B%9C%EB%8F%84%EA%B8%B0</link>
            <guid>https://velog.io/@jt_include_rw/NextJSNPM-private-npm-registry-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9B%B9-%EB%B0%B0%ED%8F%AC-%EC%8B%9C%EB%8F%84%EA%B8%B0</guid>
            <pubDate>Sun, 29 Jan 2023 13:34:23 GMT</pubDate>
            <description><![CDATA[<h1 id="상황">상황</h1>
<ul>
<li><p>기존 NextJS 웹 배포 환경은 <code>NextJS 환경으로 개발된 웹 어플리케이션 ➡️ NodeJS 환경의 ElasticBeanstalk(이하 eb) 배포</code>이다</p>
</li>
<li><p>api, util, query 등 React, React-Native 의존성이 없는 구현부만 모아둔 Core 모듈을 기존 웹 어플리케이션에 추가하여 작업하였다.</p>
</li>
<li><p>이 Core 모듈은 NPM에 private registry로 등록 되어있다.</p>
</li>
</ul>
<h1 id="어려움">어려움</h1>
<ul>
<li>로컬에서는 <code>npm login</code>을 통해 npm config가 set 되어 있기 때문에 private registry를 사용하는 데 ERR 404를 만나지 않는다 <blockquote>
<p>이 글에서 ERR 404가 뜨는 상황은 사용하고자하는 Core 모듈이 private registry이기 때문에 npm config에 authToken 혹은 email, password를 통해 authenticated 되지 않아 registry를 찾을 수 없어 발생하는 것이다.</p>
</blockquote>
</li>
<li>❗️eb 환경에서 private registry를 찾지 못해 <code>npm install</code> 시 Core 모듈 설치 과정에서 ERR 404를 마주하여 배포가 되지 않는 이슈가 있었다.</li>
</ul>
<h1 id="시도">시도</h1>
<h2 id="1-eb-ssh를-통해-직접-npmrc-수정">1. eb ssh를 통해 직접 .npmrc 수정</h2>
<p>가장 먼저 생각이 들었던 방법이다. 
eb-cli를 설치하여 eb ssh로 eb 환경에 직접 접근하여 기존에 있던 .npmrc에 아래와 같이 추가하였다.</p>
<pre><code>registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=[NPM_TOKEN값 직접 추가]</code></pre><p>❌여기서 발견한 문제점❌</p>
<ol>
<li>eb 배포, 업데이트가 롤링 배포, 업데이트여서 <code>배포에 성공한 app</code>은 <code>/var/app/current(이하 current)</code>에, <code>실패한 app</code>은 <code>/var/app/staging(이하 staging)</code>에 추가되었다.</li>
<li>배포 시도 시, <code>staging</code> 생성. -&gt; 배포 성공 시, <code>staging</code>이 <code>current</code>를 대체 -&gt; <code>staging</code> 삭제</li>
<li>그래서 <code>staging</code>의 .npmrc를 직접 수정하여도 새로운 배포 시, 다시 로컬의 .npmrc로 변경됨</li>
</ol>
<h2 id="2-private-registry에-등록된-계정의-npm_token을-npmrc에-추가">2. private registry에 등록된 계정의 NPM_TOKEN을 .npmrc에 추가</h2>
<p>첫번째 시도에서 로컬의 .nprmc로 변경된다는 점에서 시도한 방법이다.
새로운 배포 시, 로컬의 소스코드로 변경된다면, 로컬의 .npmrc를 수정해보면 될 것이다.</p>
<pre><code>registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=[NPM_TOKEN값 직접 추가]</code></pre><p>위를 동일하게 로컬의 .npmrc에 추가하여 배포하였다. ✅성공✅했다.
하지만 ❌여기서 발견한 문제점❌</p>
<ul>
<li>NPM_TOKEN이 git에 올라가면 보안 상 큰 문제가 될 것이다.</li>
<li>.gitignore에 .npmrc를 추가하여 .npmrc를 또 하나의 .env 파일처럼 사용할 수 있지만 더 좋은 방법이 있을 것 같았다.</li>
</ul>
<p>➡️ 그래서 eb의 환경변수에 NPM_TOKEN을 추가하고 .npmrc를 다음과 같이 수정하였다.</p>
<pre><code>registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}</code></pre><p>정상적으로 배포되었다. 하지만 로컬에서 npm(혹은 yarn) 명령어를 실행할 때 NPM_TOKEN을 환경변수로 찾을 수 없다는 문제가 발생했다.</p>
<h2 id="3-ebextensions를-통해-npmrc-수정-config-추가">3. .Ebextensions를 통해 .npmrc 수정 config 추가</h2>
<p>두번째 시도에서 <code>- NPM_TOKEN이 git에 올라가면 보안 상 큰 문제가 될 것이다.</code> 과 <code>로컬에서 npm(혹은 yarn) 명령어를 실행할 때 NPM_TOKEN을 환경변수로 찾을 수 없다는 문제가 발생했다.</code> 두 문제를 해결하기 위해 로컬의 .npmrc에 추가했던 것을 지우고 .Ebextensions를 활용해 보기로 하였다.</p>
<ul>
<li>.Ebextensions란 ?<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/ebextensions.html">AWS 공식 문서</a></li>
</ul>
</li>
</ul>
<p>다음과 같이 ./.Ebextensions/npm.config 파일을 추가하였다.</p>
<pre><code>files:
  &quot;/var/app/current/.npmrc&quot;:
    content: |
      registry=https://registry.npmjs.org/
      //registry.npmjs.org/:_authToken=${NPM_TOKEN}

  &quot;/var/app/staging/.npmrc&quot;:
    content: |
      registry=https://registry.npmjs.org/
      //registry.npmjs.org/:_authToken=${NPM_TOKEN}</code></pre><p>정확한 원인은 파악하지 못했으나 <code>staging</code>의 .npmrc는 로컬과 같았다. 실패하였다.</p>
<p>다음과 같이 ./.Ebextensions/npm.config 파일을 수정하였다.</p>
<pre><code>commands:
  echo_npmrc_staging:
    command: echo &quot;registry=https://registry.npmjs.org/
      //registry.npmjs.org/:_authToken=${NPM_TOKEN}&quot; &gt; /var/app/staging/.npmrc
  echo_npmrc_current:
    command: echo &quot;registry=https://registry.npmjs.org/
      //registry.npmjs.org/:_authToken=${NPM_TOKEN}&quot; &gt; /var/app/current/.npmrc</code></pre><p>이것 또한 정확한 원인은 파악하지 못했으나 .Ebextensions의 config의 <code>commands</code>는 eb 서버가 설정되고 어플리케이션 버전이 추출되기 전에 실행되는데 이 때 <code>staging</code> directory가 생성이 안되어서 그런가 싶었다. 그래서 eb 서버가 설정되고 어플리케이션 버전 아카이브의 압축이 풀린 후에 실행되는 <code>container_commands</code>로 해도 동일했다.</p>
<h1 id="결과임시조치">결과(임시조치)</h1>
<p>우선 두번째 시도로 임시조치하였다. 그리고 로컬 환경에서 환경변수를 찾을 수 없는 것은 ~/.zshrc와 ~/.bashrc에 NPM_TOKEN을 나의 npm token 값으로 환경변수화 해서 추가하여 로컬에서의 npm(혹은 yarn)이 가능토록하였다.</p>
<h1 id="추가-시도-가능">추가 시도 가능</h1>
<p>시간이 나면 다음 것을 시도하고 포스트를 업데이트 하겠다.</p>
<ol>
<li>.npmrc를 로컬과 eb환경에서 없애고 .Ebextensions의 <code>command</code>를 활용하여 어플리케이션 버전이 추출되기 이전에 글로벌하게 npm config set하기.</li>
<li>submodules를 써서 그냥 로컬 환경의 Core 모듈로 배포하기.</li>
</ol>
<h1 id="느낀-점">느낀 점</h1>
<p>역시나 배포 환경 세팅은 어렵다. 기존에 사용하지 않았던 private registry를 적용하는데에 어려움을 겪고 있다. 그럼에도 .Ebextentions라는 것을 알게 되어 기쁘다 :) 지난 번 포스트의 <a href="https://velog.io/@jt_include_rw/NPM-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-wGithub-Action">NPM 패키지 배포 자동화 w/Github Action</a>에 이어 npm package 관련 포스트를 작성하며 npm package를 조금 더 이해할 수 있었던 것 같다!</p>
<h1 id="참고한-것들">참고한 것들</h1>
<blockquote>
<p><a href="https://www.daleseo.com/js-npm-config/">npmrc 파일과 npm config 커맨드</a>
<a href="https://stackoverflow.com/questions/53099434/using-auth-tokens-in-npmrc">https://stackoverflow.com/questions/53099434/using-auth-tokens-in-npmrc</a>
<a href="https://mosei.tistory.com/entry/AWS-EBElastic-Beanstalk-ebextensions-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[AWS] EB(Elastic Beanstalk) .ebextensions 사용하기</a>
<a href="https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/ebextensions.html">AWS 공식 문서</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Native] SectionList 
 to FlashList]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native-SectionList-to-FlashList</link>
            <guid>https://velog.io/@jt_include_rw/React-Native-SectionList-to-FlashList</guid>
            <pubDate>Sat, 21 Jan 2023 15:49:01 GMT</pubDate>
            <description><![CDATA[<h1 id="sticky한-배너를-가지는-virtualizedlist">Sticky한 배너를 가지는 VirtualizedList</h1>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/e48d66c3-31cd-4e88-a546-dbf4c5a74093/image.png" alt="as-is"></p>
<p><code>최강자 리그</code>와 같은 배너가 스크롤할 때 해당 리그의 배너가 상단에 sticky하게 붙어야하는 기획이 있었다. 다음 영상처럼!
<img src="https://velog.velcdn.com/images/jt_include_rw/post/8d609303-a58f-433c-af99-dbd2eaa0b27d/image.gif" alt="as-is.gif"></p>
<h1 id="첫번째-구현">첫번째 구현</h1>
<p>위 움직이는 이미지처럼 구현하였다.</p>
<ul>
<li>React Native에는 SectionList라는 VirtualizedList가 있어서 제공하는 <code>sections={headers}</code>와 <code>stickySectionHeadersEnabled={true}</code> props를 전달하여 header가 sticky하게 구현하였다. </li>
<li>또한 <code>내 위치로 이동</code>을 통한 scrollOffset은 랭킹 아이템의 height과 header 배너의 height을 계산하여서 이동 시켜야했다. </li>
<li>keyExtractor, getItemLayout, initialNumToRender, maxToRenderPerBatch를 통해 최적화를 진행하였다.</li>
</ul>
<p>🚫 하지만! 랭킹에 1,000명이 생기고 최적화한 값에는 header의 height을 지정할 수 없으니 오히려 헤더의 flickering이 발생하였다. 어떻게 해결해볼 수 있을까?
🚫 header를 위한 section을 만들기 위해서는 util을 통해 다음과 같은 구조도 만들기 위해 서버에서 받은 값을 변경해야했다.</p>
<pre><code class="language-ts">groups : {
        // Sticky 헤더 위함
        header: {
          title: string;
          color: string;      
        }; 
          // 해당 리그에 속한 사용자 list
          users: {
          rank:number;
          name:string;
          imgUrl:string;
        }[];
    }[];  </code></pre>
<blockquote>
<p>React Native SectionList 
<a href="https://reactnative.dev/docs/sectionlist#requiredsections">https://reactnative.dev/docs/sectionlist#requiredsections</a></p>
</blockquote>
<h1 id="flashlist-도입">FlashList 도입</h1>
<p>새로운 피쳐에서 헤더가 sticky하여야하는 비슷한 랭킹 UI가 있었다. FlashList를 팀원 분이 적용하는 것이 어떻겠냐는 아이디어를 제안하였다. 그리고 나는 기존에 개발 되어있지만 버그가 많은 전체 랭킹 페이지를 리팩토링하자는 제안을 함께 했다.</p>
<ul>
<li><a href="https://shopify.github.io/flash-list/">FlashList</a></li>
</ul>
<p>이전에 팀원 분께서 공유해주시기도 했고, React Native Seoul 11월 밋업에서도 관련 주제로 발표를 듣기도 했기 때문에 적용할 법하다고 생각했다.
<code>estimatedItemSize</code>를 header 배너의 height과 랭킹 아이템의 height 중 더 큰 것으로 전달하여 예측하는 아이템 사이즈를 전달하면 라이브러리에서 최적화를 알아서 해준다는 점이 간단하다고 느껴졌으며, 기존의 VirtualizedList의 <code>ListEmptyComponent</code>, <code>ListHeaderComponent</code> 등 비슷한 props가 있어서 러닝커브도 낮았다.</p>
<blockquote>
<p>Most of the props from FlatList are available in FlashList, too. This documentation includes both FlatList and additional FlashList props and should be used as a primary reference. But you can also read more about the props available in both FlatList and FlashList here.
&lt;FlashList 공식 문서 중 발췌&gt;</p>
</blockquote>
<h1 id="결과">결과</h1>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/268622ca-f6f2-41c6-bd7a-c421f0161663/image.gif" alt="to-be"></p>
<p><a href="https://shopify.github.io/flash-list/docs/guides/section-list">FlashList: SectionList</a></p>
<p>위와 비교해보았을 때 sticky 헤더의 flickering이 없어졌다. 또한 데이터 구조가 다음과 같이 1-depth로 구현할 수 있었다. 물론 이 때도 기존의 서버 데이터를 해당 데이터 타입으로 변경할 util이 필요하다.</p>
<pre><code class="language-ts">UserType: {
          rank:number;
          name:string;
          imgUrl:string;
        };
LeagueType : {
        ...리그 데이터,
        ranks:undefined;  
        };

ranks: (user: UserType | LeagueType ) [];</code></pre>
<p>공식문서에서도 다음과 같이 적혀있다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/8a31743c-72bb-40f4-aa9f-749d33fc749e/image.png" alt=""></p>
<p>그리고 renderItem에서 UserType을 구분하는 isUserType util을 통해 render할 컴포넌트를 분기처리하였다.</p>
<h1 id="flashlist를-사용하면서-느낀-점">FlashList를 사용하면서 느낀 점</h1>
<p>✅ 확실히 코드의 양이 줄었다. 알아서 VirtualizedList를 최적화해주기 때문! 그리고 스크롤이 아이템이 많을 때도 부드럽다. 최적화 👍
✅ 코드를 조금 봤을 때 sticky 헤더를 기존의 FlatList + sticky 라이브러리를 통해 구현하였다. 아마 SectionList를 쓰지는 않은 듯!
✅ estimatedItemSize를 통해 정확한 아이템 사이즈 값이 아니어도 최적화를 해준다 ! </p>
<p>🚫 <a href="https://github.com/Shopify/flash-list/issues/727">ListHeaderComponent 관련 이슈</a>가 있다. HeaderComponent가 있을 때 Lazy Render도 해보았으나, sticky한 첫번째 배너가 최상단에 렌더링 되는 이슈가 있었다.
🚫 <a href="https://github.com/Shopify/flash-list/issues/631">scrollToIndex 관련 이슈</a>가 있다. 3,000개 정도의 data가 있을 때 scrollToIndex가 정확한 위치로 찾아가지 못하는 문제가 있다.</p>
<h3 id="결론-sticky-혹은-scrolltoindex를-사용하지-않는-sectionlist를-구현할-때는-최적화-측면에서-비교불가-정도라고-느껴서-무조건-flashlist를-사용할-것-같다-하지만-약간의-버그는-여전히-존재하며-더-보완하면-좋은-라이브러리가-될-것-같다-위의-2가지의-이슈보다-최적화로-얻는-이점이-더-크다고-생각하여-적용을-결정하였다">결론: sticky 혹은 scrollToIndex를 사용하지 않는 SectionList를 구현할 때는 최적화 측면에서 비교불가 정도라고 느껴서 무조건 FlashList를 사용할 것 같다. 하지만 약간의 버그는 여전히 존재하며 더 보완하면 좋은 라이브러리가 될 것 같다. 위의 2가지의 이슈보다 최적화로 얻는 이점이 더 크다고 생각하여 적용을 결정하였다.</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[NPM 패키지 배포 자동화 w/Github Action]]></title>
            <link>https://velog.io/@jt_include_rw/NPM-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-wGithub-Action</link>
            <guid>https://velog.io/@jt_include_rw/NPM-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-wGithub-Action</guid>
            <pubDate>Sun, 08 Jan 2023 09:33:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/77535e88-6dc0-489a-b46e-c191dc46d679/image.png" alt=""></p>
<h1 id="개요">개요</h1>
<blockquote>
<p>새롭게 이직한 회사는 npm package를 통해 디자인 시스템을 개발/배포/관리를 하고 있었다. 
11월 React Native Seoul 정기 meet-up에 팀 동료들과 함께 참여하였고 fastlane을 통한 React Native 배포 자동화에 관한 발표를 듣고 디자인 시스템 패키지만이라도 자동 배포가 되었으면 좋겠다는 생각을 했다. 그리고 이전 회사에서 github action을 통해 NextJS build check 자동화를 구축해본 기억이 있어 github action을 통해 npm package 배포 자동화를 구축하는 방법론이 떠올라 적용 시켜보기로 하였다.</p>
</blockquote>
<h1 id="구현">구현</h1>
<h2 id="구상">구상</h2>
<ul>
<li>기존 디자인 시스템의 큰 흐름은 <ol>
<li>build를 통해 배포할 <code>dist</code> 디렉터리를 만든다.</li>
<li><code>npm publish</code>를 통해 세팅 되어 있는 npm package repository에 패키지를 배포한다. <blockquote>
<p>github action에서도 기존에 package.json에 script를 명령어로 사용할 수 있기 때문에 package.json의 명령어를 통해 배포 흐름을 만들 생각이다.</p>
</blockquote>
</li>
</ol>
</li>
<li><code>patch</code>, <code>minor</code>, <code>major</code> 버저닝이 있어서 <code>patch</code> 버전 배포에 대해서는 <code>develop</code> 브랜치에 머지되면 자동으로 <code>patch</code> 버전을 하나 올려서 배포한다. <code>minor</code>, <code>major</code>에 대해서는 github action을 통해 배포하는 동료가 직접 버전을 입력할 수 있도록 할 것이다.</li>
</ul>
<h2 id="실제-구현">실제 구현</h2>
<pre><code># github action name 
name: Auto Publish - patch update

# develop 브랜치에 push 되면 자동 patch update를 한다는 구현
on:
  push:
    branches: [ develop ]

# 실제 실행 구현부
jobs:
  # build 환경
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    strategy:
      matrix:
        node-version: [16.13.1]
    # 실행할 순서    
    steps:
      # github action에서 NodeJS 세팅을 위한 actions/setup-node@v2 action을 사용
      - uses: actions/checkout@v2
      - name: Set up NodeJS ${{ matrix.node-version }}
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}
          registry-url: https://registry.npmjs.org/
      # yarn을 사용하기에 yarn을 global install하고 install dependencies 
      - name : Install Dependencies
        run: |
          npm install -g yarn
          npm install --legacy-peer-deps --save-dev --ignore-scripts install-peers
          yarn install
      # npm version을 patch 명령어를 통해 올리고, 리눅스 파이프라인 명령어를 통해 patch로 변경한 버전을 저장 
      - name : Patch
        run: |
          npm version patch &gt; version.txt
      # 저장한 버전 파일을 읽어 변수로 사용하기 위해 외부 action을 사용 -&gt; setps.{id}.outputs.content로 사용 가능
      - name: Read version.txt
        id: commit
        uses: juliangruber/read-file-action@v1
        with:
            path: ./version.txt
      # version을 변경한 package.json을 commit, 바뀐 버전으로 commit message 작성 후 push
      - name: Deploy and Push
        run: |
          git config --global user.name &quot;${GITHUB_ACTOR}&quot;
          git config --global user.email &quot;${GITHUB_ACTOR}@users.noreply.github.com&quot;
          git add package.json
          git commit -m &quot;${{ (steps.commit.outputs.content) }}&quot;
          echo ${{ (steps.commit.outputs.content) }}
          yarn deploy
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
      # slack 웹훅을 통해 채널에 전송
      - name: Send result to slack
        uses: Ilshidur/action-slack@2.0.2
        with:
          args: &quot;버전 ➡️${{ (steps.commit.outputs.content) }}의 결과는 ➡️${{ job.status }} 에요&quot;
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()
</code></pre><h1 id="어려움">어려움</h1>
<h2 id="1-github-package--npm-package">1. github package ? npm package?</h2>
<ul>
<li>처음엔 디자인 시스템 패키지가 github package인 줄 알았다. 그래서 <code>registry-url</code>을 <code>https://npm.pkg.github.com/</code>로, <code>NODE_AUTH_TOKEN</code>을 github secret에 등록한(사실 github 개인 토큰 세팅에서 권한을 workflow도 허용하면 자동으로 찾아간다.)<code>${{secrets.GITHUB_TOKEN}}</code>으로 했었다. action을 통해 배포시 <code>npm publish</code> 명령어에서 unathorized 오류를 뱉었다.</li>
</ul>
<h2 id="2-develop에-commit을-push하지-못하는-상황">2. develop에 commit을 push하지 못하는 상황</h2>
<ul>
<li>추후에 설명할 해결책으로 1번을 해결한 이후 npm package는 배포가 되었다. 하지만 commit을 push하는 과정에서 오류가 났다. permission을 설정 안했더라...🤦‍♂️ </li>
<li>그런데 permission을 설정했는데도 github action에서는 unknown user라서 push하지 못했다. 그래서 git config를 통해 user를 설정했다.</li>
</ul>
<h2 id="3-commit-message를-위한-버전-가져오기">3. commit message를 위한 버전 가져오기</h2>
<ul>
<li><code>npm patch</code>를 통해 자동 버저닝은 하였으나 해당 버전을 commit message로 commit 하고 싶었다. 어떻게 가져올지 고민이 되었다. 처음에는 pacakge.json 자체를 읽어왔다. 버저닝 하나 때문에 package.json 전체를 읽고 version이 포함된 위치를 찾아서 그 버전을 가져오는게 과하다고 생각했고, <code>npm patch</code>에서 return하는 버전 관련 문자열을 사용하는 것이 좋겠다고 생각했다.</li>
</ul>
<h2 id="4-tsc-error-cannot-find-module-">4. tsc error? cannot find module ?</h2>
<ul>
<li>1, 2, 3 문제들 이전에 가장 먼저 발생한 에러였다. <code>모듈명: cannot find module</code>.. github action에서 typescript를 위한 <code>tsc</code>, <code>tsc-alias</code>가 제대로 동작하지 않는 줄 알았다. 그리고 디자인 시스템 패키지에서는 <code>tsc</code>, <code>tsc-alias</code>가 잘 동작해서 github action의 step에 <code>tsc</code>, <code>tsc-alias</code>를 global하게 설치도 해보았다. 여전히 에러가 났고, 찾을 수 없는 모듈들을 꼼꼼히 살펴보았다... 공통점이 있었다 ...!</li>
</ul>
<h1 id="해결">해결</h1>
<h2 id="1github-package--npm-package">1.github package ? npm package!</h2>
<ul>
<li><code>npm package</code> 자동 배포를 위한 세팅으로 변경하였더니 해결했다! 그래서 <code>registry-url</code>을 <code>https://registry.npmjs.org/</code>로, <code>NODE_AUTH_TOKEN</code>을 github secret에 등록한 npm repository token인 <code>${{ secrets.NPM_TOKEN }}</code>으로 변경하여 해결하였다.</li>
</ul>
<h2 id="2-develop에-commit을-push하지-못하는-상황-➣-github-세팅으로">2. develop에 commit을 push하지 못하는 상황 ➣ github 세팅으로!</h2>
<ul>
<li>아래와 같이 build setting에서 contents를 write할 수 있는 권한을 추가하여 해결하였다.<pre><code>build:
  ...
  permissions:
    contents: write</code></pre></li>
<li>unknown user는 github action을 실행하게 한 actor의 정보를 action의 workflow의 user 정보로 아래와 같이 setting 하여 해결했다.<pre><code>git config --global user.name &quot;${GITHUB_ACTOR}&quot;
git config --global user.email &quot;${GITHUB_ACTOR}@users.noreply.github.com&quot;</code></pre><h2 id="3-commit-message를-위한-버전-가져오기-➣-리눅스-파이프라인-명령어로">3. commit message를 위한 버전 가져오기 ➣ 리눅스 파이프라인 명령어로!</h2>
</li>
<li><code>npm patch</code> 명령어에서 return 해주는 버전을 리눅스의 파이프라인 명령어로 파일을 생성하였다. <code>npm version patch &gt; version.txt</code> 를 통해 <code>version.txt</code> 파일에 생성하고, <code>juliangruber/read-file-action@v1</code> 외부 action을 사용하여 <code>setps.{id}.outputs.content</code>로 해당 action에서 읽어온 string을 commit message로 사용할 수 있도록 하였다.<h2 id="4-tsc-error-cannot-find-module--➣-peer-dependency">4. tsc error? cannot find module ? ➣ peer-dependency</h2>
</li>
<li><code>모듈명: cannot find module</code> 에러가 발생한 모듈들의 공통점은 모두 peer-dependency에 적혀있는 모듈들이었다. 그래서 다음과 같은 명령어를 github action에 추가하여 해결하였다.<pre><code>npm install --legacy-peer-deps --save-dev --ignore-scripts install-peers</code></pre></li>
</ul>
<h1 id="회고">회고</h1>
<ol>
<li>캐싱을 통해 반복되는 동작은 빠르게 실행될 수 있도록 할 수 있을 것 같다! 캐싱할 수 있을 것 같은 부분은 node setting과 dependency 설치 정도!</li>
<li><a href="https://github.com/release-it/release-it">release-it</a> 패키지를 동료분께서 알려주셨다. 이걸 통해 배포를 진행했다면 더욱 빠르고 편하게 할 수 있지 않았을까? 코어 모듈 패키지도 분리하여 작업을 진행 중이니 추후에 해당 패키지를 활용하여 CI/CD를 진행해보고 싶다.</li>
</ol>
<h1 id="마무리">마무리</h1>
<ul>
<li>자동 배포에 있어 큰 설계는 내가 생각한 것과 같이 진행 되었다고 생각한다. 하지만 중간중간 예상치 못한 짜잘한 이슈들로 시간을 쏟았던 것 같다. 그래도 팀원들이 자동 배포 추가에 만족하는 것 같아서 뿌듯하다. 팀원들의 일하는 방식, 워크플로 개선에 관심이 있고 개선했다는 사실에 재미를 느꼈던 것 같다 ☺️</li>
</ul>
<blockquote>
<p>참고</p>
</blockquote>
<ul>
<li><a href="https://blog.outsider.ne.kr/1559">https://blog.outsider.ne.kr/1559</a></li>
<li><a href="https://blog.joostory.net/585">https://blog.joostory.net/585</a>
...</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Typescript] 부모에서 자식 컴포넌트의 Props에 Type 전달하기]]></title>
            <link>https://velog.io/@jt_include_rw/Typescript-%EB%B6%80%EB%AA%A8%EC%97%90%EC%84%9C-%EC%9E%90%EC%8B%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-Props%EC%97%90-Type-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jt_include_rw/Typescript-%EB%B6%80%EB%AA%A8%EC%97%90%EC%84%9C-%EC%9E%90%EC%8B%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-Props%EC%97%90-Type-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Aug 2022 14:05:17 GMT</pubDate>
            <description><![CDATA[<h1 id="문제발생">문제발생</h1>
<blockquote>
<p><code>enum</code>값만 받으면 enum의 길이만큼 Tab Item을 만들 수 있도록 사용할 수 있는 Tab이라는 공용 컴포넌트를 만들고 싶었다.</p>
</blockquote>
<p>관심사를 분리(<a href="https://ko.wikipedia.org/wiki/%EA%B4%80%EC%8B%AC%EC%82%AC_%EB%B6%84%EB%A6%AC">SoC</a>)하기 위하여 tab을 사용할 부모 컴포넌트에서 선택된 값을 concern하기 위해 사용할 <code>useTab</code>이라는 커스텀 훅을 작성하였다.</p>
<ul>
<li>useTab에서는 선택된 tab의 값을 state로 가진다.</li>
<li>useTab은 type <code>T</code>를 전달받아 state의 type을 지정한다</li>
<li>useTab은 <code>initialValue</code>를 받아 state를 초기화한다.</li>
</ul>
<pre><code>const useTab = &lt;T&gt;(initialValue: T) =&gt; {
  const [selectedTab, setSelectedTab] = useState&lt;T&gt;(initialValue);

  const handleSelectedTab = (tab: T) =&gt; {
    setSelectedTab(tab);
  };

  return {
    selectedTab,
    handleSelectedTab,
  };
};

export default useTab;
</code></pre><p><code>Tab</code> 컴포넌트에서는 선택된 값과 state를 바꾸는 함수를 전달 받아 화면에 그려주고, interact(클릭 이벤트)에 대해 concern한다.</p>
<ul>
<li>Tab 사용할 <code>enum</code>을 전달 받아 배열화하여 TabItem만큼 반복문을 실행한다.</li>
<li>TabItem 클릭 이벤트 시, <code>enum</code>의 value를 state 바꾸는 함수에 전달한다.<pre><code>interface Props {
tabEnum: Record&lt;string, string&gt;;
tabItemWidth?: number;
selectedTab: string;
handleSelectedTab: (tab: string) =&gt; void;
}
</code></pre></li>
</ul>
<p>const Tab: React.FC<Props> = props =&gt; {
  const { tabEnum, tabItemWidth, selectedTab, handleSelectedTab } = props;
  const enumArray = objectToArray(tabEnum); //enum을 array로 만드는 util</p>
<p>  return (
    <Wrapper>
      {enumArray.map(([key, value]) =&gt; (
        &lt;TabItem
          key={key}
          enumKey={key}
          enumValue={value as string}
          tabItemWidth={tabItemWidth}
          isSelected={value === selectedTab}
          handleSelectedTab={handleSelectedTab}
        /&gt;
      ))}
    </Wrapper>
  );
};</p>
<p>const Wrapper = styled(Row)<code>width: 100%;
  justify-content: flex-start;
  align-items: center;</code>;</p>
<pre><code>
## 문제
위 코드에서 문제는 부모 컴포넌트에서 `Tab`에 `handleSelectedTab`을 전달하고자 할 때 발생하였다.
- useTab에서 지정해준 type인 `T`와 `Tab`컴포넌트의 `handleSelectedTab`의 props인 `string`의 type이 맞지 않아서 typescript 에러가 발생하였다.

## 해결
1. 가장 쉽게 해결할 수 있는 방법은 `handleSelectedTab`의 props의 type을 any로 하는 것이다.
    - 하지만 이 방법은 굉장히 찝찝함을 가져다 주었고, any type을 사용하고 싶지 않았다. ❌

### 따라서 &quot;Tab을 사용하는 부모 컴포넌트에서 Tab 컴포넌트로 type을 전달할 수 없을까?&quot;라는 생각을 하였다.
그래서 아래와 같이 수정하게 되었다.</code></pre><p>interface Props<T> {
  tabEnum: T;
  tabItemWidth?: number;
  selectedTab: T;
  handleSelectedTab: (tab: T) =&gt; void;
}</p>
<p>export type TabComponentInterface&lt;T = any&gt; = React.FC&lt;Props<T>&gt;; //추가</p>
<p>const Tab: TabComponentInterface = props =&gt; {
  const { tabEnum, tabItemWidth, selectedTab, handleSelectedTab } = props;
  const enumArray = objectToArray(tabEnum); //enum을 array로 만드는 util</p>
<p>  return (
    <Wrapper>
      {enumArray.map(([key, value]) =&gt; (
        &lt;TabItem
          key={key}
          enumKey={key}
          enumValue={value as string}
          tabItemWidth={tabItemWidth}
          isSelected={value === selectedTab}
          handleSelectedTab={handleSelectedTab}
        /&gt;
      ))}
    </Wrapper>
  );
};</p>
<p>const Wrapper = styled(Row)<code>width: 100%;
  justify-content: flex-start;
  align-items: center;</code>;</p>
<p>export default Tab;</p>
<pre><code>- interface Props를 `Type`을 전달 받아 interface 내에서 사용할 수 있도록 하였다.
- `TabComponentInterface` type을 만들어 `Type`을 전달 받을 수 있도록 하고, export 하여 부모 컴포넌트에서도 사용할 수 있게 하였다.
- `Tab` 컴포넌트를 `TabComponentInterface` type으로 지정하였다.
</code></pre><p>const TabComponent = Tab as TabComponentInterface<TempEnum>;
...
const Presenter: React.FC<Props> = props =&gt; {
    ...
    return(
        ...
        <TabComponent
            tabEnum={TempEnum}
            handleSelectedTab={handleSelectedTab}
            selectedTab={selectedTab}
        />
        ...
    )
}</p>
<p>```</p>
<ul>
<li><code>Tab</code> 컴포넌트를 <code>as TabComponentInterface&lt;TempEnum&gt;</code>으로 <code>TempEnum</code> type을 전달하도록 type 지정을 하였고, <code>TabComponent</code>를 실제 return에서 <code>JSX.element</code>로 사용하였다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React와 가상 DOM 그리고 Svelte..]]></title>
            <link>https://velog.io/@jt_include_rw/React%EC%99%80-%EA%B0%80%EC%83%81DOM-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Svelte</link>
            <guid>https://velog.io/@jt_include_rw/React%EC%99%80-%EA%B0%80%EC%83%81DOM-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Svelte</guid>
            <pubDate>Fri, 19 Aug 2022 06:55:00 GMT</pubDate>
            <description><![CDATA[<h1 id="react의-동작">React의 동작</h1>
<h2 id="브라우저의-동작">브라우저의 동작</h2>
<pre><code>1. HTML을 parsing(분석)하여 DOM(Document Object Model)을 구성하여 
   각 DOM node로 DOM tree를 구성한다.
    1-1. style을 만나면 CSS를 parsing하여 CSSOM(CSS Object Model)을 구성하여 
         각 CSSOM node로 CSSOM tree를 구성한다.
    1-2. script를 만나면 Javascript를 가져온다
2. 1을 통해서 만들어진 DOM tree와 CSSOM tree를 통하여 Render tree를 구성한다.
3. Layout/Reflow : Render tree에서 요소들의 정확한 위치와 크기를 계산한다.
4. Paint : Render tree를 화면의 픽셀로 변환한다. 
           위치, 크기와 관계 없는 CSS를 적용한다</code></pre><p><img src="https://velog.velcdn.com/images/jt_include_rw/post/cc3d8653-10eb-43aa-afa5-20cc253ad773/image.png" alt=""></p>
<ul>
<li>DOM: HTML을 Javascript와 브라우저가 읽을 수 있도록 분석하여 나온 객체 모델</li>
<li>CSSOM : CSS를 분석하여 나온 객체 모델</li>
<li>Render tree 구성 <ol>
<li><code>visibility:hidden</code>은 Layout 시 공간을 차지한다. </li>
<li><code>display:none</code>은 Render tree 구성 시에 포함되지 않는다.</li>
</ol>
</li>
</ul>
<h2 id="dom-제어--웹을-동적으로-제어한다">DOM 제어 : 웹을 동적으로 제어한다</h2>
<p>➡️ HTML 요소들을 동적으로 제어한다 
➡️ DOM API로 DOM 요소들을 CRUD(Create, Retrieve, Update, Delete) 한다</p>
<h1 id="jquery">jQuery</h1>
<p>👎: jQuery는 실제 DOM을 제어하기 때문에 interactive UI가 많은 최근의 웹 어플리케이션에서는 HTML ➡️ DOM 구성 ➡️ DOM tree 구성 ➡️ Layout ➡️ Paint가 매번 이루어지기 때문에 연산이 많아진다. 이는 브라우저 성능 저하의 원인이 된다. 그래서 가상 DOM을 사용하는 React, Vue와 같은 프레임워크의 등장과 함께 jQuery를 사용할 이유가 줄어들었다.</p>
<h1 id="가상-dom">가상 DOM</h1>
<ul>
<li>React를 기준으로 작성하였습니다.</li>
</ul>
<h2 id="render">render()</h2>
<p>React의 <code>render()</code>는 React Element tree를 만드는 함수이다.
➡️ state나 props가 변하면 새로운 element tree를 return
➡️ 어떻게 효과적으로 가상 DOM을 사용하였나?</p>
<h2 id="diffing-algorithm">Diffing Algorithm</h2>
<p>➡️ <code>old element tree</code>와 <code>new element tree</code>를 비교하여 바뀐 element만 <code>new element</code>로 갱신한다.
➡️ 재조정(Reconciliation)은 element의 root부터 비교한다.
➡️ 비교는 실제 DOM과 가상 DOM을 동시에 순회하며 진행된다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/ad8e0de5-209e-46b4-86d1-d6b48f075b3e/image.png" alt=""></p>
<h3 id="element의-type이-다른-경우-html-tag가-다른-경우">Element의 type이 다른 경우 (HTML Tag가 다른 경우)</h3>
<p>➡️ <code>old element tree</code>는 버리고 <code>new element tree</code>로 바꾼다. 그 말은 즉슨, parent의 element type이 달라지면 children도 버려지고 children도 <code>new element tree</code>로 바뀌며 state와 props도 모두 버려진다. re-rendering이 발생한다.</p>
<h3 id="element의-type이-같은-경우-html-tag가-같은-경우">Element의 type이 같은 경우 (HTML Tag가 같은 경우)</h3>
<p>➡️ 동일한 것들은 유지한다. 다만 바뀐 속성만 갱신한다. 그리고 children을 재귀처리한다.</p>
<h3 id="children-재귀처리">children 재귀처리</h3>
<pre><code>#old element
&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
&lt;/ul&gt;</code></pre><pre><code>#new element
&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
  &lt;li&gt;third&lt;/li&gt;
&lt;/ul&gt;</code></pre><p>이 경우에는 맨 마지막에 new element인 <code>&lt;li&gt;third&lt;/li&gt;</code>가 추가되는 것이라 문제가 없다. 하지만</p>
<pre><code>#old element
&lt;ul&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre><pre><code>#new element
&lt;ul&gt;
  &lt;li&gt;Connecticut&lt;/li&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre><p>위 예제에서는 동시 순회하며 비교할 때 <code>&lt;li&gt;Duke&lt;/li&gt;</code>와 <code>&lt;li&gt;Connecticut&lt;/li&gt;</code>의 children이 달라서 <code>li</code>  element의 children을 모두 <code>new element</code>로 갱신해 버릴 것이다. 지금의 예제에서는 depth가 낮아서 text만 변경될 것이지만 depth가 깊은 children element라면 바뀌지 않아도 될 것들이 갱신되어 성능적으로 안좋은 결과를 가져올 수 있다. 이 때문에 React에서는 key 속성을 확인한다.</p>
<h3 id="key-속성">key 속성</h3>
<p>key 속성이 있다면 key를 통해 <code>old element</code>와 <code>new element</code>의 자식이 일치하는지 확인한다. 따라서 key 값은 고유한 값이어야 한다.
❌주의사항❌ : key를 array의 index로 사용하게 되면 array가 재정렬되는 경우에 key값도 변경된다. <code>old element</code>와 <code>new element</code>의 key 값이 같은데 <code>new element</code>의 children이 달라서 새로운 것으로 갱신하였지만 이것이 의도한 것과 다를 수 있다. 재정렬되지 않는 경우에는 큰 문제가 발생하지 않을 것.</p>
<h2 id="그래서-react에서는">그래서 React에서는</h2>
<p>그래서 React에서는 render()를 호출하면 모든 element를 Diffing Algorithm으로 비교가 일어난다. 변화한 것이 없으면 같은 값을 반환한다.</p>
<h1 id="svelte">Svelte</h1>
<p>하지만 Svelte는 이러한 가상 DOM을 사용하지 않고 실제 DOM에 접근한다.
Svelte Blog에서는 다음과 같이 이야기한다.</p>
<pre><code>가상 DOM이 항상 빠르다라는 것은 실제 DOM에 대한 접근 보다는 브라우저 관점에서 성능적으로 
빠를 수 있다.
가상 DOM이 항상 빠르다는 것은 React가 발표될 당시 2013년에 성능이 좋지 않은 프레임워크와의
비교, 혹은 아무도 시도하지 않을 일에 대한 비교이다.
가상 DOM을 사용하기 위해서는 항상 실제 DOM과 가상 DOM을 생성해야하며, 비교하는 과정에서 
overhead가 발생할 수 있다.</code></pre><p>Svelte가 어떻게 동작하는지 정확하게는 모르지만 브라우저 관점에서 어떤 것이 더 나은가 생각해볼 필요가 있다.</p>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Virtual-DOM/#_4-diff-%E1%84%8B%E1%85%A1%E1%86%AF%E1%84%80%E1%85%A9%E1%84%85%E1%85%B5%E1%84%8C%E1%85%B3%E1%86%B7-%E1%84%8C%E1%85%A5%E1%86%A8%E1%84%8B%E1%85%AD%E1%86%BC">Vanilla Javascript로 가상돔(VirtualDOM) 만들기-개발자 황준일</a>
<a href="https://ko.reactjs.org/docs/reconciliation.html">React 공식문서 - Reconciliation</a>
<a href="https://velog.io/@juno7803/React%EA%B0%80-%ED%83%9C%EC%96%B4%EB%82%9C-%EB%B0%B0%EA%B2%BD">React가 태어난 배경 - junoflog</a>
<a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead#Where_does_the_overhead_come_from">Virtual DOM is pure overhead - Svelte blog</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js의 SSR, 우리 서비스에는?]]></title>
            <link>https://velog.io/@jt_include_rw/Next.js%EC%9D%98-SSR-%EC%9A%B0%EB%A6%AC-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%97%90%EB%8A%94</link>
            <guid>https://velog.io/@jt_include_rw/Next.js%EC%9D%98-SSR-%EC%9A%B0%EB%A6%AC-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%97%90%EB%8A%94</guid>
            <pubDate>Wed, 17 Aug 2022 08:42:50 GMT</pubDate>
            <description><![CDATA[<p>우리 회사는 Next.js로 웹 프로덕트를 개발, 유지보수, 배포 중이다. 하지만 SPA에 익숙한 나는 Next.js에서 제공하는 Server Side Rendering을 잘 활용하고 있나라는 의문이 들었고, <a href="https://www.youtube.com/watch?v=IKyA8BKxpXc">토스의 SSR에 관련된 영상</a>을 보며 다시 한 번 알아봐야겠다는 생각을 하였다.</p>
<p>data fetch를 기준으로 생각하고 작성하였습니다.</p>
<h1 id="server-side-rendering-ssr">Server Side Rendering (SSR)</h1>
<ul>
<li>SSR은 서버에서 사용자에게 보여줄 페이지를 모두 구성한 후에 브라우저에 보여주는 Rendering 방식이다. 따라서 data fetching이 필요한 경우, 서버에서 data fetch한 상태의 페이지를 구성하여 사용자에게 보여준다.</li>
<li>👍 : 브라우저에 보여줄 페이지를 Load하는 동안은 사용자는 Loading을 겪게 될 것이다. 하지만 Load 된 이후에 사용자는 이미 구성된 페이지를 보기 때문에 빠르게 페이지를 볼 수 있게 된다.(Time To View, TTV가 짧다.)</li>
<li>👎 : 하지만 SSR은 구성된 페이지가 보여진 이후에 브라우저에서 React를 실행하고, React가 실행된 이후에 사용자가 Interaction을 할 수 있다(Time To Interact, TTI). TTV와 TTI 사이에 사용자가 빠르게 Interaction을 시도한다면 아무런 반응도 없게 된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/c292fedb-536e-4f18-951f-553b91722493/image.png" alt="">
출처 : <a href="https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8">The Benefits of Server Side Rendering Over Client Side Rendering</a></p>
<h1 id="client-side-rendering-csr">Client Side Rendering (CSR)</h1>
<ul>
<li>CSR은 사용자의 요청에 따라 HTML, JS 등 필요한 것들만 먼저 Load하고 data를 클라이언트에서 axios,swr과 같은 비동기처리를 통해 하는 것이다. HTML 다운로드 -&gt; JS 다운로드 -&gt; JS 실행 -&gt; 보여짐 과정이 있기 때문에 사용자에게 TTV가 길다.</li>
<li>👍 : 한 번 JS, HTML을 다운로드 받으면 이후의 interaction이 굉장히 빠르다.</li>
<li>👎 : JS 파일을 모두 다운로드 받아야하기 때문에 TTV가 길고, data fetching이 필요한 경우에 사용자가에 필요한 정보를 보여주기 위해서는 data fetch가 될 때까지 사용자는 Loading을 기다려야한다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/3d9172a5-79f2-445a-846f-9d9fd56d8231/image.png" alt="">
출처 : <a href="https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8">The Benefits of Server Side Rendering Over Client Side Rendering</a></li>
</ul>
<h1 id="csr--ssr-">CSR ? SSR ?</h1>
<p>내 개인적인 생각에 결국 둘의 큰 차이는 data fetch를 어디서 하여서 페이지를 구성하느냐 차이인 것 같다. 이 차이점에서 TTV의 차이도 발생하는 것 같다.</p>
<h1 id="nextjs">Next.js</h1>
<p>Next.js는 페이지 Load까지는 SSR, 페이지 Load 이후는 React의 CSR을 이용하는 방식이다.</p>
<ul>
<li>pages/ 의 page file은 서버 사이드에서 Load</li>
<li>page가 그려진 이후 data fetch는 클라이언트 사이드에서</li>
</ul>
<p>따라서 페이지가 Load될 때 함께 data fetch를 하고 싶다면 Next.js에서 제공하는 getInitialProps, getServerSideProps, getStaticProps를 사용하여 SSR를 만들어야한다.</p>
<h2 id="ssr-요청-순서">SSR 요청 순서</h2>
<ol>
<li>서버에 페이지 요청</li>
<li>서버에서는 요청 받은 페이지를 찾음</li>
<li>_app.tsx의 getInitialProps가 있다면 실행</li>
<li>pages/ 아래의 페이지 파일에 getInitialProps가 있다면 실행하고 페이지의 pageProps를 받는다.</li>
<li>_document.tsx의 getInitialProps가 있다면 실행하고 custom document의 pageProps를 받는다.</li>
<li>모든 props를 구성하고 _app.tsx -&gt; 페이지 순으로 Rendering</li>
<li>모든 content를 구성하고 _document.tsx로 HTML을 출력</li>
</ol>
<h2 id="getinitialprops">getInitialProps</h2>
<blockquote>
<p><strong><em>Recommended:</em></strong> getStaticProps or getServerSideProps instead of getInitialProps. These data fetching methods allow you to have a granular choice between static generation and server-side rendering.</p>
</blockquote>
<p>Next.js의 공식문서에서는 getInitialProps보다는 9.3 버전 이후에 나온 getServerSideProps와 getStaticProps의 사용을 권장한다.</p>
<ul>
<li>_app.tsx에서 getInitialProps를 전역으로 사용하게 될 경우 Next.js에서 제공하는 <strong>자동정적최적화</strong>가 비활성화되어 모든 페이지가 SSR 된다.</li>
<li>getInitialProps를 사용한다면, getServerSideProps와 getServerSideProps는 실행되지 않음</li>
</ul>
<blockquote>
<ul>
<li>자동정적최적화 : Next.js에서는 페이지가 정적인지 확인하고, getInitialProps나 getServerSideProps를 사용하지 않는다면 페이지를 정적 HTML로 사전에 렌더링하여 클라이언트에 전달. 이후 js도 전달. <strong>hydration</strong>을 하여 최적화한다. SSR이 없기 때문에 사전에 렌더링 된 HTML이 사용자에게는 즉시 뿌려지는 매우 빠른 로딩.</li>
</ul>
</blockquote>
<ul>
<li><strong>hydration</strong>: Server Side 단에서 렌더링 된 정적 페이지와 번들링 된 JS 파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 JS코드를 서로 매칭시키는 과정
참고 : <a href="https://velog.io/@tchaikovsky/Next.js-Automatic-Static-Optimization">Next.js - Automatic Static Optimization</a></li>
</ul>
<h2 id="getstaticprops">getStaticProps</h2>
<ul>
<li>Static Site Generation (SSG)</li>
<li>getStaticProps를 사용하면 build time 한 번에만 data fetch가 이루어진다. 따라서 유저에 따라 정보가 변경되거나 하는 data는 getStaticProps에는 맞지 않을 것 같다.</li>
<li>유저에 따라 변하지 않고 퍼블릭하게 사용할 수 있는 데이터에 사용하는 것이 좋을 것 같다. 예를 들어 식단 관련 서비스인 우리 회사 서비스라면, 백앤드에서 알러지 리스트를 받아오는데 이것은 유저에 따라 변하지 않기 때문에 getStaticProps를 통해서 받아오면 좋을 것 같다.</li>
</ul>
<h2 id="getstaticpaths">getStaticPaths</h2>
<ul>
<li>Static Site Generation (SSG)</li>
<li>동적 routing에서 사용. getStaticProps와 함께 사용한다.</li>
<li>정확히 이해하지는 못했지만, 동적 routing에서 /[id].tsx에 id에 들어갈 수 있는 id list들을 미리 static하게 두고, getStaticProps에서 해당 id를 params로 받아서 data fetch하는데 사용한다.</li>
</ul>
<h2 id="getserversideprops">getServerSideProps</h2>
<ul>
<li>server에서 해당 페이지를 request할 때마다 실행하여 data fetch</li>
<li>자주 바뀌는 data에 대해 SSR을 하기 위해서는 getServerSideProps가 적절한 것 같다.</li>
</ul>
<h1 id="but">But,</h1>
<blockquote>
<p>Fetching data on the client side
If your page contains frequently updating data, and you don’t need to pre-render the data, you can fetch the data on the client side. An example of this is user-specific data. Here’s how it works:
First, immediately show the page without data. Parts of the page can be pre-rendered using Static Generation. You can show loading states for missing data.
Then, fetch the data on the client side and display it when ready.
출처: <a href="https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#fetching-data-on-the-client-side">Next.js 공식 문서 Fetching data on the client side</a></p>
</blockquote>
<p>이것을 보고 우리 서비스와 맞나라는 생각이 들었다..</p>
<ol>
<li>우리 서비스는 localStorage로 token 관리하고 있다. 그래서 SSR을 위한 함수에서 token 값을 받아올 수가 없었다. 해결하기 위해서는 token을 localStorage에서 cookie로 바꾸는 작업이 선행 되어야한다. 이후 다음 포스트를 참고하면 될 것 같다.
<a href="https://wonmocyberschool.tistory.com/93?category=959185">https://wonmocyberschool.tistory.com/93?category=959185</a></li>
<li>우리 서비스는 개인 맞춤 식단 서비스라서 대부분은 user-specific data이다.</li>
</ol>
<p>사이드 프로젝트나 포트폴리오 프로젝트에 적용해 봐야지 ,,</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Clean Code : Front end  그리고 짧은 회고]]></title>
            <link>https://velog.io/@jt_include_rw/Clean-Code-Front-end-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%A7%A7%EC%9D%80-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jt_include_rw/Clean-Code-Front-end-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%A7%A7%EC%9D%80-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 12 Aug 2022 06:58:42 GMT</pubDate>
            <description><![CDATA[<p>토스 개발자 컨퍼런스의 프론트엔드 클린코드 관련 컨퍼런스를 보았을 때의 충격이 잊히지 않는다!
왜냐하면 추상화 개념을 처음 접했었고, 코드의 큰 그림에서의 convention, 그리고 내가 자주 실수하는 기능 추가 시에 놓치는 것들에 대한 것을 다루고 있었기 때문이다.</p>
<p>많은 프론트엔드 개발자들이 공감할만한 컨퍼런스 영상이라고 생각한다.(<a href="https://www.youtube.com/watch?v=edWbHp_k_9Y">링크</a>)
그래서 또 돌려보았다. 영상 마지막에 클린코드가 무엇인지 글로 정리해보라는 말에 이렇게 게시물을 포스트하려고 한다.</p>
<h1 id="내가-생각하는-클린-코드란">내가 생각하는 클린 코드란?</h1>
<p>영상에서는 어려운 클린하지 못한 코드를 다음과 같이 정의한다.</p>
<blockquote>
<ul>
<li>흐름 파악이 어렵다.</li>
</ul>
</blockquote>
<ul>
<li>도메인 맥락 표현이 안되어있다.</li>
<li>동료(짠 사람)에게 물어봐야 알 수 있다.</li>
<li>그래서 <strong><em>클린한 코드</em></strong>는 유지 보수(코드 파악, 디버깅, 리뷰) 시간의 단축할 수 있는 코드라고 말한다.</li>
</ul>
<p>비슷한 맥락에서 프론트엔드를 98% 정도 혼자 작업하는 내 코드는 나만 알 수 있다. CTO님도 내가 초반에 짠 코드들은 불친절하다고 했다. 
해당 영상을 처음 보고 난 후, 추상화라는 것을 생각했지만 결국 빠르게, 급하게, 일주일 단위의 배포 등을 핑계로 다시 원상복구 된 것 같다...😭</p>
<h3 id="내가-생각하는-클린-코드">내가 생각하는 클린 코드</h3>
<blockquote>
<ul>
<li>친절한 코드
다음에 어떤 프론트엔드 개발자가 와도 쉽게 이해할 수 있는 코드</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>어떻게 쉽게 이해할 수 있도록 할 것인가?</li>
</ul>
</blockquote>
<ol>
<li>함수명, component명을 누가 봐도 알 수 있게 적자</li>
<li>함수는 하나의 역할만을 하도록 하여 And, Or 이런 글자를 함수명으로 사용하지 말자</li>
<li>hooks를 써서 코드를 응집하려고 할 때, 핵심 정보는 보일 수 있게 작업하자 custom할 수 있는 hook을 짜자</li>
<li>추상화 레벨을 맞추자 적어도 한 컴포넌트 안에서는</li>
<li>기능이 추가 될 때도 이것을 지키자</li>
<li>코드가 길어짐을 두려워하지 말자</li>
<li>SoC를 통해 필요한 코드들끼리 묶자</li>
</ol>
<p><code>코드를 작성하는 것은 한 편의 소설을 쓰는 것과 마찬가지이며, 하나의 훌륭한 코드는 하나의 훌륭한 소설과 같다.</code>
로버트 C.마틴의 &quot;클린 코드&quot;에서</p>
<p>예전에 이 책을 읽었을 때는 &quot;이게 맞나?&quot;라는 생각을 했었다. 코딩을 하면 할 수록 이 말이 무슨 말인지 깨닫는 것 같다.</p>
<h1 id="클린-코드를-적용했을-때-어떤-모습이-되고-싶은가">클린 코드를 적용했을 때, 어떤 모습이 되고 싶은가?</h1>
<p>클린 코드를 사내, 개인 프로젝트에 적용했을 때 나는 이런 모습이 되고 싶다.</p>
<blockquote>
<ol>
<li>디렉터리와 파일명만 봐도 어떤 역할을 할 파일인지 알았으면..</li>
<li>함수명만 봐도 어떤 역할을 하는 함수인지 알았으면..</li>
<li>hooks와 components들이 props만 넣으면 되게끔 모듈처럼 동작했으면..</li>
<li>글을 읽듯 읽히는 코드</li>
</ol>
</blockquote>
<h1 id="이렇게-안짜면-향후-어떤-점에서-위험할-수-있을까">이렇게 안짜면 향후 어떤 점에서 위험할 수 있을까?</h1>
<p>이렇게 안짜면 내가 이 프로젝트에서 손을 떼는 날에는 훗날 누군가 이 프로젝트를 짠 사람을 쉽게 욕할 수 있을 거 같다..
&#39;그 땐 맞고 지금은 틀리다&#39;라는 말에서 틀리다의 화살표 방향이 나를 가르킬 것 같다.</p>
<h1 id="어떻게-개선할-수-있을까">어떻게 개선할 수 있을까?</h1>
<ol>
<li>이전에는 custom hook들을 디렉터리의 규칙 없이 한 디렉터리에 때려넣었다..! 앞으로 코드의 응집성과 추상화 레벨을 고려하여 hook을 더 많이 짜게 될 것 같다. 이것이 영상에서 설명하는 선언적 프로그래밍을 하기 위함과 코드를 더 클린하게 짤 수 있을 것 같다.</li>
<li>SoC와 응집하기를 통해 관심사를 필요한 컴포넌트, 함수에만 전해준다.</li>
<li>하나의 함수에는 하나의 역할만 주자<h1 id="리팩토링">리팩토링..</h1>
솔직하게 말하면 회사의 모든 코드를 리팩토링하기에는 프론트엔드 개발자가 혼자인 상황, 일주일 단위의 배포에서 쉽지는 않을 것 같다.
그래서 나의 목표는 앞으로 짤 코드에 대해서 클린 코드를 도입하여 오늘을 기점으로 누가봐도 clean/dirty가 구분 될 수 있을 코드를 짤 것이다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[styled-component에서의 후손, 자식 선택자]]></title>
            <link>https://velog.io/@jt_include_rw/styled-component%EC%97%90%EC%84%9C%EC%9D%98-%ED%9B%84%EC%86%90-%EC%9E%90%EC%8B%9D-%EC%84%A0%ED%83%9D%EC%9E%90</link>
            <guid>https://velog.io/@jt_include_rw/styled-component%EC%97%90%EC%84%9C%EC%9D%98-%ED%9B%84%EC%86%90-%EC%9E%90%EC%8B%9D-%EC%84%A0%ED%83%9D%EC%9E%90</guid>
            <pubDate>Tue, 09 Aug 2022 15:10:52 GMT</pubDate>
            <description><![CDATA[<p>어제 작성한 블로그에서는 <a href="https://velog.io/@jt_include_rw/CSS%EC%9E%90%EC%8B%9D-%ED%9B%84%EC%86%90-%EC%84%A0%ED%83%9D%EC%9E%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%9D%B4%EA%B1%B4-%EB%AD%90%EB%9D%BC%EA%B3%A0-%EB%B6%80%EB%A5%B4%EB%82%98%EC%9A%94">CSS의 후손 선택자, 자식 선택자</a>에 대해 공부해보았다!</p>
<p>오늘은 공부한 선택자를 styled-component에 적용해보았다! </p>
<p>포트폴리오 사이트를 만들고 있었는데 마침 자식 선택자가 필요한 순간이 왔다.</p>
<h1 id="styled-component">styled-component</h1>
<h2 id="문제">문제</h2>
<p>전체를 감싸고 있는 최상위 div에서 임의로 TBD component(아래 이미지의 파란색 영역) 간의 <code>margin-bottom</code>을 주기 위해 사용한 후손 선택자로 인해 TBD component가 아닌 div 아래의 모든 div에 대해서 <code>margin-bottom</code>이 적용 되어 버렸다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/55920f10-2a86-49a4-9cc9-7425db6c78b9/image.png" alt="">
엉망진창으로 보일 수 있지만 빨간 영역이 margin-bottom을 후손 선택자로 받은 녀석이고 코드는 다음과 같다</p>
<pre><code>const Wrapper = styled.div`
    ...
    div {
        margin-bottom:24px;

        :last-child {
            margin-bottom:0;
        }
    }

`;</code></pre><p>그래서 이 녀석을 자식인 TBD component에만 <code>margin-bottom</code>을 적용하기 위해 자식 선택자를 styled-component에 적용해 보고자 하였다.</p>
<h2 id="자식-선택자">자식 선택자</h2>
<p>위 코드를 다음과 같이 바꾸어 보았다</p>
<pre><code>const Wrapper = styled.div`
    ...
    &gt; div {
      margin-bottom:24px;

          :last-child {
              margin-bottom:0;
          }
    }
`;</code></pre><p><img src="https://velog.velcdn.com/images/jt_include_rw/post/3040f6fd-fdb1-44bb-9e02-08726dd0c6b7/image.png" alt="">
전과 다르게 파란색 네모 박스 영역의 div에는 <code>margin-bottom</code>이 적용되지 않은 모습을 볼 수 있다.</p>
<h2 id="나는-이게-더-이해하기-편했다">나는 이게 더 이해하기 편했다.</h2>
<p>사실 styled-component에 <code>&amp;</code>라는 것을 사용할 수가 있다.
그래서 나는 다음과 같이 이해하는 게 더 편했다.</p>
<ul>
<li><code>&amp;</code>는 자기 자신을 참조하는 것이다.</li>
<li><code>&amp; &gt; div</code> 는 참조한 자신의 자식 선택자</li>
<li><code>&amp; div</code> 는 참조한 자신의 후손 선택자</li>
<li><code>&amp;</code>는 생략가능</li>
</ul>
<h2 id="styled-component를-조금-훑어보며-느낀-점">styled-component를 조금 훑어보며 느낀 점</h2>
<p>styled-component로 회사 프로젝트, 포트폴리오 프로젝트를 작성하고 있지만 attrs와 같이 유용한 것들을 알고만 있지 실제로 적용해본 경험이 없다는 것을 느꼈다. 알고만 있는 상태에서 그치지 않고 편리한 것이라면 적용을 해보고 편리함을 느껴 자연스럽게 사용하도록 해야 할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS]자식, 후손 선택자 그리고 이건 뭐라고 부르나요 ?]]></title>
            <link>https://velog.io/@jt_include_rw/CSS%EC%9E%90%EC%8B%9D-%ED%9B%84%EC%86%90-%EC%84%A0%ED%83%9D%EC%9E%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%9D%B4%EA%B1%B4-%EB%AD%90%EB%9D%BC%EA%B3%A0-%EB%B6%80%EB%A5%B4%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@jt_include_rw/CSS%EC%9E%90%EC%8B%9D-%ED%9B%84%EC%86%90-%EC%84%A0%ED%83%9D%EC%9E%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%9D%B4%EA%B1%B4-%EB%AD%90%EB%9D%BC%EA%B3%A0-%EB%B6%80%EB%A5%B4%EB%82%98%EC%9A%94</guid>
            <pubDate>Mon, 08 Aug 2022 15:17:27 GMT</pubDate>
            <description><![CDATA[<p>지난 번 Design System을 만들며 부족했던 부분인 CSS의 자식, 후손, 전체 선택자에 대해 알아보았다.</p>
<p><a href="https://velog.io/@jt_include_rw/%EB%B6%80%EC%A1%B1%ED%95%9C-CSS-%EC%A7%80%EC%8B%9D-%EC%84%A0%ED%83%9D%EC%9E%90">지난 게시물 보기</a></p>
<h1 id="후손-선택자">후손 선택자</h1>
<h2 id="css-문법">CSS 문법</h2>
<pre><code>ol li {
    color:red;
}
</code></pre><p>이는 <code>ol</code> tag 아래에 있는 모든 <code>li</code> tag에 대해 해당 스타일을 입히는 것이다.</p>
<h2 id="html">html</h2>
<pre><code>&lt;body&gt;
    &lt;ol&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;ul&gt;
            &lt;li&gt;li Tag 입니다&lt;/li&gt;
            &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/ol&gt;
&lt;/body&gt;</code></pre><p>화면에 출력해보면 <code>ul</code> tag의 children인 <code>li</code>도 red color로 출력될 것이다.
한 번 확인 해보자!</p>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/a4095e4a-8be5-4f42-87ca-87ecb1e8fb44/image.png" alt=""></p>
<h1 id="자식-선택자">자식 선택자</h1>
<h2 id="css-문법-1">CSS 문법</h2>
<pre><code>ol &gt; li {
  color: red;
}
</code></pre><p>이는 <code>ol</code> tag 바로 아래에 있는 children인 <code>li</code> tag에 대해서만 해당 스타일을 입히는 것이다.</p>
<h2 id="html-1">html</h2>
<pre><code>&lt;body&gt;
    &lt;ol&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;ul&gt;
            &lt;li&gt;li Tag 입니다&lt;/li&gt;
            &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/ol&gt;
&lt;/body&gt;</code></pre><p>화면에 출력해보면 <code>ul</code> tag의 children인 <code>li</code>는 red color로 출력되지 않고 <code>ol</code> tag의 직계 자식인 <code>li</code> tag만 red color로 나올 것이다.
한 번 확인 해보자!</p>
<h2 id="결과-1">결과</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/959ebbb7-21ec-4018-a980-4f88f14883b7/image.png" alt=""></p>
<h1 id="이건-뭐라고-부르나요">이건 뭐라고 부르나요?</h1>
<h2 id="css-문법-2">CSS 문법</h2>
<pre><code>ol * li {
  color: red;
}
</code></pre><p>공부를 위해 검색을 하다가 위와 같은 문법을 보았다. 뭐라고 부르는지는 모르겠다. 코드를 작성하고 돌려보니 다음과 같은 결과가 나왔다.
<img src="https://velog.velcdn.com/images/jt_include_rw/post/bd506edf-84b8-457f-b79a-4ab033981932/image.png" alt="">
뭔가 자식 선택자와 반대의 의미를 갖는 것 같았지만 하위 depth의 <code>li</code> tag를 보면 또 막상 그런 거 같지는 않고....
<code>ol</code> tag에서 최상단의 직계 자식을 제외한 <code>li</code> tag 모두 red color로 나왔기 때문에... 정답을 알려줘...</p>
<h2 id="html-2">html</h2>
<pre><code>&lt;body&gt;
    &lt;ol&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;ul&gt;
            &lt;ol&gt;
                &lt;li&gt;li Tag 입니다&lt;/li&gt;
                &lt;ul&gt;
                    &lt;li&gt;li Tag 입니다&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/ol&gt;
            &lt;li&gt;li Tag 입니다&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/ol&gt;
&lt;/body&gt;</code></pre><h2 id="결과-2">결과</h2>
<p><img src="https://velog.velcdn.com/images/jt_include_rw/post/d655535d-26fb-4e1f-a2d1-40456dcdeb49/image.png" alt=""></p>
<h1 id="결론">결론</h1>
<p>아직 이름을 알지 못하는 녀석이 하나 있지만, className 혹은 id와 함께 사용하여 적용한다면 내가 원하는 방향대로 보다 잘 사용할 수 있을 것 같다는 생각이 들었다. 다음 번엔 styled-component에 적용해봐야지 ~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native로 웹뷰 앱 배포하기 - 2. React Native ➡️ 웹뷰 이벤트 보내기]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native%EB%A1%9C-%EC%9B%B9%EB%B7%B0-%EC%95%B1-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-2.-React-Native-%EC%9B%B9%EB%B7%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B3%B4%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jt_include_rw/React-Native%EB%A1%9C-%EC%9B%B9%EB%B7%B0-%EC%95%B1-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-2.-React-Native-%EC%9B%B9%EB%B7%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B3%B4%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Sun, 07 Aug 2022 14:56:59 GMT</pubDate>
            <description><![CDATA[<p>지난 1편에 이어 2편은 React Native에서 웹뷰로 이벤트를 보내는 것을 포스팅할 것이다.</p>
<h1 id="rneventpropstype">RNEventPropsType</h1>
<p>웹뷰와 React Native에서 들어온 Event를 처리하기 위해 interface를 사용하기 위해 아래와 같은 type을 만들었다.</p>
<pre><code>export interface RNEventPropsType&lt;U = any, T = any&gt; {
  event: U;
  data?: T;
}</code></pre><h2 id="다음과-같은-rneventenum을-가정한다">다음과 같은 RNEventEnum을 가정한다.</h2>
<pre><code>export enum RNEventEnum {
    Example:&#39;Example&#39;;
}</code></pre><h1 id="웹뷰의-rnlistener">웹뷰의 RNListener</h1>
<p>웹뷰에서 React Native에서 보내온 event를 handling하는 Listener 컴포넌트를 만들었다. 
기본 컨셉은 다음과 같다.</p>
<ol>
<li><p>iOS는 window 객체, android는 document 객체에 event listener를 등록한다.</p>
</li>
<li><p>event명에 따라 핸들링할 수 있도록 switch-case로 핸들링 함수를 실행하도록 한다.</p>
</li>
<li><p>실행할 핸들링 함수를 구현한다.</p>
<pre><code>const RNListener:React.FC = () =&gt; {

 //핸들링 함수
 const handleExaple = (parsed:RNEventPropsType&lt;RNEventEnum, any&gt;) =&gt; {
     ...
     // 함수 구현
 }

 // event Listener의 event명(약속된 RNEventEnum)에 따른 핸들링 함수 실행
 const listener = (receive:{data:string}) =&gt; {
     if(receive){
         const parsed:RNEventPropsType&lt;RNEventEnum, any&gt; = JSON.parse(receive.data);
         swtich(parsed.event){
             case RNEventEnum.Example:
                 handleExample(parsed);
                 return;
             ...
         }
     }
 }

 //Event Listener 등록
 useEffect(() =&gt; {
       if (window.ReactNativeWebView) {
         /** android */
         document.addEventListener(&#39;message&#39;, listener);
         /** ios */
         window.addEventListener(&#39;message&#39;, listener);
       }
}, []);
return &lt;React.Fragment/&gt;;
}</code></pre><h1 id="react-native에서-이벤트-보내기">React Native에서 이벤트 보내기</h1>
<p>React-Native-Webview는 postMessage function을 통해 웹뷰로 이벤트를 보낼 수 있도록 제공한다.
따라서 webViewRef를 만들어 WebView Component를 참조하고 다음과 같이 이벤트를 보낸다.</p>
<pre><code>webViewRef.current?.postMessage({
 event: RNEventEnum.Example,
 data:{
     ... // 웹뷰에서 처리를 위해 필요한 데이터
 },
})</code></pre><h1 id="웹뷰에서-rn인지-어떻게-알지">웹뷰에서 RN인지 어떻게 알지?</h1>
<p>React Native WebView에 userAgent를 설정할 수 있는 것이 있다. 기존의 userAgent에 <code>[RNWebView]</code>라는 것을 concat했고, 웹뷰에서 userAgent에 <code>[RNWebView]</code>이 있으면 RNAtom recoil을 &#39;ios&#39; 혹은 &#39;android&#39;로 값을 할당해줬다. ios인지 android인지도 userAgent를 보고 할당했다.</p>
<pre><code>React Native userAgent 설정
...
&lt;WebView 
 ...
 userAgent={`${기존 userAgent}[RNWebView]`}
 ...
/&gt;
...</code></pre></li>
</ol>
<pre><code>웹뷰에서의 RN 설정
...
  const setRN = useSetRecoilState(RNAtom);
  export const isRNWebView = () =&gt; {
    return navigator.userAgent.match(/RNWebView/);
  };

  export const isAndroid = () =&gt; {
    return navigator.userAgent.match(/Android/);
  };

  export const isIOS = () =&gt; {
    return navigator.userAgent.match(/(iPod|iPhone|iPad)/);
  };


  useEffect(() =&gt; {
    if (isRNWebView()) {
      if (isIOS()) {
        setRN(&#39;ios&#39;);
      } else {
        setRN(&#39;android&#39;);
      }
    }
  }, []);
...</code></pre><h1 id="마무리">마무리</h1>
<p>1편과 2편을 통해 React Native ↔️ 웹뷰 간의 이벤트 통신을 설명했다.
위를 통해 구현한 것들 중 하나를 예시로 들면</p>
<ul>
<li>웹에서 카카오 로그인과 앱에서 카카오 로그인은 intent 등의 이슈로 다르게 하여야했다. 그래서 앱 내 웹뷰에서 카카오 로그인 버튼을 누르면 RNEvent를 웹뷰에서 RN으로 보냈다. 그러면 React Native 모듈을 통해 구현 카카오 로그인을 RN에서 처리하고 백앤드에서 받은 token을 다시 웹뷰로 보내는 방식으로 구현하였다.</li>
</ul>
<p>시스템 만드는 것은 구조를 생각하는 것은 어려운데 막상 만들고 잘 돌아가는 것을 보면 재밌다 🥳🥳</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[부족한 CSS 지식 - 선택자]]></title>
            <link>https://velog.io/@jt_include_rw/%EB%B6%80%EC%A1%B1%ED%95%9C-CSS-%EC%A7%80%EC%8B%9D-%EC%84%A0%ED%83%9D%EC%9E%90</link>
            <guid>https://velog.io/@jt_include_rw/%EB%B6%80%EC%A1%B1%ED%95%9C-CSS-%EC%A7%80%EC%8B%9D-%EC%84%A0%ED%83%9D%EC%9E%90</guid>
            <pubDate>Fri, 05 Aug 2022 14:22:05 GMT</pubDate>
            <description><![CDATA[<p>회사에서 Design System V1을 디자이너님과 함께 개발을 진행 중이다. 오늘은 Design System을 개발하다가 마주한 나의 부족한 CSS 실력에 대해 회고+반성+해결을 적어보려고한다.</p>
<h1 id="문제상황">문제상황</h1>
<p>ColumnLayout이라는 컴포넌트를 만들었다. 이는 디자이너님과 약속한 width로 하위 children div의 width를 지정해주는 Grid 성격의 Layout 컴포넌트이다.<img src="https://velog.velcdn.com/images/jt_include_rw/post/15f053e6-7e3e-41f6-8b08-fb51745bf7be/image.png" alt="">
Storybook으로 만들었을 때는 잘되는 줄 알았다. <em><strong>왜냐면 children의 depth가 1이었기 때문에....</strong></em></p>
<h2 id="문제는-depth가-2-이상일-때-등장했다">문제는 depth가 2 이상일 때 등장했다..</h2>
<pre><code>기존코드
const Wrapper = styled.div`
    ...

    div {
        margin-right: 12px;
        width: 300px;
    }
`;</code></pre><p>CSS의 선택자에 대한 지식이 부족하다. 그래서 styled-component에서 위 코드와 같이 div { ... }를 적으면 후손 선택자가 되어 해당 컴포넌트 아래에 있는 후손들 중 div tag가 모두 <code>margin-right:12px; width:300px;</code>을 갖게 된다. 그래서 후손의 모든 div가 내가 원하는대로 style을 갖지 못했다..😭</p>
<h1 id="1차-해결">1차 해결</h1>
<p>우선은 후손 div들 중 내가 원하는 margin-right과 width를 주기 위해서 <code>!important</code>를 지정해줬다.</p>
<p><a href="https://www.w3schools.com/css/css_important.asp">w3schools의 CSS The !important Rule</a></p>
<blockquote>
<p>The !important rule in CSS is used to add more importance to a property/value than normal.
In fact, if you use the !important rule, it will override ALL previous styling rules for that specific property on that element!</p>
</blockquote>
<p>근데 찜찜해....😔😔😔😔😔</p>
<h1 id="2차-해결">2차 해결</h1>
<p>그래서 내가 해당 style을 적용하고 싶은 div에 className을 지정하기로 했다. className을 &quot;child&quot;로 지정하고 기존 코드를 다음과 같이 수정하였다. 그리고 하위 후손 div의 <code>!important</code>는 모두 삭제!</p>
<pre><code>수정코드
const Wrapper = styled.div`
    ...

    div.child {
        margin-right: 12px;
        width: 300px;
    }
`;</code></pre><h1 id="배운-것">배운 것</h1>
<p>이번을 계기로 <code>!important</code>와 후손 선택자, 자손 선택자라는 것이 있다는 것을 배울 수 있었다.
후손 선택자와 자손 선택자에 대해서는 추후에 예제를 만들어가면서 공부할 필요가 있다.</p>
<h3 id="어려워😈">어려워............😈</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native로 웹뷰 앱 배포하기 - 1. 웹뷰 ➡️ React Native 이벤트 보내기]]></title>
            <link>https://velog.io/@jt_include_rw/React-Native%EB%A1%9C-%EC%9B%B9%EB%B7%B0-%EC%95%B1-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-1.-%EC%9B%B9%EB%B7%B0-React-Native-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B3%B4%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jt_include_rw/React-Native%EB%A1%9C-%EC%9B%B9%EB%B7%B0-%EC%95%B1-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-1.-%EC%9B%B9%EB%B7%B0-React-Native-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B3%B4%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Thu, 04 Aug 2022 15:39:07 GMT</pubDate>
            <description><![CDATA[<h1 id="react-native와-react-native-webview-간의-이벤트-송수신-1편-웹뷰-➡️-react-native-이벤트-보내기">React Native와 React Native WebView 간의 이벤트 송수신 (1편. 웹뷰 ➡️ React Native 이벤트 보내기)</h1>
<h2 id="나에게-주어진-과제">나에게 주어진 과제</h2>
<p>회사에서 웹으로 만들어진 프로덕트를 앱으로 만들어야하는 일이 주어졌다.
유일한 프론트앤드 개발자.. CTO님과 머리를 굴렸다!
...
내가 React Native 경험도 있고 웹 프로덕트도 모바일 사이즈로 개발하였으니 React Native로 웹뷰 앱을 만들자 !</p>
<h2 id="오늘-이야기할-것">오늘 이야기할 것</h2>
<p>이런저런 이야기를 떠나서, WebView를 사용할 때 React Native 모듈이 아니면 실행되지 않는 문제가 있다. 
예를 들어 웹뷰 내의 kakao channel link를 실행할 때 android의 intent::로 인해 실행 실패 등등..</p>
<h3 id="그래서-react-native-모듈을-사용할-때-어떠한-방식으로-웹뷰에서-이벤트를-쏘았고-react-native에서는-어떻게-처리했는지에-대해-블로깅을-해보고자한다">그래서 React Native 모듈을 사용할 때 어떠한 방식으로 웹뷰에서 이벤트를 쏘았고, React Native에서는 어떻게 처리했는지에 대해 블로깅을 해보고자한다!</h3>
<h2 id="즉-react-native와-웹뷰-간의-이벤트-기반-핸들링">즉, React Native와 웹뷰 간의 이벤트 기반 핸들링!</h2>
<h1 id="webvieweventpropstype">WebViewEventPropsType</h1>
<p>웹뷰와 React Native에서 동일한 interface를 사용하기 위해 아래와 같은 type을 만들었다.</p>
<pre><code>interface WebViewEventPropsType&lt;T = any&gt; {
  event: string; // 약속된 Event명
  data?: T; // data가 필요한 경우가 있다
}</code></pre><h1 id="웹뷰-내의-이벤트-보내기">웹뷰 내의 이벤트 보내기</h1>
<p>약속 된 이벤트명들은 Enum으로 만들었다
예를 들어, 가장 간단히 React Native의 개발 모드에서 console을 보고 싶은 경우.</p>
<pre><code>enum RNEventEnum {
      ConsoleLog = &#39;CONSOLE_LOG&#39;
}</code></pre><p>그리고 React Native로 event를 보내는 util
❌ 주의할 점은 꼭 JSON.stringify해서 보내야한다는 것 ❌</p>
<pre><code>export const sendEvent = (data: RNEventPropsType&lt;any, RNEventEnum&gt;) =&gt; {
  if (window &amp;&amp; window.ReactNativeWebView) {
      window.ReactNativeWebView.postMessage(JSON.stringify(data));
  }
};
</code></pre><p>그래서 합쳐진 React Native로 console.log를 찍는 event인 ConsoleLog를 보내는 코드는 다음과 같다.</p>
<pre><code>sendEvent({
    event: RNEventEnum.ConsoleLog
    data: &#39;console log event 실행&#39;
})</code></pre><h1 id="react-native에서-이벤트-받기">React Native에서 이벤트 받기</h1>
<ul>
<li>웹뷰 이벤트를 핸들링할 custom hook을 작성하였다.</li>
<li>WebViewEventHandlerType은 웹뷰에서 작성한 event명에 따라 핸들링 함수를 실행하기 위해 다음과 같이 작성하였다.</li>
<li>WEB_VIEW_REF를 사용한 이유는 WEB_VIEW에서 사용하는(현재 예시 코드에는 없지만) navigation, window와 같은 다른 객체의 변화에 따라 값을 변경해주고 싶어서이다. <pre><code>interface WebViewEventHandlerType&lt;T = any&gt; {
[index: string]: (props?: WebViewEventPropsType&lt;T&gt;) =&gt; void;
}
</code></pre></li>
</ul>
<p>const useWebViewEvent = (webViewRef: RefObject<WebView>) =&gt; {
  const handleConsoleLog = (props?: WebViewEventPropsType<any>) =&gt; {
      console.log(props?.data);
    };
     const WEB_VIEW_EVENT: WebViewEventHandlerType = {
         CONSOLE_LOG: handleConsoleLog,
     }
       const WEB_VIEW_EVENT_REF = useRef(WEB_VIEW_EVENT);</p>
<p>  useEffect(() =&gt; {
    WEB_VIEW_EVENT_REF.current = WEB_VIEW_EVENT;
  });
  return WEB_VIEW_EVENT_REF;
  }</p>
<pre><code>
[React Native Webview](https://github.com/react-native-webview/react-native-webview)에서 onMessage props를 다음과 같이 넘겼다.
❌주의사항❌ + 여담
나는 ```   if (webViewEvent.current[parsed.event]) {
            webViewEvent.current[parsed.event](parsed);
          }```를 처음에 적지 않아 앱 심사 완료, 배포 이후 앱 크래시가 나는 엄청난 상황을 겪었다.. 정말 멘탈이 터진 7월 1일.. 아직 기억한다 😭 회사분들이 괜찮냐고 여럿 전화를 주시고 10시까지 야근을 했던 기억...😔😔 
_**          다행히 Deep Link와 FCM에 관련한 event 처리를 하지 않았던 것이라 두 가지의 사용을 미루고 웹에서 두 가지 event를 보내지 않는 것으로 급한 불을 껐다. 이후 앱을 재심사 받고, 유저들 중 60%가 재심사 받은 버전을 다운 받기를 기다리고 다시 웹에서 두 가지 event를 보냈다....**_</code></pre><p>  const onMessage = (e: WebViewMessageEvent) =&gt; {
    const { data } = e.nativeEvent;
    if (data !== &#39;undefined&#39;) {
      const parsed: WebViewEventPropsType<any> | undefined = JSON.parse(data);
      if (parsed) {
          if (webViewEvent.current[parsed.event]) {
            webViewEvent.current<a href="parsed">parsed.event</a>;
          }
      }
    }
  };</p>
<p>```</p>
<h1 id="마무리">마무리</h1>
<p>이러면 React Native와 웹뷰 간의 약속한 event명 기반의 핸들링이 완성된다.
두 개 간의 Message가 송수신이 가능한 것을 보고 이전 서비스에서 FCM 처리를 event명 기반으로 했던 것이 떠올라 다음과 같은 컨셉으로 이벤트 처리를 해보았다.</p>
<p>2편에서는 React Native ➡️ 웹뷰 이벤트 보내기를 다뤄볼 것이다.</p>
<h1 id="reference">Reference</h1>
<p><a href="https://github.com/react-native-webview/react-native-webview">React Native Webview</a></p>
]]></description>
        </item>
    </channel>
</rss>