<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>seonye-98.log</title>
        <link>https://velog.io/</link>
        <description>의미있는 훈련 기록 저장소</description>
        <lastBuildDate>Sat, 30 Dec 2023 10:42:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>seonye-98.log</title>
            <url>https://velog.velcdn.com/images/seonye-98/profile/7c3594ee-b38b-471e-9009-3795f1cd5de8/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. seonye-98.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seonye-98" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[트러블슈팅] Jest가 twin.macro 코드를 트랜스파일링하지 못하는 이슈(Vite+React+TypeScript+Twin+Jest)]]></title>
            <link>https://velog.io/@seonye-98/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Jest%EA%B0%80-twin.macro-%EB%AC%B8%EB%B2%95%EC%9D%84-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EB%A7%81%ED%95%98%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@seonye-98/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Jest%EA%B0%80-twin.macro-%EB%AC%B8%EB%B2%95%EC%9D%84-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EB%A7%81%ED%95%98%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Sat, 30 Dec 2023 10:42:00 GMT</pubDate>
            <description><![CDATA[<h3 id="이슈가-발생한-환경">이슈가 발생한 환경</h3>
<p>빌드 도구 : &quot;vite&quot;: &quot;^4.4.5&quot;
프로젝트 라이브러리 : &quot;react&quot;: &quot;^18.2.0&quot;
테스트 라이브러리 : &quot;jest&quot;: &quot;^29.7.0&quot;, &quot;@testing-library/react&quot;: &quot;^14.1.0&quot;
스타일 라이브러리 : &quot;twin.macro&quot;: &quot;^3.4.0&quot;, &quot;tailwindcss&quot;: &quot;^3.3.5&quot;, &quot;@emotion/styled&quot;: &quot;^11.11.0&quot;</p>
<h3 id="설명">설명</h3>
<p>방탈출 프로젝트에서 초기에 emotion을 스타일 라이브러리로 사용하기로 했었는데, 프로젝트 기간이 6주밖에 되지 않아서 디자인을 픽셀 단위로 상세하게 완성하기 힘들었다. </p>
<p>디자인 토큰을 만들어 글꼴 크기, 마진/패딩 크기 등에 약간의 제약을 주기 위해 tailwind를 도입하려했지만, 기존에 css-in-js 라이브러리를 사용하기로 선택했었기 때문에 둘을 같이 사용할 수 있는 twin.macro를 도입하게 되었다.</p>
<p>그런데, twin.macro를 도입한 후 프로젝트에서 공통 컴포넌트인 버튼, 라벨을 개발했는데, 공통 컴포넌트를 사용한 컴포넌트의 테스트 코드의 경우 vite 설정에서 분명 다음과 같이 바벨 설정을 해주었는데도 기존에 통과하던 테스트 코드를 통과하지 못하고 에러가 발생했다.</p>
<pre><code class="language-ts">  plugins: [
    react({
      babel: {
        plugins: [&#39;babel-plugin-macros&#39;, &#39;@emotion/babel-plugin&#39;],
      },
    }),
  ],</code></pre>
<p>발생한 에러는 다음과 같다.
<img src="https://velog.velcdn.com/images/seonye-98/post/eca7dd88-239c-4249-adac-489f8c018b7b/image.png" alt=""></p>
<p>우리 프로젝트에서, PR을 올리는 경우 git actions에서 테스트와 빌드를 모두 통과해야 merge할 수 있는 플로우를 설정해두었기 때문에, 해당 이슈를 빠르게 고쳐야했었다.</p>
<h3 id="해결과정">해결과정</h3>
<p>먼저, 위의 에러사진에서 보면, <code>you don&#39;t have the babel plugin &quot;babel-plugin-macro&quot; configured correctly.</code>라고 되어있는걸 볼 수 있다.
vite 설정에 플러그인을 주입했어도, jest에서는 이를 인식하지 못해서 트랜스파일링하지 못한다는 얘기인 것 같았다.</p>
<p><a href="https://jestjs.io/docs/getting-started#using-webpack">jest 공식 홈페이지</a>를 보면, webpack이나 parcel과 호환성이 좋지만, vite는 위의 번들 라이브러리와 달리, 플러그인 시스템 작동 방식으로 인해 jest를 완벽하게 지원하지 않는다고 한다.
이에 대한 해결 방식으로 vite-jest 패키지를 설치해서 <code>jest test</code>대신 <code>vite-jest test</code>를 사용하는 것을 제안하고 있다.</p>
<p>하지만, 공식 홈페이지에서도 위의 해결 방식을 제안하면서 <a href="https://github.com/sodatea/vite-jest/tree/main/packages/vite-jest#limitations-and-differences-with-commonjs-tests">vite-jest의 한계점</a>을 읽어보기를 권유하고 있다. 요약해보면, jest.mock 호출의 호이스팅은 esm에서 작동하지 않아, jest.unstable_mockModule을 통해 지원한다고 한다.
이는, 아직 개발중인 API라 버그 발생 가능성이 있어보여 vite-jest 사용하는 건 적합하지 않다고 생각했다.</p>
<p>다른 해결 방법을 찾기 위해 twin.macro 이슈 탭을 보다가 <a href="https://github.com/ben-rogerson/twin.macro/issues/147">관련 이슈</a>를 찾을 수 있었다.</p>
<p>위의 이슈 페이지 하단에 관련 예시를 twin.macro 개발자가 링크를 걸어두었다.
하지만, 해당 링크에서는 react가 아닌 next를 사용하고 있었고, TypeScript도 사용하지 않고 있어서 jest에서 바벨을 사용하기 위해 ts-jest가 아닌 babel-jest를 사용했을때 타입스크립트를 트랜스파일링하지 못했다. 
하지만, TypeScript를 트랜스파일링하는 바벨 플러그인이 있기 때문에 babel-jest를 사용하돼 우리 프로젝트에 맞는 프리셋, 플러그인을 설정으로 넣어주면 되겠다고 생각했다.</p>
<h3 id="해결">해결</h3>
<p>그래서 우리가 필요한 preset, plugin이 뭘까?</p>
<p>React를 사용하니까 <a href="https://jestjs.io/docs/tutorial-react">jest 공식 홈페이지: tutorial-react</a>에서 관련 preset을 찾았다.</p>
<pre><code class="language-tsx">//babel.config.js
module.exports = {
  presets: [
    &#39;@babel/preset-env&#39;,
    [&#39;@babel/preset-react&#39;, {runtime: &#39;automatic&#39;}],
  ],
};</code></pre>
<p>TypeScript를 트랜스파일링하기 위해 ts-jest말고 babel-jest를 사용할 때 해결법을 <a href="https://jestjs.io/docs/getting-started#using-typescript">jest 공식 홈페이지: getting-started</a>에서 찾았다.</p>
<pre><code class="language-tsx">//babel.config.js
module.exports = {
  presets: [
    [&#39;@babel/preset-env&#39;, {targets: {node: &#39;current&#39;}}],
    &#39;@babel/preset-typescript&#39;,
  ],
};</code></pre>
<p>CSS로 twin.macro를 사용하므로 vite config에서도 사용했던 플러그인인 babel-plugin-macros와 같이 사용할 emotion 플러그인 @emtion/babel-plugin까지 추가해주면 된다.</p>
<p>따라서, 필요한 preset과 plugin은 다음과 같다.</p>
<pre><code class="language-tsx">const babelConfigTwin = {
  presets: [
    &#39;@babel/preset-env&#39;, //es6 plugin을 모두 설치
    [&#39;@babel/preset-react&#39;, { runtime: &#39;automatic&#39; }],
    &#39;@babel/preset-typescript&#39;,
  ],
  plugins: [&#39;babel-plugin-macros&#39;, &#39;@emotion/babel-plugin&#39;],
};</code></pre>
<p>위의 config를 jestConfig의 transform에서 option으로 사용하면 된다.</p>
<pre><code class="language-tsx">const jestConfig = {
//생략
  transform: {
    &#39;^.+\\.(js|jsx|ts|tsx|mjs)$&#39;: [&#39;babel-jest&#39;, babelConfigTwin],
  },
//생략
};</code></pre>
<p>이렇게 설정하고 테스트 코드를 돌려보니 통과하는 것을 확인할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/af4edb2a-e3f0-4436-951a-79fec99c4e79/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅] yarn v1에서 storybook이 호환되지 않는 이슈]]></title>
            <link>https://velog.io/@seonye-98/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-yarn-v1%EC%97%90%EC%84%9C-storybook%EC%9D%B4-%ED%98%B8%ED%99%98%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@seonye-98/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-yarn-v1%EC%97%90%EC%84%9C-storybook%EC%9D%B4-%ED%98%B8%ED%99%98%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Sat, 23 Dec 2023 15:41:46 GMT</pubDate>
            <description><![CDATA[<h3 id="이슈가-발생한-환경">이슈가 발생한 환경</h3>
<p>yarn 버전 : 1.22.19
storybook 버전 : 7.6.6</p>
<h3 id="설명">설명</h3>
<p>처음에 스토리북을 설치할 때, 프로젝트 빌드 도구로 vite를 사용하고 있었기 때문에, storybook에서도 통일하고자 vite로 빌더를 지정해 다음과 같이 설치했다.
빌더를 지정하지 않는 경우 기본적으로 webpack5를 빌더로 설치한다고 한다.
<a href="https://storybook.js.org/docs/builders">storybook builders</a></p>
<pre><code class="language-bash">npx storybook@latest init --builder vite</code></pre>
<p>그런데, 스토리북을 실행하니까 다음과 같이 오류가 발생했다.
<img src="https://velog.velcdn.com/images/seonye-98/post/4b7eb87d-55ef-4196-bb8c-7728c293c178/image.png" alt=""></p>
<p>사진에서 관련 이슈를 <a href="https://github.com/storybookjs/storybook/issues/22431">링크</a>해둬서 들어가보니까, storybook에 포함된 의존성 패키지중 jackspeak이 2.1.2 버전이상으로 업데이트되면서 yarn v1과 하위 호환성이 사라졌다고 한다.</p>
<p>jackspeak 라이브러리를 설치한적이 없는데, node_modules에서 해당 라이브러리가 설치된 것을 찾아볼 수 있었다.</p>
<p>이는, npm과 yarn v1로 라이브러리를 설치했을때, node_modules 디렉토리 구조를 어떻게 형성하는지를 살펴보면 답을 찾을 수 있었다.</p>
<p>npm 및 yarn v1에서는 node_modules의 크기를 최소화하기 위해 여러 라이브러리내에서 사용되는 의존성 패키지를 상위로 끌어올리는 호이스팅 기법을 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/c492df62-c18c-429f-95a2-0cf7cb823294/image.png" alt=""></p>
<p>따라서, storybook의 의존성 패키지인 jackspeak이 설치되면서 node_modules에 위치된 것이다.</p>
<h3 id="해결과정">해결과정</h3>
<p>해결방법은 두 가지이다.</p>
<ol>
<li>yarn v1을 yarn v3(yarn berry)로 마이그레이션한다.
꼭, yarn v3가 아니라 다른 패키지매니저를 사용해서 해결할 수 있다.
하지만, 위에서 언급했듯이 npm은 yarn v1과 의존성 관리 시스템이 유사하고 많이 개선됐지만 속도 측면에서 yarn보다는 느리기 때문에 pnpm이나 yarn v3으로 마이그레이션하는 것이 좋을 것 같다.</li>
</ol>
<ol start="2">
<li>하위 의존성 패키지인 jackspeak의 버전을 2.1.1로 지정한 후, yarn.lock파일을 지우고 다시 설치한다.
package.json에 resolutions 속성으로 하위 의존성 패키지의 버전을 지정할 수 있다.</li>
</ol>
<pre><code> &quot;resolutions&quot;: {
    &quot;jackspeak&quot;: &quot;2.1.1&quot;
  },</code></pre><p>2번 방법을 먼저 시도해봤을때, storybook이 잘 실행되는 것을 확인했다. 
하지만, 해당 에러 하단에 다음과 같은 에러가 덧붙여져 있었다.</p>
<pre><code>Error [ERR_REQUIRE_ESM]: require() of ES Module
C:\Users\Desktop\practice2\node_modules\string-width\index.js 
from C:\Users\Desktop\practice2\node_modules\cli-table3\src\utils.js not supported.
Instead change the require of index.js in C:\Users\Desktop\practice2\node_modules\cli-table3\src\utils.js 
to a dynamic import() which is available in all CommonJS modules.</code></pre><p>jackspeak 버전이 업그레이드 되면서 commonjs방식을 지원하지 않아서 생기는 오류인 것 같다.
이 <a href="https://github.com/isaacs/jackspeak/issues/5">이슈</a> 마지막에 보면, jackspeak 개발자가 yarn v1의 resolution algorithm에 문제가 있는 것 같고, 많은 패키지들이 cjs모듈 방식을 지원하지 않는 방향으로 가고 있다고 이런 이슈는 앞으로 빈번하게 발생할 것으로 예상한다고 한다.
따라서, 이 방법은 근본적인 해결 방법은 아닌 것 같다.</p>
<h3 id="해결">해결</h3>
<p>일단, 해결방법 2번을 사용해서 해결했다. 이유는 git actions에 dev브랜치에 push하는 경우 스토리북이 크로마틱에 자동으로 배포되는 스크립트를 작성해놨는데, 빠른 배포를 위해 의존성 캐싱도 사용하고 있다.
다른 패키지 매니저로 변경하게 되면 해당 스크립트도 수정해야하므로, 임시로 해결해놓고 이후에 yarn berry나 pnpm으로 마이그레이션 할 예정이다.</p>
<h3 id="참고">참고</h3>
<p><a href="https://toss.tech/article/node-modules-and-yarn-berry">https://toss.tech/article/node-modules-and-yarn-berry</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[npm 라이브러리 배포] React 컴포넌트 라이브러리 배포전 테스트 및 배포하기]]></title>
            <link>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EC%A0%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%8F-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EC%A0%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%8F-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 23 Dec 2023 10:54:20 GMT</pubDate>
            <description><![CDATA[<p>이전 포스팅에 이어서 작성합니다.
<a href="https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0yarnViteReactTypeScriptStorybook">[npm 라이브러리 배포] 개발환경 세팅하기(yarn+Vite+React+TypeScript+Emotion+Storybook)</a>
<a href="https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EC%A0%84-%EC%84%A4%EC%A0%95">[npm 라이브러리 배포] React 컴포넌트 라이브러리 배포전 설정</a></p>
<hr>
<h3 id="1-테스트-컴포넌트-만들기">1. 테스트 컴포넌트 만들기</h3>
<p>라이브러리로 배포할 컴포넌트 파일들을 lib폴더 내에 생성한다.
먼저, TestComponent.tsx파일에 간단한 컴포넌트를 만들어준다.</p>
<pre><code class="language-ts">const TestComponent = () =&gt; {
  return (
    &lt;div
      css={{
        backgroundColor: &#39;red&#39;,
        fontSize: &#39;large&#39;,
      }}&gt;
      Test Component
    &lt;/div&gt;
  );
};

export default TestComponent;
</code></pre>
<p>index.ts에서 컴포넌트를 export 해준다.</p>
<pre><code class="language-ts">export { default as TestComponent } from &#39;./components/TestComponent&#39;;
</code></pre>
<p>최종적으로 디렉토리 구조는 다음과 같다.</p>
<pre><code>├─ src
│  ├─ lib
│  │  ├─ components
│  │  │  ├─ TestComponent.stories.tsx
│  │  │  └─ TestComponent.tsx
│  │  └─ index.ts
│  ├─ main.tsx
│  └─ vite-env.d.ts</code></pre><p>브라우저로 띄워보면, 다음과 같이 렌더링 된다.
<img src="https://velog.velcdn.com/images/seonye-98/post/bdad0a2e-330a-4d74-af5b-264b22ea9d26/image.png" alt=""></p>
<hr>
<h3 id="2-로컬에서-테스트하기">2. 로컬에서 테스트하기</h3>
<p>매번, npm에 배포하게 되면 불필요하게 버전을 변경해야하므로 로컬에서 테스트하는 단계가 필요하다.</p>
<p>먼저 package.json에 scripts를 추가한다.</p>
<pre><code class="language-json">  &quot;scripts&quot;: {
    &quot;prepare&quot;: &quot;rm -rf dist &amp;&amp; tsc &amp;&amp; vite build&quot;,
    &quot;clean:package&quot;: &quot;yarn remove react-carousel-image-optimized &amp;&amp; yarn cache clean &amp;&amp; rm -rf ./react-carousel-image-optimized-v0.0.1.tgz&quot;,
    &quot;add:package&quot;: &quot;yarn add ./seonye-test-v0.1.0.tgz&quot;
  },
</code></pre>
<ul>
<li>prepare: 기존의 빌드된 파일을 지우고 다시 빌드 (npm에 배포하기전 해당 스크립트가 실행되므로 필수)</li>
<li>clean:package: 테스트 중인 라이브러리를 지우고, 패키징된 라이브러리 파일도 삭제</li>
<li>add:package: 패키징된 라이브러리를 로컬에 설치</li>
</ul>
<p>먼저, prepare로 라이브러리를 빌드하고, yarn pack 명령어를 실행하면 package.json에 지정된 name과 version을 파일명으로 압축파일이 생긴다.
만약 name이 @seonye/test, version이 0.1.0이면 다음과 같이 압축파일이 생긴다.
<img src="https://velog.velcdn.com/images/seonye-98/post/de3ce43d-8f37-4a7c-8170-f589f73aa8bb/image.png" alt=""></p>
<p>이제 로컬에서 라이브러리를 설치하기위해 add:package 명령어를 실행하면 다음과 같이 package.json에 라이브러리가 설치된 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/848aae73-fe20-40ee-b1ec-04cbd5e78437/image.png" alt=""></p>
<p>이제, main.tsx에서 컴포넌트를 import해서 사용할 수 있다.</p>
<pre><code class="language-tsx">import { TestComponent } from &#39;@seonye/test&#39;;</code></pre>
<p>라이브러리를 지우고, 다시 빌드해서 설치하는 경우 다음과 같이 명령어를 순서대로 실행한다.</p>
<pre><code class="language-bash">yarn clean:pacakge
yarn prepare
yarn add:package</code></pre>
<hr>
<h3 id="3-npm에-배포하기">3. npm에 배포하기</h3>
<p>사실 여기까지왔다면 배포하는건 정말 쉬웠다.
먼저, .npmignore파일을 만들어 저장소에 올라가지 않아도 되는 파일을 제외시켜준다.</p>
<pre><code>index.html                        
/src    
tsconfig.json            
tsconfig.node.json       
vite.config.ts
.eslintrc.cjs
yarn.lock
node_modules
.gitignore
.storybook
*.tgz</code></pre><p>그 다음 npm에 로그인하고, 배포 명령어를 실행한다.</p>
<pre><code>npm login
npm publish --access=public</code></pre><p>여기서, access를 public으로 지정하지 않으면 private package로 간주해 무료 이용자의 경우 에러가 난다.
정상적으로 배포됐다면, 터미널에 다음과 같은 화면을 볼 수 있다.
<img src="https://velog.velcdn.com/images/seonye-98/post/d32fc8b5-ef6e-4ab5-a602-d2da90b85c60/image.png" alt="">
그리고 npm 사이트에서 내가 배포한 라이브러리를 검색해서 확인해 볼 수 있다.
<img src="https://velog.velcdn.com/images/seonye-98/post/d3a01510-2627-430d-8a93-26229ca26421/image.png" alt=""></p>
<p>이제 내가 만든 패키지명으로 라이브러리를 설치할 수 있다. 처음에 CommonJS와 ES모듈 방식을 모두 지원하게 설정해서 설치하고 다음과 같이 불러올 수 있다.</p>
<pre><code class="language-bash">yarn add @seonye/test</code></pre>
<pre><code class="language-ts">//commonjs
const { TestComponent } = require(&#39;@seonye/test&#39;);
//esm
import { TestComponent } from &#39;@seonye/test&#39;;
</code></pre>
<p>npm 배포 테스트를 위해 임의로 배포를 한 라이브러리라서 이후에 삭제를 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[npm 라이브러리 배포] React 컴포넌트 라이브러리 배포전 설정]]></title>
            <link>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EC%A0%84-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EC%A0%84-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sat, 23 Dec 2023 06:43:30 GMT</pubDate>
            <description><![CDATA[<p>이전 포스팅에 이어서 작성합니다.
<a href="https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0yarnViteReactTypeScriptStorybook">[npm 라이브러리 배포] 개발환경 세팅하기(yarn+Vite+React+TypeScript+Emotion+Storybook)</a></p>
<hr>
<h3 id="1-viteconfigts-수정하기">1. vite.config.ts 수정하기</h3>
<ul>
<li>TypeScript를 사용해서 라이브러리 프로젝트를 개발하려면 타입 정의 파일(.d.ts)를 생성해줘야하므로 vite-plugin-dts 플러그인을 설치해줘야한다.</li>
<li>insertTypesEntry옵션을 사용해서 package.json types속성에 지정된 위치에 타입 정의 파일을 생성할 수 있게 한다.</li>
</ul>
<pre><code class="language-bash">yarn add -D vite-plugin-dts</code></pre>
<h4 id="라이브러리-빌드-설정">라이브러리 빌드 설정</h4>
<ol>
<li>lib 속성</li>
</ol>
<ul>
<li>entry: 라이브러리 진입점, 제공하고자하는 컴포넌트를 모두 export하는 부분</li>
<li>name: 라이브러리 이름</li>
<li>formats: 라이브러리를 어떤 형식으로 빌드할지 지정, ES모듈과 CommonJS 형식으로 빌드</li>
<li>fileName: 출력 파일 이름 지정</li>
</ul>
<ol start="2">
<li>rollupOptions 속성</li>
</ol>
<ul>
<li>external: 라이브러리에 포함하지 않을 dependency 명시</li>
<li>output : 번들 출력에 대한 옵션 설정</li>
<li>globals: 라이브러리 외부에 존재하는 dependency를 위해 번들링 시 사용될 전역 변수 명시</li>
<li>banner: 번들 앞에 문자열을 추가함, &quot;use client&quot;;를 추가해 컴포넌트의 모든 사용을 클라이언트 컴포넌트로 보장 (리액트 서버 컴포넌트가 나온 시점에서 명시하는게 더 안전할 것 같다고 판단)
<a href="https://react.dev/reference/react/use-client#when-to-use-use-client">use client 공식문서</a></li>
<li>interop: 외부 의존성과의 모듈 간 상호 작용 방식 설정 (기본 모드에서 Node.js 동작 방식을 따르며, TypeScript의 esModuleInterop 동작과 다르므로 auto로 설정하여 ES모듈과 CommonJS모듈 간의 상호 운용성 문제를 줄임)
<a href="https://rollupjs.org/configuration-options/#output-interop">output.interop 공식문서</a></li>
</ul>
<details>
  <summary><b>vite.config.ts 코드</b></summary>
<div markdown="1">

<pre><code class="language-ts">import * as path from &#39;path&#39;;

import { defineConfig } from &#39;vite&#39;;
import react from &#39;@vitejs/plugin-react&#39;;
import dts from &#39;vite-plugin-dts&#39;;

export default defineConfig({
  assetsInclude: [&#39;/sb-preview/runtime.js&#39;],
  build: {
    lib: {
      entry: path.resolve(__dirname, &#39;src/lib/index.ts&#39;),
      name: &#39;react-carousel-image-optimized&#39;,
      formats: [&#39;es&#39;, &#39;cjs&#39;],
      fileName: (format) =&gt; `index.${format}.js`,
    },
    rollupOptions: {
      external: [&#39;react&#39;, &#39;react-dom&#39;, &#39;**/*.stories.tsx&#39;],
      output: {
        globals: {
          react: &#39;React&#39;,
          &#39;react-dom&#39;: &#39;ReactDOM&#39;,
        },
        banner: &#39;&quot;use client&quot;;&#39;,
        interop: &#39;auto&#39;,
      },
    },
    commonjsOptions: {
      esmExternals: [&#39;react&#39;],
    },
  },
  plugins: [
    react({
      jsxImportSource: &#39;@emotion/react&#39;,
      babel: {
        plugins: [&#39;@emotion/babel-plugin&#39;],
      },
    }),
    dts({
      insertTypesEntry: true,
    }),
  ],
});
</code></pre>
</div>
</details>



<hr>
<h3 id="2-packagejson-수정하기">2. package.json 수정하기</h3>
<p>CommonJS와 ES모듈 두 가지 방식을 모두 지원하기 위해, exports에서 require을 사용하는 경우와 import를 사용하는 경우 어떤 파일에서 불러올 지 지정해준다.</p>
<ul>
<li>main: CommonJS형식으로 빌드된 메인 파일의 경로</li>
<li>module: ECMAScript모듈 형식으로 사용하는 환경에서 사용되는 진입 지점 파일의 경로</li>
<li>types: TypeScript 프로젝트에서 사용되는 타입 정의 파일의 경로</li>
<li>exports: node_modules 조회 또는 자체 이름에 대한 자체 참조를 통해 로드된 이름으로 가져올 때 패키지의 진입점을 정의, 환경별로 다른 패키지 진입점을 정의</li>
</ul>
<pre><code class="language-json">{
  //패킹된 라이브러리의 이름을 결정
  &quot;name&quot;: &quot;라이브러리 이름&quot;, 
  &quot;description&quot;: &quot;라이브러리 설명&quot;,
  &quot;keywords&quot;: [
    관련 키워드
  ],
  &quot;author&quot;: {
    &quot;name&quot;: &quot;이름&quot;,
    &quot;email&quot;: &quot;이메일&quot;
  },
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git주소&quot;
  },
  &quot;private&quot;: false,
  &quot;license&quot;: &quot;MIT&quot;, //라이브러리 라이센스
  &quot;version&quot;: &quot;0.0.1&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;main&quot;: &quot;dist/index.cjs.js&quot;,
  &quot;module&quot;: &quot;dist/index.es.js&quot;,
  &quot;types&quot;: &quot;dist/index.d.ts&quot;,
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;module&quot;: &quot;./dist/index.es.js&quot;,
      &quot;import&quot;: &quot;./dist/index.es.js&quot;,
      &quot;default&quot;: &quot;./dist/index.cjs.js&quot;
    }
  },
}</code></pre>
<p>참고 문서
    <a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json">npm package.json 공식문서</a>
<a href="https://nodejs.org/docs/latest-v20.x/api/packages.html#exports">Node.js 공식문서</a></p>
<hr>
<h3 id="3-tsconfigjson-수정하기">3. tsconfig.json 수정하기</h3>
<p>최종적으로 빌드됐을 때, 배포에 필요한 파일과 폴더를 지정해준다.(TypeScript 컴파일러가 JavaScript 코드로 변환할 소스 파일을 지정하는 것)
나같은 경우 src/lib에 컴포넌트 라이브러리로 배포할 파일을 넣었고, lib폴더 내에 테스트 코드를 제외시켜줬다.</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
//생략
  },
  &quot;include&quot;: [&quot;src/lib&quot;],
  &quot;exclude&quot;: [&quot;**/*.stories.tsx&quot;],
  &quot;references&quot;: [{ &quot;path&quot;: &quot;./tsconfig.node.json&quot; }]
}</code></pre>
<hr>
<p>여기까지 하면 라이브러리를 배포할 준비는 모두 마쳤고, 다음 포스팅에서는 테스트 컴포넌트를 만들어서 로컬에서 테스트해보고, 실제 npm registry에도 배포하는 과정을 기록할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[npm 라이브러리 배포] 개발환경 세팅하기(yarn+Vite+React+TypeScript+Emotion+Storybook)]]></title>
            <link>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0yarnViteReactTypeScriptStorybook</link>
            <guid>https://velog.io/@seonye-98/npm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%B0%ED%8F%AC%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0yarnViteReactTypeScriptStorybook</guid>
            <pubDate>Fri, 22 Dec 2023 08:58:57 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p><a href="https://github.com/boostcampwm2023/web03-LockFestival">방탈출 프로젝트</a>
프로젝트 메인페이지에 react-slick 라이브러리를 사용하고 있는데,직접 캐러셀을 구현하고 사용해보면서 불편했던 점도 개선해보고 싶어서 나만의 캐러셀 컴포넌트를 만들어보기로 했다.</p>
<h2 id="개발환경-세팅하기">개발환경 세팅하기</h2>
<h3 id="1-프로젝트-만들기">1. 프로젝트 만들기</h3>
<p>vite로 쉽게 React, Typescript를 사용하는 프로젝트를 만들 수 있다. </p>
<pre><code class="language-bash">yarn create vite . --template react-ts</code></pre>
<hr>
<h3 id="2-스토리북-설치하기">2. 스토리북 설치하기</h3>
<p>yarn으로 의존성 패키지들을 설치해주고 라이브러리 개발하는데에 컴포넌트 테스트 도구로 storybook을 설치해보자.</p>
<pre><code class="language-bash">npx storybook@latest init --builder vite</code></pre>
<p>그러면, 스토리북이 잘 실행은 되는데 에러가 난다.
<img src="https://velog.velcdn.com/images/seonye-98/post/a6d0352c-74dd-4c93-87ae-bbab17d4c9c1/image.png" alt=""></p>
<p>vite5로 버전이 바뀌면서 생긴 오류라고 하는데, 정확한 원인은 잘 모르겠어서 관련 이슈를 보고 해결했다.
vite.config.ts에서 다음 코드를 추가해준다.</p>
<pre><code class="language-ts">export default defineConfig({
  assetsInclude: [&#39;/sb-preview/runtime.js&#39;],
  //--- 생략 ---
});</code></pre>
<p><a href="https://github.com/vitejs/vite/issues/15374">참고 이슈</a></p>
<p>다시 yarn storybook을 해보면 다음과 같이 깔끔하게 실행되는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/fc0b8e02-9807-49aa-95d4-bfb8bb1f729e/image.png" alt=""></p>
<hr>
<h3 id="3-불필요한-파일-폴더-제거하기">3. 불필요한 파일, 폴더 제거하기</h3>
<p>라이브러리 개발을 위한 프로젝트기 때문에, src폴더의 main.tsx만 남기고 전부 삭제해준다.
public폴더와 index.html에서 favicon 설정하는 link태그도 삭제해준다.
스토리북 설치시 같이 설치되는 src/stories도 삭제해야 하는데 스토리북을 처음 사용해서 참고용으로 남겨두고 .gitignore에는 추가했다.</p>
<hr>
<h3 id="4-emotion-설치하기">4. Emotion 설치하기</h3>
<p>아무래도 라이브러리로 배포할 목적으로 컴포넌트를 개발할 것이라 동적 스타일링이 필요해서 css-in-js로 개발할 예정이고, styled-components보다 파일 사이즈가 적어서 Emotion으로 선택했다.
css prop과 styled방식이 있는데, 반응형도 고려해야하기 때문에 css prop을 사용하기로 결정했다. 공식문서에서도 두 개를 섞어서 사용하는 것은 코드 일관성을 해치기 때문에 공식 문서에서 권장하지 않는다고 한다.
또한, 필수적이진 않지만 Emotion 스타일 코드 최적화하는 플러그인인 @emotion/babel-plugin도 같이 설치해서 세팅한다.</p>
<p><a href="https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles">참고 공식문서</a>
<a href="https://emotion.sh/docs/@emotion/babel-plugin">@emotion/babel-plugin</a>
<a href="https://emotion.sh/docs/media-queries#reusable-media-queries">Emotion_Media_Queries</a></p>
<h4 id="설치하기">설치하기</h4>
<pre><code class="language-bash">yarn add @emotion/react
yarn add --dev @emotion/babel-plugin</code></pre>
<h4 id="viteconfigts-수정하기">vite.config.ts 수정하기</h4>
<pre><code class="language-ts">  //--- 생략 ---
  plugins: [
    react({
      jsxImportSource: &#39;@emotion/react&#39;,
      babel: {
        plugins: [&#39;@emotion/babel-plugin&#39;],
      },
    }),
  ],
  //--- 생략 ---</code></pre>
<h4 id="tsconfigjson-수정하기">tsconfig.json 수정하기</h4>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
  //--- 생략 ---
    &quot;jsxImportSource&quot;: &quot;@emotion/react&quot;,
    &quot;types&quot;: [&quot;@emotion/react/types/css-prop&quot;],
  //--- 생략 ---
  },
}</code></pre>
<hr>
<p>다음 포스트에 이어서 라이브러리 배포를 위한 package.json, vite.config.ts 등 설정과 테스트 컴포넌트를 만들어 빌드하고 로컬에 설치해서 테스트하는 과정을 기록할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 웹・모바일 8기 멤버십 수료 후기]]></title>
            <link>https://velog.io/@seonye-98/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-8%EA%B8%B0-%EB%A9%A4%EB%B2%84%EC%8B%AD-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@seonye-98/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-8%EA%B8%B0-%EB%A9%A4%EB%B2%84%EC%8B%AD-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 17 Dec 2023 10:31:52 GMT</pubDate>
            <description><![CDATA[<h2 id="🥺멤버십-수료🥺">🥺멤버십 수료🥺</h2>
<p> 부스트캠프 멤버십 과정이 끝났네요ㅠㅜ 진부한 멘트지만.. 짧다면 짧고 길다면 긴 6개월간의 부스트캠프 과정이 모두 끝났습니다. </p>
<h3 id="챌린지보다는-덜-빡세다">챌린지보다는 덜 빡세다?</h3>
<p>학습 스프린트와 그룹 프로젝트에서 느끼고, 배운점에 관한 글을 잘 작성하지 못할만큼 바쁘다.. 
많이 알 수록, 그리고 지향하고 싶은 개발자가 뚜렷해질수록 고려해야할게 많고 대충 작성하고 싶지않은 마음에 간결하게 하고 싶지 않아서 그런지는 몰라도 기록하지 못했다.. 결론은, 전혀.전혀.전혀 덜 빡세지 않다.</p>
<h3 id="학습-스프린트-그룹-프로젝트에서-제일-좋았던-점">학습 스프린트, 그룹 프로젝트에서 제일 좋았던 점</h3>
<p>부스트캠프는 작은 야생이지만 우리가 지속 가능한 개발자로 성장하기 위한 장치가 곳곳에 숨겨져있는 느낌이다.. 그래서 뭔가 지금 당연히 개발자가 이래야지! 생각이 드는게 세뇌당한것이 아닐까??ㅎㅎ
하지만, 그중에 한 가지를 뽑자면 <strong>코드리뷰</strong>를 말하고 싶다. 앞으로 취준을 하면서 코드 리뷰 문화가 있는 기업을 선택하고 싶다.
내가 짠 코드를 다른 사람의 관점에서 봐준다는 것, 그리고 피드백 받을 수 있다는 점이 아주 큰 메리트다. 뿐만 아니라 내가 다른 사람의 코드를 보면서도 배울 점이 많다.</p>
<h3 id="처음과-지금-달라진-점">처음과 지금 달라진 점</h3>
<p>어떤 기능을 구현할 때 사고 과정도 달라졌지만, 이건 차차 글을 업데이트하면서 풀어 나가도록 하자. 
이 글에서 다루고 싶은 내용은 나는 어떤 개발자가 되고 싶은가, 나는 어떤 개발자인가?에 대한 내용이다.
처음에는 막연하게 <strong>왜?에 대한 답을 할 수 있는 개발자</strong>가 되고 싶었고 프론트엔드가 되고싶은 이유는 <strong>눈에 보이는게 좋았기</strong> 때문이다.
그런데, 부스트캠프를 하면서 더욱 뚜렷해졌다.</p>
<h3 id="나는-어떤-개발자일까">나는 어떤 개발자일까?</h3>
<h4 id="나는-트래킹을-좋아하는-개발자다">나는 트래킹을 좋아하는 개발자다.</h4>
<p>내 성격과도 연관 지을 수 있을 것 같은데, 책임감 있는 성격 때문에 내가 구현한 기능에 대해서 에러가 생기면 이 에러 때문에 다른 팀원들이 불편해할까 봐 하던 것을 멈추고 에러 먼저 해결하러 달려간다.
예를 들어, twin.macro를 공통 컴포넌트 개발을 위해 도입했었는데 테스트할 때 jest가 twin.macro 문법을 번역하지 못해서 에러가 났었다.
우리 팀 CI 스크립트에서 테스트 코드를 모두 통과해야 커밋할 수 있어서 빠르게 해결했던 적이 있다.</p>
<h4 id="공통-컴포넌트를-팀원들이-사용하는-것이-너무-기분이-좋다">공통 컴포넌트를 팀원들이 사용하는 것이 너무 기분이 좋다.</h4>
<p>이번 그룹 프로젝트에서 스타일 단일화를 위해 간단한 버튼, 라벨을 반응형 컴포넌트로 만들었는데, 미리 만들어놓으니까 최소한 버튼, 라벨만큼은 스타일을 고려하지 않아도 돼서 너무 편했고, 이후에 사용법이 익숙해지면서 개발 속도 향상에도 도움이 됐다.
메인페이지에서 react-slick 라이브러리를 사용해서 캐러셀을 적용했는데 사용하면서 아쉬웠던 점들이 있어서 한 번 나만의 캐러셀 라이브러리를 만들고 유지, 보수해 보고 싶다!</p>
<h4 id="디자인과-똑같이-만들어졌을때의-그-쾌감">디자인과 똑같이 만들어졌을때의.. 그 쾌감!</h4>
<p>학습 스프린트에서는 요구사항 명세서를 figma로 받을 수 있었는데, 여기서 조금 완벽주의의 성향이 발달하는지 figma와 완전히 똑같이 페이지가 만들어졌을 때 성취감을 느낀다.
그룹 프로젝트에서는 우리가 직접 디자인해야 해서 디자인을 완벽하게 하기에는 개발시간이 너무 부족해서 컨셉과 주요 페이지 디자인만 정하고 개발을 시작했었는데, 이렇다 보니 개발하면서 이렇게 하는 게 더 낫겠다 생각해서 조금 변경된 부분들도 있었다.
그래서 어떤 기능이나 버그를 개발하려다가도 마음에 안 드는 부분이 있으면 CSS를 수정하고는 했었는데.. 현업에서는 디자이너가 있으니까 완벽하게 만들면 디자이너분 직업 만족도 상승에 내가 기여할 수 있지 않을까? 라는 생각을 해본다..ㅎㅎ</p>
<h3 id="수료식-후기">수료식 후기</h3>
<p>이번 기수는 전부 온라인으로 진행되었고, 12월 16일 하루 수료식만 오프라인으로 진행되었다.
이전에 노트북이 고장 나서 모각코라던지 한 번도 참여해 본 적이 없어서 어색하지 않을까 걱정돼서 가지말까 생각도 했었는데, 그날이 아니면 언제 볼 수 있을지 모르고 이번에 미루면 다음에 미루는 건 더 쉬울 것이라는 생각으로 무조건 가야지! 마음먹고 다녀왔다.
결론은, 너무 좋았다.. 그동안 화면으로 인사하던 캠퍼분들 모두 반갑게 인사해 주셨고 얼굴 보고 얘기할 수 있어서 값진 시간이었다. 또한, 마스터분들과 운영진분들 실제로 보니 더 따뜻한 분들이라는 게 느껴졌다.
네트워킹 시간에 그동안 했던 고민을 나누면서 앞으로 어떻게 할지 마지막으로 다 같이 얘기할 수 있는 시간을 가질 수 있다. 다음 기수에 나처럼 고민하고 계신 분들은 꼭 참여하는 걸 추천한다.</p>
<h3 id="앞으로">앞으로..</h3>
<p>위와 같이 어떤 개발자가 되고 싶은지 더 구체적으로 생각할 수 있었던 이유는 부스트캠프 하는 동안 많은 인사이트를 얻을 수 있었기 때문이 아닐까..</p>
<ol>
<li>학습 스프린트에서 다졌던 자바스크립트 기초를 까먹기 전에 책을 읽으면서 다시 상기시켜 나의 지식으로 만들어야겠다.</li>
<li>그룹프로젝트 리팩토링 및 버그 수정하면서 위에 언급했던 캐러셀 라이브러리 만들기 해봐야겠다.</li>
<li>또한, 노션에 기록했던 글을 정제해서 벨로그에 업데이트해야겠다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 애플리케이션 구조 (Feat. CORS)]]></title>
            <link>https://velog.io/@seonye-98/%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@seonye-98/%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 26 Sep 2023 13:19:00 GMT</pubDate>
            <description><![CDATA[<h3 id="서론">서론</h3>
<p>이번에 기획서를 바탕으로 처음부터 배포까지 하나의 웹 서비스를 개발해보았다. 
처음에 많이 혼동스러웠던 부분이 웹 애플리케이션의 구조였다.
이전에 팀 프로젝트를 할 때는 프론트엔드와 백엔드 포지션이 나눠져 있어서, 자연스럽게 각자 맡은 포지션에서 프레임워크를 사용해서 개발을 했었다.
하지만 이번에는 혼자 개발을 해야했기 때문에 어디서부터 어디서까지가 프론트엔드고 백엔드일까? 고민거리가 생겼다. 초반에 이 고민에 대한 해답을 찾기위해 시간을 많이 썼지만, 이 후 프로젝트를 완성해나가면서 하나하나의 기능을 구현할 때마다 UI개발, API 개발 등 명확하게 구분해서 단계적으로 개발할 수 있어서 구조를 잡고 개발하길 잘했다는 생각이 들었다.</p>
<hr>
<p>먼저 웹 애플리케이션의 일반적인 시스템 아키텍처인 Web Application Three Tier Architecture를 보자.</p>
<h3 id="웹-애플리케이션의-3단계-계층-구조">웹 애플리케이션의 3단계 계층 구조</h3>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/8e4d9a9b-8ef2-480b-8654-33c370851f78/image.png" alt=""></p>
<ol>
<li><p>Client/Presentation Layer (웹 프론트 서버, 웹 서버)</p>
<ul>
<li>사용자 인터페이스(UI)를 처리하는 역할</li>
<li>주로 웹 브라우저를 통해 클라이언트와 상호작용하고, 사용자의 웹 브라우저가 보낸 HTTP요청을 받아 HTTP응답을 반환하는 서버</li>
<li>API요청의 경우 Application Layer에 처리를 위임하며 결과를 최종 Client에 전달해준다.</li>
<li>웹 서버의 기능은 Nginx, Apache와 같은 미들웨어로 구축할 수 있다.</li>
</ul>
</li>
<li><p>Server/Application Layer</p>
<ul>
<li>웹 애플리케이션의 비즈니스 로직을 처리하는 역할
(즉, 앱을 통해 제공하고자 하는 서비스를 구현하기 위해 데이터를 직접적으로 처리하는 로직)</li>
</ul>
</li>
<li><p>Database/Data Access Layer</p>
<ul>
<li>DB에 접근하여 데이터를 저장하거나 조회하는 역할</li>
</ul>
</li>
<li><p>Cross-Cutting</p>
<ul>
<li>보안, 통신, 운영 관리 등의 애플리케이션 전체에 걸쳐 사용되는 부가기능을 모듈화하여 재사용할 수 있도록 지원하는 역할
<img src="https://velog.velcdn.com/images/seonye-98/post/0006b636-8c6d-4078-98e1-b40a52f7ebe5/image.png" alt=""></li>
<li>부가기능적인 측면에서 보았을때 공통된 요소를 추출하여, 가로(횡단) 영역의 공통 부분을 잘라냈다고 하여 Cross-Cutting이라 부른다.</li>
</ul>
</li>
</ol>
<ol start="5">
<li>Third-party integrations<ul>
<li>제 3의 API 서비스 이용하는 것을 의미</li>
<li>소셜 로그인 API, PG사를 이용한 결제 API 등</li>
</ul>
</li>
</ol>
<hr>
<p>2,3번을 실제 예시로 들어보면, 앱 서버에 Routes-Controller-Services 구조를 적용했을때, 다음과 같이 역할을 분리할 수 있다.</p>
<ol>
<li>Routes - Server/Application Layer
 엔드포인트에 따라 어떤 Controller가 수행되어야 하는지 정의, 사용자 입력의 유효성을 체크하는 부분을 Controller가 실행되기 전에 미들웨어로 추가할 수 있다.</li>
<li>Controller - Server/Application Layer
라우트로부터 요청을 받아 해당 요청에 대한 비즈니스 로직(Service)을 수행하고 클라이언트에게 응답 데이터를 반환하거나 에러를 처리한다.</li>
<li>Service - Database/Data Access Layer
비즈니스 로직을 구현하고 컨트롤러에게 해당 로직을 제공, 주로 데이터베이스와 상호작용한다.
이때, Routes, Controller가 Application Layer에, Service가 Database Access Layer에 해당된다.</li>
</ol>
<hr>
<p>이렇게 웹 애플리케이션 구조를 나눴을 때, 동작 원리는 다음과 같다.
<img src="https://velog.velcdn.com/images/seonye-98/post/43dd215b-9f64-498a-b17e-49c2967eea26/image.png" alt=""></p>
<p>웹 서버는 HTML, CSS, JavaScript, 이미지와 같은 정적인 리소스를 전달하고, 웹 애플리케이션 서버는 요청에 따라 생성되거나 DB에서 추출 또는 변경이 될 수 있는 동적인 데이터를 웹 서버에 제공하여 웹 서버는 이를 클라이언트에게 전달한다. 클라이언트는 동적 데이터를 기반으로 웹 페이지를 렌더링하게 된다.</p>
<hr>
<h3 id="qa">Q&amp;A</h3>
<p><strong>WAS 하나로 정적, 동적인 데이터를 클라이언트에 제공할 수 있는데 굳이 서버를 두 개로 나눠야 하나요?</strong></p>
<ol>
<li><p>WAS만으로 정적, 동적인 데이터를 처리할 수 있으나, 하나의 서버에 역할이 몰리면 동적 콘텐츠 처리가 지연될 수 있다. 
따라서, 서버 부하를 방지하기 위해 단순 정적 콘텐츠는 웹 서버가 제공하도록 역할을 분리하는 것이다.</p>
</li>
<li><p>클라이언트의 상호 작용에 따라 바로 서버에 요청하는 것이 아니라 웹 서버를 거쳐서 앱 서버에 전달하는 것이 포트 번호를 한 번 더 우회해 보안을 더 강화할 수 있다.</p>
</li>
<li><p>규모가 커질수록 개발 영역을 명확히하여 관리와 개발 효율을 높일 수 있고, 배포 및 유지보수가 편리하다.</p>
<p>이 외에도 웹 서버의 중요한 역할인 리버스 프록시에 대해 알아보면 좋을 것 같다.</p>
</li>
</ol>
<p><strong>웹 서버와 앱 서버 분리 시 CORS(Cross-Origin Resource Sharing)이슈가 발생하지 않나요?</strong></p>
<p>브라우저가 origin을 비교하는 기준은 다음과 같다.   </p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/39d43fe0-2412-41d2-88dd-9566719040a4/image.png" alt=""></p>
<p>출처(Origin)는 Protocol, Host, Port를 모두 합친 URL을 의미하고, 셋 중 하나라도 다르다면 Origin이 다르다고 판단한다.
https의 기본 포트는 4403이고, http의 기본포트는 80으로 포트를 명시하지 않아도 프로토콜에 따라 포트가 달라진다.
따라서 웹 서버와 앱 서버를 분리한다면 클라이언트의 요청을 받아 웹 서버에서 API요청을 하게 되는데, 이때 API의 주소가 달라지므로 cors 이슈가 발생한다.
이를 해결하는 방법은 Node.js 기반의 웹 애플리케이션을 개발한다고 가정했을때, 프론트엔드 입장에서와 백엔드 입장에서 제시하자면 다음과 같다.</p>
<ol>
<li>프론트엔드에서 백엔드 서버에서 cors를 해결하는 코드가 없다면, proxy를 사용해 cors정책을 우회할 수 있다.
http-proxy-middleware 라이브러리를 사용하거나 webpack dev server를 사용중이라면, webpack dev server에서 proxy기능을 제공하고 있기 때문에 webpack config에서 다음과 같이 설정할 수 있다.<pre><code class="language-js">module.exports = {
  devServer: {
   proxy: { //api요청 프록시 설정
     &#39;/api/&#39;: {
       // /api/로 시작하는 url은 아래의 전체 도메인을 추가하고, 옵션을 적용
       target: &#39;http://localhost:5000&#39;, // 클라이언트에서 api로 보내는 요청은 주소를 5000포트로 바꿔서 보내겠다 라는 뜻
       changeOrigin: true, // cross origin 허용 설정
     },
   },
 },
};</code></pre>
nginx로 배포하는 경우, nginx conf  다음과 같이 설정할 수 있다.<pre><code>server {
 location /api {
   proxy_pass http://localhost:5000; // /api 경로로 오는 요청을 이 url로 전달
 }
}</code></pre></li>
<li>백엔드에서 cors 해결하는 방법으로는, express를 사용한다고 가정했을 때 Access-Control-Allow-Origin 헤더 세팅을 하거나 cors 미들웨어를 사용해 해결할 수 있다.
직접 헤더를 설정하는 경우는 다음과 같이 응답에 헤더를 추가할 수 있다.<pre><code class="language-js">app.get(&#39;/api&#39;, (req, res) =&gt; {
 res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;허용하고자 하는 도메인&#39;);
 res.send(data)
});</code></pre>
cors 미들웨어를 사용하는 경우 먼저 cors 라이브러리를 설치하고, 아래와 같이 설정할 수 있다.<pre><code class="language-js">app.use(cors({
 origins: &#39;허용하고자 하는 도메인&#39;,
}))</code></pre>
</li>
</ol>
<p>이는 CORS 정책을 준수하여 서로 다른 origin 간의 리소스를 공유할 수 있게 하는 것이고, XSS나 CSRF와 같은 공격에 대한 대비는 따로 해줘야 한다.</p>
<hr>
<p><a href="https://ittrue.tistory.com/189">[Web] 웹 애플리케이션 아키텍처 개념 정리 및 구현, 기술</a>
<a href="https://velog.io/@ikswary/%EC%9B%B9%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%A1%B0">웹 서버 구조</a>
<a href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F">CORS 개념 &amp; 해결법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 등장 배경]]></title>
            <link>https://velog.io/@seonye-98/React-%EB%93%B1%EC%9E%A5-%EB%B0%B0%EA%B2%BD</link>
            <guid>https://velog.io/@seonye-98/React-%EB%93%B1%EC%9E%A5-%EB%B0%B0%EA%B2%BD</guid>
            <pubDate>Tue, 15 Aug 2023 09:49:32 GMT</pubDate>
            <description><![CDATA[<h4 id="들어가기에-앞서">들어가기에 앞서...</h4>
<p>먼저 우리는 정적인 웹 사이트를 만드는 것이 아니라, 웹 애플리케이션을 만든다는 것을 인지하고 있어야 한다.
왜? 요즘 시대에는 단순 정보 관람을 목적으로 하는 웹 사이트보다 웹 애플리케이션이 좀 더 보편화되어있기 때문이다.</p>
<h2 id="web-site-vs-web-application">Web Site vs Web Application</h2>
<p>웹 사이트와 웹 애플리케이션의 정의를 이분화하기는 어렵지만, 각각 어떤 컨셉을 가지고 있는지 알아보자.</p>
<h3 id="1-웹-사이트-web-site">1. 웹 사이트 (Web Site)</h3>
<ul>
<li>여러가지 명칭 : 웹 사이트, 웹 페이지, 웹, 사이트, 페이지 등</li>
<li>정보관람을 목적으로 한 정적인 &#39;사이트&#39;</li>
<li>유저들이 &#39;수동적&#39;으로 &#39;관람&#39;하는 곳</li>
<li>ex. 위키피디아, 공식 문서 등</li>
</ul>
<h3 id="2-웹-애플리케이션-web-application">2. 웹 애플리케이션 (Web Application)</h3>
<ul>
<li>여러가지 명칭 : 웹 애플리케이션, 웹 앱, 웹 서비스 등</li>
<li>동적인 기능을 가진 웹</li>
<li>유저가 상호작용을 가능하게 함으로써, &#39;능동적&#39;으로 웹 서비스를 이용하는 목적을 가짐</li>
<li>ex. 네이버 지도, 카카오 맵 등</li>
<li>리뷰 작성과, 좋아요 클릭 등 유저가 적극적으로 서비스에 참가하고, 정보를 창출하는 것을 가능하게 함</li>
</ul>
<p><a href="https://velog.io/@iamhayoung/%EC%9B%B9-%EC%82%AC%EC%9D%B4%ED%8A%B8-Website%EC%99%80-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9B%B9-%EC%95%B1-Web-application%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90">참고 블로그</a></p>
<hr>
<p>우리가 웹 애플리케이션을 만들어야 하는데에 있어서 가장 많은 문제점을 일으키는 것이 DOM API이다. 왜 이녀석이 문제인지 알아보자!</p>
<h2 id="dom">DOM</h2>
<blockquote>
<p>DOM? 문서 객체 모델(Document Object Model)은 HTML, XML문서의 프로그래밍 interface이다. DOM은 문서의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/Introduction">공식문서 출처</a></p>
</blockquote>
<p>즉, DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조이다.</p>
<p>내가 작성한 HTML 코드가 브라우저에 의해 파싱되면 DOM이 되고,
DOM은 웹 페이지의 객체 지향 표현이며, 자바스크립트와 같은 스크립팅 언어를 이용해 DOM을 접근 및 변경할 수 있다.</p>
<blockquote>
<p>간단하게 브라우저가 웹페이지를 렌더링하는 과정을 설명하면,</p>
</blockquote>
<ol>
<li>서버에서 웹 페이지를 요청하여 응답 받는다.</li>
<li>응답 받은 HTML, CSS 파일을 렌더링 엔진의 HTML파서와 CSS파서에 의해 파싱되어 DOM, CSSOM 트리로 변환한다.</li>
<li>DOM, CSSOM트리를 렌더 트리로 결합한 후 이를 브라우저에서 실행하여 화면을 표시한다.</li>
</ol>
<h3 id="dom을-사용하는-이유">DOM을 사용하는 이유?</h3>
<p><strong>동적인 웹 페이지를 만들기 위해서</strong>
동적으로 웹페이지를 변경하기 위해서 브라우저 메모리 상에 존재하는 DOM을 변경해야하고, DOM API는 JavaScript를 이용해서 웹 앱의 UI를 핸들링 할 수 있는 유일한 방법이기 때문에 실제로 모든 웹 앱이 DOM API를 사용하게 된다.
DOM API가 많은 기능을 제공해주고, 모든 것을 다 할 수 있게 해주지만 웹 앱을 만드는데 최적화된 형태로 되어 있지는 않기때문에 웹 앱을 만드는데에 있어서 많은 문제점을 일으키게 된다.</p>
<h3 id="dom-왜-나왔는데">DOM 왜 나왔는데?</h3>
<p>결론부터 말하자면, 과거 Http의 서버 통신의 한계를 극복하기 위해서 등장했다.
과거 Http는 Html문서 전체를 주고받았다. 이를 애플리케이션 관점에서 보게되면 여러가지 UX 문제점을 야기한다.</p>
<ol>
<li><p>사소한 사용자 상호작용에도 기존 문서를 없애고 새로운 문서를 로딩해야하고, 그 안에서 <code>Context</code>맥락이 있다고 하면 맥락도 사라진다.</p>
<p> 예를 들면, 페이스북 타임라인에서 &#39;좋아요&#39; 를 누를 때마다, 페이지 전체가 새로고침 되고 이에 따라 맥락이 사라지면서, 스크롤은 최상단으로 이동하게 되어 사용자는 보고 있었던 콘텐츠까지 다시 스크롤 다운 해야한다.</p>
</li>
<li><p>애플리케이션 특성상 여러 화면을 가지게 되는데, 화면 단위가 페이지였다면 화면이 깜빡거리며 넘어가게 된다.</p>
</li>
</ol>
<p>이러한 Http 통신 방법의 문제를 해결하기 위해 서버 요청시 전체를 받는 것이 아니라, <strong>필요한 부분만 주고 받는 방법</strong>인 <code>비동기 통신</code>의 개념이 생기게 된다.</p>
<p>이에, XMLHttpRequest와 DOM이 등장하게 된 것이다.</p>
<p>비동기 통신 방법 중 가장 성공적으로 안착된 Ajax를 예를 들어 설명하자면,
Ajax엔진은 서버에 XMLHttpRequest 객체를 활용해 서버와 상호작용하게 되며, 일부분(DOM)만 변경한다. 따라서, 동적으로 <code>html</code>을 <code>자바스크립트</code>를 통해 읽어들여서 화면에 페이지 전환 없이 <code>UI</code>를 갱신하게 된다. 우리가 아는 SPA(Single Page Application)는 Ajax를 활용한 애플리케이션이다.</p>
<p><a href="https://velog.io/@jun094/%EC%84%9C%EB%B2%84%ED%86%B5%EC%8B%A0%EB%B6%80%ED%84%B0-DOM%EC%9D%98-%EB%93%B1%EC%9E%A5%EA%B9%8C%EC%A7%80">참고 블로그</a></p>
<h3 id="dom의-문제점">DOM의 문제점</h3>
<ol>
<li><p><code>Cross browing 이슈</code> : 개발자가 웹 앱 하나를 만들기위해 사용자의 브라우저 환경을 고려하여 어떤 브라우저에서 실행 될 건지 모두 점검할 수 없다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/Document#%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80_%ED%98%B8%ED%99%98%EC%84%B1">DOM 브라우저 호환성</a></p>
<p>최근 JavaScript에서는 쉽게 DOM에 접근 할 수 있게 메서드를 추가하고 있고, 또 대부분의 모던 브라우저들이 ES2015 등의 표준을 따르고 있기 때문에 크게 개선되고는 있다.</p>
</li>
<li><p>DOM API를 이용해서, Html파일을 조작, 생성하면 구조가 전혀 보이지 않는다.</p>
</li>
<li><p>까다롭고, 일관성이 없다.</p>
<pre><code class="language-js">//DOM API를 사용해서 h1태그 가져오기
const h1 = document.getElementsByTagName(&#39;h1&#39;);

h1.length // 0
//h1태그 만들어서 삽입하기 x1
document.body.appendChild(document.createElement(&#39;h1&#39;));
// &lt;h1&gt;&lt;/h1&gt;

h1.length // 1
//h1태그 만들어서 삽입하기 x2
document.body.appendChild(document.createElement(&#39;h1&#39;));
// &lt;h1&gt;&lt;/h1&gt;

h1.length // 2</code></pre>
<p>DOM API가 반환하는 값이 <strong>라이브 오브젝트</strong>라는 컨셉을 갖고 있어서 위 예시 코드와 현상이 발생한다. <strong>DOM트리에 대한 참조를 그대로 유지</strong>하고 있다.
이것을 코드상에서 핸들링하기에 까다롭다는 것이다.</p>
</li>
</ol>
<p>반대로, h1태그를 만들어서 삽입을 한 후, h1태그를 가져와도 결과는 동일하다.</p>
<blockquote>
<p>그럼 DOM API reuturn값은 모두 라이브 오브젝트, 즉 참조형인가? 아니다.</p>
</blockquote>
<p>DOM API의 return값으로 HTMLCollection과 NodeList가 있다.</p>
<ul>
<li>HTMLCollection : 동적이다.
ex. getElementByTagName( ), getElementByClassName( )의 리턴 타입</li>
<li>NodeList : 정적이다.
ex. getElementByName( ), querySelectorAll( )의 리턴 타입</li>
</ul>
<p>즉, DOM에 새로운 요소가 추가되면 HTMLCollection은 새로운 요소를 가져오지만, NodeList는 가져오지 못한다. </p>
<p>이러한 참조 객체를 코드상에서 다루기 까다롭고, 또한 모든 DOM API의 리턴값이 일관성이 있는 것도 아니다.</p>
<h3 id="결론">결론</h3>
<p>Cross browing이슈뿐만 아니라, SPA라는 것은 하나의 앱으로 규모가 커지면 커질수록 관리해야하는 상태도 많고 화면도 점점 많아져서 복잡도가 굉장히 증가하게 된다.
이렇게 복잡해지고 규모가 커질수록 DOM API를 가지고 UI를 만들면 여러가지 문제들이 발생하게 된다.</p>
<ol>
<li>웹 페이지처럼 화면이 바뀌게 되면 모든게 reload되어 새로 갱신된다.</li>
<li>해당 화면에서만 존재하는 상태를 다루는 코드만 격리되어 있지 않다.</li>
</ol>
<p>즉, 모든 화면에 들어가 있는 다양한 데이터(상태)들을 모두 앱 단위에서 유지 관리해야하므로, 참조 무결성의 원칙이 위배되거나 혼란미묘하게 작동되는 것들은 다양한 버그를 일으키는 가능성을 가지고 있다.</p>
<hr>
<h2 id="react의-등장">React의 등장</h2>
<p>React가 등장하기 이전에 DOM의 여러 가지 문제점을 개선하기 위한 시도로 JQuery가 등장했었다.
하지만, JQuery는 DOM을 쉽게 포장했을뿐이지, 근본적으로 DOM이 가지는 문제점들을 해결하지 못했다. 당연히 규모가 커지면 복잡도도 올라갔다.</p>
<p>이에 React는 <code>DOM</code>자체를 쓰지 않는다는 아이디어를 가지고 등장하였다.</p>
<blockquote>
<p>정확히 말하자면, DOM이 없는 것처럼 React가 제공하는 편리한 도구를 제공해주고 DOM은 React가 제어한다는 것이다.</p>
</blockquote>
<h3 id="react의-mvp최소-기능-제품">React의 MVP(최소 기능 제품)</h3>
<p><a href="https://github.com/facebook/react/tags?after=v0.9.0">React의 초기버전</a></p>
<p>초기버전의 React는 웹 애플리케이션의 <strong>UI를 만들기 위한 library</strong>라고 정의한다.</p>
<p>README 문서를 살펴보면, MVP에 3가지의 컨셉을 가지고 있다.</p>
<blockquote>
<h4 id="1-선언적이다">1. 선언적이다.</h4>
<p><code>Declarative: React uses a declarative paradigm that makes it easier to reason about your application.</code></p>
<ul>
<li>이 부분은 현재까지도 유지되고 있는 <code>React</code>의 철학중 하나이다.</li>
<li>선언적인 코드가 생산되도록 유도 -&gt; 대표 컨셉중 하나가 될 수 있다.</li>
</ul>
<hr>
<h4 id="2-효율적이다">2. 효율적이다</h4>
<p><code>Efficient: React minimizes interactions with the DOM by using a mock representation of the DOM.</code></p>
<ul>
<li><code>DOM</code>과 interaction하는 것을 최소화한다</li>
<li><code>DOM</code>에 모의 표현을 사용하여 <code>DOM</code>을 처리한다(= <code>DOM</code>을 없앤다는 측면, 완전히 없애진 않고 <code>mock</code> 형태로 가짜 <code>DOM</code>을 제공해서 <code>DOM</code>과의 연결성을 최소화한다)</li>
</ul>
<hr>
<h4 id="3-유연하다">3. 유연하다.</h4>
<p><code>Flexible: React works with the libraries and frameworks that you already know.</code></p>
<ul>
<li><code>library</code> or <code>framework</code> 같은 것들과 같이 동작할 수 있어서 유연성이 높다.</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] Type Narrowing (타입 좁히기)]]></title>
            <link>https://velog.io/@seonye-98/TypeScript-Type-Narrowing-%ED%83%80%EC%9E%85-%EC%A2%81%ED%9E%88%EA%B8%B0</link>
            <guid>https://velog.io/@seonye-98/TypeScript-Type-Narrowing-%ED%83%80%EC%9E%85-%EC%A2%81%ED%9E%88%EA%B8%B0</guid>
            <pubDate>Thu, 10 Aug 2023 12:01:44 GMT</pubDate>
            <description><![CDATA[<h2 id="type-narrowing타입-좁히기">Type Narrowing(타입 좁히기)</h2>
<p>유니온 타입처럼 여러 타입이 될 수 있는 경우, 타입이 확정되어 있지 않은 상태에 타입을 하나로 확정 시켜주는 것을 Narrowing 이라고 한다.</p>
<p>다음과 같은 함수가 있다고 생각해보자.</p>
<pre><code class="language-tsx">function padLeft(padding: number | string, input: string): string {
  return &quot; &quot;.repeat(padding) + input;
}</code></pre>
<aside>
💡 string.repeat(number) : 인자로 받은 수 만큼 문자열을 반복해서 붙인 새로운 문자열 반환

</aside>

<blockquote>
<p>Argument of type &#39;string | number&#39; is not assignable to parameter of type &#39;number&#39;.
  Type &#39;string&#39; is not assignable to type &#39;number&#39;.Argument of type &#39;string | number&#39; is not assignable to parameter of type &#39;number&#39;.
  Type &#39;string&#39; is not assignable to type &#39;number&#39;.</p>
</blockquote>
<p>reapeat 메소드는 인자로 number타입만 받는데 padLeft함수 인자인 padding은 number와 string 둘 다 될 수 있기 때문에 에러가 난다.</p>
<p>먼저 padding이 number 타입인 경우 repeat메소드의 인자로 넣을 수 있게 예외처리를 해주면 에러를 방지할 수 있다.</p>
<pre><code class="language-tsx">function padLeft(padding: number | string, input: string) {
  if (typeof padding === &quot;number&quot;) {//타입 가드
    return &quot; &quot;.repeat(padding) + input;
  }
  return padding + input;
}</code></pre>
<p>if문으로 함수안에서 예외처리하는 것은 좋은 코드로 보이지 않을 수있지만, </p>
<p>TypeScript의 타입 시스템은 타입 안정성을 얻기 위해 어려운 방법을 사용하지 않고도 일반적인 JavaScript 코드를 가능한 쉽게 작성할 수 있게 하는것을 목표로 한다.</p>
<p>따라서, if/else, 삼항 연산자, 루프, truthiness check등과 같은 구조들이 모두 해당 타입에 영향을 미칠 수 있다.</p>
<blockquote>
<p>typeof 연산자 return 값 : <code>&quot;string&quot;</code>, <code>&quot;number&quot;</code>, <code>&quot;bigint&quot;</code>, <code>&quot;boolean&quot;</code>, <code>&quot;symbol&quot;</code>, <code>&quot;undefined&quot;</code>, <code>&quot;object&quot;</code>, <code>&quot;function&quot;</code></p>
</blockquote>
<p>typeof의 리턴값 중 null은 없다. 다음 예제를 보자.</p>
<pre><code class="language-tsx">function printAll(strs: string | string[] | null) {
  if (typeof strs === &quot;object&quot;) {
    for (const s of strs) {
//Error : &#39;strs&#39; is possibly &#39;null&#39;.
      console.log(s);
    }
  } else if (typeof strs === &quot;string&quot;) {
    console.log(strs);
  } else {
    // do nothing
  }
}</code></pre>
<p>첫번째 if문을 통해 strs를 string | null 로 타입을 좁힐 수 있다. 하지만 null타입의 경우 iterable하지 않은 값으로 반복문의 인자로 사용할 수 없기 때문에 에러가 나는 것을 볼 수 있다.</p>
<h2 id="truthiness-narrowing">Truthiness narrowing</h2>
<pre><code class="language-tsx">function getUsersOnlineMessage(numUsersOnline: number) {
  if (numUsersOnline) {
    return `There are ${numUsersOnline} online now!`;
  }
  return &quot;Nobody&#39;s here. :(&quot;;
}</code></pre>
<p>if문은 조건을 부울(boolean) 값으로 &quot;강제 형변환(coerce)&quot;한 다음 결과가 참(true)인지 거짓(false)인지에 따라 분기를 선택한다.</p>
<blockquote>
<p>false로 return하는 경우 : <code>0</code>, <code>NaN</code>, <code>&quot;&quot;</code> (the empty string), <code>0n</code> (the <code>bigint</code> version of zero), <code>null</code>, <code>undefined</code></p>
</blockquote>
<p>Boolean 함수에 넣거나, <code>!!</code>을 사용하여 강제 형변환할 수 있다. (후자의 경우 TypeScript는 좁은 리터럴 불리언 타입 true를 추론하며, 전자는 boolean 타입으로 추론된다.)</p>
<pre><code class="language-tsx">// both of these result in &#39;true&#39;
Boolean(&quot;hello&quot;); // type: boolean, value: true
!!&quot;world&quot;; // type: true,    value: true</code></pre>
<p>null 또는 undefined와 같은 값에 대한 가드(guard) 역할을 하는 데 사용</p>
<pre><code class="language-tsx">function printAll(strs: string | string[] | null) {
  if (strs &amp;&amp; typeof strs === &quot;object&quot;) {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === &quot;string&quot;) {
    console.log(strs);
  }
}</code></pre>
<p>만약 다음과 같이 전체를 진리값 확인을 한다면, 빈 문자열의 경우 올바르게 처리를 하지 못할 수 있다.</p>
<pre><code class="language-tsx">function printAll(strs: string | string[] | null) {
  // !!!!!!!!!!!!!!!!
  //  DON&#39;T DO THIS!
  //   KEEP READING
  // !!!!!!!!!!!!!!!!
  if (strs) {
    if (typeof strs === &quot;object&quot;) {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === &quot;string&quot;) {
      console.log(strs);
    }
  }
}</code></pre>
<h2 id="equality-narrowing">Equality narrowing</h2>
<p>switch 문과 ===, !==, ==, != 같은 등호 비교를 사용하여 타입을 좁힌다.</p>
<pre><code class="language-tsx">function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // We can now call any &#39;string&#39; method on &#39;x&#39; or &#39;y&#39;.
    x.toUpperCase();  
//(method) String.toUpperCase(): string
    y.toLowerCase();
//(method) String.toLowerCase(): string
  } else {
    console.log(x);     
//(parameter) x: string | number
    console.log(y);       
//(parameter) y: string | boolean
  }
}</code></pre>
<p>위 예제에서 x와 y가 모두 같다는 것을 확인했을 때, TypeScript는 x와 y가 가질 수 있는 유일한 공통 타입이 문자열이기 때문에 문자열로 타입을 좁힐 수 있다.</p>
<p>위의 printAll함수에서 <code>null</code> 을 다음과 같이 제거할 수 있다.</p>
<pre><code class="language-tsx">function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    if (typeof strs === &quot;object&quot;) {
      for (const s of strs) {                     
//(parameter) strs: string[]
        console.log(s);
      }
    } else if (typeof strs === &quot;string&quot;) {
      console.log(strs);                 
//(parameter) strs: string
    }
  }
}</code></pre>
<p><code>==</code> , <code>!=</code> 를 사용해서 타입 좁히기를 하는 경우, <code>==null</code>  값이 null 또는 undefined 중 하나인지를 확인한다.</p>
<pre><code class="language-tsx">interface Container {
  value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
  // Remove both &#39;null&#39; and &#39;undefined&#39; from the type.
  if (container.value != null) {
    console.log(container.value);     
//(property) Container.value: number
    // Now we can safely multiply &#39;container.value&#39;.
    container.value *= factor;
  }
}</code></pre>
<h2 id="the-in-operator-narrowing">The <code>in</code> operator narrowing</h2>
<p>JavaScript에서 객체나, 객체의 프로토타입 체인에 특정 속성이 있는지 여부를 확인하는 <code>in</code> 연산자가 있다. TypeScript에서는 이를 타입 좁히기에 활용할 수 있다.</p>
<p><code>&quot;value&quot; in x</code> : “value”는 문자열, x는 유니온 타입이다.</p>
<ul>
<li>“true”분기 : ‘x’를 속성 “value”를 선택적 또는 필수 속성으로 가지고 있는 타입으로 좁힌다.</li>
<li>“false”분기 : ‘x’를 “value”속성이 없거나 선택적 또는 누락된 속성으로 가지고 있는 타입으로 좁힌다.</li>
</ul>
<pre><code class="language-tsx">type Fish = { swim: () =&gt; void };
type Bird = { fly: () =&gt; void };

function move(animal: Fish | Bird) {
  if (&quot;swim&quot; in animal) {
    return animal.swim();
  }

  return animal.fly();
}</code></pre>
<p>다음의 예시처럼, 선택적속성은 true분기, false분기 모두 존재한다.</p>
<pre><code class="language-tsx">type Fish = { swim: () =&gt; void };
type Bird = { fly: () =&gt; void };
type Human = { swim?: () =&gt; void; fly?: () =&gt; void };

function move(animal: Fish | Bird | Human) {
  if (&quot;swim&quot; in animal) {
    animal;   
//(parameter) animal: Fish | Human
  } else {
    animal;
//(parameter) animal: Bird | Human
  }
}</code></pre>
<h2 id="instanceof-narrowing"><code>instanceof</code> narrowing</h2>
<p><code>x instanceof Foo</code> : x의 프로토타입 체인에 Foo.prototype이 포함되어있는지 확인</p>
<pre><code class="language-tsx">function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString()); 
//(parameter) x: Date
  } else {
    console.log(x.toUpperCase());         
//(parameter) x: string
  }
}</code></pre>
<h2 id="assignments">Assignments</h2>
<p>변수에 값을 할당할 때, TypeScript는 오른쪽에서 왼쪽으로 타입을 확인하고 좁힌다.</p>
<pre><code class="language-tsx">let x = Math.random() &lt; 0.5 ? 10 : &quot;hello world!&quot;;
   //let x: string | number
x = 1;
console.log(x);
           //let x: number
x = &quot;goodbye!&quot;;
console.log(x);
           //let x: string
x = true;
//Type &#39;boolean&#39; is not assignable to type &#39;string | number&#39;.
console.log(x);
           //let x: string | number</code></pre>
<p>처음에 x를 선언할 때 타입이 string | number 이기 때문에, 이후에 선언된 타입에 해당하는 값을 할당할 수 있다. boolean타입을 할당했을 때 오류가 나는 것을 위의 예제를 통해 확인할 수 있다.</p>
<h2 id="control-flow-analysis">Control flow analysis</h2>
<pre><code class="language-tsx">function padLeft(padding: number | string, input: string) {
  if (typeof padding === &quot;number&quot;) {
    return &quot; &quot;.repeat(padding) + input;
  }
  return padding + input;
}</code></pre>
<p><strong><code>padLeft</code></strong> 함수는 첫 번째 if 블록 내에서 반환되어 나머지 부분(return padding + input;)이 숫자인 경우에는 실행되지 않는다. </p>
<p>따라서, padding의 타입에서 number를 제거하여 함수의 나머지 부분에서는 (string | number에서 string으로 좁혀짐) string 타입만 남게 된다.</p>
<p>이러한 도달 가능성에 기반한 코드 분석을 통해 타입을 좁히는 것을 &quot;<strong>제어 흐름 분석(control flow analysis)</strong>&quot;이라고 하며, TypeScript는 이러한 흐름 분석을 타입 가드와 할당을 만나면서 타입을 좁히는 데 사용한다. 변수가 분석될 때, 제어 흐름은 반복적으로 분할되고 다시 병합되며, 해당 변수는 각 지점에서 다른 타입을 가질 수 있다.</p>
<pre><code class="language-tsx">function example() {
  let x: string | number | boolean;
  x = Math.random() &lt; 0.5;
  console.log(x);
               //let x: boolean
  if (Math.random() &lt; 0.5) {
    x = &quot;hello&quot;;
    console.log(x);
               //let x: string
  } else {
    x = 100;
    console.log(x);
               //let x: number
  }
  return x;
        //let x: string | number
}</code></pre>
<h2 id="using-type-predicates">Using <code>type predicates</code></h2>
<p>코드에서 타입이 어떻게 변경되는지 직접적으로 제어하기 위함</p>
<pre><code class="language-tsx">function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}</code></pre>
<p><code>pet is Fish</code> : 타입 프리디케이트로, <code>parameterName is Type</code> 형식을 가지며, parameterType은 현재 함수의 매개변수 이름이어야 한다.</p>
<p>isFish가 어떤 변수와 함께 호출될 때마다, TypeScript는 Fish | Bird 타입에 해당되는 경우 특정 타입으로 좁힌다.</p>
<pre><code class="language-tsx">// Both calls to &#39;swim&#39; and &#39;fly&#39; are now okay.
let pet = getSmallPet();

if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}</code></pre>
<p>if 분기에서 pet이 Fish임을 알 뿐만 아니라, else 분기에서는 Fish가 아닌 경우이므로 Bird여야 한다는 것을 알고 있다는 것이다.</p>
<p>isFish 타입 가드를 사용하여 Fish | Bird 배열을 필터링하여 Fish 배열을 얻을 수 있다.</p>
<pre><code class="language-tsx">const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];

// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish =&gt; {
  if (pet.name === &quot;sharkey&quot;) return false;
  return isFish(pet);
});</code></pre>
<p>이렇게 하면 <code>zoo</code> 배열에서 <strong><code>Fish</code></strong>인 요소들만 걸러진 <code>underWater</code> 배열을 얻을 수 있다.</p>
<h2 id="assertion-function">Assertion Function</h2>
<p><code>assert(someValue === 42)</code> : someValue가 42가 아니면 <code>throw</code> an error 한다.</p>
<p>이를 사용해서 타입가드 코드를 작성할 수 있다.</p>
<pre><code class="language-tsx">function yell(str) {
  assert(typeof str === &quot;string&quot;);
  return str.toUppercase();
  // Oops! We misspelled &#39;toUpperCase&#39;.
  // Would be great if TypeScript still caught this!
}</code></pre>
<p>위의 코드를 다음처럼 작성할 수도 있다.</p>
<pre><code class="language-tsx">function yell(str) {
  if (typeof str !== &quot;string&quot;) {
    throw new TypeError(&quot;str should have been a string.&quot;);
  }
  // Error caught!
  return str.toUppercase();
}</code></pre>
<p>하지만, 최종적으로 TypeScript의 목표는 기존의 JavaScript 구조를 최대한 파괴하지 않고 타입을 지정해주는 것이다.</p>
<p>이를 위해 TypeScript 3.7에서는 &quot;assertion signatures&quot;라는 새로운 개념을 도입했는데, 이는 이러한 단언 함수를 모델링하는 역할을 한다.</p>
<p>assertion signatures로 <code>asserts condition</code> , <code>asserts val is string</code>, <code>val is string</code> 가 있다. </p>
<ol>
<li><strong>asserts condition</strong></li>
</ol>
<pre><code class="language-tsx">function yell(str) {
  assert(typeof str === &quot;string&quot;);
  return str.toUppercase();
  //         ~~~~~~~~~~~
  // error: Property &#39;toUppercase&#39; does not exist on type &#39;string&#39;.
  //        Did you mean &#39;toUpperCase&#39;?
}
function assert(condition: any, msg?: string): asserts condition {
  if (!condition) {
    throw new AssertionError(msg);
  }
}</code></pre>
<p>여기서 사용된 <strong><code>assert</code></strong> 함수를 통해 <strong><code>yell</code></strong> 함수 내에서 문자열 타입인지 확인하고, 그 이후에 해당 조건이 true일 것이라는 확신을 가지고 <strong><code>toUppercase()</code></strong> 메서드를 호출하려고 하지만, 오타로 인해 오류가 발생한다.</p>
<ol start="2">
<li><strong>asserts val is string</strong> : 조건을 확인하지 않고, 특정 변수나 속성이 특정 타입을 가지고 있다고 TypeScript에게 알려준다.</li>
</ol>
<pre><code class="language-tsx">function assertIsString(val: any): asserts val is string {
  if (typeof val !== &quot;string&quot;) {
    throw new AssertionError(&quot;Not a string!&quot;);
  }
}
function yell(str: any) {
  assertIsString(str);
  // assertIsString 함수 호출 이후에, TypeScript knows that &#39;str&#39; is a &#39;string&#39;.
  return str.toUppercase();
  //         ~~~~~~~~~~~
  // error: Property &#39;toUppercase&#39; does not exist on type &#39;string&#39;.
  //        Did you mean &#39;toUpperCase&#39;?
}</code></pre>
<ol start="3">
<li><strong>val is string</strong> : type predicate와 유사한 assertion signature</li>
</ol>
<pre><code class="language-tsx">function isString(val: any): val is string {
  return typeof val === &quot;string&quot;;
}
function yell(str: any) {
  if (isString(str)) {
    return str.toUppercase();
  }
  throw &quot;Oops!&quot;;
}</code></pre>
<h2 id="discriminated-unions-식별된-유니온">Discriminated unions 식별된 유니온</h2>
<p>유니온 타입 내의 모든 유형이 문자열 타입을 가진 공통 속성을 포함하는 경우, TypeScript는 식별된 유니온으로 간주하고 유니온 멤버를 좁힐 수 있다.</p>
<pre><code class="language-tsx">interface Circle {
  kind: &#39;circle&#39;;
  radius: number;
}

interface Square {
  kind: &#39;square&#39;;
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case &#39;circle&#39;:
      return Math.PI * shape.radius ** 2;
    case &#39;square&#39;:
      return shape.sideLength ** 2;
    default:
      // TypeScript는 모든 경우가 다루어졌음을 보장합니다
      throw new Error(&quot;알 수 없는 도형&quot;);
  }
}</code></pre>
<p><code>kind</code> 속성이 판별자 역할을 하며, TypeScript는 <code>switch</code> 문에서 이를 사용하여 <code>shape</code> 의 유형을 좁힌다.</p>
<h2 id="the-never-type">The <code>never</code> type</h2>
<p>유니온을 좁힐 때, 옵션을 줄여서 모든 가능성을 제거하고 아무것도 남지 않는 상태로 만들 수 있다. 이러한 경우에 TypeScript는 존재해서는 안 되는 상태를 나타내기 위해 <code>never</code> 타입을 사용한다. 다음 완전성 검사의 예제를 보자.</p>
<h2 id="exhaustiveness-checking-완전성-검사">Exhaustiveness checking 완전성 검사</h2>
<p><code>never</code> 타입의 값은 모든 타입의 변수에 할당될 수 있지만, <code>never</code> 자체를 제외한 어떤 타입도 <code>never</code>타입의 변수에 할당될 수 없다.</p>
<p>이를 이용해서, switch문에서 default 블록에서 값을 <code>never</code> 로 할당하려는 경우, 모든 경우가 처리되었을 때 오류가 발생하지 않는다. </p>
<pre><code class="language-tsx">interface Triangle {
  kind: &quot;triangle&quot;;
  sideLength: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case &quot;circle&quot;:
      return Math.PI * shape.radius ** 2;
    case &quot;square&quot;:
      return shape.sideLength ** 2;
    default:
      const _exhaustiveCheck: never = shape;
      // Type &#39;Triangle&#39; is not assignable to type &#39;never&#39;.
      return _exhaustiveCheck;
  }
}</code></pre>
<p>이렇게 함으로써 <code>default</code> 블록을 사용하여 switch 문이 모든 경우를 다루고 있는지 확인할 수 있다.</p>
<hr>
<p>타입스크립트 핸드북을 번역하고, 정리한 내용입니다.
<a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html">출처</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] 객체(key:value) 생성하기]]></title>
            <link>https://velog.io/@seonye-98/TypeScript-%EA%B0%9D%EC%B2%B4keyvalue-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@seonye-98/TypeScript-%EA%B0%9D%EC%B2%B4keyvalue-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 08 Aug 2023 12:59:42 GMT</pubDate>
            <description><![CDATA[<h4 id="시작하기에-앞서">시작하기에 앞서..</h4>
<p>TypeScript로 객체를 생성하면서 단순한 interface로 객체 타입을 정하면 변수에 key값을 넣었을때, value값을 제대로 가져오지 못했었던 경험이 있다. 다음 코드를 보자.</p>
<pre><code class="language-ts">interface IPerson {
  name: string;
  age: number;
}

let person: IPerson = {
  name: &#39;sunny&#39;,
  age: 26,
};

let key = &#39;name&#39;;
console.log(person.name); //sunny
console.log(person[key]); //Error
</code></pre>
<span style="color: red; background-color: #f6f8fa;">
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IPerson'.
  No index signature with a parameter of type 'string' was found on type 'IPerson'.ts(7053)</span><br>

<p>기본적으로 변수로 객체의 값을 가져올 때는 점표기법(Dot notation)을 사용할 수 없고 괄호표기법(Bracket Notation)을 사용해야한다.
이 에러를 해결하기 위해서는 key, value의 타입을 명확하게 지정해주면 된다.</p>
<h4 id="1-index-signature대괄호로-객체를-접근하는-방법">1. Index Signature(대괄호로 객체를 접근하는 방법)</h4>
<pre><code class="language-ts">
let person: { [key: string]: string | number } = {
  name: &#39;sunny&#39;,
  age: 26,
};
</code></pre>
<p>Union Type을 사용하여 두 가지 이상의 타입으로 지정할 수 있다.</p>
<h4 id="2-record-type">2. Record Type</h4>
<p>TypeScript Version2.1 부터 도입된 문법으로 Record&lt;Key, Type&gt;형식으로 키가 Key이고 값이 Type인 객체 타입이다.</p>
<pre><code class="language-ts">let person: Record&lt;string, string | number&gt; = {
  name: &#39;sunny&#39;,
  age: 26,
};
</code></pre>
<p>인덱스 시그니처의 단점으로 리터럴 타입을 key나 value 타입으로 사용할 수 없다. </p>
<blockquote>
<p>리터럴 타입? string, number 두 가지가 있다.</p>
</blockquote>
<pre><code class="language-ts">type strLiterType = &#39;MON&#39;|&#39;TUE&#39;|&#39;WED&#39;;
type numLiterType = 1 | 2 | 3;</code></pre>
<p>이 문제점을 Record나 맵드 타입을 사용하면 쉽게 해결할 수 있다.
(본 포스팅에서는 Record를 사용하는 방법만 제시하겠다.)</p>
<pre><code class="language-ts">type names = &#39;홍길동&#39; | &#39;둘리&#39; | &#39;마이콜&#39;;
type age = 26 | 27 | 28;
//key값으로 names, value값으로 age 이외의 값을 넣으면 Error 발생
let human: Record&lt;names, age&gt; = {
  홍길동: 26,
  둘리: 27,
  마이콜: 28,
};

//key에만 literal Type을 적용하는 경우, 
//value에는 number타입의 아무 숫자나 넣을 수 있다.
let human: Record&lt;names, number&gt; = {
  홍길동: 21,
  둘리: 22,
  마이콜: 23,
};</code></pre>
<p><span style="color: red;">하지만, 주의해야할 것은 리터럴 타입으로 key값을 지정해줄 경우 변수로 객체의 값에 접근할 수 없다.<span></p>
<h4 id="3-map-객체-사용">3. Map 객체 사용</h4>
<blockquote>
<p>ES6부터 사용 가능한 데이터 구조로, 다음 문서에서 Object와 Map을 비교하는 내용을 알 수 있었다.
  <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Keyed_collections">https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Keyed_collections</a>
  Map 공식 문서 : <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Map">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Map</a></p>
</blockquote>
<pre><code class="language-ts">  let key = &#39;name&#39;;
  let person4 = new Map&lt;string, string | number&gt;();
//데이터 저장
person4.set(&#39;name&#39;, &#39;sunny&#39;);
person4.set(&#39;age&#39;, 26);
//데이터 조회
person4.get(key);
//데이터 존재 여부
person4.has(key);
</code></pre>
<p>변수로 Map 인스턴스 메서드 사용이 가능하다.</p>
<h4 id="4-enum-사용">4. Enum 사용</h4>
<pre><code class="language-ts">
  enum E {
    X,
    Y,
    Z,
}

let enumKey: keyof typeof E = &#39;X&#39;;
console.log(E[enumKey]);</code></pre>
<p>typeof 키워드 : 객체 데이터를 객체 타입으로 변환해주는 연산자</p>
<p>keyof 키워드 : 개체 형태의 타입을, 따로 속성들만 뽑아 Union 타입으로 만들어주는 연산자</p>
<p>따라서, keyof typeof E = &#39;X&#39; | &#39;Y&#39; | &#39;Z&#39;로 문자열 리터럴 타입이 되고, 이것을 enumKey의 타입으로 지정해주는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 부스트캠프 웹・모바일 8기 챌린지 수료 후기(+멤버십 합격)]]></title>
            <link>https://velog.io/@seonye-98/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-8%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@seonye-98/%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EC%9B%B9%E3%83%BB%EB%AA%A8%EB%B0%94%EC%9D%BC-8%EA%B8%B0-%EC%B1%8C%EB%A6%B0%EC%A7%80-%EC%88%98%EB%A3%8C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 05 Aug 2023 10:09:18 GMT</pubDate>
            <description><![CDATA[<h3 id="😎드디어-끝났다😎">😎드디어 끝.났.다😎</h3>
<p>첫 주에는 정말 처음 경험해보는 미션 난이도와 익숙하지 않은 부스트캠프 시스템에 적응한다고 시간이 빠른듯 느린듯 그렇게 흘러갔었던 것 같은데.. 벌써 수료했다니! 끝나고도 아직 믿기지 않는다.👏🏻👏🏻👏🏻<br>
부족한 CS지식을 채우고 코드리뷰를 통해 나의 코드를 깔끔하게 만들자는 목표로 처음 부스트캠프를 지원했던 것 같은데 어느정도는 달성한 것 같아 알차게 한 달을 보냈구나 스스로 칭찬해주고싶다. 그리고 잘 버텨온 다른 캠퍼분들도 수고했다고 전해주고 싶다.<br>
먼저 매 주마다 느끼고, 성장했던 부분이 다른 것 같은데 까먹기 전에 전체 회고로 남겨두고자 글을 써본다.</p>
<hr>
<h3 id="👍1주차-회고">👍1주차 회고</h3>
<p>기본적으로 주 언어? 라고 하긴 부끄럽지만 코테를 파이썬으로 준비했기 때문에 JavaScript가 굉장히 어색했다.
2년전 React 프레임워크를 사용한 프론트엔드 개발을 한 이후로 JavaScript를 사용한 적이없어서 1주차는 JavaScript 적응 + CS공부가 주를 이뤘다.</p>
<p>이 때는 어떤 성장을 했다기 보단, 정말 아무도 시키지 않았는데 스스로 몰입해서 12시간넘게 책상에 앉아서 공부하는 나를 발견했었고, CS공부를 항상 후순위로 미뤘는데 공부하고 미션을 수행하면서 CS 중요성을 알게 되었다.</p>
<hr>
<h3 id="✌️2주차-회고">✌️2주차 회고</h3>
<p>2주차에는 어느정도 JavaScript에도 익숙해졌고, 잘하는 캠퍼들을 보면서 욕심이 생기기 시작한다..
뭔가 먼저 깊은 학습을 해야 미션 요구사항을 제대로 정의할 수 있을것 같다는 생각에 2주차에는 구현보다 학습을 먼저하고 구현을 했었다.
설계를 할 줄도 몰랐던 것 같고.. 디자인패턴이라는 것이 있는 줄도 몰랐었다..
그렇다보니, 학습에 많은 시간을 할애해 구현할 시간은 없고.. 시간에 쫓기다보니 코드는 내가 해석하기도 힘들었던 것 같다.</p>
<p>하지만 2주차 첫 미션에서 마스터님이 TypeScript를 사용해보라는 권유에 원래도 관심이 있었고, 잠깐 공부도 했었기 때문에 바로 적용을 했더니 자연스럽게 이 후에도 모든 미션을 TypeScript로 구현을 했었다.
역시 첫 시작이 제일 어려운 것 같다.</p>
<hr>
<h3 id="👌3주차-회고">👌3주차 회고</h3>
<p>2주차까지 진행을 하면서 살짝 번아웃이 왔던 것 같다. 왜냐면 2주차 주말까지 한번도 마음편히 쉬어본적이 없었고, 학습정리 할 시간이 부족해 주말에도 정리를 했었기 때문이다. 그리고 2주차에 잘하려고 마음을 먹고 했는데, 마음처럼 되지 않았었던 기억때문에 조금은 괴로웠다.</p>
<p><strong>피어세션이 없었더라면..</strong> 괴로운 마음에 그냥 그냥 하루를 버티면서 챌린지를 수료했을지도 모른다.</p>
<p>하지만 이 때 정말 잘하는 캠퍼 한 분을 만나게 됐다. 코드를 짤 때 어떤 생각을 하는지, 클래스를 나눌 때는 어떤 기준으로 나누는지 이해가 안갔던 부분이 있으면 물어보고 적극적으로 배우려고 했었다.</p>
<p>그래서 3주차 두 번째 미션부터는 적용해볼만한 디자인 패턴을 찾고, 적용해봤다. 파일도 의도적으로 역할별로 나누려고 했었고, 그럼에도 중복코드가 있으면 제거하려고 했었다.</p>
<p>그리고 완벽한 요구사항 정의는 한 번에 이루어지지 않는다. 하나하나의 기능을 구현하기전에 확실하게 정의하고 구현하는게 더 효율적이다는 것을 깨달았다.</p>
<hr>
<h3 id="✌️➕✌️4주차-회고">✌️➕✌️4주차 회고</h3>
<p>4주차부터는 시간에 쫓기며 구현을 하게되면 구조를 설계하고 모든 요구사항을 구현하기 힘들다는 생각이 들었다.
그리고 피어세션전에 모든 요구사항을 구현하자는 목표를 세웠다. 그래야 리팩토링을 할 시간을 벌 수 있으니까.
필요한 배경 지식을 간단히 학습하고, 구현을 먼저 했다.
또 중요한 것은, 구현과 클린코드 두 마리 토끼를 잡는 것은 현재 나의 역량으로는 어려운 일이라고 생각하고 구현을 할 때 구현에만 몰두했다.
정말 놀라웠던 것은, 구현에만 몰두했는데도 코드가 이전만큼 더럽지 않았다는 것이다.
물론, 다른 캠퍼가 보기에는 중복된 코드가 많고, 파일 하나에 모든 기능이 들어가있어서 더럽다고 생각했을 수도 있다.
여기서 내가 생각했을 때 <code>코드가 더럽지 않다</code>는 기준은 다음과 같다.</p>
<ol>
<li>코드를 다시 읽었을 때 <code>왜 이렇게 했더라?</code> 의문이 들지 않고, 해석할 필요가 없는 코드</li>
<li>중첩된 조건문, 반복문 즉 복잡한 구조가 없는 코드</li>
<li>코드의 흐름이 역행하지 않는 코드</li>
</ol>
<p>자연스럽게 함수를 분리할 때 많은 역할을 하지않게 하고, 고차함수와 early return하는 코드를 작성함으로써 위의 조건을 만족할 수 있었다.</p>
<p>따라서, 리팩토링하는데에 어려움이 없었고, 두 번째 미션에서는 모든 기능 구현 + 리팩토링 까지 전부 완료할 수 있었다.</p>
<hr>
<h3 id="✍-종합하자면">✍ 종합하자면..</h3>
<p>모든 것은 처음부터 완벽할 수 없다.
완성하고자 하는 프로그램을 작은 단위로 나누고 단위마다 요구사항을 더 명확하게 정의하고(물론 처음에 전체적인 요구사항 파악은 해야한다.) 완성했을때 리팩토링을 진행하고.. 이러한 과정을 반복 하다보면 좋은 코드가 완성될 수 있을것 같다고 생각했다.</p>
<hr>
<h3 id="🤓-앞으로의-계획-🤓">🤓 앞으로의 계획 🤓</h3>
<p>아직 멤버십 발표가 나지 않았지만, 3주동안 마냥 놀 수 는 없기 때문에, 지금 생활 패턴을 유지하면서 스터디도 하고, 노션에 정리했던 학습정리를 조금 더 구체화해서 블로그에 업로드하려고 한다.</p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/64afc27b-262d-4171-b7cf-dcf83ed0bf10/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/751ecb49-87d3-4b9c-a431-92d423efa58e/image.png" alt=""></p>
<hr>
<h3 id="🥳--멤버십-합격-🎉">🥳 + 멤버십 합격 🎉</h3>
<p><img src="https://velog.velcdn.com/images/seonye-98/post/8e77a826-2890-4f4b-a301-7b7f36232823/image.png" alt="">
8월 18일 오후 1시 35분 드디어 메일이 왔다! 오늘이 발표 예정일이여서 저녁에 오겠거니하고 마음 조리며 기다리고있었는데 결과가 예상보다 일찍 나왔다! 멤버십 시작까지 일주일 남았는데 마저 열심히 준비해야겠다.</p>
<p>+진행중인 M.C.S(Morning Call Study) 스터디 상황)
챌린지 이후에 스터디를 했는데, 다들 챌린지에서 혹독한 학습과정을 거쳐서 그런지 하나의 주제로 공부를 해도 다들 계획이라도 한 것 처럼 각각 의미있는 주제를 가져와서 정리 내용을 같이 공유하고, 토론도 했다.
이 후에 멤버십 들어가기전에 공부하고 싶은 주제 리스트 중에서 각자 공부하고 싶은 주제를 골라서 공부하고 발표하는 식으로 진행했었는데, 챌린지 처럼 학습과 구현을 겸비한 공부를 해오셔서 너무 좋았다.
비슷한 결(?)의 학습태도를 지닌 사람들의 스터디 너무 추천한다!!</p>
]]></description>
        </item>
    </channel>
</rss>