<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>물민초</title>
        <link>https://velog.io/</link>
        <description>최고의 효율을 창출하기 위해 겸손히 노력합니다.</description>
        <lastBuildDate>Fri, 10 Oct 2025 06:36:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>물민초</title>
            <url>https://velog.velcdn.com/images/watermincho_96/profile/27cc1692-cc99-49ee-9197-6510d889d37c/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 물민초. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/watermincho_96" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Core Web Vitals]]></title>
            <link>https://velog.io/@watermincho_96/Core-Web-Vitals</link>
            <guid>https://velog.io/@watermincho_96/Core-Web-Vitals</guid>
            <pubDate>Fri, 10 Oct 2025 06:36:56 GMT</pubDate>
            <description><![CDATA[<h1 id="히스토리">히스토리</h1>
<p>웹사이트 성능을 측정하는 <strong>Core Web Vitals</strong>는 2014년부터 Google의 여러 팀이 협력하여 AMP 프로젝트의 한계를 극복하고, 모든 웹사이트에 적용 가능한 개방형 표준 성능 지표를 정의하려는 노력에서 시작했다.
<strong>2020년 5월 세 가지 핵심 지표(로딩 속도의 LCP, 상호작용 응답성의 FID, 시각적 안정성의 CLS)가 공식 발표</strong>되었으며, 실제 사용자 경험을 반영하는 필드 측정 가능한 지표로 설계되었다.
2021년 Google Search의 Page Experience 업데이트를 통해 검색 순위 요소로 도입되었고, Top Stories에서 AMP 독점 요구사항이 제거되어 개방형 웹 경쟁 환경이 조성되었다.
Chrome 브라우저 최적화, WordPress 등 주요 CMS 개선, JavaScript 프레임워크 협업을 통해 2023년 기준 사용자 대기 시간 누적 10,000년 이상 절약, 2024년에는 30,000년 절약을 달성했다.
2024년 FID를 INP로 교체하고 SPA를 위한 Soft Navigation API를 도입하는 등 지속적으로 진화하며, 사용자 중심의 빠르고 안정적인 웹 생태계 구축에 기여하고 있다.</p>
<hr>
<h1 id="개요">개요</h1>
<p>사용자 경험 최적화의 중요성은 항상 강조된다. Google 및 기타 검색 엔진은 UX를 순위 요소로 더욱 강조하고 있다. 이는 웹 사이트 소유자가 사용하기 쉽고, 빠르게 로드되며, 모든 장치에서 액세스할 수 있는 웹 사이트를 만드는 데 집중해야 함을 의미한다.
웹사이트를 UX에 맞게 최적화하려면 페이지 속도, 모바일 응답성, 탐색 등의 요소에 집중해야 한다. 이를 위해서는 명확하고 간결한 메뉴와 링크를 사용하여 웹 사이트를 탐색하기 쉽게 만들어야 한다.
사이트의 전반적인 로딩 속도, 상호작용, 웹페이지의 시각적 안정성, 보안 문제 등 여러 요소를 포함하고 있다. 구글에서는 웹 사이트에 방문하는 유저들의 경험을 좌우하는 여러 요소들 중 가장 기본이자 핵심 지표로 보기 때문에 &#39;코어 (Core)&#39;라는 단어를 붙여 부른다.</p>
<h1 id="seo에서-코어-웹-바이탈이-중요한-이유">SEO에서 코어 웹 바이탈이 중요한 이유</h1>
<p>코어 웹 바이탈은 웹 사이트의 사용자 경험에 직접적인 영향을 미치고, 결과적으로 검색 엔진 순위에 영향을 미칠 수 있기 때문에 SEO에 중요하다. Google에서는 좋은 사용자 경험을 제공하는 웹 사이트에 우선순위를 두고 싶다는 점을 분명히 밝혔으며, 코어 웹 바이탈은 그러한 경험을 측정하는 Front-end개발자로써 할 수 있는 한 가지 방법이다.
또한, 코어 웹 바이탈을 우선시하는 웹 사이트 소유자는 검색 엔진 순위 향상 이상의 이점을 누릴 가능성이 높다. 빠르게 로드되는 웹 사이트는 사용하기 쉽고 안정적인 사용자 경험을 제공할 수 있기 때문에 사용자의 참여를 유지하고 고객으로 전환할 가능성이 더 높다.</p>
<h1 id="측정-항목">측정 항목</h1>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/cdafe9e8-cbfd-4d8c-91c5-82762f75aa75/image.png" alt=""></p>
<h2 id="lcp-largest-contentful-paint">LCP (Largest Contentful Paint)</h2>
<p>LCP는 웹 페이지의 로딩 속도에 대한 지표로 <strong>콘텐츠 렌더링 시간</strong>을 의미한다. 웹 페이지가 완전히 로드되는 데 걸리는 시간을 측정하는 것이 아니라, <strong>가장 중요한 부분이 로드되는 시점에만 집중</strong>한다. 즉, 뷰포트* 내에서 가장 큰 페이지 요소(큰 텍스트 블록, 이미지 또는 비디오)를 표시하는 데 걸리는 시간을 측정한다.
텍스트와 큰 이미지만을 포함한 간단한 웹페이지의 경우, 해당 <strong>큰 이미지가 LCP로 간주</strong>된다. 이는 브라우저에 로드할 수 있는 콘텐츠 중 가장 큰 부분이므로 사용자에게 깊은 인상을 남길 수 있다. 더 빠르게 로드하면 웹페이지가 훨씬 더 빠르게 나타날 수 있다.
구글에 따르면 웹페이지 로드 후 처음 2.5초 이내에 LCP가 발생하도록 목표를 세워야 한다. 2.5초~4초 미만은 개선이 필요하며, 그 이상의 시간이 소요되는 것은 성능이 좋지 않다고 간주할 수 있다.</p>
<ul>
<li>좋음 – 가장 큰 콘텐츠 요소가 2.5초 이내에 렌더링된다.</li>
<li>개선 필요 – 가장 큰 콘텐츠 요소는 4초 이내에 렌더링되지만 2.5초보다 오래 걸린다.</li>
<li>나쁨 – 가장 큰 콘텐츠 요소를 렌더링하는 데 4초 이상 걸린다.<h2 id="fid-first-input-delay-→-inp-interaction-to-next-paint">FID (First Input Delay) → INP (Interaction to Next Paint)</h2>
</li>
<li><em>2024년 3월부터 FID가 INP로 교체*</em>되었다. FID는 사이트의 상호작용성과 반응성에 대한 사용자의 첫인상을 결정한다고 볼 수 있다. 왜냐하면 <strong>FID는 사용자가 웹페이지와 상호작용을 시도하는 첫 번째 순간부터 웹페이지가 응답하는 시간</strong>을 측정하기 때문이다. 즉, 브라우저에서 다음 액션이 발생되는 시간까지의 길이를 측정한 지표이며, 밀리세컨드(ms)로 측정한다.
INP는 FID의 한계를 보완하여 모든 상호작용의 응답성을 측정한다. FID는 첫 번째 상호작용만 측정했지만, INP는 페이지의 모든 상호작용을 고려하여 더 포괄적인 사용자 경험을 평가한다.
이 때, <strong>FID가 중요하게 여기는 요소는 요청받은 액션을 처리하는데 걸리는 시간이 아닌, 입력 지연을 시키는 시간</strong>이다.</li>
</ul>
<p>좋은 사용자 경험을 제공하기 위해 사이트는 첫 번째 입력 지연이 100ms 이하가 되도록 노력해야 한다. 100ms에서 300ms 사이는 개선이 필요하며, 그 이상은 성능이 좋지 않은 것으로 판단하게 된다.
연결과 장치가 빨라짐에 따라 사용자들의 인내심과 관심은 짧아졌다. 사용자의 상호작용에 대한 즉각적인 피드백을 제공하는 것은 사용자의 관심을 유지하는 데 중요하다. 대부분의 상호작용 문제는 페이지 로드 중에 발생한다. 초기에 모든 문제를 제거함으로써 웹사이트 전반적으로 개선을 할 수 있다.</p>
<ul>
<li><p>양호 – 페이지가 100ms 미만인 경우 페이지의 FID 점수가 매우 높다.</p>
</li>
<li><p>개선 필요 – 페이지 점수가 100ms를 초과하고 300ms 미만인 경우 FID 점수를 개선해야 한다.</p>
</li>
<li><p>나쁨 – FID 점수가 300ms보다 길면 성능이 좋지 않은 것으로 간주된다.</p>
<h2 id="cls-cumulative-layout-shift">CLS (Cumulative Layout Shift)</h2>
<p>CLS는 페이지가 로드될 때 페이지가 얼마나 안정적인지 측정하고 &#39;<strong>시각적 안정성</strong>&#39;을 이루고자 한다. <strong>CLS는 모바일 중심의 지표</strong>다. 데스크톱 웹사이트에서는 화면 크기 문제가 대부분 존재하지 않기 때문이다.
CLS는 특히 페이지 요소가 화면에서 얼마나 자주 이동하는지 측정한다. 이로 인해 사용자가 작은 모바일 화면의 콘텐츠와 상호작용 하기가 더 어려워진다. 화면 요소가 이동하는 일반적인 원인으로는 사용자가 이미 페이지 콘텐츠와 상호작용하고 탐색하기 시작한 후에 렌더링되는 광고, 이미지 및 기타 레이아웃 요소가 있다.
따라서 <strong>페이지가 로드될 때 페이지의 요소가 너무 많이 움직인다면 CLS가 높다는 것</strong>이므로 개선이 필요하다. 이 때, CLS 점수 또한 세 가지 범주로 나뉜다.</p>
</li>
<li><p>좋음 – 페이지의 CLS 점수가 0.1 미만이면 좋다.</p>
</li>
<li><p>개선 필요 – 페이지의 CLS 점수가 0.25 미만, 0.1 이상인 경우 개선이 필요하다.</p>
</li>
<li><p>나쁨 – 페이지의 CLS 점수가 0.25 이상으로 낮으면 많은 작업이 필요하다.</p>
</li>
</ul>
<hr>
<h1 id="코어-웹-바이탈-측정-방법">코어 웹 바이탈 측정 방법</h1>
<h2 id="chrome-ux-보고서crux">Chrome UX 보고서(CrUX)</h2>
<p>Chrome 사용자가 보고한 필드 데이터를 제공하여 사이트 소유자에게 실제 사용자가 웹 사이트를 어떻게 경험하는지에 대한 데이터를 제공한다.</p>
<h2 id="google-lighthouse">Google Lighthouse</h2>
<p>코어 웹 바이탈에 대한 실험실 지표를 제공하는 무료 도구다. 성능, SEO, 접근성 등을 개선하기 위한 실행 가능한 통찰력을 제공한다.</p>
<h2 id="google-pagespeed-insights">Google PageSpeed Insights</h2>
<p>CrUX와 Lighthouse의 기능을 결합하여 CWV 및 기타 웹 바이탈에 대한 현장 및 실험실 데이터를 모두 제공한다. 사용자는 PageSpeed Insights를 사용하여 웹 사이트 소유 여부에 관계없이 웹 사이트의 성능을 확인할 수 있다.</p>
<h2 id="추가-측정-도구">추가 측정 도구</h2>
<p><strong>Web Vitals Chrome Extension:</strong> 실시간으로 코어 웹 바이탈을 측정할 수 있는 브라우저 확장 프로그램
<strong>Search Console:</strong> Google Search Console에서 코어 웹 바이탈 보고서를 통해 사이트 전반의 성능을 모니터링할 수 있다
<strong>GTmetrix, Pingdom:</strong> 서드파티 성능 측정 도구들도 코어 웹 바이탈 지표를 제공한다</p>
<hr>
<h1 id="코어-웹-바이탈-개선-방법">코어 웹 바이탈 개선 방법</h1>
<h2 id="lcp-개선-방법">LCP 개선 방법</h2>
<ul>
<li><strong>이미지 최적화:</strong> WebP, AVIF 포맷 사용, 적절한 크기로 리사이징</li>
<li>*<em>Critical CSS: *</em>Above-the-fold 콘텐츠의 CSS를 인라인으로 포함</li>
<li><strong>리소스 우선순위:</strong> 중요한 리소스에 preload 사용</li>
<li><strong>서버 응답 시간 단축:</strong> CDN 사용, 서버 최적화<h2 id="inp-개선-방법">INP 개선 방법</h2>
</li>
<li><strong>JavaScript 최적화:</strong> 불필요한 JavaScript 제거, 코드 스플리팅</li>
<li><strong>이벤트 핸들러 최적화:</strong> 디바운싱, 쓰로틀링 적용</li>
<li><strong>메인 스레드 블로킹 방지:</strong> Web Workers 활용</li>
<li><strong>Third-party 스크립트 최적화:</strong> 비동기 로딩, 지연 로딩<h2 id="cls-개선-방법">CLS 개선 방법</h2>
</li>
<li><strong>이미지/비디오 크기 지정:</strong> width, height 속성 명시</li>
<li><strong>폰트 로딩 최적화:</strong> font-display: swap 사용</li>
<li><strong>동적 콘텐츠 예약 공간:</strong> 광고, 임베드 콘텐츠를 위한 공간 미리 확보</li>
<li><strong>CSS 애니메이션 최적화:</strong> transform, opacity 속성 사용</li>
</ul>
<hr>
<h1 id="2024년-업데이트-사항">2024년 업데이트 사항</h1>
<h2 id="inp-도입">INP 도입</h2>
<p>2024년 3월부터 FID가 INP(Interaction to Next Paint)로 완전히 교체되었다. INP는 모든 상호작용을 측정하여 더 정확한 사용자 경험을 평가한다.</p>
<h2 id="새로운-지표들">새로운 지표들</h2>
<ul>
<li><strong>TTFB (Time to First Byte):</strong> 서버 응답 시간</li>
<li><strong>FCP (First Contentful Paint):</strong> 첫 번째 콘텐츠 렌더링 시간</li>
<li><strong>SI (Speed Index):</strong> 페이지 로딩 속도 지표</li>
</ul>
<h2 id="모바일-우선-접근">모바일 우선 접근</h2>
<p>모바일 사용자가 데스크톱 사용자를 초과하는 현실을 반영하여, 모든 코어 웹 바이탈 지표가 모바일 환경을 우선적으로 고려한다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>코어 웹 바이탈은 단순한 성능 지표를 넘어 사용자 경험의 핵심을 측정하는 중요한 도구다. 2024년 INP 도입으로 더욱 정확한 상호작용 측정이 가능해졌으며, 지속적인 모니터링과 개선을 통해 사용자 만족도와 검색 엔진 순위를 동시에 향상시킬 수 있다.
웹 개발자와 마케터는 이러한 지표들을 정기적으로 측정하고 개선하여, 사용자에게 최고의 경험을 제공하는 웹사이트를 구축해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker Multi-stage builds(멀티 스테이지 빌드)]]></title>
            <link>https://velog.io/@watermincho_96/Docker-Multi-stage-builds%EB%A9%80%ED%8B%B0-%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%80-%EB%B9%8C%EB%93%9C</link>
            <guid>https://velog.io/@watermincho_96/Docker-Multi-stage-builds%EB%A9%80%ED%8B%B0-%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%80-%EB%B9%8C%EB%93%9C</guid>
            <pubDate>Sun, 23 Feb 2025 06:34:50 GMT</pubDate>
            <description><![CDATA[<h1 id="멀티-스테이지-빌드란">멀티 스테이지 빌드란?</h1>
<p>Multi-stage build 방식은 <u>Docker 17.05</u> 버전에 도입된 기능으로, Dokcerfile 내에서 <strong>여러 개의 빌드 단계를 정의</strong>하여 이미지를 생성하는 방법.
애플리케이션 실행에 필요한 최소한의 구성만 포함하여 이미지 크기를 최소화할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/769e3c30-6658-42ff-ae72-68a43044fe8b/image.png" alt=""></p>
<hr>
<h1 id="왜-사용할까">왜 사용할까?</h1>
<ol>
<li>위의 설명처럼 최소한의 구성만 포함하여 이미지 크기를 최소화할 수 있기 떄문에 Docker 이미지 크기 최소화의 장점이 있다.</li>
<li>캐싱 기능을 지원하므로 소스 코드나 종속성이 변경되지 않은 경우 이를 재사용할 수 있다. -&gt; 빌드 속도가 빨라지고 개발 주기가 단축될 수 있다.</li>
</ol>
<hr>
<h1 id="mono-repo환경일때의-cicd파이프라인-및-배포-시나리오">Mono Repo환경일때의 CI/CD파이프라인 및 배포 시나리오</h1>
<ol>
<li>특정 브랜치에 Git Push -&gt; 변경 감지</li>
<li>CI/CD 스크립트로 어떤 app이 변경됐는지 감지하는 로직 정의</li>
<li>apps/~ 의 각 어플리케이션의 변경이 있다면 해당 어플리케이션만 개별적으로 배포 가능</li>
<li>공유 패키지 변경 시 영향받는 모든 앱 재배포.</li>
</ol>
<hr>
<h1 id="스테이지단계별-dockerfile-예시">스테이지(단계)별 Dockerfile 예시</h1>
<pre><code class="language-docker">FROM node:20.11.0-alpine3.19 AS base

FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update

WORKDIR /app
RUN npm install -g turbo
COPY . .
RUN turbo prune --scope=web --docker

FROM base AS installer
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app

COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN corepack enable
RUN pnpm install

COPY --from=builder /app/out/full/ .
RUN pnpm dlx turbo run build --filter=web

FROM base AS runner
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=installer /app/apps/web/next.config.mjs .
COPY --from=installer /app/apps/web/package.json .

COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public

CMD node apps/web/server.js</code></pre>
<h2 id="base-stage">base stage</h2>
<pre><code class="language-docker">FROM node:20.11.0-alpine3.19 AS base</code></pre>
<ul>
<li>사용할 node버전 명시 및 alpine(최소 단위의 Linux 이미지) 정의</li>
</ul>
<h2 id="builder-stage">builder stage</h2>
<pre><code class="language-docker">FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update

WORKDIR /app
RUN npm install -g turbo
COPY . .
RUN turbo prune --scope=web --docker</code></pre>
<ol>
<li><strong>Run apk add --no-cache libc6-compat</strong> 를 수행하여 Node.js process.dlopen 사용에 필요한 공유 라이브러리가 alpine 버전에 없는 문제를 해결함.</li>
<li>turbo를 글로벌로 설치</li>
<li><strong>turbo prune</strong>으로 빌드할 애플리케이션과 그 종속성만 포함하도록 가지치기하여 빌드 최적화</li>
</ol>
<h2 id="installer-stage">installer stage</h2>
<pre><code class="language-docker">FROM base AS installer
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app

COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN corepack enable
RUN pnpm install

COPY --from=builder /app/out/full/ .
RUN pnpm dlx turbo run build --filter=web
</code></pre>
<ol>
<li>builder 스테이지에서 turbo prune으로 생성된 out 폴더에 있는 파일들을 복사</li>
<li>corepack을 활성화 하여 package.json에 명시된 패키지 매니저 사용하도록 적용</li>
<li>pnpm install로 종속성 설치</li>
<li>builder 스테이지에 web 애플리케이션의 전체 소스 복사</li>
<li>turbo로 web 애플리케이션 빌드.</li>
</ol>
<ul>
<li><strong>*중요</strong> 이때, next.config에 output: &quot;standalone&quot; 옵션을 사용하여 프로덕션 배포에만 필요한 파일만 생성하도록 함.</li>
<li>참고 링크 → <a href="https://nextjs-ko.org/docs/pages/api-reference/next-config-js/output">next-config-output</a></li>
</ul>
<h2 id="runner-stage">runner stage</h2>
<pre><code class="language-docker">FROM base AS runner
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=installer /app/apps/web/next.config.mjs .
COPY --from=installer /app/apps/web/package.json .

COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public

CMD node apps/web/server.js</code></pre>
<ol>
<li>시스템 group, user를 생성하고 nextjs 사용자로 전환하여 실행.</li>
</ol>
<ul>
<li>왜 group, user를 생성하고 nextjs 사용자로 전환해서 실행하는가? -&gt; 보안강화. 컨테이너 내부에서 애플리케이션이 루트 권한으로 실행되면 애플리케이션의 취약점을 악용하여 시스템 전체에 악영향이 끼치는 것을 방지하기 위함</li>
</ul>
<ol start="2">
<li>installer 스테이지에서 next 설정 파일과 package.json을 현재 스테이지로 복사.</li>
<li>installer 스테이지에서 빌드 결과물, 정적 파일, public 폴더를 복사한다. 이때 소유자를 nextjs:nodejs로 설정.</li>
<li>output: standalone 옵션을 사용하여 빌드했기 때문에 이때 복사되는 파일들은 프로덕션에만 필요한 파일들임.</li>
<li>애플리케이션 실행</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native에서 UI렌더링되는 과정]]></title>
            <link>https://velog.io/@watermincho_96/React-Native%EC%97%90%EC%84%9C-UI%EB%A0%8C%EB%8D%94%EB%A7%81%EB%90%98%EB%8A%94-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@watermincho_96/React-Native%EC%97%90%EC%84%9C-UI%EB%A0%8C%EB%8D%94%EB%A7%81%EB%90%98%EB%8A%94-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Thu, 12 Dec 2024 07:55:22 GMT</pubDate>
            <description><![CDATA[<h1 id="렌더링-과정">렌더링 과정</h1>
<ol>
<li>JavaScript 번들 실행 (JavaScript Thread)</li>
<li><strong>앱이 시작</strong>되면 JavaScript 번들이 JavaScript 스레드에서 실행<ul>
<li>이 스레드는 React 컴포넌트 로직을 실행하고 UI 구조 정의</li>
</ul>
</li>
<li>React Element Tree 생성 (JavaScript Thread)<ul>
<li>React Element Tree는 UI 구조를 표현하는 JavaScript 객체 트리.</li>
<li>React 컴포넌트의 타입, props, 자식 요소 정보를 포함</li>
</ul>
</li>
<li>React는 컴포넌트 로직을 실행하여 React Element Tree 생성<ul>
<li>React Element Tree는 순수한 JavaScript 객체로, UI의 구조와 속성 표현</li>
<li>이 트리는 React 컴포넌트의 계층 구조를 나타내며, 각 노드는 컴포넌트의 타입, props, 자식 요소 등의 정보 포함.</li>
</ul>
</li>
<li>React <strong>Shadow Tree</strong> 생성 (Shadow Thread)<ul>
<li>React Element Tree를 기반으로 React Shadow Tree가 생성</li>
<li>React Shadow Tree는 레이아웃 계산을 위한 <strong>중간 표현</strong>으로, C++로 구현.</li>
<li>각 Shadow Node는 React 컴포넌트에 대응하며, 레이아웃 관련 정보를 포함.</li>
<li>Shadow Tree는 JavaScript와 네이티브 플랫폼 사이의 브릿지 역할을 하며, 효율적인 레이아웃 계산을 가능하게 함.</li>
</ul>
</li>
<li><strong>레이아웃 계산</strong> (Shadow Thread)<ul>
<li><strong>Yoga</strong> 레이아웃 엔진을 사용하여 React Shadow Tree의 각 노드에 대한 레이아웃을 계산</li>
<li>이 과정에서 각 컴포넌트의 크기와 위치 결정</li>
</ul>
</li>
<li>네이티브 UI 업데이트 명령 생성 (Shadow Thread)<ul>
<li>레이아웃 계산 결과를 바탕으로 네이티브 UI를 업데이트하기 위한 명령 생성.</li>
</ul>
</li>
<li><strong>네이티브 UI 업데이트</strong> (Main/UI Thread)<ul>
<li>생성된 업데이트 명령이 <strong>Main/UI 스레드로 전달.</strong></li>
<li>이 스레드에서 <strong>실제 네이티브 UI 컴포넌트가 생성되거나 업데이트.</strong></li>
</ul>
</li>
<li>화면에 <strong>렌더링</strong> (Main/UI Thread)<ul>
<li>최종적으로 업데이트된 네이티브 UI 컴포넌트가 화면에 렌더링.</li>
</ul>
</li>
</ol>
<hr>
<h1 id="주요-개념-요약">주요 개념 요약</h1>
<h2 id="javascript-thread">JavaScript Thread</h2>
<p>React Native 앱의 <u>JavaScript 코드가 실행되는 스레드.</u> React 컴포넌트 로직과 상태 관리가 이루어짐.</p>
<h2 id="shadow-thread">Shadow Thread</h2>
<p><u>레이아웃 계산</u>과 <u>UI 업데이트 준비를 담당하는 백그라운드 스레드.</u> React Shadow Tree를 생성/관리.</p>
<h2 id="mainui-thread">Main/UI Thread</h2>
<p><u>실제 네이티브 UI 컴포넌트를 생성하고 화면에 렌더링하는 스레드.</u> 사용자 상호작용도 이 스레드에서 처리.</p>
<h2 id="react-element-tree">React Element Tree</h2>
<p>React 컴포넌트의 렌더링 결과를 나타내는 <u>가벼운 JavaScript 객체 트리.</u> 각 요소는 컴포넌트의 타입, props, 자식 요소 등을 포함. 이 트리는 UI의 구조를 설명하지만 실제 DOM이나 네이티브 뷰와는 직접 연결되지 않음.</p>
<h2 id="react-shadow-tree">React Shadow Tree</h2>
<p><u>UI의 구조와 레이아웃 정보를 담고 있는 <strong>중간 표현</strong></u>. JavaScript와 네이티브 플랫폼 사이의 <strong>브릿지 역할</strong>을 한다. C++로 구현되어 있어 빠른 레이아웃 계산이 가능. Shadow Tree는 React Element Tree의 각 요소에 대응하는 노드를 가지며, 이 노드들은 레이아웃 계산에 필요한 추가 정보 포함.</p>
<h2 id="yoga-레이아웃-엔진">Yoga 레이아웃 엔진</h2>
<p>Facebook에서 개발한 크로스 플랫폼 레이아웃 엔진으로, <strong>Flexbox 스타일의 레이아웃 계산</strong> 수행.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[그놈의 import와 require차이]]></title>
            <link>https://velog.io/@watermincho_96/%EA%B7%B8%EB%86%88%EC%9D%98-import%EC%99%80-require%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@watermincho_96/%EA%B7%B8%EB%86%88%EC%9D%98-import%EC%99%80-require%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Tue, 03 Sep 2024 08:10:18 GMT</pubDate>
            <description><![CDATA[<p><code>프론트엔드 면접 질문</code>에서 지긋지긋하게 듣는 질문 중 하나다.</p>
<h1 id="왜-물어볼까">왜 물어볼까?</h1>
<p>온전한 내 생각을 나열해보겠다.</p>
<blockquote>
<ol>
<li>모듈 시스템에 대한 이해도 평가 -&gt; 코드 구조화와 관리 능력을 가늠하는 지표</li>
<li>프로젝트 설정 및 빌드 도구 경험 -&gt; import와 require는 각각 다른 환경과 도구설정이 요구된다. 해당 예시는 프로젝트에 대한 설정 경험에 대한 지표.</li>
<li>성능 최적화 인식 -&gt; 트리 쉐이킹 가능 여부 등 성능 최적화에 대한 지원자의 인식을 평가할 수 있다.</li>
<li>비동기 프로그래밍 이해 -&gt; import 문의 동적 임포트 기능은 비동기 프로그래밍 개념과 연관됨.</li>
<li>생태계 파악에 얼마나 민감한지 확인 가능</li>
</ol>
</blockquote>
<p>위의 흐름대로 보면 어떤 꼬리 질문이 나오는지 어느정도 가늠이 된다.</p>
<p><code>require</code>와 <code>import</code>는 모두 <code>JavaScript</code>에서 모듈을 불러오는 데 사용되는 방식이지만, 몇 가지 중요한 차이점이 있다.</p>
<p>내가 알고 싶은 것만 조금 길게 쓰고 그 외의 것들은 요약해서 정리해봤으니 도움이 된다면 좋겠다.</p>
<hr>
<h1 id="1-문법과-사용-방식">1. 문법과 사용 방식</h1>
<h2 id="require">require</h2>
<p><code>CommonJS</code> 모듈 시스템에서 사용된다.
<strong>함수 호출 형태</strong>로 사용된다: <code>const module = require(&#39;module-name&#39;);</code>
<u>파일의 어느 위치에서든 사용할 수 있다.</u></p>
<p>import:</p>
<p><code>ES6(ES2015)</code> 모듈 시스템에서 사용된다.
<strong>선언적 형태</strong>로 사용된다: <code>import module from &#39;module-name&#39;;</code>
<u>파일의 최상단에서만 사용할 수 있다.</u></p>
<h1 id="2-동적-vs-정적">2. 동적 vs 정적</h1>
<h2 id="require-1">require</h2>
<p>동적으로 모듈을 불러올 수 있다. 즉, 조건문 내에서 사용하거나 변수를 통해 모듈 이름을 지정할 수 있다.</p>
<pre><code class="language-typescript">if (condition) { 
  const module = require(&#39;module-name&#39;); 
}</code></pre>
<h2 id="import">import</h2>
<p>정적으로 모듈을 불러올 수 있다.(핵심) 컴파일 시점에 모듈 의존성이 결정된다.
동적 임포트를 위해서는 <code>import()</code> 함수를 사용해야 한다. -&gt; 비동기 패턴</p>
<pre><code class="language-typescript">&lt;script async type=&quot;module&quot;&gt;
  import {counter} from &#39;./test.js&#39;;

  counter.count();
&lt;/script&gt;</code></pre>
<h1 id="3-실행-시점">3. 실행 시점</h1>
<h2 id="require-2">require</h2>
<p>런타임에 동기적으로 실행된다.
모듈이 필요한 시점에 로드되고 실행된다.</p>
<h2 id="import-1">import</h2>
<p>파일의 시작 부분에서 실행되며, 모든 <code>import문이 실행된 후에 모듈 본문이 실행</code>된다.
이를 통해 더 효율적인 정적 분석과 <code>트리 쉐이킹(tree shaking)이 가능</code>하다.</p>
<h1 id="4-모듈-로딩">4. 모듈 로딩</h1>
<h2 id="require-3">require</h2>
<p>모듈 전체를 로드</p>
<h2 id="import-2">import</h2>
<p>필요한 부분만 선택적으로 로드</p>
<pre><code class="language-typescript">import { specificFunction } from &#39;module-name&#39;;</code></pre>
<h1 id="5-캐싱">5. 캐싱:</h1>
<h2 id="require-4">require</h2>
<p>모듈을 처음 불러올 때 캐시하고, 이후 호출에서는 캐시된 버전을 반환</p>
<h2 id="import-3">import</h2>
<p>모듈은 한 번만 평가되며, 여러 번 import해도 같은 인스턴스 공유</p>
<h1 id="6-비동기-로딩">6. 비동기 로딩</h1>
<h2 id="require-5">require</h2>
<p>기본적으로 동기적. 비동기 로딩을 위해서는 별도의 방법을 사용해야함.</p>
<h2 id="import-4">import</h2>
<p>import() 함수를 사용하여 동적으로 모듈을 비동기적으로 로드할 수 있음.</p>
<h1 id="7-브라우저-지원">7. 브라우저 지원</h1>
<h2 id="require-6">require</h2>
<p>브라우저에서 직접 지원되지 않는다. 그래서 <code>Browserify</code>나 <code>Webpack</code>이 필요한거다.</p>
<h2 id="import-5">import</h2>
<p>최신 브라우저에서 네이티브로 지원함 (type=&quot;module&quot; 스크립트 태그 사용).</p>
<h1 id="8-순환-의존성-처리">8. 순환 의존성 처리</h1>
<h2 id="require-7">require</h2>
<p>순환 의존성을 허용하지만, 부분적으로 초기화되지 않은 객체를 반환할 수 있다.</p>
<h2 id="import-6">import</h2>
<p>순환 의존성을 더 잘 처리한다. 모듈이 완전히 평가되기 전에 바인딩 제공.</p>
<h1 id="9-typescript와의-통합">9. TypeScript와의 통합</h1>
<h2 id="require-8">require</h2>
<p>개인적으로 조금 까다롭다. TypeScript에서 사용할 수 있지만, 타입 정보를 자동으로 가져오지 않기 때문이다.</p>
<h2 id="import-7">import</h2>
<p>TypeScript와 더 잘 통합되며, 모듈의 타입 정보를 자동으로 가져온다.</p>
<hr>
<h1 id="한-단계-더">한 단계 더</h1>
<p>위 차이점을 천천히 읽다 보면 어떠한 키포인트를 얻어갈 수 있을 것이다.
미리 파악했으면 이 포스팅을 더이상 읽을 필요가 없다. 뒤로가기 눌러라.</p>
<p>바로 </p>
<h2 id="메모리의-관점에서-본다면">메모리의 관점에서 본다면!</h2>
<p><code>두괄식으로 풀자면 import가 require보다 메모리를 덜 사용할 수 있다</code>는게 결론이다.
이유는 아래와 같다. 매우 많지만 핵심 몇가지만 나열한다.</p>
<p>*<em>1. 정적분석이 가능하면 코드가 실행되기 전에 그 모듈의 어떤 부분이 사용될 지 미리 알 수 있다. 위에 핵심이라고 마킹한 문장이 핵심이다. *</em>
-&gt; 즉, 이를 통해 불필요한 코드를 제거하는 트리쉐이킹이 가능해져, 결과적으로 번들 크기가 줄어들고 메모리 사용량이 감소한다.
*<em>2. import로 가져온 모듈은 한 번만 평가된다. 여러 곳에서 같은 모듈을 import해도 단일 인스턴스만 생성된다. *</em>
-&gt; 중복된 모듈 인스턴스로 인한 메모리 낭비를 방지한다.
*<em>3. import()를 사용한 동적 import는 필요한 시점에 모듈을 비동기적으로 로드할 수 있게 해준다 *</em>
-&gt; 초기 로딩 시 메모리 사용량을 줄이고, 필요할 때만 메모리를 사용하게 해준다.</p>
<hr>
<h1 id="팁">팁</h1>
<h2 id="commonjs-방식으로-사용시-주의-사항">[CommonJs 방식으로 사용시 주의 사항]</h2>
<p>CommonJs 방식으로 모듈을 내보낼때는 ES6처럼 명시적으로 선언하는 것이 아니라 특정 변수나 그 변수의 속성으로 내보낼 객체를 세팅해줘야 한다.특히, 제일 햇갈리는 부분이 바로 유사해 보이는 <code>export</code> 변수와 <code>module.exports</code> 변수를 상황에 맞게 잘 사용해야 한다는 점이다</p>
<blockquote>
<p>2가지 규칙만 기억하면 된다.</p>
</blockquote>
<ol>
<li>여러개의 객체를 내보낼 경우 → export.변수 의 개별 속성으로 할당</li>
<li>딱 하나의 객체를 내보낼 경우 → module.exports = 객체 자체에 할당</li>
</ol>
<hr>
<h1 id="환경에-따른-시스템-처리">환경에 따른 시스템 처리</h1>
<p>import와 require는 개발 환경에 따라 사용 방식과 지원 범위에 차이가 있다. </p>
<h2 id="nodejs-환경">Node.js 환경</h2>
<h3 id="import-8">import</h3>
<p><code>Node.js 13.2.0</code> 버전부터 실험적으로 지원되기 시작했다.
<code>.mjs</code> 확장자를 사용하거나 <code>package.json에 &quot;type&quot;: &quot;module&quot;</code>을 설정해야함.</p>
<h3 id="require-9">require</h3>
<p>Node.js의 기본 모듈 시스템으로, 모든 버전에서 지원된다.</p>
<h2 id="typescript">TypeScript</h2>
<h3 id="import-9">import</h3>
<p>기본적으로 지원되며, 타입 정보도 함께 가져올 수 있다.</p>
<h3 id="require-10">require</h3>
<p>지원되지만, 타입 정보를 자동으로 가져오지 않는다.</p>
<h2 id="babel">Babel</h2>
<h3 id="import-10">import</h3>
<p>ES6+ 코드를 변환할 때 자동으로 지원함</p>
<h3 id="require-11">require</h3>
<p>CommonJS 모듈 시스템을 사용하는 코드로 변환할 수 있다.</p>
<h2 id="webpack">Webpack</h2>
<p>import와 require 모두 지원. <code>트리 쉐이킹</code>은 import 문을 사용할 때만 가능.</p>
<h2 id="react-native---내-주력">React Native -&gt; 내 주력</h2>
<p>import와 require 모두 사용 가능. 최신 버전에서는 import 사용을 권장.</p>
<h2 id="esm-ecmascript-modules-in-nodejs">ESM (ECMAScript Modules) in Node.js</h2>
<p>import를 사용. 파일 확장자를 명시적으로 지정해야 함.</p>
<h2 id="모노레포-환경">모노레포 환경</h2>
<p>import와 require 모두 사용 가능하지만, 설정에 따라 다를 수 있다. 일관성을 위해 하나의 방식을 선택하는 것이 일반적.</p>
<h2 id="서버리스-환경-예-aws-lambda">서버리스 환경 (예: AWS Lambda):</h2>
<p>대부분 Node.js 기반이므로 require를 주로 사용. 최신 환경에서는 import도 사용 가능.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[javascript 이벤트루프]]></title>
            <link>https://velog.io/@watermincho_96/javascript-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A3%A8%ED%94%84</link>
            <guid>https://velog.io/@watermincho_96/javascript-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A3%A8%ED%94%84</guid>
            <pubDate>Thu, 01 Aug 2024 08:05:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.devh.kr/posts/2021-11-24-JavaScript-Visualized-Event-Loop">https://www.devh.kr/posts/2021-11-24-JavaScript-Visualized-Event-Loop</a> 를 참고하여 작성한 글입니다.</p>
</blockquote>
<p><code>이벤트 루프(event loop)</code>가 무엇이며 왜 신경을 써야 할까?</p>
<p>JavaScript는 <code>싱글 스레드</code>다(single-threaded): 한 번에 하나의 작업만 실행할 수 있다. 일반적으로 큰 문제가 아니지만 30초가 걸리는 작업을 실행하고 있다고 상상해보자. 그 작업 동안 우리는 다른 일이 일어날 때까지 30초 동안 기다리고 있다 <u>JavaScript는 기본적으로 브라우저의 메인 스레드에서 실행되기에 전체 UI가 멈춘다</u></p>
<p>다행히 브라우저는 <code>JavaScript 엔진</code> 자체에서 제공하지 않는 몇 가지 기능인 <code>Web API</code>를 제공한다. 여기에는 <code>DOM API</code>, <code>setTimeout HTTP 요청</code> 등 이 포함 된다. 이것은 <code>async(비동기)</code>, <code>non-blocking(비차단)</code> 동작을 만드는 데 도움이 될 수 있다. 🚀</p>
<p>함수를 호출하면 <code>콜스택(call stack:호출스택)</code>이라는 것에 추가된다. 콜스택은 JS 엔진의 일부이며 브라우저에 따라 다르다. 이것은 <code>스택</code>이다. 즉, 먼저 들어간 것이 후에 나오는 것(first in, last out)이다. 함수가 값을 반환하면 해당 함수가 스택에서 꺼낸다(popped off). 👋</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/57909c2e-7fcd-41c7-b0b3-eb5ba7b98fb0/image.gif" alt=""></p>
<p><code>respond</code> 함수는 <code>setTimeout</code> 함수를 반환한다. <code>setTimeout</code>은 웹 API에 의해 우리에게 제공된다: 메인 스레드를 차단하지 않고 작업을 지연시킬 수 있다. <code>setTimeout</code> 함수에 전달한 콜백 함수, 화살표 함수 () =&gt; { return &#39;Hey&#39; }가 <code>Web API</code>에 추가된다. 그러는 동안 <code>setTimeout</code>함수와 <code>respond</code> 함수가 스택에서 꺼내지면서(popped off) 둘 다 값을 반환한다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/a2706c9a-574c-4716-883b-8b12b4078113/image.gif" alt=""></p>
<p><code>Web API</code>에서 타이머는 우리가 전달한 두 번째 인수인 1000ms 동안 실행된다. 콜백은 즉시 콜스택에 추가되지 않고 <code>큐(queue)</code>라고 하는 것으로 전달된다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/2ec09533-428f-42b9-9c83-ed6425b71f7d/image.gif" alt=""></p>
<p>혼란스러운 부분일 수 있다: <code>1000ms</code> 후에 콜백 함수가 콜스택에 추가되어 값을 반환한다는 의미는 아니다! <code>1000ms</code> 후에 단순히 큐에 추가된다. 하지만 이건 <code>큐</code>다. 함수는 차례를 기다려야 한다.</p>
<p>이벤트 루프가 유일한 작업을 수행할 시간: 큐를 콜스택과 연결하는 것이다. 콜스택이 비어 있으면 이전에 호출된 모든 함수가 값을 반환하고 스택에서 꺼낸 경우 큐의 첫 번째 항목 이 콜스택에 추가된다. 이 경우 다른 함수가 호출되지 않았다. 이는 콜백 함수가 큐의 첫 번째 항목이 될 때까지 <code>콜스택</code>이 비어 있었다는 것을 의미한다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/795f176d-496d-4985-8e4d-4c2ecaa6d3e0/image.gif" alt="">
콜백이 호출 스택에 <u>추가되고, 호출되고, 값을 반환하고, 스택에서 꺼낸다</u>.
<img src="https://velog.velcdn.com/images/watermincho_96/post/b042fc8c-2966-48d6-aefa-f87f826ad559/image.gif" alt="">
포스트를 읽는 것도 재미있지만 실제로 계속 반복해서 작업해야만 이 포스트에 완전히 익숙해질 수 있다. 다음을 실행하면 콘솔에 무엇이 로깅되는지 알아보자</p>
<pre><code class="language-javascript">const foo = () =&gt; console.log(&quot;First&quot;);
const bar = () =&gt; setTimeout(() =&gt; console.log(&quot;Second&quot;), 500);
const baz = () =&gt; console.log(&quot;Third&quot;);

bar();
foo();
baz();</code></pre>
<p>브라우저에서 이 코드를 실행할 때 어떤 일이 일어나는지 빠르게 살펴보겠다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/4282cf8d-21bb-46dc-864a-de56383e1e3b/image.gif" alt=""></p>
<ol>
<li>bar 호출. <code>bar</code>는 <code>setTimeout</code> 을 반환</li>
<li>전달한 콜백 <code>setTimeout</code>이 <code>Web API</code>에 추가되고 <code>setTimeout</code>함수 bar가 콜스택에서 out.</li>
<li>타이머가 실행되는 동안 <code>foo</code>가 호출되고 <code>First</code>가 로그. <code>foo(undefined)가 반환</code>되고 <code>baz</code>가 호출되며 <code>콜백이 큐에 추가</code>.</li>
<li>baz 로그 <code>Third</code>. 이벤트 루프는 baz가 반환된 후 콜스택이 비어 있음을 확인한 후에 콜백이 콜스택에 추가.</li>
<li><code>콜백</code> 로그 <code>Second</code>.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[git pull 할 때 fatal: Need to specify how to reconcile divergent branches. 처리 방안]]></title>
            <link>https://velog.io/@watermincho_96/git-pull-%ED%95%A0-%EB%95%8C-fatal-Need-to-specify-how-to-reconcile-divergent-branches.-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%95%88</link>
            <guid>https://velog.io/@watermincho_96/git-pull-%ED%95%A0-%EB%95%8C-fatal-Need-to-specify-how-to-reconcile-divergent-branches.-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%95%88</guid>
            <pubDate>Tue, 23 Jul 2024 07:59:35 GMT</pubDate>
            <description><![CDATA[<p>branch <code>pull</code>을 하지 않은 상태(해당 branch에 MR이 된 후)에서 git <code>commit</code> 후 원격 <code>push</code>를 하게 되면 <code>pull</code>도 안되고 원격 <code>push</code>도 안 되게 된다. (제목과 같은 에러 발생)</p>
<p>병합 전략에 따라 다르겠지만 이 때 해결 방안은 아래와 같다.</p>
<pre><code class="language-shell"># git config pull.rebase false
# git push
--&gt; vi 화면에서 log 작성 후 저장
# git pull</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[next.js 내장 swc  VS esbuild-plugin-babel-next]]></title>
            <link>https://velog.io/@watermincho_96/next.js-%EB%82%B4%EC%9E%A5-swc-vs-esbuild-plugin-babel-next</link>
            <guid>https://velog.io/@watermincho_96/next.js-%EB%82%B4%EC%9E%A5-swc-vs-esbuild-plugin-babel-next</guid>
            <pubDate>Mon, 22 Jul 2024 06:19:20 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>지난 포스트에 <code>esbuild-plugin-babel-next</code>를 적용하여 비교랍시고 자신있게 올렸지만 결론은 제대로 공부하지 못하여 잘못된 정보를 전달하게 됐다.
아래는 해당 게시물의 댓글이다.(한음님 감사합니다)</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/67cf3e41-5898-46f6-ab12-3c5e86f000aa/image.png" alt=""></p>
<h1 id="그래서">그래서!</h1>
<p>실제로 <code>time yarn build</code> 명령어로 직접 몇 차례 빌드하며 시간 비교를 해봤다.(<code>next/bundle-analyzer</code>로 용량 비교를 했지만 변화가 없던 것을 확인)
아래는 기본 SWC를 사용한 build 결과 이력이다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/692baad0-1852-4278-b577-4a8fa09d3955/image.png" alt="">총 세 번의 빌드로 최초엔 21초, 그 뒤엔 19초대(캐시)로 19~21초의 범위를 보였다.</p>
<p>아래는 esbuild-plugin-babel-next로 적용된 build 결과이력이다.</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/647fe8ee-033a-4de6-beec-732f9ac26f53/image.png" alt="">
총 세 번의 빌드로 최초엔 23초, 그 뒤엔 20초대(캐시)로 20~23초의 범위를 보였다.</p>
<h1 id="결론">결론</h1>
<p>범위 편차로 따지면 아무것도 없을 때 <code>10%</code>정도 차이가 있다고 보면 될 것 같다.</p>
<h1 id="회고">회고</h1>
<p>시작부터 모든걸 세팅해버리면 아무것도 하지 못할까봐 완벽주의적 성향을 내려놓고 시작했으나 이번 일로 next.js 생태계를 좀 딥하게 알고 세팅할 필요가 있을 것 같다는 생각이 든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가  esbuild-plugin-babel-next 를 선택한 이유]]></title>
            <link>https://velog.io/@watermincho_96/%EB%82%B4%EA%B0%80-esbuild-plugin-babel-next-%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@watermincho_96/%EB%82%B4%EA%B0%80-esbuild-plugin-babel-next-%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Fri, 19 Jul 2024 06:29:44 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<blockquote>
<p>정정 <a href="https://velog.io/@watermincho_96/next.js-%EB%82%B4%EC%9E%A5-swc-vs-esbuild-plugin-babel-next">게시물</a> 추가. 꼭 봐주세요.</p>
</blockquote>
<p>esbuild를 사용해보고 싶어서 <code>next.js</code>세팅 후 설치를 해봤는데 선택권이 여러 개가 있었다.</p>
<ol>
<li><code>esbuild-loader</code>를 <code>webpack</code>에 세팅하는 법</li>
<li><code>esbuild-plugin-babel-next</code>를 <code>next.config.mjs</code>에 세팅하는 법</li>
</ol>
<p>근데 난 두번째 방향으로 진행했다. 그 이유는 간단하다. 플젝이 커질지 안커질지 모르니까 우선 작고 간편한 방식을 채택한거다.</p>
<p>상세 비교 내용을 아래에 적어놨으니 확인해보자.</p>
<hr>
<p><code>esbuild-loader</code>와 <code>esbuild-plugin-babel-next</code>는 모두 Next.js 프로젝트에서 빌드 속도를 향상시키기 위해 esbuild를 활용하는 도구이지만, 사용 방식, 특징, 그리고 적합한 프로젝트 유형에 있어 차이가 있다.</p>
<h1 id="1-사용-방식-및-통합">1. 사용 방식 및 통합</h1>
<h2 id="esbuild-loader">esbuild-loader</h2>
<h3 id="사용-방식">사용 방식</h3>
<p>주로 <code>Webpack</code> 프로젝트에서 기존 <code>Babel 로더</code>를 <code>대체</code>하여 <u><code>Webpack</code> 빌드 프로세스 내에서 <code>esbuild</code>를 사용</u>하도록 한다.</p>
<h3 id="통합-방식">통합 방식</h3>
<p><code>Webpack</code> 설정 파일에서 <code>esbuild</code> 옵션을 <u>직접</u> 설정해야 한다. 이는 <code>트랜스파일</code>, <code>번들링</code> 및 기타 <code>esbuild</code> 관련 작업에 대한 설정을 <code>수동</code>으로 정의하는 것을 의미한다.</p>
<h2 id="esbuild-plugin-babel-next">esbuild-plugin-babel-next</h2>
<h3 id="사용-방식-1">사용 방식</h3>
<p><code>Next.js</code> 프로젝트에 특화적으로 설계되어 <code>Next.js</code> 빌드 프로세스에 <code>esbuild</code>를 통합한다.</p>
<h3 id="통합-방식-1">통합 방식</h3>
<p><code>Next.js</code> 설정을 크게 변경하지 않고도 <code>esbuild</code>를 활용할 수 있도록 <u><code>Next.js</code> 빌드 프로세스와 매끄럽게 통합</u>된다.</p>
<hr>
<h1 id="2-성능-및-최적화">2. 성능 및 최적화</h1>
<h2 id="esbuild-loader-1">esbuild-loader</h2>
<h3 id="성능">성능</h3>
<p>기존 Babel 로더보다 훨씬 빠른 빌드 속도를 제공하여 성능을 크게 향상시킨다. 이유는 크게... </p>
<ol>
<li>Go 언어가 컴파일 언어기 때문에 실행 자체의 속도가 빠름</li>
<li>병렬프로세스 기능을 사용하여 cpu코어를 효율적으로 활용</li>
<li><a href="https://yceffort.kr/2021/05/ast-for-javascript">AST(추상 구문 트리)</a> 기반 트랜스파일</li>
<li>빌드 캐싱기법</li>
<li>번들 재사용
등이 있다.<h3 id="최적화">최적화</h3>
특정 프로젝트 요구 사항에 맞게 최적의 성능을 달성하기 위해 <code>esbuild</code> 옵션을 수동으로 구성해야 한다.</li>
</ol>
<h2 id="esbuild-plugin-babel-next-1">esbuild-plugin-babel-next</h2>
<h3 id="성능-1">성능</h3>
<p><code>esbuild</code>의 강점을 활용하여 <code>Next.js</code> 애플리케이션에 최적화된 성능 제공.</p>
<h3 id="최적화-1">최적화</h3>
<p><code>Next.js</code> 프로젝트에 특화된 최적화 및 설정을 자동으로 적용하여 <u>수동 미세 조정의 필요성을 줄인다.</u></p>
<hr>
<h1 id="3-타겟-대상-및-적합성">3. 타겟 대상 및 적합성</h1>
<h2 id="esbuild-loader-2">esbuild-loader</h2>
<h3 id="타겟-대상">타겟 대상</h3>
<p>빠른 빌드 속도와 성능 향상을 추구하는 <code>Webpack</code> 프로젝트, 특히 코드 기반이 크거나 의존성이 복잡한 프로젝트.</p>
<h3 id="적합성">적합성</h3>
<p><code>esbuild</code> 옵션에 대한 세밀한 제어와 <code>Webpack</code> 빌드 프로세스 내 <code>통합</code>이 필요한 프로젝트에 적합함.</p>
<h2 id="esbuild-plugin-babel-next-2">esbuild-plugin-babel-next</h2>
<h3 id="타겟-대상-1">타겟 대상</h3>
<p>빠른 프로토타입 제작 및 반복 작업에 중점을 둔 <code>Next.js</code> 프로젝트, 특히 개발 경험의 <code>간소화</code> 및 <code>최적화</code>를 추구하는 프로젝트.</p>
<h3 id="적합성-1">적합성</h3>
<p>광범위한 구성이나 통합 노력 없이 <code>esbuild</code>의 성능 이점을 활용할 수 있는 <code>Next.js</code> 프로젝트에 이상적.</p>
<hr>
<h1 id="올바른-도구-선택">올바른 도구 선택</h1>
<p><code>esbuild-loader</code>와 <code>esbuild-plugin-babel-next</code> 중 어떤 도구를 선택할지는 프로젝트 유형, 개발 목표 및 기술 전문성에 따라 달라진다.</p>
<p>최대 성능과 <code>esbuild</code> 구성에 대한 제어를 원하는 <code>Webpack</code> 프로젝트: <code>esbuild-loader</code></p>
<p><code>Next.js</code> 프로젝트에서 개발 경험을 간소화하고 <code>Next.js</code> 특정 작업에 최적화된 성능을 원하는 경우: <code>esbuild-plugin-babel-next</code></p>
<hr>
<h1 id="추가-고려-사항">추가 고려 사항</h1>
<p>프로젝트 <code>복잡성</code>: 의존성이 복잡하거나 코드 기반이 큰 프로젝트에서는 <code>esbuild-loader</code>의 세밀한 제어가 유용할 수 있다.
개발 워크플로우: 빠른 프로토타입 제작 및 반복 작업이 중요한 경우 <code>esbuild-plugin-babel-next</code>의 간소화된 통합으로 시간과 노력을 절약할 수 있다.</p>
<hr>
<h1 id="구현">구현</h1>
<h2 id="nextconfigmjs">next.config.mjs</h2>
<pre><code class="language-js">import withEsbuild from &quot;esbuild-plugin-babel-next&quot;;
/** @type {import(&#39;next&#39;).NextConfig} */

// Use:
export default withEsbuild({
  esbuild: {
    // esbuild 옵션
    loader: &quot;tsx&quot;, // TypeScript 및 JSX 지원
    target: &quot;es2015&quot;, // 대상 ECMAScript 버전
    minify: true, // 프로덕션 빌드시 코드 최소화
    drop_annotations: true, // 주석 제거
    dro_debugger: true, // 디버거 문 제거
  },
  // 기타 Next.js 옵션
  webpack: (config, { dev, isServer }) =&gt; {
    // 추가적인 webpack 설정
    return config;
  },
});
</code></pre>
<h2 id="종속성-설치">종속성 설치</h2>
<p>참고로 필자는 yarn 사용한다.</p>
<p><code>devDependency</code>로 <code>esbuild-plugin-babel-next add</code></p>
<pre><code>~~ % yarn add --dev esbuild-plugin-babel-next
➤ YN0000: · Yarn 4.3.1
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + esbuild-plugin-babel-next@npm:1.0.0
➤ YN0000: └ Completed
➤ YN0000: ┌ Post-resolution validation
➤ YN0002: │ trade-world@workspace:. doesn&#39;t provide @babel/core (p47c5e), requested by esbuild-plugin-babel-next.
➤ YN0086: │ Some peer dependencies are incorrectly met by your project; run yarn explain peer-requirements &lt;hash&gt; for details, where &lt;hash&gt; is the six-letter p-prefixed code.
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0013: │ A package was added to the project (+ 5.44 KiB).
➤ YN0000: └ Completed in 0s 861ms
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done with warnings in 1s 211ms</code></pre><hr>
<pre><code>~~ % yarn dev
node:internal/modules/esm/resolve:844
  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package &#39;@babel/core&#39; imported from /Users/jominsu/Documents/workspace/trade-world/node_modules/esbuild-plugin-babel-next/src/index.js
    at packageResolve (node:internal/modules/esm/resolve:844:9)
    at moduleResolve (node:internal/modules/esm/resolve:901:20)
    at defaultResolve (node:internal/modules/esm/resolve:1121:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:396:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:365:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:240:38)
    at ModuleWrap.&lt;anonymous&gt; (node:internal/modules/esm/module_job:85:39)
    at link (node:internal/modules/esm/module_job:84:36) {
  code: &#39;ERR_MODULE_NOT_FOUND&#39;
}

Node.js v20.10.0</code></pre><hr>
<p>위처럼 run 돌렸을 때 에러났더니 봤는데 <code>babel core</code> 필요하다 해서 설치</p>
<pre><code>~~ % yarn add @babel/core
➤ YN0000: · Yarn 4.3.1
➤ YN0000: ┌ Resolution step
➤ YN0085: │ + @babel/core@npm:7.24.9, @types/babel__core@npm:7.20.5, @ampproject/remapping@npm:2.3.0, @babel/code-frame@npm:7.24.7, @babel/compat-data@npm:7.24.9, @babel/generator@npm:7.24.10, @babel/helper-compilation-targets@npm:7.24.8, @babel/helper-environment-visitor@npm:7.24.7, and 43 more.
➤ YN0000: └ Completed in 1s 179ms
➤ YN0000: ┌ Fetch step
➤ YN0013: │ 51 packages were added to the project (+ 9.01 MiB).
➤ YN0000: └ Completed in 1s 17ms
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed in 0s 552ms
➤ YN0000: · Done in 2s 827ms
![](https://velog.velcdn.com/images/watermincho_96/post/4b084460-d1fb-4242-b7c0-03518585cb28/image.png)
</code></pre><p>아래와 같이 <code>esbuild-plugin-babel-next</code>의 <code>peerDependency</code>에는 기재가 돼있지만 내가 설치 안해서 발생한거임.
<img src="https://velog.velcdn.com/images/watermincho_96/post/c5060c90-ac7c-4439-b1d8-29869f80998c/image.png" alt="esbuild-plugin-babel-next의 package.json"></p>
<hr>
<h1 id="보일러-플래이트-빌드-속도-차이수정-의미-없습니다">보일러 플래이트 빌드 속도 차이(수정. 의미 없습니다.)</h1>
<p><del>아무것도 얹지 않는 상태에서의 빌드 속도 차이를 수치는 아니지만 비교가 가능하게끔 기록해놓겠다.</del></p>
<p><del>esbuild-plugin-babel-next 적용 전</del></p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/58c73373-9d8d-4021-8b98-a184036bdf94/image.jpg" alt=""></p>
<p><del>esbuild-plugin-babel-next 적용 후</del>
<del><img src="https://velog.velcdn.com/images/watermincho_96/post/e9911d2e-b8b7-4480-b321-5ac3b4ef7f13/image.jpg" alt=""></del></p>
<p><del>우선 내가 <code>next.js</code> 빌드 원리 및 순서를 대강 알고 정확하게 모르지만 확실한건 <code>Ready in</code>에 해당하는 시간이 <code>5분의 1수준</code>으로 감소하고 나머지는 `0.1</del>0.5초`대 소폭 증가한 수준인건 보인다. 체감으로도 금방되는건 느껴진다. 나중에 프로젝트가 커졌을 때의 차이가 과연 클지도 기대가 된다. (이건 내가 반드시 더 파내고 만다.  어쨌든 할거임)~~</p>
<hr>
<h1 id="new-insight">NEW Insight</h1>
<blockquote>
<p>새로운 인사이트를 얻어 esbuild-plugin-babel-next 제거했습니다. 
자세한건 해당 <a href="https://velog.io/@watermincho_96/next.js-%EB%82%B4%EC%9E%A5-swc-vs-esbuild-plugin-babel-next">포스팅</a>을 확인해주세요!</p>
</blockquote>
<h1 id="기술-전문성-및-번외">기술 전문성 및 번외</h1>
<p><code>Webpack</code> 구성 및 <code>esbuild</code> 개념을 정확하게 알 필요가 있다. Next.js가 빌드되는 순서부터 파악해보자. 개념도 모르고 개발하는건 더이상 안된다. 시간이 오래걸리더라도 정확히 알고 개발하자. <del>난 겸손하니까.</del>
번외로, 추후 프로젝트 확장시 <code>esbuild-loader</code>로 마이그레이션할 의지는 있다.</p>
<blockquote>
<p>2024.07.22) 댓글과 같이 기본 swc를 사용하면 어떨까 하여 비교 글을 작성할 예정이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Yarn 이 왜빠른지 궁금해서 쓴 글]]></title>
            <link>https://velog.io/@watermincho_96/Yarn-%EC%9D%B4-%EC%99%9C%EB%B9%A0%EB%A5%B8%EC%A7%80-%EA%B6%81%EA%B8%88%ED%95%B4%EC%84%9C-%EC%93%B4-%EA%B8%80</link>
            <guid>https://velog.io/@watermincho_96/Yarn-%EC%9D%B4-%EC%99%9C%EB%B9%A0%EB%A5%B8%EC%A7%80-%EA%B6%81%EA%B8%88%ED%95%B4%EC%84%9C-%EC%93%B4-%EA%B8%80</guid>
            <pubDate>Fri, 19 Jul 2024 02:34:35 GMT</pubDate>
            <description><![CDATA[<h1 id="1-병렬-설치">1. 병렬 설치</h1>
<h2 id="npm-초기-버전">NPM (초기 버전)</h2>
<p>초기 NPM 버전은 <code>순차적 설치 방식</code>을 사용했다. 
예를 들어, A, B, C 세 개의 패키지를 설치한다고 가정해 보자</p>
<blockquote>
<ol>
<li>A 패키지 다운로드</li>
<li>A 패키지 설치</li>
<li>B 패키지 다운로드</li>
<li>B 패키지 설치</li>
<li>C 패키지 다운로드</li>
<li>C 패키지 설치</li>
</ol>
</blockquote>
<p>이 방식에서는 각 패키지의 설치가 완료될 때까지 다음 패키지의 설치를 시작하지 않는다.</p>
<h2 id="yarn">Yarn</h2>
<p>Yarn은 처음부터 <code>병렬 설치</code>를 지원했다.</p>
<p>같은 세 개의 패키지를 설치할 때</p>
<blockquote>
<ol>
<li>A, B, C 패키지 동시 다운로드 시작</li>
<li>다운로드 완료된 순서대로 설치 시작</li>
</ol>
</blockquote>
<p>이 방식은 네트워크 대역폭을 최대한 활용하고, CPU와 디스크 I/O를 효율적으로 사용한다.
예를 들어, A 패키지가 크고 다운로드에 시간이 오래 걸린다면, 그 동안 B와 C의 다운로드와 설치가 진행될 수 있다.</p>
<h2 id="npm-최신-버전">NPM (최신 버전)</h2>
<p><code>NPM 5</code> 이후 버전에서는 Yarn과 유사한 병렬 설치를 도입했다. 
그러나 초기 구현에서는 Yarn만큼 효율적이지 않았다</p>
<hr>
<h1 id="2-캐싱-메커니즘">2. 캐싱 메커니즘</h1>
<h2 id="yarn-1">Yarn</h2>
<p>Yarn의 캐싱 시스템은 매우 공격적이라고 볼 수 있다.
패키지를 처음 다운로드할 때, <code>전역 캐시</code>에 저장한다. 이 캐시는 버전별로 관리된다.
예를 들어, <code>lodash@4.17.20</code>을 설치한다고 가정해 보자</p>
<blockquote>
<ol>
<li>Yarn은 먼저 <code>로컬 캐시</code> 확인.</li>
<li>캐시에 없다면 다운로드하고 캐시에 저장.</li>
<li>다음에 같은 버전의 lodash가 필요할 때, 네트워크 요청 없이 캐시에서 직접 가져옴.</li>
</ol>
</blockquote>
<p>또한, Yarn은 <code>오프라인 미러링</code>을 지원한다. 이는 모든 필요한 패키지를 로컬에 미리 다운로드해 놓고, 완전히 오프라인 상태에서도 설치할 수 있게 한다.</p>
<h2 id="npm">NPM</h2>
<p>NPM도 <code>캐싱</code>을 지원하지만, 초기 버전에서는 덜 효율적이었다. 
<code>NPM 5</code> 이후 개선되었지만, 여전히 일부 시나리오에서 Yarn보다 덜 효율적일 수 있다.
NPM의 캐시는 <code>패키지 이름</code>과 <code>버전</code>을 기반으로 하는 <code>폴더 구조</code>를 사용한다.
캐시된 패키지를 사용할 때, NPM은 <code>무결성 검사</code>를 수행하는데, 이 과정이 추가적인 시간을 필요로 할 수 있다.</p>
<h1 id="3-잠금-파일-사용">3. 잠금 파일 사용</h1>
<h2 id="yarn-yarnlock">Yarn (yarn.lock)</h2>
<p>Yarn의 잠금 파일은 매우 상세하다.</p>
<ol>
<li>각 패키지의 정확한 버전</li>
<li>의존성 트리</li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%B2%B4%ED%81%AC%EC%84%AC">무결성 체크섬</a></li>
</ol>
<p>등을 포함한다. 이 정보는 매우 구조화되어 있어 빠르게 읽고 처리할 수 있다.</p>
<p>예를 들어, yarn.lock 파일의 일부분은 다음과 같을 수 있다:</p>
<pre><code class="language-json">lodash@^4.17.20:
  version &quot;4.17.20&quot;
  resolved &quot;https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52&quot;
  integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==</code></pre>
<p>이 상세한 정보 덕분에 Yarn은 의존성 트리를 빠르게 재구성하고 설치할 패키지를 정확히 결정할 수 있다.</p>
<h2 id="npm-package-lockjson">NPM (package-lock.json)</h2>
<p><code>NPM 5</code>에서 도입된 package-lock.json도 유사한 기능을 한다. 
그러나 <code>초기 버전</code>에서는 일부 일관성 문제가 있었고, Yarn의 잠금 파일만큼 효율적이지 않았다.</p>
<p>NPM의 잠금 파일 예:</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;lodash&quot;,
  &quot;version&quot;: &quot;4.17.20&quot;,
  &quot;resolved&quot;: &quot;https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz&quot;,
  &quot;integrity&quot;: &quot;sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==&quot;
}</code></pre>
<hr>
<h1 id="4-네트워크-최적화">4. 네트워크 최적화</h1>
<h2 id="yarn-2">Yarn</h2>
<p>Yarn은 네트워크 요청을 매우 효율적으로 관리한다. 요청을 큐에 넣고 우선순위를 지정하며, 동시에 여러 요청을 처리한다. 또한, 네트워크 오류에 대한 재시도 메커니즘이 잘 구현되어 있어, 일시적인 네트워크 문제를 잘 처리한다.</p>
<p>예를 들어, 100개의 패키지를 설치해야 한다면</p>
<blockquote>
<ol>
<li>Yarn은 이를 여러 그룹으로 나눈다 (예: 10개씩 10그룹)</li>
<li>각 그룹을 <code>병렬</code>로 다운로드 시작.</li>
<li><u>다운로드가 완료된 패키지부터</u> 즉시 설치 시작</li>
<li>네트워크 오류가 발생하면 <code>자동</code>으로 재시도.</li>
</ol>
</blockquote>
<h2 id="npm-1">NPM</h2>
<p>초기 NPM 버전은 이러한 최적화가 부족했다. 
각 패키지를 <code>개별적</code>으로 처리하고, 네트워크 오류 처리도 덜 효율적이었다. 
최신 버전에서는 많이 개선되었지만, 여전히 <code>일부 시나리오</code>에서 Yarn보다 덜 효율적일 수 있다.</p>
<hr>
<h1 id="5-알고리즘-최적화">5. 알고리즘 최적화!</h1>
<h2 id="yarn-3">Yarn</h2>
<p>Yarn은 의존성 트리를 구성할 때 매우 효율적인 알고리즘을 사용한다. 이는 <code>결정론적</code> 알고리즘으로, 같은 입력에 대해 항상 같은 출력을 생성한다.</p>
<p>Yarn의 의존성 해결 알고리즘은 다음과 같은 단계로 작동한다.</p>
<h3 id="의존성-그래프-구축">의존성 그래프 구축</h3>
<p>Yarn은 먼저 모든 패키지의 의존성 정보를 수집하여 <a href="https://ko.wikipedia.org/wiki/%EC%9C%A0%ED%96%A5_%EB%B9%84%EC%88%9C%ED%99%98_%EA%B7%B8%EB%9E%98%ED%94%84">방향성 비순환 그래프(DAG)</a>를 구축한다. 각 노드는 패키지를, 엣지는 의존성 관계를 나타낸다.</p>
<h3 id="버전-제약-조건-수집">버전 제약 조건 수집</h3>
<p>각 패키지가 요구하는 의존성의 버전 범위를 수집한다. 예를 들어:</p>
<blockquote>
<p>A 패키지: &quot;C&quot;: &quot;^1.0.0&quot;
B 패키지: &quot;C&quot;: &quot;^2.0.0&quot;</p>
</blockquote>
<h3 id="버전-후보-목록-생성">버전 후보 목록 생성</h3>
<p>각 패키지에 대해 가능한 모든 버전의 목록을 생성한다. 이 때 <code>semver(Semantic Versioning)</code> 규칙을 적용한다</p>
<h3 id="제약-조건-해결">제약 조건 해결</h3>
<p>Yarn은 <a href="https://ko.wikipedia.org/wiki/%EC%B6%A9%EC%A1%B1_%EA%B0%80%EB%8A%A5%EC%84%B1_%EB%AC%B8%EC%A0%9C">SAT(Boolean Satisfiability Problem)</a> solver를 사용하여 모든 버전 제약 조건을 동시에 만족시키는 조합을 찾는다.</p>
<blockquote>
<p>a) 각 패키지 버전을 boolean 변수로 변환
b) 버전 제약 조건을 논리식으로 표현
c) SAT solver를 사용하여 모든 조건을 만족하는 해를 찾음</p>
</blockquote>
<h3 id="최적화">최적화</h3>
<p>여러 가능한 해결책 중에서 Yarn은 다음 기준을 사용하여 최적의 해결책을 선택한다.</p>
<blockquote>
<p>a) 최신 버전 선호
b) 중복 설치 최소화
c) breaking changes 가능성 최소화</p>
</blockquote>
<h3 id="잠금-파일-생성">잠금 파일 생성</h3>
<p><a href="#3-%EC%9E%A0%EA%B8%88-%ED%8C%8C%EC%9D%BC-%EC%82%AC%EC%9A%A9">3. 잠금파일 사용</a>과 중복되는 내용이지만 알고리즘 관점으로 본다면 최종 결정된 의존성 트리를 <code>yarn.lock</code> 파일에 기록한다. 이 파일은 <code>정확한 버전 정보</code>와 <code>무결성 해시</code>를 포함한다.</p>
<p>구체적인 예시</p>
<p>패키지 <code>A</code>와 <code>B</code>가 모두 <code>C</code>에 의존하지만 다른 버전을 요구하는 경우:</p>
<pre><code>A@1.0.0
└── C@^1.0.0

