<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>king_nono_1030.log</title>
        <link>https://velog.io/</link>
        <description>우리 인생 화이팅~</description>
        <lastBuildDate>Tue, 08 Apr 2025 08:18:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>king_nono_1030.log</title>
            <url>https://velog.velcdn.com/images/king_nono_1030/profile/a37ab36e-e3ec-47e8-ac6e-90e99e5c0ff5/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. king_nono_1030.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/king_nono_1030" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[term]]></title>
            <link>https://velog.io/@king_nono_1030/term</link>
            <guid>https://velog.io/@king_nono_1030/term</guid>
            <pubDate>Tue, 08 Apr 2025 08:18:50 GMT</pubDate>
            <description><![CDATA[<p>Glossary
Learn about the terms and concepts used in Vercel&#39;s products and documentation.
Recognizing that terminology can vary across different organizations and contexts, we&#39;ve created this glossary to provide clear and consistent definitions for the terms and concepts we use in our products and documentation at Vercel.</p>
<p>Directory
A directory, also known as a folder in some operating systems, is a file system structure used to organize and store files on a computer. It helps to manage files by grouping them into a hierarchical structure of directories and subdirectories.</p>
<p>In programming and scripting, &quot;directory&quot; is often abbreviated to &quot;dir&quot;.</p>
<p>Repository
In version control systems like Git, a repository is a location where files, including source code, are stored and managed. It not only houses the current version of every file but also maintains a history of all changes made to these files over time. This history is crucial for tracking modifications, collaborating on code, and reverting to previous versions if necessary.</p>
<p>Monorepo
A monorepo, short for &quot;monolithic repository&quot;, is a version control strategy in which many packages or modules are stored in a single repository. This approach stands in contrast to a multi-repo approach, where each package or module has its own separate repository.</p>
<p>Monorepos facilitate easier code sharing and collaboration across the different parts of a codebase, and are not limited to a specific package manager or programming language.</p>
<p>Multi-repo
A multi-repo, short for &quot;multi-repository&quot; and also known as &quot;polyrepo&quot;, is a version control strategy where each package or module has its own separate repository. This approach stands in contrast to a monorepo approach, where multiple packages or modules are stored in a single repository.</p>
<p>Workspace
In JavaScript, a workspace refers to a specific entity in a repository that can be either be a single package or a collection of packages.</p>
<p>The package manager&#39;s root lockfile (i.e. pnpm-lock.yaml) is located at the workspace root, along with any additional configuration, such as the paths where packages are located when working in a multi-package workspace.</p>
<p>A workspace is often located at the root of a repository, but this is not a requirement. For example, some monorepos may have multiple workspaces, each located in a subdirectory of the repository.</p>
<p>Single-package workspace
A workspace that represents a standalone package with a single package.json file at the workspace root.</p>
<p>Multi-package workspace
A workspace that contains multiple packages.</p>
<p>A multi-package workspace contains multiple package.json files, including one at the workspace root for global configuration, and one in each package directory.</p>
<p>The configuration for these packages is stored at the root of the workspace. For pnpm, this configuration is in pnpm-workspace.yaml. npm and Yarn use the workspaces key in package.json.</p>
<p>This type of workspace is often associated with a monorepo.</p>
<p>Package
A package is a collection of files and directories that are grouped together based on a common purpose. Types of packages include libraries, applications, services, and development tools.</p>
<p>Packages enable complex code bases to be decomposed into smaller, manageable, and independent components. This modular approach is essential to monorepos, a repository structure that houses multiple interconnected packages, facilitating streamlined management and development of large-scale projects.</p>
<p>In JavaScript, each package has a package.json file at its root, which contains metadata about the package, including its name, version, and any dependencies. Versioning of JavaScript packages generally follows semantic versioning, and JavaScript packages are often managed and distributed through npm.</p>
<blockquote>
<p>reference: <a href="https://vercel.com/docs/glossary">https://vercel.com/docs/glossary</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[트러블 슈팅 #1]]></title>
            <link>https://velog.io/@king_nono_1030/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-1</link>
            <guid>https://velog.io/@king_nono_1030/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-1</guid>
            <pubDate>Tue, 04 Feb 2025 11:57:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/db00ecdc-875a-41bd-be56-9b8a362adbae/image.png" alt=""></p>
<blockquote>
<p>필드에 입력하니까</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/24fe297e-45a5-4c19-8b96-29b00bf41c43/image.png" alt=""></p>
<blockquote>
<p>아하</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/3c762050-4d16-4c28-92a2-c3235b437a32/image.png" alt=""></p>
<pre><code class="language-tsx">&lt;TextInput
  value={education.grade}
/&gt;  </code></pre>
<p>이걸 빼니까 해결~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소셜 로그인: 프론트 vs 백엔드 처리 방식]]></title>
            <link>https://velog.io/@king_nono_1030/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%94%84%EB%A1%A0%ED%8A%B8-vs-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@king_nono_1030/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%94%84%EB%A1%A0%ED%8A%B8-vs-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Fri, 31 Jan 2025 10:17:54 GMT</pubDate>
            <description><![CDATA[<p>🚀 소셜 로그인 흐름: 프론트엔드 vs 백엔드 처리 방식 비교</p>
<p>소셜 로그인 구현 방식에는 여러 가지 방법이 있지만, 프론트엔드 중심 처리 방식과 백엔드 중심 처리 방식 두 가지가 대표적입니다.</p>
<p>1️⃣ 기존 프로젝트 방식 (프론트엔드 중심)</p>
<pre><code>✅ 과정:

1.    사용자가 프론트엔드에서 직접 카카오 로그인 URL로 이동 (window.location.href = kakao_auth_url)
2.    카카오가 리디렉트 URL을 통해 프론트엔드로 임시 코드(Authorization Code) 전달
3.    프론트엔드에서 받은 Authorization Code를 백엔드로 전송하여 액세스 토큰(JWT) 요청
4.    백엔드는 Authorization Code로 카카오 API에 요청 → 액세스 토큰 발급 → 사용자 정보 조회 후 자체 로그인 처리 및 JWT 발급
5.    프론트엔드는 받은 JWT를 로컬스토리지, 쿠키, Zustand 등의 상태 관리에 저장</code></pre><p>✅ 📌 특징
    •    프론트엔드에서 Authorization Code를 직접 백엔드로 보내서 로그인 요청을 수행
    •    보안이 상대적으로 낮음 (클라이언트에서 Authorization Code를 직접 다루기 때문)
    •    구현 방식이 비교적 단순하며, 다양한 OAuth 제공업체와 쉽게 연동 가능</p>
<p>✅ 🔴 단점
    •    Authorization Code를 클라이언트가 다루기 때문에 악성 코드 공격에 취약
    •    쿠키 기반 인증을 적용하기 어렵고, JWT를 로컬스토리지에 저장하면 XSS 취약점이 생길 수 있음</p>
<p>2️⃣ 최근 프로젝트 방식 (백엔드 중심)</p>
<pre><code>✅ 과정:

1.    사용자가 프론트엔드에서 백엔드에서 제공하는 로그인 URL로 이동 (window.location.href = backend_auth_url)
2.    백엔드가 해당 요청을 카카오 OAuth 로그인 URL로 리디렉트
3.    사용자가 카카오 로그인을 완료하면 카카오가 백엔드로 Authorization Code를 전달
4.    백엔드가 Authorization Code를 사용해 카카오에서 액세스 토큰을 발급받고, 사용자 정보를 가져옴
5.    백엔드는 JWT를 생성하고, 프론트엔드로 리디렉트하면서 헤더에 JWT를 포함하여 전달
6.    프론트엔드는 백엔드에서 받은 JWT를 쿠키에 저장 (or 상태 관리)</code></pre><p>✅ 📌 특징
    •    프론트엔드는 OAuth 인증 과정에 직접 관여하지 않고, 백엔드가 모든 OAuth 인증을 처리
    •    백엔드에서 JWT를 발급하고, 리디렉트 URL을 통해 응답 헤더에 JWT를 담아서 전달
    •    JWT를 쿠키(HttpOnly) 에 저장할 수 있어 보안성이 높아짐</p>
<p>✅ 🟢 장점
    •    Authorization Code를 프론트엔드에서 다루지 않음 → 보안성이 높아짐
    •    JWT를 쿠키(HttpOnly, Secure)로 관리 가능 → XSS 공격 방지
    •    API 요청마다 JWT를 Authorization 헤더로 보내는 방식보다 자동 로그인 유지에 유리</p>
<p>✅ 🔴 단점
    •    로그인 과정이 더 복잡하고, 백엔드에서 OAuth 처리 부담이 증가
    •    프론트엔드에서 JWT를 직접 다루지 않기 때문에, 상태 관리 라이브러리(Zustand 등)와의 연동이 어렵고, API 요청 시 인증이 자동으로 포함되지 않는 문제가 있을 수 있음.</p>
<p>3️⃣ 어느 방식이 더 흔한가?</p>
<p>📌 OAuth 로그인 방식 트렌드</p>
<p>방식    보안성    사용 사례    쿠키 기반 인증 가능 여부
프론트엔드 중심    낮음 (취약점 존재)    일반적인 OAuth 연동, SPA    ❌ (JWT를 직접 저장해야 함)
백엔드 중심    높음 (Code 처리 없음)    보안이 중요한 서비스 (금융, 기업 내부 서비스)    ✅ (HttpOnly 쿠키로 가능)</p>
<p>💡 최근에는 백엔드 중심 방식이 보편화되는 추세입니다.
특히, XSS / CSRF 방지를 위해 HttpOnly 쿠키를 사용하는 방식이 보안적으로 우수하기 때문입니다.
그래서 OAuth 인증 후 JWT를 헤더에 담아서 프론트엔드로 리디렉트하는 방식이 종종 사용됩니다.</p>
<p>4️⃣ 프론트엔드에서 JWT를 응답 헤더로 받아서 처리하는 방법</p>
<p>백엔드가 로그인 후 리디렉트 시 JWT를 응답 헤더에 포함하여 프론트엔드로 전달하면, 프론트엔드는 이를 쿠키에 저장해야 합니다.</p>
<p>useEffect(() =&gt; {
  fetch(&#39;/auth/callback/kakao&#39;, { credentials: &#39;include&#39; }) // JWT 포함된 응답 받기
    .then((response) =&gt; {
      const token = response.headers.get(&#39;Authorization&#39;); // JWT 헤더에서 가져오기
      if (token) {
        document.cookie = <code>jwt=${token}; path=/; Secure; HttpOnly</code>; // 쿠키에 저장
      }
    })
    .catch((err) =&gt; console.error(err));
}, []);</p>
<p>✅ credentials: &#39;include&#39;를 사용하면 쿠키 기반 인증을 활용할 수 있음</p>
<p>5️⃣ 최종 결론</p>
<p>📌 프론트엔드 중심 방식 → 구현이 쉽지만 보안이 취약
📌 백엔드 중심 방식 → 보안성이 높고 JWT를 HttpOnly 쿠키에 저장 가능</p>
<pre><code>최근에는 “백엔드 중심 OAuth 인증”이 더 선호되는 방식입니다.</code></pre><p>특히, JWT를 응답 헤더에 담아서 리디렉트하는 방법은 보안성을 높이기 위한 설계이며, 금융 서비스나 보안이 중요한 서비스에서 자주 사용됩니다.</p>
<p>✅ 결론적으로, 이 방식이 흔한 방법인지?
→ 최근 보안 강화 트렌드에 따라 점점 더 많이 사용되는 방식이며, OAuth 로그인 후 JWT를 응답 헤더에 담아 리디렉트하는 방식도 꽤 일반적입니다. 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 최적화]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@king_nono_1030/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 27 Jan 2025 15:18:37 GMT</pubDate>
            <description><![CDATA[<ul>
<li>서버의 응답 속도 개선</li>
<li>이미지, 폰트, 코드 파일 등의 정적 파일 로딩 개선</li>
<li>불필요한 네트워크 요청 줄임</li>
</ul>
<hr>
<ul>
<li>컴포넌트 내부의 불필요한 연산 방지</li>
<li>컴포넌트 내부의 불필요한 함수 재생성 방지</li>
<li>컴포넌트의 불필요한 리렌더링 방지</li>
</ul>
<hr>
<ul>
<li>useMemo</li>
<li>memo</li>
<li>useCallBack</li>
</ul>
<hr>
<p>프로젝트를 완성한 뒤에 최적화
기능 구현이 우선</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useReducer]]></title>
            <link>https://velog.io/@king_nono_1030/useReducer</link>
            <guid>https://velog.io/@king_nono_1030/useReducer</guid>
            <pubDate>Mon, 27 Jan 2025 15:16:33 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-jsx">const [state, dispatch] = useReducer(reducer, 0)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[신기]]></title>
            <link>https://velog.io/@king_nono_1030/%EC%8B%A0%EA%B8%B0</link>
            <guid>https://velog.io/@king_nono_1030/%EC%8B%A0%EA%B8%B0</guid>
            <pubDate>Tue, 14 Jan 2025 16:53:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://tebly.kr/blog-recap/zptDya1736873252143">https://tebly.kr/blog-recap/zptDya1736873252143</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.01.13]]></title>
            <link>https://velog.io/@king_nono_1030/2025.01.13</link>
            <guid>https://velog.io/@king_nono_1030/2025.01.13</guid>
            <pubDate>Sun, 12 Jan 2025 15:44:07 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/watch?v=wkYXV45duII">https://www.youtube.com/watch?v=wkYXV45duII</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나만의 테크 블로그 만들기 (6) 블로그 이전]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A0%84</link>
            <guid>https://velog.io/@king_nono_1030/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A0%84</guid>
            <pubDate>Thu, 25 Jul 2024 09:47:51 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nono-log.vercel.app/">https://nono-log.vercel.app/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[js - 반복문 비교]]></title>
            <link>https://velog.io/@king_nono_1030/js-%EB%B0%98%EB%B3%B5%EB%AC%B8-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@king_nono_1030/js-%EB%B0%98%EB%B3%B5%EB%AC%B8-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Mon, 15 Jul 2024 05:01:50 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트에서 루프를 도는 방법에는 여러 가지가 있다. 각 루프는 특정한 상황에서 유용하며, 각각의 장단점이 있다. 여기서는 대표적인 루프 방법인 <code>for</code> 문, <code>for...in</code> 문, <code>for...of</code> 문, 그리고 <code>forEach</code> 메서드를 비교해 보겠다.</p>
<h3 id="1-for-문">1. <code>for</code> 문</h3>
<p>가장 기본적인 루프 방법이다. 반복 횟수를 지정하거나 조건을 설정하여 반복할 수 있다.</p>
<pre><code class="language-javascript">for (let i = 0; i &lt; array.length; i++) {
  console.log(array[i]);
}</code></pre>
<ul>
<li><strong>장점</strong>: 초기화, 조건, 증감 부분을 자유롭게 설정할 수 있어 유연하다.</li>
<li><strong>단점</strong>: 코드가 길어질 수 있으며, 반복 횟수를 명시적으로 관리해야 한다.</li>
</ul>
<h3 id="2-forin-문">2. <code>for...in</code> 문</h3>
<p>객체의 열거 가능한 속성에 대해 반복한다. 배열에도 사용할 수 있지만 권장되지 않는다.</p>
<pre><code class="language-javascript">const object = { a: 1, b: 2, c: 3 };
for (let key in object) {
  console.log(key, object[key]);
}</code></pre>
<ul>
<li><strong>장점</strong>: 객체의 모든 열거 가능한 속성을 반복할 때 유용하다.</li>
<li><strong>단점</strong>: 배열에도 사용할 수 있지만, 배열의 인덱스를 문자열로 처리하므로 예기치 않은 동작을 할 수 있다.</li>
</ul>
<h3 id="3-forof-문">3. <code>for...of</code> 문</h3>
<p>반복 가능한(iterable) 객체(Array, Map, Set, String 등)의 요소에 대해 반복한다.</p>
<pre><code class="language-javascript">const array = [10, 20, 30];
for (let value of array) {
  console.log(value);
}</code></pre>
<ul>
<li><strong>장점</strong>: 배열 및 다른 반복 가능한 객체의 요소를 반복하는 데 적합하다. 코드가 간결하다.</li>
<li><strong>단점</strong>: 객체의 속성을 반복할 수 없다.</li>
</ul>
<h3 id="4-foreach-메서드">4. <code>forEach</code> 메서드</h3>
<p>배열에서 사용 가능한 메서드로, 각 요소에 대해 함수를 실행한다.</p>
<pre><code class="language-javascript">const array = [10, 20, 30];
array.forEach(value =&gt; {
  console.log(value);
});</code></pre>
<ul>
<li><strong>장점</strong>: 간결하고 가독성이 좋다. 배열의 각 요소에 대해 함수형 프로그래밍 스타일로 작업할 수 있다.</li>
<li><strong>단점</strong>: <code>break</code>나 <code>continue</code>를 사용할 수 없다. 비동기 처리를 잘 지원하지 않는다.</li>
</ul>
<h3 id="비교-요약">비교 요약</h3>
<table>
<thead>
<tr>
<th>루프 방법</th>
<th>사용 대상</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>for</code> 문</td>
<td>모든 종류</td>
<td>유연성 있다, 초기화/조건/증감 자유</td>
<td>코드가 길어질 수 있다, 반복 횟수 명시적 관리 필요</td>
</tr>
<tr>
<td><code>for...in</code> 문</td>
<td>객체(또는 배열)</td>
<td>객체의 열거 가능한 모든 속성 반복 가능</td>
<td>배열에 사용 시 예기치 않은 동작 발생 가능</td>
</tr>
<tr>
<td><code>for...of</code> 문</td>
<td>반복 가능한 객체</td>
<td>배열 및 다른 iterable의 요소 반복에 적합, 코드 간결</td>
<td>객체 속성 반복 불가</td>
</tr>
<tr>
<td><code>forEach</code> 메서드</td>
<td>배열</td>
<td>간결하다, 함수형 프로그래밍 스타일</td>
<td><code>break</code>나 <code>continue</code> 사용 불가, 비동기 처리 지원 미비</td>
</tr>
</tbody></table>
<p>이 표를 통해 각 루프 방식의 사용 사례와 장단점을 이해하고, 상황에 맞는 적절한 루프를 선택할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react - 컴파운드 컴포넌트]]></title>
            <link>https://velog.io/@king_nono_1030/react-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@king_nono_1030/react-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Wed, 10 Jul 2024 06:51:34 GMT</pubDate>
            <description><![CDATA[<h2 id="출처">출처</h2>
<p><a href="https://canvas.workday.com/get-started/for-developers/resources/compound-components/#:~:text=Compound%20components%20is%20a%20pattern,Shared%20model%20(optional%2C%20advanced)">canva developer resources - compound components</a>
<a href="https://www.patterns.dev/react/compound-pattern">patterns.dev</a></p>
<h2 id="학습-배경">학습 배경</h2>
<p>다음은 &#39;오픈마인드&#39; 프로젝트에서 내가 맡은 드롭다운 컴포넌트이다.</p>
<pre><code class="language-jsx">// Dropdown.js
export default function Dropdown({ options, handleOption }) {
  const [isOpen, toggleDropdown] = useToggle(false);
  const { selectedOption, selectOption } = useDropdown(options);

  return (
    &lt;S.DropdownWrapper&gt;
      &lt;S.DropdownButton $isOpen={isOpen} onClick={toggleDropdown} type=&#39;button&#39;&gt;
        {selectedOption.content}
        {getCaret(isOpen)}
      &lt;/S.DropdownButton&gt;
      {isOpen &amp;&amp; (
        &lt;S.OptionList&gt;
          {options.map((option) =&gt; (
            &lt;S.Option
              key={option.value}
              onClick={() =&gt; {
                selectOption(option);
                handleOption(option.value);
              }}
              $isSelected={option.content === selectedOption.content}
            &gt;
              {option.content}
            &lt;/S.Option&gt;
          ))}
        &lt;/S.OptionList&gt;
      )}
    &lt;/S.DropdownWrapper&gt;
  );
}</code></pre>
<pre><code class="language-js">// useDropdown.js
import { useState, useCallback } from &#39;react&#39;;

export default function useDropdown(options) {
  const [selectedOption, setSelectedOption] = useState(options[0]);
  const selectOption = useCallback((option) =&gt; {
    setSelectedOption(option);
  }, []);

  return {
    selectedOption,
    selectOption,
  };
}</code></pre>
<p>UI 와 디자인을 구분한다고, useDropdown 커스텀훅까지 만들었지만, Dropdown 컴포넌트는 여전히 지저분하다.</p>
<pre><code class="language-jsx">// FeedSection.jsx
    &lt;S.SectionContainer className={className}&gt;
      &lt;Dropdown options={orderOptions} handleOption={handleOption} /&gt;
      &lt;FeedList feeds={feeds} /&gt;
    &lt;/S.SectionContainer&gt;</code></pre>
<p>사용되는 Dropdown 은 지나치게 생략되었다. 과연 options 프롭스만 넘겨주면 내가 원하는 드롭다운을 매번 재사용하는게 가능할까?</p>
<p><strong>정답은 그렇지 않다</strong>
위는 대표적인 안티패턴으로, 하나의 컴포넌트가 너무 많은 일을 맡은 경우가 해당된다.</p>
<h3 id="기존-코드의-문제점">기존 코드의 문제점</h3>
<ul>
<li>복잡성 증가: 모든 상태와 로직을 한 컴포넌트에 모아두면, 컴포넌트가 복잡해 유지보수가 어렵다.</li>
<li>재사용성 저하: 드롭다운 컴포넌트의 외부에서 이를 커스텀할 수 있는 것은 오직 options와 handleOption 뿐이다.</li>
<li>테스트 어려움: 컴포넌트가 복잡하니, 개별 기능을 테스트하기 어렵다.</li>
<li>성능 저하: 복잡한 컴포넌트는 쓸데없이 리렌더링이 일어날 가능성이 높다.
나는 위 문제점을 해결하기 위한 방안을 모색하던 중, 컴파운드 컴포넌트 디자인 패턴에 대해 알게 되었다.<h2 id="컴파운드-컴포넌트">컴파운드 컴포넌트</h2>
컴파운드 컴포넌트(Compound Component) 패턴은 여러 개의 독립적인 하위 컴포넌트들을 조합하여 UI를 구성하는 방식이다.
드롭다운과 같이 부모 컴포넌트와 자식 컴포넌트가 같은 상태를 공유하고, 함께 동작하는 경우에 컴파운트 컴포넌트를 따르는 것이 매우 효과적이다.</li>
</ul>
<p>컴파운드 컴포넌트 패턴은 UI를 더욱 유연하게 구성할 수 있도록 하며, 각 하위 컴포넌트를 독립적으로 관리하면서도 상위 컴포넌트와 잘 연동되도록 할 수 있다.</p>
<h3 id="예시--드롭다운">예시 : 드롭다운</h3>
<p><img src="https://i.imgur.com/c9U2Z2i.png" alt="|100">
드롭다운 컴포넌트를 분석하면</p>
<blockquote>
<ol>
<li>모두를 감싸는 컨테이너</li>
<li>드롭다운을 토글하는 버튼</li>
<li>토글 시 나타나는 메뉴</li>
<li>메뉴 안의 각 선택지</li>
</ol>
</blockquote>
<p>이렇게 4가지로 구분할 수 있다.</p>
<ol>
<li>컨테이너 컴포넌트<pre><code class="language-jsx">// Dropdown
const DropdownContext = createContext();
</code></pre>
</li>
</ol>
<p>const Dropdown = ({ children }) =&gt; {
    const [isOpen, setIsOpen] = useState(false);
    const toggle = () =&gt; setIsOpen(!isOpen);</p>
<pre><code>return (
    &lt;DropdownContext.Provider value={{ isOpen, toggle }}&gt;
        &lt;div className=&#39;dropdown&#39;&gt;{children}&lt;/div&gt;
    &lt;/DropdownContext.Provider&gt;
);</code></pre><p>};</p>
<pre><code>- 드롭다운은 컨텍스트로 감싸 isOpen 과 toggle 이라는 상태를 드롭다운과 하위 컴포넌트들이 동시에 사용할 수 있다.
1. 토글 컴포넌트
```jsx
// Toggle
const Toggle = ({ children }) =&gt; {
    const { toggle } = useContext(DropdownContext);

return (
        &lt;button className=&#39;dropdown-toggle&#39; onClick={toggle}&gt;
            {children}
        &lt;/button&gt;
    );
};</code></pre><ul>
<li>토글 컴포넌트는 toggle 이라는 동작을 받아, 클릭시에 isOpen 이라는 상태의 값을 변경할 수 있다.</li>
</ul>
<ol start="3">
<li><p>메뉴 컴포넌트</p>
<pre><code class="language-jsx">// Menu
const Menu = ({ children }) =&gt; {
 const { isOpen } = useContext(DropdownContext);

return isOpen ? &lt;div className=&#39;dropdown-menu&#39;&gt;{children}&lt;/div&gt; : null;
};</code></pre>
</li>
</ol>
<ul>
<li>메뉴 컴포넌트는 isOpen 에 따라 조건부로 렌더링 된다.</li>
</ul>
<ol start="4">
<li>아이템 컴포넌트<pre><code class="language-jsx">// Item
const Item = ({ children, onClick }) =&gt; {
 return (
     &lt;div className=&#39;dropdown-item&#39; onClick={onClick}&gt;
         {children}
     &lt;/div&gt;
 );
};</code></pre>
<pre><code class="language-jsx">// export
Dropdown.Toggle = DropdownToggle;
Dropdown.Menu = DropdownMenu;
Dropdown.Item = DropdownItem;
</code></pre>
</li>
</ol>
<p>export default Dropdown;</p>
<pre><code>- 어차피 하위 컴포넌트들은 Dropdown 이랑 한 세트이므로, 한 번에 import 받을 수 있게 Dropdown의 프로퍼티로 넘겨준다.
5. 사용 예시
```jsx
// App.jsx
    &lt;Dropdown&gt;
        &lt;Dropdown.Toggle&gt;드롭다운 버튼&lt;/Dropdown.Toggle&gt;
        &lt;Dropdown.Menu&gt;
            &lt;Dropdown.Item onClick={() =&gt; handleItemClick(1)}&gt;
                옵션 1
            &lt;/Dropdown.Item&gt;
            &lt;Dropdown.Item onClick={() =&gt; handleItemClick(2)}&gt;
                옵션 2
            &lt;/Dropdown.Item&gt;
        &lt;/Dropdown.Menu&gt;
    &lt;/Dropdown&gt;</code></pre><ul>
<li>만든 드롭다운 컴포넌트는 위 처럼 조합하여 만든다.</li>
<li>이렇게 될 경우, 우리는 Dropdown에 종속되어 있는 하위 컴포넌트들을 바깥에서 직접 커스터마이징 할 수 있어 디자인이 유연하다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li><strong>유연성</strong>: 하위 컴포넌트들을 원하는 대로 조합하여 상위 컴포넌트를 구성할 수 있다.</li>
<li><strong>재사용성</strong>: 하위 컴포넌트들이 독립적이므로 다양한 조합으로 재사용할 수 있다.</li>
<li><strong>캡슐화</strong>: 상위 컴포넌트는 하위 컴포넌트의 상태와 동작을 관리하고, 하위 컴포넌트는 자신의 역할에만 집중할 수 있다.</li>
<li><strong>가독성</strong>: 컴포넌트의 역할이 명확하게 분리되어 있어 코드의 가독성이 향상된다.</li>
</ul>
<h2 id="결론">결론</h2>
<p>컴파운드 컴포넌트 패턴은 복잡한 UI를 더 유연하고 유지보수하기 쉽게 만들어준다. 이 패턴을 사용하면 각 구성 요소를 독립적으로 관리할 수 있어 코드의 가독성과 재사용성을 높일 수 있다. 특히, 다양한 상황에서 쉽게 확장 가능하고, 사용자 정의가 필요한 경우에 매우 유용하다.</p>
<p>React로 개발하면서 컴포넌트의 복잡성이 증가할 때, 컴파운드 컴포넌트 패턴을 도입해보길 권해본다. 이를 통해 더 깨끗하고 관리하기 쉬운 코드를 작성할 수 있을 것이다. 또한, 팀 협업 시 컴포넌트의 역할이 명확해져 개발 효율성을 높일 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 동작 원리]]></title>
            <link>https://velog.io/@king_nono_1030/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@king_nono_1030/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sat, 06 Jul 2024 03:55:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://i.imgur.com/pmsdhu9.png" alt="|600"></p>
<h4 id="대다수의-프로그래밍-언어">대다수의 프로그래밍 언어</h4>
<ul>
<li>사람이 사용하는 프로그래밍 언어를 컴퓨터가 이해할 수 있게, 중간에 컴파일이라는 과정이 수반됨</li>
<li>프로그래밍 언어 -&gt; AST: 추상 문법 트리 -&gt; 바이트 코드<h4 id="타입스크립트">타입스크립트</h4>
</li>
<li>타입스크립트 -&gt; AST -(타입 검사)&gt; JavaScript</li>
<li>타입스크립트의 결과물은 바이트 코드가 아닌 자바스크립트 코드</li>
<li>타입 관련 문법들은 자바스크립트로 변환되어 사라짐, 프로그래밍 실행에 영향 X</li>
</ul>
<h2 id="타입스크립트-주요-동작-단계">타입스크립트 주요 동작 단계</h2>
<p>타입스크립트는 크게 두 가지 주요 단계로 동작한다.</p>
<ul>
<li>타입 검사</li>
<li>트랜스파일링<h3 id="1-타입-검사-type-checking">1. 타입 검사 (Type Checking)</h3>
</li>
<li>타입 지정: 타입스크립트에서는 변수, 함수 매개변수, 반환 값 등의 타입을 명시적으로 선언할 수 있다. <pre><code class="language-typescript">let message: string = &quot;Hello, TypeScript!&quot;;
</code></pre>
</li>
</ul>
<pre><code>
```ts
function hello(a: number){
  console.log(a)
}
hello(33)
//correct


function hello(a: number){
  console.log(a)
}
hello(&quot;Joah&quot;)
//error</code></pre><ul>
<li><p>타입 추론: 명시적으로 타입을 선언하지 않은 경우, 타입스크립트는 변수의 초기 값이나 함수의 반환 값 등을 통해 타입을 추론한다.</p>
<pre><code class="language-typescript">let count = 42; // 이 변수의 타입은 자동으로 number로 추론된다.</code></pre>
</li>
<li><p>컴파일 타임 검사: 타입스크립트는 컴파일 중에 모든 타입 선언과 추론이 일치하는지 확인한다. 타입 불일치나 오류가 발생하면 컴파일 오류를 발생시킨다.</p>
<pre><code class="language-typescript">let userName: string = 123; // 컴파일 오류: &#39;number&#39; 타입을 &#39;string&#39; 타입에 할당할 수 없다.</code></pre>
<h3 id="2-트랜스파일링-transpiling">2. 트랜스파일링 (Transpiling)</h3>
</li>
<li><p>트랜스파일러 역할: tsc는 타입스크립트 코드를 자바스크립트 코드로 변환한다. 이 과정에서 모든 타입 정보는 제거되고, 순수한 자바스크립트 코드만 남게 된다. </p>
<pre><code class="language-typescript">// 타입스크립트 코드
let greeting: string = &quot;Hello, TypeScript!&quot;;
console.log(greeting);
</code></pre>
</li>
</ul>
<p>// 변환된 자바스크립트 코드
var greeting = &quot;Hello, TypeScript!&quot;;
console.log(greeting);</p>
<pre><code>
- 타겟 설정: 개발자는 tsconfig.json 파일을 통해 트랜스파일링 설정을 정의할 수 있다. 특정 ECMAScript 버전을 지정하거나, 모듈 시스템 등이 이에 해당한다.
```json
{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;,
    &quot;module&quot;: &quot;commonjs&quot;
  }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[TS - 타입스크립트 사용 이유]]></title>
            <link>https://velog.io/@king_nono_1030/TS-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@king_nono_1030/TS-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sat, 06 Jul 2024 03:49:25 GMT</pubDate>
            <description><![CDATA[<h2 id="1-정적-타입-검사">1. 정적 타입 검사</h2>
<p><strong>오류 사전 방지</strong>: 타입스크립트는 정적 타입 검사를 통해 코드 작성 중에 잠재적인 오류를 사전에 발견한다. 이는 디버깅 시간을 크게 줄이고, 코드의 안정성을 높인다. 예를 들어, 변수의 타입을 미리 지정함으로써 잘못된 타입의 값이 할당되는 것을 방지한다.</p>
<p><strong>IDE 지원 강화</strong>: 타입스크립트는 정적 타입 정보를 제공하므로, 코드 작성 시 더 나은 자동 완성, 코드 탐색 및 리팩토링 지원을 받을 수 있다. 이는 개발 생산성을 크게 향상시킨다. </p>
<h2 id="2-코드-가독성-및-유지보수성-향상">2. 코드 가독성 및 유지보수성 향상</h2>
<p><strong>명시적 타입 선언</strong>: 타입스크립트에서 변수와 함수에 명시적으로 타입을 선언할 수 있다. 이는 코드의 의도를 더 명확하게 만들어 팀원 간의 이해도를 높이고, 유지보수성을 향상시킨다. </p>
<p><strong>인터페이스와 제네릭</strong>: 타입스크립트는 인터페이스와 제네릭 타입을 통해 복잡한 타입 정의를 더 명확하고 구조적으로 할 수 있게 해준다. 이는 복잡한 애플리케이션 개발 시 특히 유용하다.</p>
<h2 id="3-대규모-프로젝트-관리-용이">3. 대규모 프로젝트 관리 용이</h2>
<p><strong>모듈 시스템</strong>: 타입스크립트는 모듈 시스템을 통해 대규모 프로젝트를 더 쉽게 관리할 수 있게 해준다. 이는 여러 개발자가 협업하는 프로젝트에서 특히 유용하다.</p>
<p><strong>스케일링</strong>: 코드베이스가 커질수록 타입스크립트의 정적 타입 시스템과 모듈 시스템은 코드의 구조를 더 잘 유지하고, 복잡성을 관리하는 데 유리하다.</p>
<h2 id="결론">결론</h2>
<p>타입스크립트는 코드의 안전성, 가독성, 유지보수성 면에서 자바스크립트보다 많은 이점을 제공한다. 따라서 대규모 프로젝트나 장기적인 유지보수가 필요한 프로젝트에서는 타입스크립트를 사용하는 것이 더 바람직하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS - 공변성과 반공변성]]></title>
            <link>https://velog.io/@king_nono_1030/TS-%EA%B3%B5%EB%B3%80%EC%84%B1%EA%B3%BC-%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1-q7fz94ko</link>
            <guid>https://velog.io/@king_nono_1030/TS-%EA%B3%B5%EB%B3%80%EC%84%B1%EA%B3%BC-%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1-q7fz94ko</guid>
            <pubDate>Wed, 03 Jul 2024 06:51:19 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>타입스크립트에서 악명이 높은 공변성과 반공변성에 대해 알아보자.