B@1.0.0
└── C@^2.0.0</code></pre><blockquote>
<ol>
<li>Yarn이 의존성 그래프 구축.</li>
<li><code>C</code>의 가능한 모든 버전(예: 1.0.0, 1.1.0, 2.0.0, 2.1.0) 나열.</li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%B6%A9%EC%A1%B1_%EA%B0%80%EB%8A%A5%EC%84%B1_%EB%AC%B8%EC%A0%9C">SAT solver</a>를 사용하여 다음 논리식을 해결: (<a href="mailto:C@1.0.0">C@1.0.0</a> OR <a href="mailto:C@1.1.0">C@1.1.0</a>) AND (<a href="mailto:C@2.0.0">C@2.0.0</a> OR <a href="mailto:C@2.1.0">C@2.1.0</a>)</li>
<li>가능한 해결책: <a href="mailto:C@1.1.0">C@1.1.0</a>과 <a href="mailto:C@2.1.0">C@2.1.0</a> 설치</li>
<li>최적화: 최신 버전인 <a href="mailto:C@2.1.0">C@2.1.0</a>을 선택하고, A 패키지에 대해 호환성 경고 발생.</li>
</ol>
</blockquote>
<p>최종 의존성 트리</p>
<pre><code>root
├── A@1.0.0
├── B@1.0.0
└── C@2.1.0</code></pre><p>이 과정은 <code>밀리초</code> 단위로 매우 빠르게 실행되며, 수천 개의 패키지가 있는 복잡한 프로젝트에서도 효율적으로 작동한 Yarn의 이러한 알고리즘은 의존성 해결 과정을 매우 빠르고 <code>결정론적</code>으로 만들어, 항상 동일한 환경에서 동일한 결과를 보장한다.</p>
<h2 id="npm-2">NPM</h2>
<p>초기 NPM 버전은 이러한 복잡한 의존성 해결에 더 많은 시간이 걸렸다. 
최신 버전에서는 개선되었지만, 특히 매우 복잡한 의존성 트리에서는 여전히 Yarn보다 느릴 수 있다.</p>
<hr>
<h1 id="6-플랫-모드">6. 플랫 모드</h1>
<h2 id="yarn-4">Yarn</h2>
<p>Yarn은 가능한 경우 의존성을 &#39;플랫하게&#39; 설치하려고 시도한다. 이는 중복 설치를 최소화하고 디스크 공간을 절약한다.</p>
<p>예를 들어</p>
<pre><code>A
├── C@1.0.0
└── B
    └── C@1.1.0</code></pre><p>이를 다음과 같이 평탄화할 수 있다.</p>