먼저, 이 포스트는 tsconfig의 strictFunctionTypes 옵션이 true 인 경우를 기준으로 말함을 알린다.</p>
<pre><code class="language-json">{
    &quot;compilerOptions&quot;: {
        &quot;strictFunctionTypes&quot;: true;
    }
}</code></pre>
<h2 id="공변성-covariance-좁은-타입을-넓은-타입에-대입-o--넓은-타입을-좁은-타입에-대입-x">공변성 (Covariance): 좁은 타입을 넓은 타입에 대입 O / 넓은 타입을 좁은 타입에 대입 X</h2>
<p>공변성은 한 타입이 다른 타입의 서브타입(subtype)일 때, 그 타입을 사용하는 다른 모든 맥락에서도 서브타입 관계가 유지되는 성질을 말한다.</p>
<p>예를 들어, 배열 타입은 공변적인데, <code>Animal</code>이 <code>Dog</code>의 슈퍼타입이라면, <code>Animal[]</code>은 <code>Dog[]</code>의 슈퍼타입이다.</p>
<pre><code class="language-typescript">interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

let animals: Animal[] = [{ name: &#39;여름이&#39; }];
let dogs: Dog[] = [{ name: &#39;여름이&#39;, breed: &#39;말티즈&#39; }];

animals = dogs; // 정상: Dog[]는 Animal[]의 서브타입
dogs = animals; // 에러: 넓은 타입을 좁은 타입에 대입 X</code></pre>
<p><img src="https://i.imgur.com/8G6PooF.png" alt="|300"><img src="https://i.imgur.com/AfOZvZz.png" alt="|300"></p>
<p>즉, Dog ⊂ Animal 이므로, Dog[] ⊂ Animal[] 의 관계 또한 성립하는 것을 공변성이라고 한다.</p>
<p>함수의 반환값의 타입 역시 공변적이다.</p>
<pre><code class="language-typescript">type giveDog = (a: string) =&gt; Dog;
declare const giveMaltese: (a: string) =&gt; Maltese;
declare const giveAnimal: (a: string) =&gt; Animal;

const dog: giveDog = giveMaltese; // 정상: Maltese ⊂ Dog 이므로 giveMaltese ⊂ giveDog
const dog: giveDog = giveAnimal; // 넓은 타입을 좁은 타입에 대입 X</code></pre>
<p><img src="https://i.imgur.com/aOlpyL1.png" alt="|300"></p>
<h2 id="반공변성-contravariance-넓은-타입을-좁은-타입에-대입-o--좁은-타입을-넓은-타입에-대입-x">반공변성 (Contravariance): 넓은 타입을 좁은 타입에 대입 O / 좁은 타입을 넓은 타입에 대입 X</h2>
<p>반공변성은 한 타입이 다른 타입의 서브타입일 때, 그 타입을 매개변수로 사용하는 모든 맥락에서 역으로 서브타입 관계가 유지되는 성질을 말한다.</p>
<p>예를 들어, 함수 매개변수 타입은 반공변적이다.</p>
<pre><code class="language-typescript">type takeDog = (a: Dog) =&gt; string;
declare const takeMaltese: (a: Maltese) =&gt; string;
declare const takeAnimal: (a: Animal) =&gt; string;

const dog: takeDog = takeMaltese; // 에러: 함수의 매개변수는 반공변성을 따라 좁은 타입을 넓은 타입에 대입 X
const dog: takeDog = takeAnimal; // 정상: Dog ⊂ Animal 이지만 takeAnimal ⊂ takeDog</code></pre>
<p><img src="https://i.imgur.com/lBvttNI.png" alt="|300"></p>
<ul>
<li>첫 번째 대입에서 에러가 발생하는 이유는 takeMaltese 함수는 Maltese 타입만 받아들일 수 있는데, takeDog 타입은 더 넓은 범위인 Dog 타입을 받아들일 수 있어야 하기 때문이다. 만약 takeDog 타입이 takeMaltese 타입을 받아들이면, Dog 타입의 다른 서브타입을 전달할 때 문제가 발생할 수 있다. 따라서 이는 타입 안전성을 보장하지 못한다.</li>
<li>두 번째 대입은 정상이다. takeAnimal 함수는 Animal 타입을 받아들이기 때문에 Dog 타입도 받아들일 수 있다. 즉, takeAnimal은 더 넓은 범위를 받아들이기 때문에 takeDog 타입으로 대입이 가능하다. 이는 타입 안전성을 유지한다.</li>
</ul>
<blockquote>
<p>반공변성은 타입 시스템에서 매개변수의 타입을 설정할 때 매우 중요하다. 함수의 매개변수 타입이 서브타입으로 대체될 경우, 호출 시점에 잘못된 타입이 전달될 수 있다. 예를 들어, takeDog 타입이 takeMaltese 타입으로 대체되면, Dog 타입의 다른 서브타입 (예: Bulldog)이 전달될 때 문제가 발생할 수 있다. 반대로, 더 넓은 타입인 Animal 타입을 사용하는 것은 안전하다.</p>
</blockquote>
<h2 id="출처">출처</h2>
<p><a href="https://www.youtube.com/shorts/DQAtLLBQa0w">제로초 유튜브</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react - useMemo]]></title>
            <link>https://velog.io/@king_nono_1030/react-useMemo</link>
            <guid>https://velog.io/@king_nono_1030/react-useMemo</guid>
            <pubDate>Thu, 27 Jun 2024 04:06:24 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>useMemo 는 비용이 큰 연산에 대해 결과를 메모이제이션해 두고, 저장된 값을 반환하는 훅이다.
리액트 최적화를 얘기할 때 가장 많이 언급되는 훅이다.</p>
<pre><code class="language-jsx">import { useMemo } from &#39;react&#39;

const memoizedValue = useMemo(() =&gt; expensiveComputation(a, b), [a, b]);</code></pre>
<p>첫번째 인수는 특정 값을 반환하는 생성 함수를, 두번째 인수로는 해당 함수가 의존하는 값의 배열을 전달한다.
렌더링 발생 시 의존성 배열의 값의 변경이 없다면, 함수를 재실행하지 않고 이전에 기억해 둔 해당 값을 반환한다.
변경이 있었다면, 함수를 새로 실행해 값을 반환하고 해당 값을 기억한다.</p>
<blockquote>
<p>메모이제이션의 반환값으로 컴포넌트도 올 수 있다.</p>
</blockquote>
<ul>
<li>Memoized Component: 기억되고 있는 컴포넌트로, 의존성 배열의 값이 변화되지 않으면 리렌더링되지 않는다.</li>
</ul>
<p>비용이 많이 드는 연산에서 useMemo 가 쓰인다는 것은 알았다. 그럼 모든 함수와 컴포넌트에 useMemo 를 적용하면 좋은거 아닌가?</p>
<p>하는 궁금증이 들었다.</p>
<p><a href="https://velog.io/@king_nono_1030/react-optimization">관련 내용은 추가로 공부해 정리해보았다.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react - 리액트 최적화: 메모이제이션, 언제하면 되나]]></title>
            <link>https://velog.io/@king_nono_1030/react-optimization</link>
            <guid>https://velog.io/@king_nono_1030/react-optimization</guid>
            <pubDate>Thu, 27 Jun 2024 04:05:10 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>리액트 제공 API 중 useMemo, useCallback, memo 는 리액트에서 발생하는 렌더링을 최소한으로 줄이기 위해 사용된다. 그런데, 언제 정확히 써야하는걸까?