<pre><code>A
├── C@1.1.0
└── B</code></pre><p>전통적인 방식에서는 B의 두 버전을 모두 설치하지만, Yarn은 가능한 경우 <code>하나의 버전</code>만 설치하려고 시도한다. 이는 설치 시간과 디스크 공간을 절약한다.</p>
<h2 id="npm-3">NPM</h2>
<p>NPM도 최근 버전에서 이와 유사한 기능을 제공하지만, 초기에는 이러한 최적화가 없었다.</p>
<hr>
<h1 id="7-메모리-사용">7. 메모리 사용!</h1>
<h2 id="yarn-5">Yarn</h2>
<p>Yarn은 메모리 사용을 매우 효율적으로 관리한다. 대규모 프로젝트에서도 메모리 사용량이 급격히 증가하지 않는다.</p>
<p>예를 들어, 1000개의 패키지를 설치할 때</p>
<blockquote>
<ol>
<li>Yarn은 필요한 정보만 메모리에 유지</li>
<li>사용하지 않는 데이터는 신속하게 메모리에서 해제</li>
<li>가비지 컬렉션을 효율적으로 관리하여 <a href="https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%88%84%EC%88%98">memory leaking </a>방지</li>
</ol>
</blockquote>
<h3 id="yarn의-가비지컬렉션이하-gc-방식">yarn의 가비지컬렉션(이하 GC) 방식</h3>
<h4 id="1-캐시-구조">1. 캐시 구조</h4>
<p>Yarn은 <code>글로벌 캐시</code>를 사용하여 다운로드한 패키지를 저장한다. 이 캐시는 보통 사용자의 홈 디렉토리에 위치한다 (예: <code>~/.yarn/cache</code>).</p>
<h4 id="2-캐시-엔트리">2. 캐시 엔트리</h4>
<p>각 패키지 버전은 고유한 식별자(해시)를 가지고 캐시에 저장된다.
예: <code>react-16.8.6-994e41b6.tar.gz</code></p>
<h4 id="3-컬렉션-트리거">3. 컬렉션 트리거</h4>
<p>GC는 다음 상황에서 자동으로 또는 수동으로 실행될 수 있다.</p>
<ul>
<li><code>yarn install</code> 실행 시 (자동)</li>
<li><code>yarn cache clean</code> 명령어 실행 시 (수동)</li>
<li>특정 시간이 경과했을 때 (자동, 설정 가능)</li>
</ul>
<h4 id="4-gc-프로세스">4. GC 프로세스</h4>
<ol>
<li><p>사용 중인 패키지 식별</p>
<ul>
<li>현재 프로젝트의 <code>yarn.lock</code> 파일을 분석</li>
<li>전역적으로 설치된 패키지 확인</li>
<li>다른 프로젝트의 <code>yarn.lock</code> 파일도 선택적으로 검사 (설정에 따라)</li>
</ul>
</li>
<li><p>미사용 패키지 식별</p>
<ul>
<li><code>캐시 디렉토리</code>의 모든 파일을 스캔</li>
<li>사용 중인 패키지 목록과 비교하여 미사용 패키지 식별</li>
</ul>
</li>
<li><p>삭제 대상 결정</p>
<ul>
<li><u>미사용 상태가 특정 기간(기본 30일) 이상 지속된 패키지를 삭제 대상으로 표시</u></li>
<li>이 기간은 설정을 통해 조정 가능 <code>(pruneOffsetInDays)</code></li>
</ul>
</li>
<li><p>삭제 실행</p>
<ul>
<li><code>삭제 대상</code>으로 표시된 패키지들을 <code>캐시에서 제거</code></li>
<li>삭제 과정에서 파일 시스템 오류 등이 발생하면 해당 항목을 건너뛰고 계속 진행</li>
</ul>
</li>
</ol>
<h4 id="5-부분-gc">5. 부분 GC</h4>
<p>전체 GC 대신 특정 조건에 따라 부분적으로 GC를 실행할 수 있다.</p>
<ul>
<li>특정 패키지나 버전에 대해서만 GC 실행</li>
<li>특정 시간 범위 내의 캐시 항목만 GC 대상으로 설정</li>
</ul>
<h4 id="6-gc-최적화">6. GC 최적화</h4>
<ul>
<li><code>병렬 처리</code>: <u>여러 캐시 항목을 동시에 처리</u>하여 GC 속도 향상</li>
<li><code>증분 GC</code>: 전체 캐시를 한 번에 처리하지 않고, <code>일부분씩 처리</code>하여 시스템 부하 감소</li>
</ul>
<h4 id="7-안전-장치">7. 안전 장치</h4>
<ul>
<li><code>잠금 파일</code>: GC 실행 중 다른 Yarn 작업이 캐시를 수정하지 못하도록 잠금</li>
<li><code>롤백 메커니즘</code>: GC 중 오류 발생 시 캐시 상태를 이전으로 복원할 수 있는 기능</li>
</ul>
<h4 id="8-설정-옵션">8. 설정 옵션</h4>
<p><code>yarn config</code> 세팅으로 GC 관련 설정을 조정할 수 있다.</p>
<ul>
<li><code>cacheFolder</code>: 캐시 위치 지정</li>
<li><code>cacheMaxAge</code>: 캐시 항목의 최대 유지 기간</li>
<li><code>pruneOffsetInDays</code>: 미사용 패키지 삭제 전 대기 기간</li>
</ul>
<p>예시 명령어:</p>
<pre><code>yarn cache clean  # 전체 캐시 정리
yarn cache clean package-name  # 특정 패키지의 캐시만 정리</code></pre><h2 id="npm-4">NPM</h2>
<p>초기 NPM 버전은 대규모 프로젝트에서 메모리 사용이 비효율적이었다. 
이는 특히 제한된 리소스의 환경(예: <code>CI/CD</code> 파이프라인)에서 문제가 될 수 있었다. 
최신 버전에서는 개선되었지만, 여전히 일부 시나리오에서 Yarn보다 더 많은 메모리를 사용할 수 있다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>이러한 모든 요소들이 결합되어 Yarn이 NPM보다 일반적으로 더 빠른 성능을 보인다. 
그러나 NPM도 지속적으로 개선되고 있어, 최신 버전에서는 그 격차가 많이 줄어들었다고는 한다. </p>
<p>실제 성능 차이는 <code>프로젝트의 크기</code>, <code>의존성의 복잡성</code>, <code>사용 중인 NPM과 Yarn의 버전</code> 등에 따라 달라질 수 있다.<u>대규모 프로젝트나 복잡한 의존성 구조를 가진 프로젝트</u>에서는 Yarn의 장점이 더 두드러질 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitHub, GitLab Merge Request & Pull Request]]></title>
            <link>https://velog.io/@watermincho_96/GitHub-GitLab-Merge-Request-Pull-Request</link>
            <guid>https://velog.io/@watermincho_96/GitHub-GitLab-Merge-Request-Pull-Request</guid>
            <pubDate>Wed, 17 Jul 2024 08:03:07 GMT</pubDate>
            <description><![CDATA[<p>서로 기능은 동일하다. 주체의 입장에 따른 단어적 차이일 뿐.</p>
<p>간단히 설명하자면 <code>Github</code>에서는 내가 작업한 브랜치를 <u>Master의 입장에서 Pull</u>하는 것이기에 <u>Pull Request</u>라 하는 것이고 <code>Gitlab</code>에서는 <u>내가 작업한 브랜치 입장에서 Master에 Merge</u>하는 것이기에 <u>Merge Request</u>라 하는 것이다.</p>
<p><code>Merge Request</code>를 설명하자면 내가 <code>팀원으로서</code> MR, PR을 날렸을 경우 나의 로컬 작업내용(branch, fork)을 특정 Origin(원격서버) 브랜치(mater나 develop 등)에 끌어와서(pull)병합(merge)해 달라고 요청(request) 하는 것이다.</p>
<p><strong>예를 들어</strong>, <code>팀장</code>(MR, PR을 받아주는 사람)이 MR, PR을 승낙할 경우 master나 develop 등 내가 내 소스를 병합하길 요청한 브랜치에 나의 작업물이 합쳐지는 것이다.</p>
<p><strong>참고</strong>: MR, PR이 많이 쌓여있는 상태의 원격 브랜치에 본인이 이전(또는 한참 전)에 로컬에 작업한 feature또는 브랜치를 병합하고 싶다면</p>
<p>매 시간 생각날때마다 fetch를 하고 알맞은 pull을 받아 업데이트를 한다.(실제로 내가 많이 쓰는 방법이다.)</p>
<p><code>본인이 작업한 파일과 겹치는지 여부를 파악하기 위해 팀원들과 지속적인 소통 과 MR이력의 설명을 유의깊게 본다.</code></p>
<p>Reqest 전에 체리픽이나 충돌검사 등을 사용하여 <strong>내 로컬에서 Conflict를 해결하여 MR, PR해야 함을 주의하자. 원격에서 <code>Conflict가 나오는 것을 최대한 방어하기 위함</code>이다</strong>.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Naver Deview 2023 교육외근 후기]]></title>
            <link>https://velog.io/@watermincho_96/Naver-Deview-2023-%EA%B5%90%EC%9C%A1%EC%99%B8%EA%B7%BC-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@watermincho_96/Naver-Deview-2023-%EA%B5%90%EC%9C%A1%EC%99%B8%EA%B7%BC-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 17 Jul 2024 07:36:05 GMT</pubDate>
            <description><![CDATA[<p>저날 교육외근 신청으로 Naver에서 주최한 Deview행사를 참여했었다.(사실 내가 하는 현업과는 크게 상관없었지만 <code>Naver</code>잖아, 한잔해~)</p>
<h1 id="수강한-세션리스트">수강한 세션리스트</h1>
<ol>
<li>자바스크립트 화이트박스 암호와 크롬 라인 메신저의 보안 강화</li>
<li>웨일 브라우저 오픈 소스 생존기</li>
<li>그 여자 APP, 그 남자 SDK <code>Kotlin</code> Multiplatform 적용기</li>
<li><code>SNOW</code> AI FILTER</li>
<li>Remember Me: 맞춤 케어를 위한 기억하기 챗봇</li>
</ol>
<h1 id="인상적인-세션-파헤치기---kotlin-multipaltform-적용기">인상적인 세션 파헤치기 - Kotlin Multipaltform 적용기</h1>
<h2 id="개요">개요</h2>
<p>미디어 플레이어인 Prizm Player 운영/개발하며 부족했던 리소스를 커버하고 확장성을 위해 Kotlin으로 멀티플랫폼으로 전향하며 겪은 일화를 바탕으로 원리, 구현법, 이슈 등을 공유하는 세션이었으며, 코틀린으로 멀티플랫폼 지원이 된다는 점이 아주 매력적으로 느껴졌었다.</p>
<hr>
<h2 id="1--ios와-web을-kotlin으로-구현이-가능한가-왜-코틀린-멀티플랫폼인가">1.  iOS와 Web을 Kotlin으로 구현이 가능한가? 왜 코틀린 멀티플랫폼인가?</h2>
<p>결론만 말하면 비즈니스로직만 코틀린으로 공유 가능하고 Android, iOS 의 UI 코드는 각 플랫폼 코드를 사용해야한다!
코틀린, JVM말고도 구현가능한 방식은 많다. 그리고 각 플랫폼 주력 언어로 빌드되기 때문에 유의미한 성능 저하가 없었다. 대신, 다른 MultiPlatform 방식보다는 꽤 불친절한 방식임.(직접 구현해. 근데! 언어는 Kotlin이야)
하지만 다른 방식은 너무 친절했기 때문에 실무에는 쓰이기 어렵지 않겠냐는 역발상으로 선택하게 됐다고 함.(다 알 필요 없고 세팅 이정도만 하면 나머지는 내가 다 만들어줄게!)
동작 원리:  Kotlin Shared 코드를 작성하고, 이 코드가 컴파일되는데 자동적으로 플랫폼에 대한 코드로 컴파일되는 것이다. 예를 들면, JVM 위에서 코드가 컴파일되면, 그 컴파일된 코드는 Android나 JVM 플랫폼 위에서 동작하는 것이다. </p>
<hr>
<h2 id="2-멀티플랫폼-전환-결정을-위해-던진-다섯-개의-질문">2. 멀티플랫폼 전환 결정을 위해 던진 다섯 개의 질문</h2>
<ul>
<li>기존의 개발 경험/도구/설정을 최대한 유지</li>
<li>기구축된 <code>CI/CD</code> 프로세스를 해치지 않아야함</li>
<li>UI개발도 <code>공통화</code> 가능할 것</li>
<li>러닝 커브는 최소화해야함</li>
<li>이득이 고통보다 클 것(단순하네)</li>
</ul>
<hr>
<h2 id="3-sdk에서-개발한-방식">3. SDK에서 개발한 방식</h2>
<p>선택권은 두 가지 안이 있었다고 함.</p>
<ol>
<li>기존 프로젝트의 기능 일부를 Kotlin Multiplatform으로 전환
a. 검증된 기존 프로젝트 기반으로 하기에 리스크가 적음
b. 플랫폼 별 서로 다른 설계 및 스펙으로 이를 맞추는 데에도 상당한 리소스</li>
<li>신규 Kotlin Multiplatform 프로젝트에 기존 기능 도입(선택)
a. 차라리 각 플랫폼의 노하우를 취합하여 신규 설계하는 것이 나을 것 같음.
b. 그동안 아쉬웠으나 호환성 이슈로 개선하지 못했던 API들을 개선해볼 계기</li>
</ol>
<h3 id="기존-구현을-참고하여-새로운-설계에-맞추어-개발">기존 구현을 참고하여 새로운 설계에 맞추어 개발</h3>
<p>이미 코틀린으로 작성되어있던 안드로이드 코드 위주로 참고(아주 많은 도움이 되었다고 함)
타입스크립트, 스위프트 등으로 작성된 코드도 필요시 활용이 됐었고 공유 코드를 극대화하기 위한 <code>pure Kotlin</code>을 지향했다고 함. 그렇지 않은 경우, <code>expect/actual</code>로 각 플랫폼 기능 연동 적용.</p>
<h3 id="expectactual이란">expect/actual이란?</h3>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/890beb24-12be-4b39-acd9-2c7f60f19e0c/image.png" alt=""></p>
<p>만약에 플랫폼별로 코드를 구현하려면 아래과 같이 분기처리를 해야할 것이다.</p>
<pre><code>if(platform == &quot;iOS&quot;){
}else if(platform ==&quot;android&quot;) {
}</code></pre><p>하지만 <code>코틀린</code>에서는 <code>expect/actual</code> 키워드를 제공한다. 예를 들면, 공통으로 사용하는 모듈에서 <code>expect</code>로 <code>선언</code>해놓고</p>
<pre><code class="language-kotlin">expect fun getPlatform(): String</code></pre>
<p>각 플랫폼에서 <code>actual</code>로 연동하면 된다.</p>
<h4 id="android">[Android]</h4>
<pre><code class="language-kotlin">actual fun getPaltform():String = “Android ${Build.VERSION.RELEASE}“</code></pre>
<h4 id="ios">[iOS]</h4>
<pre><code class="language-swift">actual fun getPlatform():String = UIDevice.currentDevice.run {“$systemName $systemVersion“}</code></pre>
<h4 id="js">[JS]</h4>
<pre><code class="language-typescript">actual fun getPlatform(): String = window.navigator.userAgent.toPlatform()
private fun String.toPlatform(): String = …</code></pre>
<p>일단 <code>expect</code> 키워드는 함수 이름이나 클래스 앞에 선언이 가능하다. 플랫폼별로 <code>expect</code>되는 함수나 클래스에 선언한다.</p>
<p><code>actual</code> 키워드는 <code>플랫폼 별</code>로 <code>common</code>의 코드에서 <code>expect</code> 로 선언한 동일한 함수나 클래스 이름으로 선언한다. 매칭되는 <code>expect</code> 정의에 실제 구현이 필요하다.</p>
<p><code>expect</code>로 정의되었던 모든 클래스나 함수는 플랫폼별로 <code>actual</code> 정의를 가져야한다.</p>
<p>expect 함수나 클래스는 구현되지 않는다.</p>
<hr>
<h2 id="4-kotlinjs">4. Kotlin/JS</h2>
<p><code>external modifier</code>를 통한 <code>javascript</code> 코드 접근</p>
<ul>
<li><code>kotlin-stdlib-js</code>에 선언되어있는 것을 활용하거나 직접 정의</li>
</ul>
<p>[kotlin-stdlib-js ]</p>
<pre><code class="language-kotlin">/** * Exposes the [console API](https://developer.mozilla.org/en/DOM/console ) to Kotlin. */ 
@Suppress(&quot;NOT_DOCUMENTED&quot;) 
public external interface Console { 
public fun dir(o: Any): Unit 
public fun error(vararg o: Any?): Unit 
public fun info(vararg o: Any?): Unit 
public fun log(vararg o: Any?): Unit 
public fun warn(vararg o: Any?): Unit 
} 
/** * Exposes the [console API](https://developer.mozilla.org/en/DOM/console ) to Kotlin. */ 
public external val console: Console</code></pre>
<h3 id="kotlin">[kotlin]</h3>
<pre><code class="language-kotlin">console.log(“Hello, World“)</code></pre>
<h3 id="javascript">[javascript]</h3>
<p><code>JS()function</code>을 통해 <code>JavaScript</code>코드 직접 호출도 가능</p>
<pre><code class="language-javascript">const shakaPlayer = js(“new Shaka.Player(element)“)</code></pre>
<h3 id="kotlin-1">[kotlin]</h3>
<pre><code class="language-kotlin">fun &lt;T&gt; buildObject(block: T.() → Unit) : T =
(js(“{}“).unsafeCast&lt;T&gt;()).apply(block) </code></pre>
<p><code>NPM</code>을 통해 원하는 패키지를 <code>Kotlin</code>코드로 불러와 사용할 수 있음.
<code>Kotlin</code> 코드에 <code>external</code>도 별도 정의해야함.</p>
<h3 id="external이란">external이란?</h3>
<p><code>external</code> 는 <code>C++</code>로 작성된 코드를 <code>JNI</code>를 통해 호출하거나 <code>JavaScript</code> 코드를 <code>호출</code> 할 때 사용한다.</p>
<pre><code class="language-kotlin">external fun foo (...) {...}</code></pre>
<p>근데 매번 <code>external</code>로 직접 정의할 필요 없이 <strong>Dukat</strong>사용하면 <code>external</code> 코드가 자동생성된다.</p>
<hr>
<h2 id="5--kotlin-stdlib는-bytearray-→-utf-8-string-변환-기능을-제공한다">5.  kotlin-stdlib는 byteArray → UTF-8 <code>String</code> 변환 기능을 제공한다.</h2>
<ul>
<li>그 외 다른 encoding에 대한 지원은 아직 없다.</li>
<li>게다가 모든 플랫폼에 대해 UTF-16, ISO-8859-1 디코딩을 지원하지 않음.</li>
</ul>
<hr>
<h2 id="6-sdk에게-라이브러리-종속성이란">6. SDK에게 라이브러리 종속성이란?</h2>
<p>SDK를 개발할 때는 라이브러리 사용에 더욱 더 신중해야함.</p>
<ul>
<li>다양한 곳에 적용되면서 버전 충돌(제일 흔함) 등으로 예기치 못한 이슈가 종종 발생 </li>
</ul>
<hr>
<h2 id="7-테스트코드">7. 테스트코드</h2>
<p>플랫폼 별 테스트 실행 gradle task</p>
<h3 id="android-1">[android]</h3>
<pre><code>./gradlew :core:testDebugUnitTest –tests “path.to.CharsetsTest.testUtf8“
</code></pre><h3 id="iossimulatorarm64">[iosSimulatorArm64]</h3>
<pre><code>./gradlew :core:iosSimulatorArm64Test --tests “path.to.CharsetsTest.testUtf8“</code></pre><h3 id="javascript-1">[javaScript]</h3>
<pre><code>./gradlew :core:jsBrowserTest --tests “path.to.CharsetsTest.testUtf8“
iOS에서 테스트시 Simulator를 찾지 못하는 경우가 있다고 함.</code></pre><p>그럴 땐 <code>Simulator</code> 기기 하드 코딩 이슈라고 한다. <code>Kotlin 1.8 미만</code>에서는 <code>-xcrun simctl list</code>를 활용하여 <code>iOS Simulator deviceId</code> 주입 후 터미널에 <code>xcrun simctl list devices available</code> 명령어로 조회 가능하다.</p>
<hr>
<h2 id="8-kotlinnative는-swift가-아닌-objective-c로-컴파일함">8. Kotlin/Native는 Swift가 아닌 Objective-C로 컴파일함</h2>
<ul>
<li>언제 개선될지 기약 없음</li>
<li>그래서 코틀린으로 작성된 코드를 Swift로 감싸서 배포</li>
</ul>
<hr>
<h2 id="9-kotlinjs-계층-모듈-구조-미지원">9. Kotlin/JS 계층 모듈 구조 미지원</h2>
<ul>
<li><code>Kotlin/JS</code>는 계층 모듈 구조가 아직 불가함. → 배포 타겟에 따라 별도 <code>gradle</code> 모듈을 만듦.</li>
</ul>
<hr>
<h2 id="10-ios-멀티모듈-지원-이슈">10. iOS 멀티모듈 지원 이슈</h2>
<ul>
<li><code>Kotlin Multiplatform</code>에서 iOS빌드 시 모듈별로 코드 복사</li>
<li>별도 <code>gradle</code> 모듈에서 멀티모듈의 종속성을 갖고 하나의 바이너리로 묶어서 배포 </li>
</ul>
<hr>
<h2 id="11-android-배포-시-이슈">11. Android 배포 시 이슈</h2>
<ul>
<li>Kotlin Metadata 버전 이슈<ul>
<li>SDK를 사용하는 앱의 <code>Kotlin</code> 버전이 더 낮을 경우</li>
<li><code>Kotlin Metadata</code>가 호환되지 않아 에러 발생</li>
<li><code>Kotlin/JS 및 Kotlin/Native</code> 개선사항이 많기 때문에 낮게만 유지할 수 없음</li>
<li>대신 <code>api version</code> 및 <code>Language version</code>을 낮게 유지하여 빌드 시 낮은 버전에서 컴파일</li>
</ul>
</li>
</ul>
<hr>
<h2 id="12-모듈-관리">12. 모듈 관리</h2>
<ul>
<li>멀티-모듈 구조로 변경<ul>
<li>오래 운영된 앱일수록 싱글모듈로 구성</li>
<li>기능 단위로 멀티모듈화: 코드 속성에 따라 네 가지 모듈로 분류</li>
</ul>
</li>
</ul>
<hr>
<h2 id="13-저장소-관리">13. 저장소 관리</h2>
<ul>
<li>플랫폼 별로 저장소를 운영하는 것이 일반적<ul>
<li>플랫폼 단위로 조직이 나눠진 경우</li>
<li>운영/커뮤니케이션의 효율성</li>
</ul>
</li>
<li>Kotlin Multiplatform 코드는 common code이기 때문에 위치가 애매함</li>
<li>조직마다 상황에 따라 결정이 달라질 수 밖에 없음</li>
</ul>
<h3 id="해결법-monorepo">해결법: <code>monorepo</code></h3>
<ul>
<li>멀티플랫폼/앱 모듈을 한 저장소에 모음<ul>
<li>A monorepo is a single repository containing multiple distict projects, with well-defined relationships.<ul>
<li>저장소의 크기가 커지고 타 플랫폼 코드 수정이 가능한 문제점</li>
</ul>
</li>
<li>플랫폼별로 디렉토리 분리: <code>root/app</code> → <code>root/app-android/app</code></li>
<li><code>CODEOWNERS</code>: 특정 디렉토리/파일에 대해 소유권을 부여하여 PR 강제 리뷰어 지정]</li>
<li><code>sparse-checkout</code>: 특정 디렉토리/파일만 체크아웃할 수 있는 기능</li>
</ul>
</li>
</ul>
<hr>
<h2 id="14-의존성-관리">14. 의존성 관리</h2>
<ul>
<li><code>Kotlin Compiler</code> 버전 의존성<ul>
<li>코틀린은 빠른 속도로 발전하고 있는 언어임</li>
<li><code>compiler plugin</code>을 사용하는 라이브러리에 의존성이 있으면 문제 발생 가능</li>
<li>대표적 라이브러리: <code>Coroutines/Compose/Kotlinx Serialization/Kotlin SymbolProcessing</code></li>
</ul>
</li>
<li>문제사례<ul>
<li>라이브러리 간 버전 <code>의존성</code>이 다른 경우</li>
<li>코틀린보다 <code>낮은</code> 버전인 경우</li>
</ul>
</li>
<li>그냥 처음부터 코틀린 버전(/컴파일러)을 맞추고 개발을 시작하면 문제발생을 최소화할 수 있다.<ul>
<li>릴리즈 노트 확인하고 빌드하여 직접 확인</li>
<li>gradle이 제공하는 VersionCatalogs확인<ul>
<li>동일 프로젝트에서는 번들로 사용하도록 룰 셋</li>
</ul>
</li>
<li>gralde plugin 구현</li>
</ul>
</li>
</ul>
<hr>
<h2 id="15-ui는-compose-multiplatform으로-구현">15. UI는 compose multiplatform으로 구현</h2>
<ul>
<li>플랫폼별로 따로 제공되는 위젯이 있거나 별도의 구현방식이 존재한다면, <code>expect / actual</code> 선언으로 별도 ui구현</li>
<li>모든 과정을 통해서도 해결하지 못하는 경우엔 모든 플랫폼이 공통으로 사용가능한 인터페이스를 이용하여 직접 <code>custom composable</code>제작</li>
</ul>
<hr>
<h2 id="16-적용한-viewmodel">16. 적용한 ViewModel</h2>
<ul>
<li><code>ViewModel(expect)</code> → <code>actual(android, desktop, …)</code> → <code>ViewModelImpl(서비스 공통 비즈니스로직)</code> ↔︎ <code>View</code></li>
</ul>
<h3 id="viewmodel">[ViewModel]</h3>
<pre><code class="language-java">interface RemoteBaseViewModel {
val coroutineScope: CoroutineScope
}
expect abstract class RemoteMainViewModel() : RemoteBaseViewModel {
override val coroutineScope: CoroutineScope
}</code></pre>
<h3 id="viewmodeiimpl">[ViewModeIImpl]</h3>
<pre><code class="language-java">class RemoteMainViewModelImpl() : RemoteMainViewModel() {
  private val _isEditMode = MutableStateFlow(false)
  val isEditMode: StateFlow&lt;Boolean&gt; get() = _isEditMode
  private val _decks = SynchronizedStateList&lt;RemoteDeckModel&gt;()
  val decks: List&lt;RemoteDeckModel&gt; get() = _decks.list
  .
  .
  .
  private override fun loadShortcuts() {
  coroutineScope.launch {repo.loadBroadcasts()}
  .
  .
  .
  }
}
</code></pre>
<h1 id="후기">후기</h1>
<p>이 세션 저 세션 듣다가 본받아야 할 점(?)인지는 모르겠으나 결국 거인의 어깨위에 누가 제일 높게 올라서냐의 싸움인 것 같더라. <u>세상에서 수요가 제일 많은 언어나 플랫폼의 의존 및 업데이트를 빠르게 따라가는 서비스 일수록 개발자가 능력을 인정받을 수 있는 기회가 넓어진다</u>는 것을 다시 한번 깨닫게 되었고</p>
<p>1일차 세션 중에</p>
<p>CFCs Reactive
SSR환경의 micro-frontEnd 구현과 캐시전략</p>
<p>이 두가지는 발표자료를 보며 공부해보고 싶은 아이템이었는데 직접 보지 못하여 아쉬웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Gitlab] MR conflict 발생한 후 `resolve locally` 상황 발생했을 때 조치 방법]]></title>
            <link>https://velog.io/@watermincho_96/Gitlab-MR-conflict-%EB%B0%9C%EC%83%9D%ED%95%9C-%ED%9B%84-resolve-locally-%EC%83%81%ED%99%A9-%EB%B0%9C%EC%83%9D%ED%96%88%EC%9D%84-%EB%95%8C-%EC%A1%B0%EC%B9%98-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@watermincho_96/Gitlab-MR-conflict-%EB%B0%9C%EC%83%9D%ED%95%9C-%ED%9B%84-resolve-locally-%EC%83%81%ED%99%A9-%EB%B0%9C%EC%83%9D%ED%96%88%EC%9D%84-%EB%95%8C-%EC%A1%B0%EC%B9%98-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 17 Jul 2024 06:47:28 GMT</pubDate>
            <description><![CDATA[<p>GitLab 웹페이지에서 <code>Merge Request</code> 생성/요청하였으나 <code>conflict</code> 발생하고 <code>resolve locally</code> 만 표시되는 상황에서 해결 방법</p>
<p>아래는 dev/improvements_ux 에서 development_v2 로 MR을 요청한 예시로 branch 이름을 상황에 맞게 적절하게 변경해서 시도하면 된다.</p>
<ol>
<li>MR 시도할 브랜치 <code>Checkout git fetch origin git checkout -b &quot;dev/improvements_ux&quot; &quot;origin/dev/improvements_ux&quot;</code></li>
<li>MR 적용할 브랜치 <code>Checkout git fetch origin git checkout &quot;origin/development_v2_&quot; git merge --no-ff &quot;dev/improvements_ux&quot;</code></li>
<li>conflict가 발생한 코드 직접 수정</li>
<li>3.에서 수정한 내역 반영 <code>git push origin HEAD:development_v2_mydata</code></li>
<li>MR 완료</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ios] framework library에서 특정 architecture 제거하기]]></title>
            <link>https://velog.io/@watermincho_96/ios-framework-library%EC%97%90%EC%84%9C-%ED%8A%B9%EC%A0%95-architecture-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-k8d77hb5</link>
            <guid>https://velog.io/@watermincho_96/ios-framework-library%EC%97%90%EC%84%9C-%ED%8A%B9%EC%A0%95-architecture-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-k8d77hb5</guid>
            <pubDate>Wed, 17 Jul 2024 06:38:03 GMT</pubDate>
            <description><![CDATA[<p>file <library file> 혹은 lipo -info <library file> 명령어로 library에 어떤 architecture를 지원하는지 조회를 한다</p>
<pre><code class="language-shell"># file AnyLibrary
or
# lipo -info AnyLibrary</code></pre>
<p> 제거하고자 하는 architecture를 아래와 같이 lipo -remove <architecture name> <library file> -o &lt;제거 후 생성할 library file name&gt; </p>
<pre><code class="language-shell"># lipo -remove x86_64 AnyLibrary -o AnyLibrary-reduced</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] Receiver로 전달된 extra data 로그 출력하기]]></title>
            <link>https://velog.io/@watermincho_96/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Receiver%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%90%9C-extra-data-%EB%A1%9C%EA%B7%B8-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@watermincho_96/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Receiver%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%90%9C-extra-data-%EB%A1%9C%EA%B7%B8-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 17 Jul 2024 06:15:32 GMT</pubDate>
            <description><![CDATA[<p>아래와 같이 <code>BroadcastReceiver</code>로 전달된 <code>Intent</code> 정보를 확인하기 위해서는</p>
<pre><code class="language-java">public class CustomReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
  ...</code></pre>
<p>와 같이 전달된 <code>intent</code>의 <code>extra data</code>를 가져와서 <code>Bundle</code> 정보를 <code>key</code>, <code>value</code>로 출력하도록 한다.</p>
<pre><code class="language-java"> private void printBundleInfo(Intent i) {
    Bundle extras = i.getExtras();
    try {
      Log.e(&quot;&quot;, &quot;----------------------------------------------------&quot;);
      Log.e(&quot;&quot;, &quot;intent = &quot; + i);
      if (i != null) {
        Log.e(&quot;&quot;, &quot;extras = &quot; + extras);
        if (extras != null) {
          Set keys = extras.keySet();
          Log.e(&quot;&quot;,&quot;++ bundle key count = &quot; + keys.size());
          for (String _key : extras.keySet()) {
            Log.e(&quot;&quot;,&quot;key=&quot; + _key + &quot; : &quot; + extras.get(_key));
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      Log.e(&quot;&quot;,&quot;----------------------------------------------------&quot;);
    }
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android 디버그 브리지(adb)]]></title>
            <link>https://velog.io/@watermincho_96/Android-%EB%94%94%EB%B2%84%EA%B7%B8-%EB%B8%8C%EB%A6%AC%EC%A7%80adb</link>
            <guid>https://velog.io/@watermincho_96/Android-%EB%94%94%EB%B2%84%EA%B7%B8-%EB%B8%8C%EB%A6%AC%EC%A7%80adb</guid>
            <pubDate>Wed, 17 Jul 2024 06:02:25 GMT</pubDate>
            <description><![CDATA[<p><code>Android 디버그 브리지(adb)</code>는 기기와 통신할 수 있도록 지원하는 다목적 명령줄 도구다.</p>
<p><code>adb</code> 명령어는 앱의 설치 및 디버깅과 같은 다양한 기기 작업에 도움이 되며, 기기에서 다양한 명령어를 실행하는 데 사용할 수 있는 <code>Unix</code> 셸에 대한 액세스를 제공한다.</p>
<p>adb는 아래의 세 가지 구성요소를 포함하는 클라이언트-서버 프로그램이다.</p>
<ul>
<li>명령어를 전송하는 클라이언트. 클라이언트는 개발 머신에서 실행된다. adb 명령어를 실행하여 명령줄 터미널에서 클라이언트를 호출할 수 있다.</li>
<li>기기에서 명령어를 실행하는 <code>데몬(adbd)</code>. 데몬은 각 기기에서 <code>백그라운드 프로세스</code>로 실행된다.</li>
<li>클라이언트와 데몬 간의 통신을 관리하는 <code>서버</code>. 서버는 개발 머신에서 <code>백그라운드 프로세스</code>로 실행된다.</li>
</ul>
<p><code>adb</code>는 Android SDK 플랫폼 도구 패키지에 포함되어 있다. 
이 패키지는 <code>SDK Manager</code>를 사용하여 다운로드할 수 있으며 <code>android_sdk/platform-tools/</code>에 설치된다.</p>
<h1 id="adb-작동-방식">adb 작동 방식</h1>
<ol>
<li>adb 클라이언트를 시작하면 먼저 해당 클라이언트는 이미 실행 중인 adb 서버 프로세스가 있는지 확인한다. </li>
<li>없으면 서버 프로세스를 시작한다.</li>
<li>서버가 시작되면 이 서버가 로컬 TCP 포트 <code>5037</code>에 바인딩되고 adb 클라이언트로부터 전송되는 명령어를 litsten한다. </li>
<li><code>모든</code> adb 클라이언트는 포트 <code>5037</code>을 사용하여 adb 서버와 통신한다.</li>
<li>그런 다음 서버는 실행 중인 모든 기기와의 연결을 설정한다. </li>
<li>서버는 <code>5555~5585 범위(처음 16개의 에뮬레이터가 사용하는 범위)</code>에서 <code>홀수 포트</code>를 스캔하여 에뮬레이터를 찾는다. </li>
<li>서버가 <code>adb 데몬(adbd)</code>을 찾으면 포트와의 연결을 설정한다.</li>
<li>각 에뮬레이터는 한 쌍의 순차적 포트를 사용한다. 하나는 <code>콘솔 연결용 짝수 포트</code>이고 다른 하나는 <code>adb 연결용 홀수 포트</code>다. </li>
</ol>
<p><strong>예:</strong>
에뮬레이터 1, 콘솔: 5554
에뮬레이터 1, adb: 5555
에뮬레이터 2, 콘솔: 5556
에뮬레이터 2, adb: 5557
.
.
.
등등</p>
<p>위에서 알 수 있듯이 포트 <code>5555</code>에서 <code>adb</code>에 연결된 에뮬레이터는 콘솔이 포트 <code>5554</code>에서 리슨하는 에뮬레이터와 <code>동일</code>합니다.</p>
<p>서버가 <code>모든 기기와의 연결</code>을 설정하면 사용자는 <code>adb</code> 명령어를 사용하여 기기에 액세스할 수 있다. 서버는 기기 연결을 관리하고 여러 <code>adb</code> 클라이언트의 명령어를 처리하므로 개발자는 <code>임의의 클라이언트</code>(또는 스크립트)에서 <code>임의의 기기</code>를 제어할 수 있습니다.</p>
<h1 id="기타사항">기타사항</h1>
<p>레퍼런스 참조 : <a href="https://developer.android.com/tools/adb?hl=ko#move">Android developer 디버그 브리지(adb) 공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ios build 과정에서 Operation not permitted 발생하는 경우 해결 방안]]></title>
            <link>https://velog.io/@watermincho_96/ios-build-%EA%B3%BC%EC%A0%95%EC%97%90%EC%84%9C-Operation-not-permitted-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88</link>
            <guid>https://velog.io/@watermincho_96/ios-build-%EA%B3%BC%EC%A0%95%EC%97%90%EC%84%9C-Operation-not-permitted-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88</guid>
            <pubDate>Wed, 17 Jul 2024 05:19:34 GMT</pubDate>
            <description><![CDATA[<h1 id="원인">원인</h1>
<p>macOS의 보안 제한 사항으로 watchman이 디스크 엑세스 권한을 받지 못한 상태</p>
<hr>
<h1 id="해결">해결</h1>
<h3 id="watchman-watch-del-all">watchman watch-del-all</h3>
<h3 id="watchman-shutdown-server">watchman shutdown-server</h3>
<p>터미널에서 위 두 명령을 실행하고, 다시 빌드해보면 watchman에게 디스크 권한을 줄지 물어보는 창이 나타나고, 수락해주면 문제 해결</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[더 나은 react 코드를 작성하는 8가지 방법]]></title>
            <link>https://velog.io/@watermincho_96/%EB%8D%94-%EB%82%98%EC%9D%80-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%98%EB%8A%94-8%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@watermincho_96/%EB%8D%94-%EB%82%98%EC%9D%80-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%98%EB%8A%94-8%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 17 Jul 2024 05:15:33 GMT</pubDate>
            <description><![CDATA[<h1 id="1-단일-조건인-조건부-렌더링에는--를-쓰자">1. 단일 조건인 조건부 렌더링에는 &amp;&amp; 를 쓰자</h1>
<p>만약 어떤 값이 <code>true</code> 일 때 렌더링하고 false일 때는 렌더하지 않는 식으로, 조건에 따라 렌더링을 하거나 하지 않는 경우가 있다. 이때는 <code>삼항연산자</code>를 사용하지 말고 대신 <code>&amp;&amp;연산자</code>를 활용하는 것이 좋다.</p>
<p> </p>
<h2 id="나쁜-예시">나쁜 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const ConditionalRenderingWhenTrueBad = () =&gt; {
  const [showConditionalText, setShowConditionalText] = useState(false)

  const handleClick = () =&gt;
    setShowConditionalText(showConditionalText =&gt; !showConditionalText)

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Toggle the text&lt;/button&gt;
      {/* (역주) 삼항연산자를 사용하면 null 같이 불필요한 코드가 늘어남. */} 
      {showConditionalText ? &lt;p&gt;The condition must be true!&lt;/p&gt; : null}
    &lt;/div&gt;
  )
}</code></pre>
<h2 id="좋은-예시">좋은 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const ConditionalRenderingWhenTrueGood = () =&gt; {
  const [showConditionalText, setShowConditionalText] = useState(false)

  const handleClick = () =&gt;
    setShowConditionalText(showConditionalText =&gt; !showConditionalText)

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Toggle the text&lt;/button&gt;
      {/* (역주) 삼항연산자를 활용하면 조건별 렌더링을 더 가시적으로 표현할 수 있습니다. */}
      {showConditionalText &amp;&amp; &lt;p&gt;The condition must be true!&lt;/p&gt;}
    &lt;/div&gt;
  )
}</code></pre>
<blockquote>
<p>JavaScript에서 truthy, falsy가 모호하기 때문에 &amp;&amp; 키워드를 쓸 때 !!를 쓰는 것을 추천하는 의견이 있다. 다음과 같이 작성하면 된다. 
<code>{
!!showContitionalText &amp;&amp; &lt;p&gt;The condition must be true!&lt;/p&gt;
}</code></p>
</blockquote>
<hr>
<h1 id="2-다중-조건인-조건부-렌더링에서는-삼항연산자를-쓰자">2. 다중 조건인 조건부 렌더링에서는 삼항연산자를 쓰자</h1>
<p>만약 어떤 값이 <code>true</code> 일 때 A를 렌더링하고 <code>false</code>일 때는 B를 렌더하지 식으로 <u>조건에 따라 서로 다른 결과를 렌더링해야 하는 경우</u>가 있다. 이때는 <code>삼항연산자</code>를 쓰는 것이 좋습니다.</p>
<p> </p>
<h2 id="나쁜-예시-1">나쁜 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const ConditionalRenderingBad = () =&gt; {
  const [showConditionOneText, setShowConditionOneText] = useState(false)

  const handleClick = () =&gt;
    setShowConditionOneText(showConditionOneText =&gt; !showConditionOneText)

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Toggle the text&lt;/button&gt;
    {/* (역주) 이 경우 &amp;&amp; 연산자를 활용하면 불필요하게 코드가 늘어납니다. */}
      {showConditionOneText &amp;&amp; &lt;p&gt;The condition must be true!&lt;/p&gt;}
      {!showConditionOneText &amp;&amp; &lt;p&gt;The condition must be false!&lt;/p&gt;}
    &lt;/div&gt;
  )
}
</code></pre>
<h2 id="좋은-예시-1">좋은 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const ConditionalRenderingGood = () =&gt; {
  const [showConditionOneText, setShowConditionOneText] = useState(false)

  const handleClick = () =&gt;
    setShowConditionOneText(showConditionOneText =&gt; !showConditionOneText)

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Toggle the text&lt;/button&gt;
      {/* (역주) 삼항연산자를 활용하면 조건별 렌더링을 더 가시적으로 표현할 수 있습니다. */}
      {showConditionOneText ? (
        &lt;p&gt;The condition must be true!&lt;/p&gt;
        ) : (
        &lt;p&gt;The condition must be false!&lt;/p&gt;
        )}
    &lt;/div&gt;
  )
}</code></pre>
<blockquote>
<p>렌더 내부에 로직을 포함하는 것 자체에 대한 반대의견이 있었다. 분기렌더 로직은 렌더 가장 처음이나 외부로 빼내는 것이 가독성에 좋다는 의견이었다.</p>
</blockquote>
<hr>
<h1 id="3-boolean값을-props로-넘길-때는-true를-생략하자">3. Boolean값을 Props로 넘길 때는 true를 생략하자</h1>
<p>따로 값을 할당하지 않아도 <code>prop명</code>만으로 컴포넌트에 참으로 평가되는 값을 제공할 수 있는 경우가 있다. 이런 경우에 해당 <code>prop</code>값으로 <code>true</code>를 굳이 명시할 필요가 없다. 가령 <code>myTruthProp={true}</code> 이런 식으로 작성할 필요는 없다는 뜻이다.</p>
<p> </p>
<h2 id="나쁜-예시-2">나쁜 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const HungryMessage = ({ isHungry }) =&gt; (
  &lt;span&gt;{isHungry ? &#39;I am hungry&#39; : &#39;I am full&#39;}&lt;/span&gt;
)

export const BooleanPropBad = () =&gt; (
  &lt;div&gt;
    &lt;span&gt;
      &lt;b&gt;This person is hungry: &lt;/b&gt;
    &lt;/span&gt;
    {/* (역주) isHungry의 값으로 굳이 true를 명시 */} 
    &lt;HungryMessage isHungry={true} /&gt;
    &lt;br /&gt;
    &lt;span&gt;
      &lt;b&gt;This person is full: &lt;/b&gt;
    &lt;/span&gt;
    &lt;HungryMessage isHungry={false} /&gt;
  &lt;/div&gt;
)</code></pre>
<h2 id="좋은-예시-2">좋은 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const HungryMessage = ({ isHungry }) =&gt; (
  &lt;span&gt;{isHungry ? &#39;I am hungry&#39; : &#39;I am full&#39;}&lt;/span&gt;
)

export const BooleanPropGood = () =&gt; (
  &lt;div&gt;
    &lt;span&gt;
      &lt;b&gt;This person is hungry: &lt;/b&gt;
    &lt;/span&gt;
    {/* (역주) isHungry의 값으로 굳이 true를 명시하지 않아도 됩니다. */}
    &lt;HungryMessage isHungry /&gt;
    &lt;br /&gt;
    &lt;span&gt;
      &lt;b&gt;This person is full: &lt;/b&gt;
    &lt;/span&gt;
    &lt;HungryMessage isHungry={false} /&gt;
  &lt;/div&gt;
)</code></pre>
<blockquote>
<p>isHungry에 대한 기본값을 설정하는 방법이 있다는 의견이 있다.
true와 같은 Boolean 값을 생략하기보다 개발자의 의도를 명확하게 하기 위해 명시적으로 true, false 값을 입력해주는 것이 좋다는 의견이 있다.</p>
</blockquote>
<hr>
<h1 id="4-문자열-값을-props로-넘길-때는-쌍따옴표를-이용하자">4. 문자열 값을 Props로 넘길 때는 쌍따옴표를 이용하자</h1>
<p>문자열 <code>prop</code>값은 별도의 중괄호({}, curly brace)나 백틱(``, backticks)없이 그저 쌍따옴표만을 통해서도 전달할 수 있다.</p>
<p> </p>
<h2 id="나쁜-예시-3">나쁜 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const Greeting = ({ personName }) =&gt; &lt;p&gt;Hi, {personName}!&lt;/p&gt;

export const StringPropValuesBad = () =&gt; (
  &lt;div&gt;
    {/* 문자열 prop값을 중괄호, 백틱, 쌍따옴표, 홑따옴표로 감싸 전달한 사례 */}
    &lt;Greeting personName={&quot;John&quot;} /&gt;
    &lt;Greeting personName={&#39;Matt&#39;} /&gt;
    &lt;Greeting personName={`Paul`} /&gt;
  &lt;/div&gt;
)</code></pre>
<h2 id="좋은-예시-3">좋은 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const Greeting = ({ personName }) =&gt; &lt;p&gt;Hi, {personName}!&lt;/p&gt;

export const StringPropValuesGood = () =&gt; (
  &lt;div&gt;
      {/* (역주) 문자열 prop값은 그저 쌍따옴표만으로도 충분히 전달할 수 있습니다. */}
    &lt;Greeting personName=&quot;John&quot; /&gt;
    &lt;Greeting personName=&quot;Matt&quot; /&gt;
    &lt;Greeting personName=&quot;Paul&quot; /&gt;
  &lt;/div&gt;
)</code></pre>
<blockquote>
<p>현업에서 은근히 동료들끼리 코드컨벤션이 안맞았던 부분중 하나였던 것 같다.</p>
</blockquote>
<hr>
<h1 id="5-인자가-단일-객체-뿐인-이벤트-핸들러-함수는-함수명만-입력하자">5. 인자가 단일 객체 뿐인 이벤트 핸들러 함수는 함수명만 입력하자.</h1>
<p>만약 이벤트 핸들러가 오직 Event 객체 하나만을 인자로 받는다면, 그냥 <code>이벤트 핸들러</code>로 <code>함수명</code>만을 입력하면 된다. 즉, <code>onChange={e =&gt; handleChange(e)}</code> 이라고 쓸 필요없이 그냥 <code>onChange={handleChange}</code>라고 쓰면 된다는 뜻이다.</p>
<h2 id="나쁜-예시-4">나쁜 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const UnnecessaryAnonymousFunctionsBad = () =&gt; {
  const [inputValue, setInputValue] = useState(&#39;&#39;)

  const handleChange = e =&gt; {
    setInputValue(e.target.value)
  }

  return (
    &lt;&gt;
      &lt;label htmlFor=&quot;name&quot;&gt;Name: &lt;/label&gt;
      {/*  Event 객체 하나만 인자로 받는데 인자를 전달하는 함수형태로 작성 */}
      &lt;input id=&quot;name&quot; value={inputValue} onChange={e =&gt; handleChange(e)} /&gt;
    &lt;/&gt;
  )
}</code></pre>
<h2 id="좋은-예시-4">좋은 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const UnnecessaryAnonymousFunctionsGood = () =&gt; {
  const [inputValue, setInputValue] = useState(&#39;&#39;)

  const handleChange = e =&gt; {
    setInputValue(e.target.value)
  }

  return (
    &lt;&gt;
      &lt;label htmlFor=&quot;name&quot;&gt;Name: &lt;/label&gt;
      {/* 그저 Event 객체 하나만 인자로 받는 함수는 함수명만을 입력 */}
      &lt;input id=&quot;name&quot; value={inputValue} onChange={handleChange} /&gt;
    &lt;/&gt;
  )
}</code></pre>
<blockquote>
<p>handleChange가 바로 위에서 정의되어 있지만, 혹시라도 스토어의 메소드를 호출하는 경우라면 해당 메소드가 화살표함수로 짜여있지 않거나, 혹은 handleChange의 bind가 제대로 처리되어 있지 않으면 this를 제대로 바인딩하지 못해 의도하지 않은 결과를 낼 수 있지 않나 생각했다. 물론 요즘은 대부분 함수형 컴포넌트로 개발하고 있고 화살표 함수가 대중화된만큼 과한 걱정이라 생각이 든다.</p>
</blockquote>
<hr>
<h1 id="6-별도의-props가-없는-컴포넌트-전달할-때는-컴포넌트명만을-입력하자">6. 별도의 props가 없는 컴포넌트 전달할 때는 컴포넌트명만을 입력하자</h1>
<p>어떤 컴포넌트(B)에 <code>prop</code>으로 또 다른 컴포넌트(A)를 전달하는 경우에, 전달되는 컴포넌트(A)가 아무 <code>props</code>를 받지 않는다면 굳이 함수로 감쌀 필요없이 <code>컴포넌트명만을 전달</code>해도 좋다.</p>
<p> </p>
<h2 id="나쁜-사례">나쁜 사례</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const CircleIcon = () =&gt; (
  &lt;svg height=&quot;100&quot; width=&quot;100&quot;&gt;
    &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; stroke=&quot;black&quot; stroke-width=&quot;3&quot; fill=&quot;red&quot; /&gt;
  &lt;/svg&gt;
)

const ComponentThatAcceptsAnIcon = ({ IconComponent }) =&gt; (
  &lt;div&gt;
    &lt;p&gt;Below is the icon component prop I was given:&lt;/p&gt;
    &lt;IconComponent /&gt;
  &lt;/div&gt;
)

export const UnnecessaryAnonymousFunctionComponentsBad = () =&gt; (
  {/* (역주) CircleIcon은 아무런 인자를 받지 않는데도 함수로 감싸서 전달 */}
  &lt;ComponentThatAcceptsAnIcon IconComponent={() =&gt; &lt;CircleIcon /&gt;} /&gt;
)</code></pre>
<p>좋은 사례</p>
<pre><code class="language-javascript">import React from &#39;react&#39;

const CircleIcon = () =&gt; (
  &lt;svg height=&quot;100&quot; width=&quot;100&quot;&gt;
    &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; stroke=&quot;black&quot; stroke-width=&quot;3&quot; fill=&quot;red&quot; /&gt;
  &lt;/svg&gt;
)

const ComponentThatAcceptsAnIcon = ({ IconComponent }) =&gt; (
  &lt;div&gt;
    &lt;p&gt;Below is the icon component prop I was given:&lt;/p&gt;
    &lt;IconComponent /&gt;
  &lt;/div&gt;
)

export const UnnecessaryAnonymousFunctionComponentsGood = () =&gt; (
  {/* 아무런 인자를 받지 않는 CircleIcon를 함수로 싸지 않고 컴포넌트명으로 전달 */}
  &lt;ComponentThatAcceptsAnIcon IconComponent={CircleIcon} /&gt;
)</code></pre>
<hr>
<h2 id="7-undefined-props">7. Undefined Props</h2>
<p><code>undefined props</code>는 제외된다. 만약 어떤 <code>props</code>가 <code>undefined</code>로 제공되어도 컴포넌트 작동에 문제가 없다면, <code>props</code>값으로 <code>undefined</code>를 전달하는 것에 대한 대비책을 걱정할 필요는 없다.</p>
<p> </p>
<h2 id="나쁜-예시-5">나쁜 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const ButtonOne = ({ handleClick }) =&gt; (
  &lt;button onClick={handleClick || undefined}&gt;Click me&lt;/button&gt;
)

const ButtonTwo = ({ handleClick }) =&gt; {
  const noop = () =&gt; {}

  return &lt;button onClick={handleClick || noop}&gt;Click me&lt;/button&gt;
}

export const UndefinedPropsBad = () =&gt; (
  &lt;div&gt;
    &lt;ButtonOne /&gt;
    &lt;ButtonOne handleClick={() =&gt; alert(&#39;Clicked!&#39;)} /&gt;
    &lt;ButtonTwo /&gt;
    &lt;ButtonTwo handleClick={() =&gt; alert(&#39;Clicked!&#39;)} /&gt;
  &lt;/div&gt;
)</code></pre>
<h2 id="좋은-예시-5">좋은 예시</h2>
<pre><code class="language-javascript">import React from &#39;react&#39;

const ButtonOne = ({ handleClick }) =&gt; (
  &lt;button onClick={handleClick}&gt;Click me&lt;/button&gt;
)

export const UndefinedPropsGood = () =&gt; (
  &lt;div&gt;
    &lt;ButtonOne /&gt;
    &lt;ButtonOne handleClick={() =&gt; alert(&#39;Clicked!&#39;)} /&gt;
  &lt;/div&gt;
)</code></pre>
<hr>
<h1 id="8-이전-상태에-의존하는-상태를-갱신할-때는-updater-함수를-전달하자">8. 이전 상태에 의존하는 상태를 갱신할 때는 updater 함수를 전달하자</h1>
<p>만약 새로운 상태가 이전의 상태값에 의존한다면, 이전 <code>state</code>값을 이용한 함수(<code>updater</code> 함수)를 전달해야 한다. <code>react</code> 상태 갱신은 <code>일괄적</code>으로 이뤄지므로 이런 식으로 갱신하지 않으면 예상치 못한 결과가 나올 수 있다.</p>
<p>아래 예시를 직접 구현해보고 <code>Toggle button state 2 times</code> 버튼을 눌러본다면 나쁜 예시에서 의도한대로 상태갱신이 이뤄지지 않음을 알 수 있을 것이다.</p>
<ul>
<li><a href="https://ko.reactjs.org/docs/faq-state.html">관련 react 공식문서</a></li>
</ul>
<p> </p>
<h2 id="나쁜-예시-6">나쁜 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const PreviousStateBad = () =&gt; {
  const [isDisabled, setIsDisabled] = useState(false)

  // (역주) 이전 값에 의존하는 상태갱신 함수에 갱신결과값만을 전달하면
  const toggleButton = () =&gt; setIsDisabled(!isDisabled)

  // (역주) 이 함수의 결과가 정상적으로 작동하지 않음을 알 수 있습니다.
  const toggleButton2Times = () =&gt; {
    for (let i = 0; i &lt; 2; i++) {
      toggleButton()
    }
  }

  return (
    &lt;div&gt;
      &lt;button disabled={isDisabled}&gt;
        I&#39;m {isDisabled ? &#39;disabled&#39; : &#39;enabled&#39;}
      &lt;/button&gt;
      &lt;button onClick={toggleButton}&gt;Toggle button state&lt;/button&gt;
      &lt;button onClick={toggleButton2Times}&gt;Toggle button state 2 times&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<h2 id="좋은-예시-6">좋은 예시</h2>
<pre><code class="language-javascript">import React, { useState } from &#39;react&#39;

export const PreviousStateGood = () =&gt; {
  const [isDisabled, setIsDisabled] = useState(false)

  // (역주) 상태갱신 함수의 인자로 이전 값을 인자로 하는 updater 함수를 전달하면
  const toggleButton = () =&gt; setIsDisabled(isDisabled =&gt; !isDisabled)

  // (역주) 아래 함수가 의도한대로 동작함을 알 수 있습니다.
  const toggleButton2Times = () =&gt; {
    for (let i = 0; i &lt; 2; i++) {
      toggleButton()
    }
  }

  return (
    &lt;div&gt;
      &lt;button disabled={isDisabled}&gt;
        I&#39;m {isDisabled ? &#39;disabled&#39; : &#39;enabled&#39;}
      &lt;/button&gt;
      &lt;button onClick={toggleButton}&gt;Toggle button state&lt;/button&gt;
      &lt;button onClick={toggleButton2Times}&gt;Toggle button state 2 times&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<blockquote>
<p>updater 함수를 전달해야 하는 것이 기본 스펙 문서에도 작성되어 있음에도 불구하고 간과하기 쉬운 부분이었다.</p>
</blockquote>
<hr>
<h1 id="언급하진-않았지만-중요한-것들">언급하진 않았지만 중요한 것들</h1>
<p>아래 내용은 React의 필수법칙은 아니지만 클린코드를 짜는 좋은 방법이 될 것이다. 
해당 문제에 대해서는 자바스크립트 뿐만 아니라 어떤 프로그래밍 언어에서든지 말이다.</p>
<ul>
<li>복잡한 로직을 명확한 이름의 함수로 뽑아내라.</li>
<li>매직넘버(?)는 상수로 뽑아내라.</li>
<li>명확한 이름의 변수를 사용해라</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[무적의 Array.prototype.reduce() 활용법]]></title>
            <link>https://velog.io/@watermincho_96/%EB%8B%A4%EC%96%91%ED%95%9C-Array.prototype.reduce-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@watermincho_96/%EB%8B%A4%EC%96%91%ED%95%9C-Array.prototype.reduce-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 16 Jul 2024 07:52:49 GMT</pubDate>
            <description><![CDATA[<h1 id="기본-문법">기본 문법</h1>
<pre><code class="language-javascript">arr.reduce(callback(accumulator, currentValue, index, array), initialValue);
// 배열.reduce(callback(누적값, 현재값, 인덱스, 요소), 초기값);</code></pre>
<p>reduce는 빈 요소를 제외하고 배열 내 에 존재하는 각 요소에 대해 callback 함수를 한 번씩 실행하며, 네 가지 인수를 받는다.</p>
<ul>
<li><p>accumulator</p>
</li>
<li><p>currentValue</p>
</li>
<li><p>currentIndex</p>
</li>
<li><p>array</p>
</li>
<li><p><strong>callback</strong> : 배열의 각 요소에 대해 실행할 함수, 다음 네 가지 인수를 받는다.</p>
<ul>
<li><strong>accumulator</strong> : 누산기는 콜백의 리턴값을 누적. 만약 콜백의 첫 번째 호출이면서 initialValue를 제공한 경우에는 initialValue의 값</li>
<li><strong>currentValue</strong>: 처리할 현재 요소.</li>
<li><strong>currentIndex</strong> (optional): 처리할 현재 요소의 인덱스. initialValue를 제공한 경우 0, 아니면 1부터 시작</li>
<li><strong>array</strong> (optional): reduce()를 호출한 배열<ul>
<li><strong>initialVlue</strong> (optional): callback의 최초 호출에서 첫 번째 인수에 제공하는 값, 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h1 id="배열-요소의-합산">배열 요소의 합산</h1>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4, 5]

arr.reduce((acc, cur, idx)=&gt;{
  console.log(acc, cur, idx);
  return acc + cur;
}, 0)

// acc cur idx
// 0    1    0
// 1    2    1
// 3    3    2
// 6    4    3
// 10   5    4
</code></pre>
<p>코드와 출력 결과를 살펴보자.
초기값(<code>0</code>)에 누적값(<code>acc</code>)에 배열의 요소(<code>cur</code>)를 <code>더한 값을 return</code> 하도록 하는 코드다.</p>
<p>초기값을 0으로 주었으므로 첫 번째 순서에 0 + 1을 return 한다. 그리고 출력값을 보면 return 된 값은 누적값(<code>acc</code>)로 할당되는 걸 확인할 수 있다. 즉, 더해진 return 값은 순차적으로 누적값(acc)로 전달된다.</p>
<blockquote>
<p>만약 초기값을 정해주지 않았다면 배열의 0번 index부터 시작한다. 아래의 코드와 위의 코드는 의미가 같다.</p>
</blockquote>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4, 5]

arr.reduce((acc, cur, idx)=&gt;{
  console.log(acc, cur, idx);
  return acc + cur;
})

// acc cur idx
// 0    1    0
// 1    2    1
// 3    3    2
// 6    4    3
// 10   5    4
</code></pre>
<hr>
<h1 id="객체-배열에서의-값-합산">객체 배열에서의 값 합산</h1>
<pre><code class="language-javascript">const arr = [{ value: 1 }, { value: 2 }, { value: 3 }];

const result = arr.reduce((acc, cur, idx) =&gt; {
  console.log(idx + `번째 idx`);
  console.log(&#39;acc: &#39; + acc +&#39;, cur: &#39; + cur.value + &quot;\n&quot;);
  return acc + cur.value;
}, 0);

console.log(&#39;result : &#39;, result);

//0번째 idx
//acc: 0, cur: 1

//1번째 idx
//acc: 1, cur: 2

//2번째 idx
//acc: 3, cur: 3

//result :  6</code></pre>
<p>reduce 함수는 객체 배열에서도 사용이 가능하다. </p>
<blockquote>
<p>다만 초기값(acc)을 설정하지 않으면, 초기값이 원시값이 아닌 객체이므로 정상적인 계산이 되지 않는다. 아래 코드를 보자</p>
</blockquote>
<pre><code class="language-javascript">const arr = [{ value: 1 }, { value: 2 }, { value: 3 }];

const result = arr.reduce((acc, cur, idx) =&gt; {
  console.log(idx + `번째 idx`);
  console.log(&#39;acc: &#39; + acc +&#39;, cur: &#39; + cur.value + &quot;\n&quot;);
  return acc + cur.value;
}, 0);

console.log(&#39;result : &#39;, result);

//1번째 idx
//acc: [object Object], cur: 2

//2번째 idx
//acc: [object Object]2, cur: 3

//result : [object Object]23</code></pre>
<hr>
<h1 id="map-구현">map 구현</h1>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4, 5]

const result = arr.reduce((acc, cur) =&gt; {
  acc.push(cur % 2 ? &quot;홀수&quot; : &quot;짝수&quot;);
  return acc;
}, []);

console.log(result);
// [ &#39;홀수&#39;, &#39;짝수&#39;, &#39;홀수&#39;, &#39;짝수&#39;, &#39;홀수&#39; ]
</code></pre>
<p>map함수는 기존 배열과 다른 새 배열(다른 객체)을 리턴한다. reduce도 초기값을 빈 배열로 제공해주고, push와 같은 배열 메서드를 사용하면 map처럼 새로운 배열을  반환할 수 있다.</p>
<hr>
<h1 id="grouping-그룹핑">grouping (그룹핑)</h1>
<pre><code class="language-javascript">const users = {
  id: [&quot;whalstn9&quot;, &quot;dkLim&quot;, &quot;bh123&quot;],
  psWord: [&quot;11111&quot;, &quot;22222&quot;, &quot;33333&quot;],
  name: [&quot;Minsu&quot;, &quot;Deagun&quot;, &quot;Byungho&quot;],
};

const id = users.id; //id 배열
const inputID = id[Math.floor(Math.random() * id.length)]; //임의의 id

const idx = users.id.indexOf(inputID); // 임의 id의 인덱스
const usersKeys = Object.keys(users); // users 객체의 key배열

const userInfo = usersKeys.reduce((newUser, info) =&gt; {
  newUser[info] = users[info][idx];
  return newUser;
}, {});
console.log(userInfo);</code></pre>
<p>먼저 users라는 객체안에 id, psWord, name 값이 저장되어있다. 실제 프로젝트일 경우 당연히 DB를 통해 불러올 것으로 가정한다.</p>
<p>우리는 해당 코드를 통해 일정 id값에 따른 같은 인덱스에 위치하는 psWord와 name을 포함하는 object를 받고자한다. 그에 따라 그 index를 결정하기 위해 indexOf()을 이용해서 랜덤하게 받아온 id값의 index를 idx 변수에 넣어준다.</p>
<p>이제 reduce( )구문을 해석할 건데 앞서 reduce는 array를 순회한다. 즉 array 가 필요할 것이다. 그러므로 users 객체의 key값인 id, psWord, name을 array로 만들어준다. ( Object.keys()이용 )</p>
<pre><code class="language-javascript">usersKeys = [&quot;id&quot;,&quot;psWord&quot;,&quot;name&quot;]</code></pre>
<p>accumulator(누적값)로 <code>newUser</code>를 받고, currentValue(현재값)로 <code>info</code>를 받게한다.
첫번째 순환에서는 acc인 <code>newUser</code>는 초깃값이 된다. 즉 빈 object인 &quot;{ }&quot;이다. 그리고 현재값 <code>info</code>는 &quot;id&quot;일 것이므로</p>
<pre><code class="language-javascript">newUser[info] = users[info][idx];</code></pre>
<p>이 코드를 통해 newUser[&quot;id&quot;] = users[&quot;id&quot;][idx]와 같이 처리될 것이다.
즉, 만약 랜덤하게 받아온 id값이 &quot;Nemoo&quot;라면 첫 번째 순환에선 리턴값으로</p>
<pre><code class="language-javascript">{id : &quot;whalstn9&quot;}</code></pre>
<p>를 가지게 될 것이다. id라는 key를 한번 순회했고 이것이 리턴값인 newUser인 것이다. 즉 리턴값 newUser은 다시 acc부분 newUser 파라미터로가 <code>userKeys</code> 배열을 다 순회할까지 <code>반복</code>한다.</p>
<p>그럼 두 번째 순환에서 리턴 값 <code>newUser</code>은</p>
<pre><code class="language-javascript">{id : &quot;whalstn9&quot;, psWord : &quot;11111&quot;}</code></pre>
<pre><code class="language-javascript">{id : &quot;whalstn9&quot;, psWord : &quot;11111&quot;, name: &quot;Minsu&quot;}</code></pre>
<p>우린 이처럼 reduce()를 이용해 array를 object형태로 필요한 요소만을 grouping 할 수 있게 된 것이다.</p>
<blockquote>
<ul>
<li>요약</li>
</ul>
</blockquote>
<ol>
<li>특정 키의 구분값 지정(id key의 특정 값-&gt; inputID)</li>
<li>특정 구분자의 index추출(inputID의 index)</li>
<li>key값 배열 추출</li>
<li>해당 key 배열을 reduce로 순회</li>
<li>순회 내부의 리턴값을 원본 객체 내 각각 배열의 인덱스에서 추출하여 새로운 key값에 대입</li>
<li>결국 원본 객체와 같은 키속성을 가진 매핑 데이터를 추출할 수 있다.</li>
</ol>
<hr>
<h1 id="counting-카운팅">Counting (카운팅)</h1>
<pre><code class="language-javascript">const users = {
  id: [&quot;minsu&quot;, &quot;minsu&quot;, &quot;minsu&quot;, &quot;deagun&quot;, &quot;huysu&quot;, &quot;huysu&quot;],
};

const id = users.id;

let countedNames = id.reduce((allNames, name) =&gt; {
  if (name in allNames) {
    allNames[name]++;
  } else {
    allNames[name] = 1;
  }
  return allNames;
}, {});

console.log(countedNames);</code></pre>
<p>중복된 이름의 아이디가 존재하는 id의 값에 해당하는 array가 있다.
코드를 해석해보자.</p>
<p>누적값 <code>allNames</code>에 현재값 <code>name</code>이 포함되있다면 <code>value</code>값을 <code>1씩 증가</code>시키고 그렇지않다면 <code>1</code>을 <code>value값으로 가지는 object</code>를 만들게 된다.</p>
<p>이 코드는 if 내부보다는 else 내부를 먼저 고려해야하는데 처음에는 초기값인 빈 object &quot;{ }&quot;에 첫 번째 요소인 &quot;minsu&quot;가 key값으로 들어가게 되고 그때 value값은 1이 될 것이다.</p>
<pre><code class="language-javascript">else {
    allNames[name] = 1;
  }</code></pre>
<p>처음 순환에는 allNames(빈 오브젝트)안에 들어가게 될 &quot;minsu&quot;요소가 하나밖에 없고 즉, <code>현재값 name</code>이 <code>allNames</code>에 포함되지 않은 상태이므로</p>
<pre><code class="language-javascript">{minsu : 1}</code></pre>
<p>다음과 같은 오브젝트를 얻게 되고 <code>return allNames</code>를 통해 다시 <code>reduce의 누적값</code>으로 들어가 순환을 시작한다.</p>
<pre><code class="language-javascript">if (name in allNames) {
    allNames[name]++;
  } </code></pre>
<p>두 번째 순환에는 &quot;minsu&quot; 요소가 이미 오브젝트에 포함되어 있으므로 기존 minsu의 value값 1에서 1증가한 2가 되는 것이다.</p>
<pre><code class="language-javascript">{minsu : 2}</code></pre>
<p>이런식으로 reduce()를 전부 순환하면서 다음과 같은 <code>object</code>를 얻게 된다.</p>
<pre><code class="language-json">{ minsu : 3, deagun: 1, huysu: 2 }</code></pre>
<blockquote>
<ul>
<li>요약</li>
</ul>
</blockquote>
<ol>
<li>객체 배열 따로 추출(id 변수)</li>
<li>id 배열을 reduce로 순회</li>
<li>조건문으로 allNames라는 누적 값에 현재값 name이 포함돼있다면 해당 키의 값에서 +1</li>
<li>포함돼있지 않으면 신규 키값 추가 및 1로 초기화</li>
<li>순회 반복 후 countedNames에 리턴</li>
</ol>
<hr>
<h1 id="removing-duplicated-items-중복-항목-제거">Removing duplicated items (중복 항목 제거)</h1>
<p>앞서 <code>Counting</code>의 연장선 상에서 생각할 수 있는데 만약 유저가 회원가입을 하는 데 있어 이미 다른 유저가 등록한 중복된 아이디를 입력할 경우 해당 id값을 가질 수 없도록 취해 주어야 할 것이다.</p>
<p>간단한 <code>reduce()</code>를 이용한 로직을 통해 중복 항목 제거를 구현해보자.</p>
<h2 id="풀이1-">풀이1 )</h2>
<pre><code class="language-javascript">const users = {
  id: [&quot;minsu&quot;, &quot;minsu&quot;, &quot;minsu&quot;, &quot;deagun&quot;, &quot;huysu&quot;, &quot;huysu&quot;],
};

const id = users.id;

let removeDuplicated = id.reduce((allNames, name, index) =&gt; {
  if (allNames.includes(name)) {
    delete allNames[index];
  } else {
    allNames[index] = name;
  }
  return allNames;
}, []);

const newArr = removeDuplicated.filter((e) =&gt; {
  return e !== &quot;&quot;;
});

console.log(newArr); // [&#39;minsu&#39;, &#39;deagun&#39;, &#39;huysu&#39;]</code></pre>
<p>첫 번째 로직은 작성자 본인이 가장 먼저 작성해보았던 중복 항목 제거 로직이다. <code>reduce()</code>부분과 <code>fillter()</code>부분으로 나누어져있다.</p>
<p>reduce 부분을 해석하자면 카운팅 부분과 마찬가지로 allNames가 name를 포함한다면 중복항목을 제거하고 그렇지 않다면 name을 <code>allNames배열에 포함</code>시켜주는 식으로 진행하였다.</p>
<pre><code class="language-javascript">delete allNames[index];</code></pre>
<p>다음과 같이 delete를 이용해 현재 index에 대한 배열의 요소값을 제거하도록 하였다.
이렇게 <code>reduce()</code> 로직만 구현한 뒤 console창에서 출력한 결과 원하는 결과인 중복 항목 제거에 성공한 줄 알았지만 다음과 같이</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/79631fc7-d658-4196-b860-847a9b128dd3/image.png" alt=""></p>
<p>배열에 중복항목들이 index를 가지고 있는 <code>empty</code>값으로 출력되게 되었다.
empty가 되면서 중복항목들이 제거가 된 것은 맞지만 인덱스를 가지는 배열 요소로써 포함되므로 완전히 중복항목들이 제거되었다고 할 수는 없다.</p>
<p><strong>그렇다면 왜 다음과 같이 empty가 나오게 된 것일까?</strong></p>
<p>delete를 어원 그대로 해석해서 배열요소를 그냥 삭재해준다고 생각할 수도 있지만 <u>delete로 배열의 요소를 제거할 시 배열의 길이는 그대로</u>이게 된다.</p>
<p>즉, <code>delete연산자는 배열 요소의 삭제가 아닌 빈 값으로 변경</code>하기 때문에 삭제보다는 변경에 가까운 개념이다.</p>
<p>결국 <code>filter 구문을 추가</code>함으로써 empty부분을 제거해 완전한 중복항목 제거된 배열을 생성할 수 있었다.</p>
<h2 id="풀이2-">풀이2 )</h2>
<pre><code class="language-javascript">const users = {
  id: [&quot;minsu&quot;, &quot;minsu&quot;, &quot;minsu&quot;, &quot;deagun&quot;, &quot;huysu&quot;, &quot;huysu&quot;],
};

const id = users.id;

let removeDuplicated = id.reduce((allNames, name, index) =&gt; {
  if (allNames.indexOf(name) === -1) {
    allNames.push(name);
  }
  return allNames;
}, []);

console.log(removeDuplicated); // [&#39;minsu&#39;, &#39;deagun&#39;, &#39;huysu&#39;]</code></pre>
<p>이번에는 <code>reduce</code> 만으로 중복요소 제거를 성공한 코드이다. <code>indexOf</code>를 사용해 <code>allNames</code>안에 <code>name</code>이 없을 때 <code>(index ==== -1일 때)</code> <code>push</code>를 사용해 <code>allNames</code>안에 <code>name</code>을 넣어주도록 코드를 작성하였다.</p>
<hr>
<h1 id="함수-합성">함수 합성</h1>
<p>reduce()를 사용해서 데이터를 단계별로 변환하는 함수 파이프라인을 생성할 수 있다.</p>
<pre><code class="language-javascript">const add5 = (x: number): number =&gt; x + 5;
const multiply3 = (x: number): number =&gt; x * 3;
const subtract2 = (x: number): number =&gt; x - 2;

const composedFunctions: ((x: number) =&gt; number)[] = [add5, multiply3, subtract2];

const result: number = composedFunctions.reduce((acc, curr) =&gt; curr(acc), 10);
console.log(result); // Output: 43</code></pre>
<p>이 예시에서, 초기값 10에 차례로 적용하길 원하는 함수 배열을 가지고 있다. (composedFunctions) . <code>reduce()</code> 메서드를 사용해서 각 함수의 결과를 다음 함수의 입력으로 전달하고, 모든 함수를 적용한 결과값을 갖는다.</p>
<hr>
<h1 id="redux같은-상태관리-구현">Redux같은 상태관리 구현</h1>
<p>reduce() 메서드를 간단한 Redux 같은 상태관리 시스템을 구현하는데에 사용할 수 있다.</p>
<pre><code class="language-typescript">interface State {
  count: number;
  todos: string[];
}

interface Action {
  type: string;
  payload?: any;
}

const initialState: State = {
  count: 0,
  todos: [],
};

const actions: Action[] = [
  { type: &#39;INCREMENT_COUNT&#39; },
  { type: &#39;ADD_TODO&#39;, payload: &#39;Learn Array.reduce()&#39; },
  { type: &#39;INCREMENT_COUNT&#39; },
  { type: &#39;ADD_TODO&#39;, payload: &#39;Master TypeScript&#39; },
];

const reducer = (state: State, action: Action): State =&gt; {
  switch (action.type) {
    case &#39;INCREMENT_COUNT&#39;:
      return { ...state, count: state.count + 1 };
    case &#39;ADD_TODO&#39;:
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
};

const finalState: State = actions.reduce(reducer, initialState);
console.log(finalState);
/*
Output:
{
  count: 2,
  todos: [&#39;Learn Array.reduce()&#39;, &#39;Master TypeScript&#39;]
}
*/</code></pre>
<p> 현재 <code>state</code>와 <code>action</code>을 파라미터로 받아서, <code>action type</code>에 따라 새로운 state를 리턴하는 <code>reducer</code> 함수를 정의했다.
<code>reduce()</code> 메서드를 사용해서, 각 <code>action</code>을 <code>state</code>에 적용해서 최종 <code>state</code>에 도달합니다. 이 방식은 mini-Redux를 사용하는 것과 같다.</p>
<hr>
<h1 id="평균-계산">평균 계산</h1>
<pre><code class="language-typescript">const grades: number[] = [85, 90, 92, 88, 95];

const average: number = grades.reduce((acc, curr, index, array) =&gt; {
  acc += curr;
  if (index === array.length - 1) {
    return acc / array.length;
  }
  return acc;
}, 0);

console.log(average); // Output: 90</code></pre>
<p><code>acc</code> 초기값을 0으로 설정했다. 각 요소(성적)을 반복해서 acc에 추가한다. 마지막 요소(<code>array.length</code>를 이용해서 체크)에서 누산기 결과값을 총 개수를 나누어 평균을 계산한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript Prototype(내장 객체) 확장]]></title>
            <link>https://velog.io/@watermincho_96/Javascript-Prototype%EB%82%B4%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%ED%99%95%EC%9E%A5</link>
            <guid>https://velog.io/@watermincho_96/Javascript-Prototype%EB%82%B4%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%ED%99%95%EC%9E%A5</guid>
            <pubDate>Tue, 09 Jul 2024 07:56:01 GMT</pubDate>
            <description><![CDATA[<h1 id="1-모든-객체숫자-문자열-객체-배열-함수-등는-내부-프로퍼티인-prototype를-가지고-있습니다">1. 모든 객체(숫자, 문자열, 객체, 배열, 함수 등)는 내부 프로퍼티인 [[Prototype]]를 가지고 있습니다.</h1>
<p>프로토타입 관련 참고 포스팅(클릭하면 해당 포스팅으로 이동합니다.)
☞ <a href="https://developer-talk.tistory.com/309">[JavaScript]프로토타입(Prototype)이란?</a>
☞ <a href="https://developer-talk.tistory.com/540">[JavaScript]프로토타입 체인(Prototype Chain)</a>
☞ <a href="https://developer-talk.tistory.com/541">[JavaScript]함수의 프로토타입(prototype in function)</a></p>
<p><code>Prototype</code>은 현재 객체의 상위 객체를 참조하며, 이 상위 객체를 프로토타입이라고 말합니다. 그리고 프로토타입 체인으로 상위 객체를 계속해서 참조했을 때, 프로토타입 체인의 종점은 <code>Object.prototype</code> 객체입니다. 즉, <u>JavaScript의 모든 객체는 <code>Object.prototype</code> 객체에 존재하는 프로퍼티와 메서드를 접근할 수 있습니다.</u></p>
<p>JavaScript는 사용자 정의 객체가 아닌 <code>Object.prototype</code> 객체와 같은 표준 프로토타입 객체에도 <u>개발자가 메서드를 추가할 수 있습니다.</u> 따라서, 모든 객체는 Object.prototype 객체에 개발자가 추가한 프로퍼티를 접근할 수 있습니다.</p>
<p>다음 예제는 <code>Object.prototype</code> 객체에 개발자가 추가한 함수를 호출합니다.</p>
<pre><code class="language-javascript">Object.prototype.hello = function() {
  console.log(&#39;hello~&#39;);
}
var arr = [];
var obj = {};
var num = new Number(10);
arr.hello(); // hello~
obj.hello(); // hello~
num.hello(); // hello~</code></pre>
<p><code>Object.prototype</code> 객체의 프로퍼티를 확인해보면 개발자가 추가한 hello 함수를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/watermincho_96/post/8e05194e-41b8-46cf-a344-9a6144a705df/image.png" alt=""></p>
<hr>
<h1 id="2-프로토타입-확장의-장점">2. 프로토타입 확장의 장점</h1>
<p>JavaScript에서 함수를 클래스처럼 구현할 수 있습니다. 다음 예제는 함수를 클래스처럼 구현하고 <code>new</code> 연산자를 사용하여 객체를 생성합니다.</p>
<pre><code class="language-javascript">function UserInfo(name, age) {
  this._name = name;
  this._age = age;
  this.getName = function() {
    return this._name;
  }
  this.setName = function(value) {
    this._name = value;
  }
  this.getAge = function() {
    return this._age;
  }
  this.setAge = function(value) {
    this._age = value;
  }
}
var user1 = new UserInfo(&#39;Bob&#39;, 20);
var user2 = new UserInfo(&#39;Tom&#39;, 24);
var user3 = new UserInfo(&#39;Nick&#39;, 63);</code></pre>
<p>위 예제는 문제없이 정상적으로 동작 하지만, 한 가지 문제가 있습니다. 메모리를 불필요하게 사용한다는 점이죠.</p>
<p>위 예제를 실행하면 다음 그림처럼 세 개의 객체가 생성되고 객체마다 프로퍼티와 함수가 존재합니다.</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/85e837d7-3bdd-463c-b0b0-b6a65ac1c927/image.png" alt="">함수를 객체에 생성하지 않고 공통적으로 사용할 수 있는 영역에 할당하기 위해 프로토타입을 활용할 수 있습니다.</p>
<pre><code class="language-javascript">function UserInfo(name, age) {
  this._name = name;
  this._age = age;
}
UserInfo.prototype.getName = function() {
  return this._name;
}
UserInfo.prototype.setName = function(value) {
  this._name = value;
}
UserInfo.prototype.getAge = function() {
  return this._age;
}
UserInfo.prototype.setAge = function(value) {
  this._age = value;
}
var user1 = new UserInfo(&#39;Bob&#39;, 20);
var user2 = new UserInfo(&#39;Tom&#39;, 24);
var user3 = new UserInfo(&#39;Nick&#39;, 63);</code></pre>
<p><code>UserInfo</code> 함수 객체의 <code>prototype</code> 프로퍼티에 함수를 정의함으로써 객체에는 함수가 생성되지 않고 프로토타입 체인으로 함수를 접근할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/watermincho_96/post/dd94f629-250c-4971-a0e1-a41fef76a708/image.png" alt=""></p>
<p>따라서, 메모리를 효율적으로 사용할 수 있습니다.</p>
<hr>
<h1 id="3-standalone-utility-function">3. Standalone Utility Function</h1>
<p>내장 객체를 확장하는 대신 코드베이스 전체에서 가져오고 사용할 수 있는 <code>유틸리티 함수</code>를 만드는 것이 좋습니다. 
이 접근 방식은 내장된 <code>Array</code> 객체를 수정하지 않고 다른 라이브러리나 개발자 코드와의 잠재적인 충돌을 방지하기 때문에 더 안전하고 유지보수가 쉽습니다.</p>
<p>예를 들어, 유틸리티 함수를 만들어 <code>Array.prototype</code>을 수정하지 않고 배열의 특정 항목을 교체하는 것입니다. 아래 코드는 위 <code>Array.prototype</code> 을 확장하던 코드를 유틸리티 함수로 구현한 코드입니다.</p>
<pre><code class="language-typescript">// utils/array.ts
type NestedArray&lt;T&gt; = Array&lt;T | NestedArray&lt;T&gt;&gt;;
const replace = &lt;T&gt;(array: NestedArray&lt;T&gt;, oldValue: T, newValue: T): NestedArray&lt;T&gt; =&gt; {
  if (Array.isArray(array)) {
    let replaced = false;
    return array.map((item) =&gt; {
      if (!replaced &amp;&amp; item === oldValue) {
        replaced = true;
        return newValue;
      }
      return item;
    });
  }
  return array;
};
const replaceAll = &lt;T&gt;(array: NestedArray&lt;T&gt;, oldValue: T, newValue: T): NestedArray&lt;T&gt; =&gt; {
  if (Array.isArray(array)) {
    return array.map((item) =&gt; {
      if (item === oldValue) {
        return newValue;
      } else if (Array.isArray(item)) {
        return replaceAll(item, oldValue, newValue);
      } else {
        return item;
      }
    });
  }
  return array;
};
export { replace, replaceAll };</code></pre>
<p>이 유틸리티 함수들을 사용하기 위해 가져옵니다.</p>
<pre><code class="language-typescript">// main.ts
import { replace, replaceAll } from &#39;./utils/array&#39;;
const arr1 = [1, [2, 1]];
const arr2 = [10, &#39;2&#39;, &#39;2&#39;, &#39;1&#39;];
console.log(replace(arr1, 2, 3)); // Output: [1, [2, 1]]
console.log(replaceAll(arr1, 2, 3)); // Output: [1, [3, 1]]
console.log(replace(arr2, &#39;2&#39;, &#39;3&#39;)); // Output: [10, &#39;3&#39;, &#39;2&#39;, &#39;1&#39;]
console.log(replaceAll(arr2, &#39;2&#39;, &#39;3&#39;)); // Output: [10, &#39;3&#39;, &#39;3&#39;, &#39;1&#39;]</code></pre>
<p>이렇게 하면 유틸리티 함수가 <code>Array.prototype</code> 을 수정하지 않으며 필요에 따라 가져와 사용할 수 있습니다.</p>
<hr>
<h1 id="4-objectdefineproperty를-사용하여-객체를-보다-안전하게-확장">4. Object.defineProperty()를 사용하여 객체를 보다 안전하게 확장</h1>
<p>그래도 배열 인스턴스에서 호출해 사용하고 싶을 수도 있겠죠. 이럴 땐 <code>Object.defineProperty()</code>을 사용하여 더 안전하게 추가할 수 있습니다. <code>Object.defineProperty()</code> 정적 함수는 객체에 새로운 속성을 직접 정의하거나 이미 존재하는 속성을 수정한 후, 해당 객체를 반환합니다. 객체에 새 속성을 정의하고 속성을 지정하며 실수로 덮어쓰거나 하는 충돌을 방지할 수 있습니다.</p>
<p>아래 코드는 <code>Object.defineProperty()</code> 을 사용해 <code>Array.prototype 에 replace 함수를 추가</code>하는 예시 코드입니다.</p>
<pre><code class="language-typescript">const arr1 = [1, 2, 3, 2];
if (!Array.prototype.replace) {
  Object.defineProperty&lt;unknown[]&gt;(Array.prototype, &#39;replace&#39;, {
    value: function (oldValue: unknown, newValue: unknown) {
      let replaced = false;
      return this.map((item: unknown) =&gt; {
        if (!replaced &amp;&amp; item === oldValue) {
          replaced = true;
          return newValue;
        }
        return item;
      });
    },
    configurable: true, // `true`로 설정하면 나중에 속성을 삭제하거나 변경할 수 있습니다.
  });
}
console.log(arr1.replace(2, 555)); // Output: [1, 555, 3, 2]</code></pre>
<p><code>configurable</code> 속성의 기본값은 <code>false</code>입니다. 따라서 정의된 속성을 재정의하려고 하면 <code>&quot;TypeError: Cannot redefine property: replace&quot;</code> 오류가 발생합니다 . 이 오류는 JavaScript가 <u>속성의 재정의를 허용하지 않기 때문</u>에 발생합니다.</p>
<p>이 오류를 방지하려면 먼저 속성이 <u>이미 존재하는지 확인</u>하면 됩니다. <code>replace</code> 함수가 <code>Array.prototype</code>에 이미 존재하는지 확인하는 것이죠. 존재하지 않는 경우 함수가 정의됩니다. 또한 <code>configurable</code> 속성을 <code>true</code>로 설정하여 속성을 다시 정의할 수 있도록 했습니다.</p>
<h1 id="결론">결론</h1>
<p>개발자로서 JavaScript에서 표준 내장 객체 확장과 관련된 잠재적인 위험을 인지하는 게 중요합니다. 따라서 다음 번에 내장된 프로토타입을 수정하고 싶을 때는 다시 한 번 생각해 보고 더 안전한 대안을 고려해 보는 것을 추천드립니다.</p>
<ul>
<li>프로토타입(=상위 객체)의 prototype 객체에 프로퍼티를 추가하는 것을 프로토타입 확장이라고 말합니다.</li>
<li>프로토타입 확장의 장점은 메모리를 효율적으로 사용할 수 있다는 점입니다.</li>
<li>유틸리티 함수와 보다 안전한 <code>Object.defineProperty()</code> 함수를 사용하면 충돌을 방지하고 코드 유지보수성을 향상시킵니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript this 바인딩]]></title>
            <link>https://velog.io/@watermincho_96/JavaScript-this-%EB%B0%94%EC%9D%B8%EB%94%A9</link>
            <guid>https://velog.io/@watermincho_96/JavaScript-this-%EB%B0%94%EC%9D%B8%EB%94%A9</guid>
            <pubDate>Tue, 09 Jul 2024 06:56:36 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">function foo() {
  const a = 10;
  console.log(this.a);
}
foo(); // ?</code></pre>
<p>처음 자바스크립트를 접할 때는 위 코드의 실행 결과 값이 10이기를 기대한다. 하지만 자바스크립트의 this는 그렇게 호락호락하지가 않다.</p>
<p>자바스크립트에서 this가 참조하는 것은 *함수가 호출되는 방식에 따라 결정되는데, 이것을 <code>&quot;this binding&quot;</code>이라고 한다.</p>
<p>this 바인딩은 다음 4+1가지 규칙만 숙지하고 있으면 어떤 코드를 보더라도 this바인딩이 헷갈리는 일은 없을 것이다.</p>
<ol>
<li><a href="#%EA%B8%B0%EB%B3%B8-%EB%B0%94%EC%9D%B8%EB%94%A9">기본 바인딩</a></li>
<li><a href="#%EC%95%94%EC%8B%9C%EC%A0%81-%EB%B0%94%EC%9D%B8%EB%94%A9">암시적 바인딩</a></li>
<li>[명시적 바인딩]</li>
<li>[new 바인딩]</li>
<li>[화살표 함수(ES6)]</li>
</ol>
<hr>
<h1 id="기본-바인딩">기본 바인딩</h1>
<p>앞으로 설명할 4가지 자바스크립트의 this 바인딩 규칙 중 해당하는 것이 없을 때 적용이 되는 기본 규칙이다. <code>기본 바인딩이 적용될 경우 this는 전역 객체에 바인딩</code>된다.(브라우저 환경인 경우 window, Node.js 환경인 경우 global)</p>
<pre><code class="language-javascript">function foo() {
  const a = 10;
  console.log(this.a);
}
foo(); // undefined</code></pre>
<p>위와 같은 경우 this는 전역객체에 바인딩되고 전역객체에는 a라는 프로퍼티가 없기 때문에 undefined가 출력된다. 전역객체에 a라는 프로퍼티가 있는 경우 해당 a프로퍼티의 값을 출력하게 된다.</p>
<pre><code class="language-javascript">window.a = 10;
function foo() {
  console.log(this.a);
}
foo(); // 10</code></pre>
<p>하지만 엄격모드(strict mode)에서는 기본 바인딩 대상에서 전역객체는 제외된다. 전역객체를 참조해야할 this가 있다면 그 값은 undefined가 된다.</p>
<pre><code class="language-javascript">&#39;use strict&#39;
window.a = 20;
function foo() {
  console.log(this.a);
}
foo(); // TypeError: Cannot read property &#39;a&#39; of undefined</code></pre>
<hr>
<h1 id="암시적-바인딩">암시적 바인딩</h1>
<p>암시적 바인딩이란, 함수가 객체의 메서드로서 호출되는 상황에서 this가 바인딩되는 것을 말한다. 이때 this는 <code>해당 함수를 호출한 객체</code>, 즉 <code>콘텍스트 객체</code>에 바인딩된다.</p>
<pre><code class="language-javascript">const foo = {
  a: 20,
  bar: function () {
    console.log(this.a);
  }
}
foo.bar(); // 20</code></pre>
<p>암시적 바인딩을 사용할 때 발생할 수 있는 문제는 위와 같은 상황에서 함수를 매개변수(콜백)로 넘겨서 실행하는 것이다. 위와 같은 상황에서 다음과 같이 객체에 정의되어있는 함수의 레퍼런스를 매개변수로 전달하는 상황에서 어떤 결과가 나올까?</p>
<pre><code class="language-javascript">setTimeout(foo.bar, 1); // ?</code></pre>
<p><strong>정답은 undefined이다.</strong></p>
<p>이와 같은 결과가 나오는 이유는 결국 <code>setTimeout</code> 함수 안에 전달한 콜백은 <code>bar라는 함수의 레퍼런스일 뿐</code>, <u>foo의 콘텍스트를 가지고 있지 않기 때문</u>이다. 이런 상황을 <u>암시적 바인딩이 소실</u>되었다고 한다.</p>
<p>따라서 다음처럼 <code>setTimeout</code> 내부에서 호출되는 콜백은 foo 객체의 콘텍스트에서 실행되는 것이 아니기 때문에, this는 기본 바인딩이 적용돼서 전역 객체에 바인딩 된다. </p>
<pre><code class="language-javascript">function setTimeout(cb, delay) {
  // delay 만큼 기다린다
  cb(); // 기본 바인딩, bar.foo()가 아닌 foo()와 같다
}</code></pre>
<hr>
<h1 id="명시적-바인딩-explicit-binding">명시적 바인딩 (Explicit Binding)</h1>
<p>자바스크립트의 모든 Function 은 <u><code>call()</code>, <code>apply()</code>, <code>bind()</code>라는 프로토타입 메소드를 가지고있다.</u> 이 3가지 메서드 중 하나를 호출함으로써 <code>this 바인딩을 코드에서 명시</code>하는 것을 명시적 바인딩이라고 한다. 이때 <u>this는 내가 명시한 객체에 바인딩된다.</u></p>
<h2 id="call-apply">call(), apply()</h2>
<pre><code class="language-javascript">const foo = {
  a: 20,
}
function bar() {
  console.log(this.a);
}
bar.call(foo); // 20
bar.apply(foo); // 20</code></pre>
<p>bar의 Function 프로토타입 메서드 call, apply의 매개변수로 바인딩할 객체를 넘겨주면서 <code>bar 함수를 실행할 때의 this 컨텍스트를 foo로 직접 바인딩</code> 해주었다.</p>
<p>call과 apply의 동작은 같지만 두번째 매개변수로 객체의 인자를 전달해주는데(e.g. 생성자의 매개변수), <code>call은 매개변수의 목록</code>, <code>apply는 배열</code>을 받는다는 차이점이 있다.</p>
<h2 id="bind">bind()</h2>
<pre><code class="language-javascript">const foo = {
  a: 20,
}
function bar() {
  console.log(this.a);
}
const bound = bar.bind(foo) //bar.bind({a: 20})
bound(); // 20</code></pre>
<p>bind 메서드는 매개변수로 전달받은 <code>오브젝트</code>로 this가 바인딩된 함수를 반환한다. 이것을 하드 바인딩이라고 하는데 하드 바인딩된 함수는 이후 호출될 때마다 처음 정해진 this 바인딩을 가지고 호출된다.</p>
<hr>
<h1 id="new-바인딩-new-binding">new 바인딩 (new Binding)</h1>
<p>자바스크립트의 new 키워드는 함수를 호출할 때 앞에 new 키워드를 사용하는 것으로 객체를 초기화할 때 사용하는데, 이때 사용되는 함수를 <code>생성자 함수</code>라고 한다.(컨벤션으로 생성자 함수는 <code>대문자로 시작</code>한다)</p>
<p>그리고 생성자 함수에서는** this키워드를 해당 생성자를 이용해 생성할 객체에 대한 참조**로 사용한다.</p>
<pre><code class="language-javascript">function Foo() {
  this.a = 20;
}
const foo = new Foo();
console.log(foo.a); // 20</code></pre>
<p>위 코드에서 <code>Foo</code> 함수가 <code>new</code> 키워드와 함께 호출되는 순간 <code>새로운 객체가 생성</code>되고, 새로 생성된 객체가 <code>this로 바인딩</code>이 된다. 그리고 생성된 객체의 a라는 프로퍼티에 20이라는 값이 할당되고, 해당 객체는 foo라는 변수에 할당된다.</p>
<hr>
<h1 id="화살표-함수">화살표 함수</h1>
<p>ES6에 추가된 화살표 함수(Arrow Function)는 this를 바인딩할 때 앞서 설명한 규칙들이 적용되지 않고, this에 <strong>어휘적 스코프(Lexical scope)</strong>가 적용된다.즉, 화살표 함수를 <code>정의하는 시점</code>의 컨텍스트 객체가 this에 바인딩된다.</p>
<pre><code class="language-javascript">const foo = {
  a: 20,
  bar: function () {
    setTimeout(() =&gt; {
      console.log(this.a);
    }, 1);
  }
}
foo.bar(); // 20</code></pre>
<p>setTimeout의 콜백인 화살표 함수의 선언시에 this는 foo 객체를 가리키고 있기 때문에<code>(렉시컬 스코프)</code>, 콜백이 실행될 때 this는 foo를 가리키게 된다.</p>
<p><strong>화살표 함수로 선언시</strong>에 <u>렉시컬 스코프를 통해 바인딩된 this는 apply, bind등의 함수나 new 함수로 오버라이드할 수 없다.</u> 그렇기때문에 주로 콜백 함수로 사용할 때 유용하다.</p>
<hr>
<h1 id="그-외">그 외</h1>
<p><strong>_this, that, self</strong></p>
<p><code>apply</code>, <code>call</code>, <code>bind</code>같은 바인딩 메소드, 또는 화살표 함수가 나오기 전에는 골치아픈 this 바인딩을 렉시컬 스코프를 이용해서 해결하였다. 호출시 결정되는 this를 렉시컬 스코프를 이용해 선언시에 정해주는 효과를 주는 것이다.</p>
<p>먼저 확인했던 것처럼, 아래와 같은 상황에서 setTimeout에 콜백으로 넘겨진 함수의 this는 setTimeout에 의해 <code>실행될때</code> 전역객체에 바인딩이 된다.</p>
<pre><code class="language-javascript">const foo = {
  a: 20,
  bar: function () {
    setTimeout(function () {
      console.log(this.a);
    }, 1);
  }
}
foo.bar(); // undefined
</code></pre>
<p>이 때, setTimeout의 콜백 내부에서 해당 setTimeout을 호출한 객체에 접근하기 위해서 어떻게 해야할까?</p>
<pre><code class="language-javascript">const foo = {
  a: 20,
  bar: function () {
    const _this = this;
    setTimeout(function () {
      console.log(_this.a);
    }, 1);
  }
}
foo.bar(); // 20</code></pre>
<p>위와같이 bar메서드 실행시에 해당 메서드를 호출한 객체(foo)에 대한 참조를 _this라는 변수에 할당하였다. 그러면 setTimeout의 콜백이 <code>실행될때</code> 렉시컬 스코프에 의해 _this에 접근할 수가 있고, _this를 통해 setTimeout을 호출한 객체(foo)에 접근할 수 있다.</p>
<p>이런 코드는 예전 선조들의 레거시 코드에서 어렵지 않게 찾아볼 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>