메모이제이션 비용과 렌더링 비용은 어떻게 비교하는걸까?</p>
<p>여기에 대해서는 갑론을박이 많다.</p>
<h3 id="주장1-섣부른-최적화는-독이다-필요한-곳에만-메모이제이션-하라">주장1: 섣부른 최적화는 독이다. 필요한 곳에만 메모이제이션 하라.</h3>
<ul>
<li>메모이제이션도 비용이 든다.</li>
<li>간단한 연산은 자바스크립트 메모리 어딘가에 두었다 꺼내오는 것보다, 매번 해당 작업을 수행하는게 더 빠르고 좋다.</li>
<li>premature optimization은 지양해야 한다.</li>
<li>렌더링이 그렇게 큰 비용이 들면, 기본 리액트에서 이미 모든 컴포넌트를 PureComponent 나 memo 로 감싸서 나왔겠지.</li>
<li>메모이제이션은 무조건 좋은게 아니라, trade off 가 있다. 희생하는 면도 고려해야.</li>
<li>무조건 좋은 방향은 없음. 하드웨어 기술의 발전이 있으면, 매번 연산을 하는게 메모리에 저장하는 것보다 비용이 줄어들 수도 있어. 최적화 전략은 상황에 따라 판단. 미래에는 useMemo 가 최적화 기법으로 남아있지 않을 수도.</li>
</ul>
<h3 id="주장2-렌더링-비용은-비싸다-모조리-메모이제이션-하라">주장2: 렌더링 비용은 비싸다. 모조리 메모이제이션 하라.</h3>
<ul>
<li>일부 컴포넌트를 메모이제이션하는건 당장 성능에 도움이 된다.</li>
<li>렌더링이 자주 일어나고, 그 렌더링 안에 비싼 연산이 있다면 메모이제이션의 이점은 분명하다.</li>
<li>일단 모두 memo로 감싸고 지불해야 하는 비용은 props 의 얕은 비교뿐? -&gt; 렌더링의 비용과 비교하면 훨씬 싸다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS - 클래스]]></title>
            <link>https://velog.io/@king_nono_1030/JS-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@king_nono_1030/JS-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Thu, 27 Jun 2024 00:39:03 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스">클래스</h2>
<p>이전에 <a href="https://velog.io/@king_nono_1030/JS-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85">프로토타입 체이닝</a>을 통해 자바스크립트에서 상속이 구현됨을 확인했다.</p>
<pre><code class="language-js">var Person = (function(){
  function Person(name){
    this.name = name;
  }

  Person.prototype.sayHi = function(){
    console.log(&#39;안녕! 내 이름은 &#39; + this.name);
  }
return Person
}());

var me = new Person(&#39;Lee&#39;);
me.sayHi();</code></pre>
<p>그런데, 많은 개발자들은 자바나 C#의 클래스 기반 객체지향 모델에 익숙하다. 이에 따라 ES6 에서는 클래스를 도입하여, 위 개발자들의 편의를 늘렸다.</p>
<p>그러나, 클래스는 껍데기만 클래스이지, 속살은 우리가 이미 아는 프로토타입 기반의 생성자 함수이다. 즉, 문법적 설탕에 해당한다. 다만, 기존 생성자 함수와는 완전히 일치하지는 않으므로 추가 공부가 필요하다.</p>
<h4 id="클래스와-기존의-생성자-함수와의-차이점">클래스와 기존의 생성자 함수와의 차이점</h4>
<ol>
<li>클래스는 new 연산자 없으면 에러</li>
<li>클래스는 extends, super 키워드를 지원</li>
<li>클래스는 호이스팅이 안되는 것처럼 동작..! -&gt; 사실은 일어난다는건가?</li>
<li>클래스 내부는 암묵적으로 strict mode 지정</li>
<li>클래스의 constructor, 메서드(프로토타입, 정적)는 모두 Enumerable 값이 false. 열거불가능. </li>
</ol>
<h3 id="클래스-정의">클래스 정의</h3>
<p>클래스는 일급 객체이다. 즉, 함수를 값처럼 쓰고 자유롭게 만지는 것 마냥, 클래스도 동일하게 다룰 수 있다. 클래스는 함수다.</p>
<pre><code class="language-js">class Person {
  constructor(name){
    this.name = name;
  }

  sayHi(){
    console.log(&#39;안녕 내 이름은 &#39; + this.name);
  }

  static sayHello(){
    console.log(&#39;아아안녕~&#39;);
  }
}

const me = new Person(&#39;윤호&#39;);
console.log(me.name); // &#39;윤호&#39;
me.sayHi(); // &#39;안녕 내 이름은 윤호&#39;
Person.sayHello(); // &#39;아아안녕~&#39;</code></pre>
<h3 id="클래스-호이스팅">클래스 호이스팅</h3>
<p>함수처럼 런타임 이전에 평가되고 함수 객체를 생성한다. ([[JS - 빌트인 객체, this, 실행 컨텍스트]] 참조) -&gt; constructor 함수와 프로토타입이 쌍으로 생성된다.</p>
<p>그런데, 클래스는 클래스 정의 이전에 호출이 불가능하다.</p>
<pre><code class="language-js">const Person = &#39;&#39;;
{
  console.log(Person); // ReferenceError
  class Person {}
}</code></pre>
<p>호이스팅이 안 일어났다면, Person 은 상위 스코프의 &#39;&#39; 값을 참조하는게 맞고, 출력도 멀쩡히 되야 한다.</p>
<p>그런데, 참조 에러가 발생한다. 이는 클래스도 호이스팅이 일어난다는 증거다. 그런데, let, const 키워드 처럼 일시적 사각지대에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.</p>
<h3 id="인스턴스-생성">인스턴스 생성</h3>
<p>클래스의 존재 의의는 인스턴스 생성이기에 항상 new 와 함께 사용된다.</p>
<pre><code class="language-js">const Person = class MyClass{};
console.log(MyClass); // 참조 오류
const you = new MyClass(); // 참조오류</code></pre>
<p>위에서 에러가 발생하는 이유는 MyClass 라는 클래스 이름은 클래스 몸체 안에서만 유효하기 때문이다. 기명 함수 표현식과 비교하면 이해하기 쉽다.</p>
<pre><code class="language-js">const var = function foo(){
   return &#39;사랑해요~&#39;;
}
console.log(var); // 함수
console.log(foo); // 참조오류</code></pre>
<h3 id="메서드">메서드</h3>
<p>클래스가 가질 수 있는 메서드 종류는 세 가지. constructor, 프로토타입 메서드, 정적 메서드가 있다.</p>
<ul>
<li><p>constructor</p>
<blockquote>
<p>클래스의 constructor 메서드와 프로토타입의 constructor 프로퍼티는 다르다!
클래스 내부에서 두 개 이상의 consturctor 는 있을 수 없다.
consturctor 는 생략 가능하다. 생략시 클래스는 빈 객체를 생성한다.
constructor 에 return 은 없어야 한다. 기본적으로 return this; 해야 하니까. 이거 건들면 인스턴스 생성이 안됨. 다만 return 1; 같이 원시값을 반환하면, construuctor 는 이를 무시하고 this 를 정상적으로 반환한다. 하지만 역시 return 을 생략하는게 가장 안전하다..!</p>
</blockquote>
</li>
<li><p>프로토타입 메서드</p>
<blockquote>
<p>클래스는 prototype 프로퍼티에 메서드 추가하지 않아도 된다.
생성자 함수와 마찬가지로 클래스로 생성한 인스턴스는 프로토타입 체이닝의 일원이 된다.
클래스는 프로토타입 기반의 객체 생성 메커니즘일뿐, 생성자 함수와 역할이 동일하다.</p>
</blockquote>
</li>
<li><p>정적 메서드</p>
<blockquote>
<p>클래스는 정적 메서드를 생성자 함수와 다르게 명시적으로 표시한다. static 키워드를 붙인다.
생성자 함수와 마찬가지로 정적 메서드는 인스턴스가 아닌 생성한 모체(생성자 함수/클래스)로 호출한다.
this를 사용하지 않는 메서드라면, 정적 메서드를 쓰는게 좋다.</p>
</blockquote>
</li>
</ul>
<h3 id="클래스의-인스턴스-생성-과정">클래스의 인스턴스 생성 과정</h3>
<ol>
<li>빈 객체로 인스턴스 생성 + 여기에 this 바인딩</li>
<li>인스턴스 초기화 ~ constructor 내부 코드에 의해</li>
<li>인스턴스 반환 (인스턴스랑 바인딩된 this가 암묵적으로 반환)</li>
</ol>
<h3 id="프로퍼티">프로퍼티</h3>
<ol>
<li>인스턴스 프로퍼티: constructor 안에서 정의</li>
<li>접근자 프로퍼티: getter와 setter. 프로토타입 프로퍼티. 인스턴스에서 참조 가능.<pre><code class="language-js">class Person{
  get FullName(){
    returen `${this.firstName} ${this.lastName}`;
  }
  set FullName(name){
    [this.firstName, this.lastName] = name.split(&#39; &#39;);
  }
}</code></pre>
</li>
<li>클래스 필드<pre><code class="language-js">class Person{
name = &#39;Lee&#39;;
getName = function(){
 return this.name;
}
}</code></pre>
클래스 필드에 함수 선언하면, 이는 프로토타입 메서드가 아니라 인스턴스 메서드가 된다. 즉 인스턴스 만들때마다 새로 함수를 만들어 내니까, 하지 마라탕...</li>
<li>private field   <pre><code class="language-js">class Person = {
  #name = &#39;&#39;; // 얘는 클래스 몸체 안에서만 참조 가능
  constructor(name){
    this.#name = name;
  }
  get name(){
    return this.#name;
  }
}</code></pre>
인스턴스에서 접근 못하는 프로퍼티 만들 때 사용. 접근자 프로퍼티로 간접적으로 접근은 가능.
프라이빗 필드는 반드시 클래스 몸체에서만 정의해야 함...</li>
</ol>
<h3 id="클래스-상속">클래스 상속</h3>
<pre><code class="language-js">class Animal {
  constructor(age, weight){
    this.age = age;
    this.weight = weight;
  }

  eat(){return &#39;eat&#39;;}

  move(){return &#39;move&#39;;}
}

class Bird extends Animal {
  fly(){return &#39;fly&#39;;}
}

class Dog extends Animal {
  bark(){return &#39;멍&#39;;}
}
</code></pre>
<p>상속을 주는 Animal 은 수퍼 클래스 / 상속을 받는 Bird 나 Dog 은 서브클래스
프로토타입 메서드는 물론, 정적 메서드도 상속 가능!</p>
<h3 id="동적-상속">동적 상속</h3>
<p>class 가 생성자 함수에게 상속을 받을 수 있다.
<code>class Derived extends Base:생성자 함수 {}</code></p>
<h3 id="서브클래스의-constructor">서브클래스의 constructor</h3>
<p>constructor 생략하면 빈 constructor 가 암묵적으로 정의되고, 얘는 빈 객체를 반환한다.
그런데... 위에서 처럼 서브클래스에서 생략하면</p>
<pre><code class="language-js">class Tiger extends Animal {
  constructor(...args){ super(...args) }
}</code></pre>
<p>사실 위의 코드가 암묵적으로 위 같은 constructor가 정의된다.
super() 는 수퍼클래스의 constructor 를 호출하여 인스턴스를 생성한다.</p>
<h3 id="super">super</h3>
<p>super 키워드는 함수처럼 호출도 되고, this 처럼 식별자처럼 참조도 된다.</p>
<ol>
<li>호출하면 수퍼클래스의 constructor 를 호출<pre><code class="language-js">class Base{
constructor(a, b){
 this.a = a;
 this.b = b;
}
}
</code></pre>
</li>
</ol>
<p>class Derived extends Base {
  constructor(a, b, c){
    super(a, b);
    this.c =. c;
  }
}</p>
<pre><code>
&gt;서브 클래스에서 constructor 를 정의하면, 위위에 있는 암묵적인 super 호출 정의가 오버라이드 되므로, super를 호출해야 한다.
&gt;super 호출은 웬만하면 constructor의 최상단
&gt;그리고 얘는 반드시 서브클래스에서만 호출

2. 참조하면 수퍼클래스의 메서드를 호출
```js
class Base{
  constructor(name){
    this.name = name;
  }

  sayHi(){
    return `안녕! ${this.name}`;
  }
}

class Derived extends Base {
  sayHi(){
    return `${super.sayHi()}, 어떻게 지내!` // 메서드 오버라이딩
  }
}</code></pre><p>여기서 super.sayHi 는 사실상 Base.prototype.sayHi 를 의미한다. 단, this 바인딩 면에서 다른 점이 있으니 조심해야 한다.</p>
<h3 id="상속-클래스의-인스턴스-생성-과정-몰라서-넘어감-다음에-딥다이브해야지">상속 클래스의 인스턴스 생성 과정 (몰라서 넘어감. 다음에 딥다이브해야지)</h3>
<ol>
<li>서브 클래스의 super 호출. 수퍼클래스한테 인스턴스 생성 위임. 해줘잉.</li>
<li>수퍼 클래스의 인스턴스 생성과 this 바인딩</li>
<li>수퍼클래스의 인스턴스 초기화</li>
<li>서브 클래스 constructor 로의 복귀와 this 바인딩</li>
<li>서브클래스의 인스턴스 초기화</li>
<li>인스턴스 반환</li>
</ol>
<h3 id="표준-빌트인-생성자-함수-확장">표준 빌트인 생성자 함수 확장</h3>
<p>extends 는 생성자 함수에게도 상속을 받을 수 있다.
<code>class MyArray extends Array{}</code>
하면, 내가 원하는 추가 메서드를 더한 배열 인스턴스를 생성할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나만의 테크 블로그 만들기 (5) 블로그 커스텀 하기]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-5-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-5-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Jun 2024 07:34:26 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/timlrx/tailwind-nextjs-starter-blog">내가 적용한 템플릿은</a> next.js, typescript, tailwind.css 로 작성되었다.
내가 아직 배우지 못한 기술 스택이라, 어떻게 커스텀할지, 당장에 커스텀을 할 수나 있을지 걱정이다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/21d491dc-82d7-4057-b2a7-163182a2dd7d/image.png" alt=""></p>
<p>그럴땐, 해당 템플릿 리포지토리의 리드미를 열람하여, 템플릿을 사용한 다른 선배들의 소스 코드를 참고하면 도움이 된다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/1b4d50f1-48fe-45c3-9521-6b1d0810634c/image.png" alt="">
이 사람은 같은 템플릿을 사용하는게 맞는지 의심될 정도로, 커스텀을 많이 했다.
로그인 기능도 추가하고, 구독 기능도 추가한 것으로 보인다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/0f093c0b-8d1a-49d1-b5f5-8f1d50f0ef12/image.png" alt="">
반면에 이 사람은 기본적인 틀은 유지하고, 원하는 컴포넌트만 따로 만들어서 추가한 것으로 보인다.</p>
<p>완전한 커스텀은 내 단계에서는 무리이므로, 아래 예시의 소스 코드를 확인해본다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/84d87448-1729-4f6d-b019-0a0e49070774/image.png" alt=""></p>
<p>리포지토리가 public 이라면, 소스코드와 커밋 히스토리 모두 열람 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/e2aa168a-9bd1-4726-97fa-53bd1bcdcd8a/image.png" alt=""></p>
<p>초기 작업이라 간단하게 로고를 수정하고, 헤더 컴포넌트의 구조를 변경한 것으로 보인다.</p>
<p>이처럼 오픈소스의 장점을 물씬 활용하여, 내가 레벨이 부족하더라도
남의 코드를 교과서 삼아 참고하면, 나의 레벨보다 높은 도전적인 코드도 학습하고, 빠르게 내 것으로 적용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나만의 테크 블로그 만들기 (4) 블로그 작성하기]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-4-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-4-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Jun 2024 07:24:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/df2799ae-4801-4ad2-a988-3b220de58fde/image.png" alt=""></p>
<p>일반적으로 블로그 연재글은 .md 또는 .mdx 형태의 파일로 /data/blog 디렉토리에 있을 것이다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/f52643d7-c25d-4402-9ffc-37e2a623e39a/image.png" alt="">
해당 글들은 깃허브 상에서 수정을 하고 새로운 글을 작성할 수도 있다.</p>
<p>vercel 에서 자동 배포를 지원하기 때문에, main 브랜치에 push 만 해주면
해당 변경사항이 배포된 사이트에 그대로 반영이 된다.</p>
<p>각 포스팅에 해당하는 md 파일들은 예시 코드들을 참고해서 작성할 수도 있고,
나만의 새로운 규칙을 코드를 직접 수정함으로써 적용할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나만의 테크 블로그 만들기 (3) vercel 템플릿 적용하기
]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-vercel-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-vercel-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Jun 2024 07:16:04 GMT</pubDate>
            <description><![CDATA[<h2 id="템플릿-고르기">템플릿 고르기</h2>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/5448c12f-f27b-4731-a7c3-cc378658406e/image.png" alt=""></p>
<p>vercel 에서 공유되는 템플릿의 종류는 매우 많다. 이 중에서 조금만 커스텀해도 원하는 디자인의 블로그로 만들 수 있게, 자신에게 맞는 구조를 가진 템플릿을 선택한다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/cdf4930b-7ba2-4d14-b938-16dfa82d3194/image.png" alt=""></p>
<p>선택한 템플릿에서 view demo 버튼을 누르면, </p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/e21e3871-aa01-4e3e-86e2-fcf72e040156/image.png" alt=""></p>
<p>해당 템플릿을 이용할 때, 만들 수 있는 블로그를 미리 확인할 수 있다.</p>
<h2 id="블로그-프로젝트-배포하기">블로그 프로젝트 배포하기</h2>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/b489aa25-4375-46a9-86de-d4857db1d475/image.png" alt="">
해당 블로그 템플릿의 깃허브 레포지토리를 찾아 use this template 을 클릭하여, 내 레포지토리에 블로그 프로젝트를 생성한다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/098cdc75-87c3-4237-a69d-516fe6ef8ff8/image.png" alt=""></p>
<p>생성한 레포지토리는 vercel 에서 start deploy 메뉴를 클릭하여</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/0c4e7f45-e4e2-40c4-9d42-060c35842117/image.png" alt=""></p>
<p>깃허브 레포지토리와 연결해준다.</p>
<p><img src="https://velog.velcdn.com/images/king_nono_1030/post/c3bbb8dc-e639-4b68-a499-5b5216b69833/image.png" alt=""></p>
<p>연결되면 자동으로 배포까지 vercel 이 무료로 지원해준다.
도메인까지 지원해준다..!
약간의 빌드 타임을 기다려주면, 블로그 프로젝트의 배포는 완료된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나만의 테크 블로그 만들기 (2) vercel]]></title>
            <link>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-vercel</link>
            <guid>https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-2-vercel</guid>
            <pubDate>Wed, 26 Jun 2024 05:38:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@king_nono_1030/%EB%82%98%EB%A7%8C%EC%9D%98-%ED%85%8C%ED%81%AC-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EB%B8%94%EB%A1%9C%EA%B7%B8-%ED%85%9C%ED%94%8C%EB%A6%BF">이전 글</a>에서 썼듯이, 나는 커스텀 블로그를 만들기로 결심했다.</p>
<p>커스텀 블로그를 배포하는 방법으로는 여러가지가 있다.</p>
<blockquote>
<p>Netlify, GitHub Pages, Heroku, vercel</p>
</blockquote>
<p><a href="https://vercel.com/home"><img src="https://velog.velcdn.com/images/king_nono_1030/post/615ba08d-d01b-4ac6-b48c-a97e17fb89b4/image.png" alt=""></a></p>
<p>이 중에서 내가 선택한 배포 플랫폼은 <a href="https://vercel.com/home">vercel</a> 이다. vercel 은 Next.js 를 개발한 회사로, 다양한 기능을 제공한다.</p>
<blockquote>
<p>서버리스 함수, 엣지 네트워크, 즉시 배포, 프론트엔드 프레임워크에 최적화, 자동 스케일링</p>
</blockquote>
<p>vercel 을 이용하여 정적인 사이트와 블로그를 배포하는 것에는 다음과 같은 이점이 있다.</p>
<ol>
<li>성능: Vercel의 엣지 네트워크와 자동 최적화는 빠른 로드 타임과 높은 성능을 보장한다.</li>
<li>사용 용이성: 플랫폼이 직관적이고 배포 과정이 간단하여 개발자에게 친숙하다.</li>
<li>개발자 경험: Vercel은 상세한 분석, 오류 추적, 내장 CI/CD 파이프라인을 제공하여 훌륭한 개발자 경험을 제공한다.</li>
<li>무료 요금제: Vercel은 소규모 프로젝트와 개인 개발자에게 적합한 무료 요금제를 제공한다.</li>
</ol>
<p>다양한 이점이 있으나, 무료라는 점이 나에게는 가장 매력적이었다.
그리고 커뮤니티가 활성화되어, 양질의 템플릿을 쇼핑하듯이 고를 수 있는 점이 가장 큰 매력이다.</p>
<blockquote>
</blockquote>
<p><a href="https://vercel.com/templates">https://vercel.com/templates</a></p>
]]></description>
        </item>
    </channel>
</rss>