<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ateals_12.log</title>
        <link>https://velog.io/</link>
        <description>https://ateals.vercel.app/</description>
        <lastBuildDate>Sun, 28 Jan 2024 07:15:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ateals_12.log</title>
            <url>https://velog.velcdn.com/images/ateals_12/profile/6bdf0974-c8b9-4cce-b8d4-375663ceb6b4/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ateals_12.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ateals_12" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[클린 코드] 2. 의미있는 이름]]></title>
            <link>https://velog.io/@ateals_12/%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-2.-%EC%9D%98%EB%AF%B8%EC%9E%88%EB%8A%94-%EC%9D%B4%EB%A6%84</link>
            <guid>https://velog.io/@ateals_12/%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-2.-%EC%9D%98%EB%AF%B8%EC%9E%88%EB%8A%94-%EC%9D%B4%EB%A6%84</guid>
            <pubDate>Sun, 28 Jan 2024 07:15:15 GMT</pubDate>
            <description><![CDATA[<h1 id="🖊️-오늘의-문장">🖊️ 오늘의 문장</h1>
<blockquote>
<p>프로그래밍은 사회 활동이다.</p>
</blockquote>
<h2 id="📚-읽은-범위">📚 읽은 범위</h2>
<aside>
👨‍💻 2. 의미 있는 이름

</aside>

<h2 id="📝-기억하고-싶은-내용">📝 기억하고 싶은 내용</h2>
<ul>
<li>이름에는 의도가 분명해야한다.</li>
<li>그릇된 정보를 피하라</li>
<li>발음하기 쉬운 이름을 사용해라</li>
<li>검색하기 쉬운 이름을 사용해라</li>
<li>개발에 관련된 이름을 사용해라, 없다면 도메인 관련 용어를 사용해라</li>
<li>맥락을 주의하라</li>
</ul>
<h2 id="💡-읽은-소감">💡 읽은 소감</h2>
<p>프로그래머가 가장 힘들어 하는일이 무엇일까?</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/9208b6f8-272c-4f06-8667-19cda08e737d/image.png" alt=""></p>
<p>그렇다. 네이밍이다. 그만큼 프로그래밍에서 네이밍이 얼마나 중요한 부분을 차지하는지 짐작할 수 있다. </p>
<p>글에서도 밝히듯이 프로그래밍은 사회 활동이다. 대부분의 개발자는 혼자 코딩하지 않고, 협업한다. 자신만이 알 수 있고 읽기 편한 코드는 일의 효율을 해치고 협업에 도움이 되지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/2c71262b-6e7c-43c6-9931-6fb0580d2a62/image.png" alt="토스 | SLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code"></p>
<p><em>토스 | SLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code</em></p>
<p>네이밍으로 한글을 거부하는 개발자도 많다. 하지만 컴파일 상에 문제가 없다면, 복잡한 영어 네이밍을 구상하는 것보다 한글 네이밍이 더욱 직관적이고 사용하기 쉽다는 것을 알 수 있다.</p>
<p>또한, 한글을 외국어로 표현했을 때, 읽기 힘들거나 어색한 표현이 있다. 예를 들어 부동산 앱에서 전세 임차 보증금을 변수로 저장한다면 영어 표현은 한글로 작성한 표현 보다 가독성이 떨어진다.</p>
<pre><code class="language-jsx">const 전세임차보증금 = money;

const depositForLease = money; // GPT</code></pre>
<p>현대의 라이브러리들은 명명 규칙을 몇 가지 권장한다. 가령 필자가 발 담고 있는 프런트엔드 라이브러리인 React에서도 다음과 같은 관례가 있다.</p>
<pre><code class="language-jsx">const [state, setState] = useState()

// 훅은 use라는 prefix로 시작한다. 
// useState는 배열을 반환하는 데 두번째 인자인 setter는 첫 번째 인자인 상태 이름 앞에 set을 붙여 사용한다.

const HelloComponent = ()=&gt;&lt;h1&gt;hello &lt;/h1&gt;

// 컴포넌트 이름은 대문자로 시작한다.</code></pre>
<p>이렇듯 네이밍과 관련된 관례는 해당 라이브러리로 작업하는 개발자들과의 약속이다. 이러한 네이밍 컨벤션은 독자로 하여금 자신의 코드를 훨씬 파악하기 쉽게 하는 역할을 한다.</p>
<p>필자도 네이밍에 신경 쓴다 하지만, 잘 챙기지 못하는 부분이 많다. 2 장에서 언급하듯, 맥락에 따라 변수, 함수, 클래스에 동일한 단어를 사용하는 습관이 필요할 것 같다.</p>
<h2 id="🤔-궁금한-내용">🤔 궁금한 내용</h2>
<p>다른 분들이 네이밍 컨벤션으로 접했던 신기했던 사례나 중요하다 느낀 사례가 궁금합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[클린코드] 1장. 깨끗한 코드]]></title>
            <link>https://velog.io/@ateals_12/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-1%EC%9E%A5.-%EA%B9%A8%EB%81%97%ED%95%9C-%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@ateals_12/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-1%EC%9E%A5.-%EA%B9%A8%EB%81%97%ED%95%9C-%EC%BD%94%EB%93%9C</guid>
            <pubDate>Sat, 27 Jan 2024 08:56:27 GMT</pubDate>
            <description><![CDATA[<h1 id="🖊️-오늘의-문장">🖊️ 오늘의 문장</h1>
<blockquote>
<p>사소한 곳에서 발휘하는 정직은 사소하지 않다.</p>
</blockquote>
<h2 id="📚-읽은-범위">📚 읽은 범위</h2>
<aside>
👨‍💻 추천사 ~ 1. 깨끗한 코드

</aside>

<h2 id="📝-기억하고-싶은-내용">📝 기억하고 싶은 내용</h2>
<p>프로그래밍도 유행이나 스타일이 변하기 때문에 독자가 읽는 시점의 현실과 책이 소개하는 원칙이 잘 맞지 않는 부분 역시 존재하기 마련이다. </p>
<p>핵심은 팀이나 공동체에서 서로 동의하는 합리적인 원칙을 세우기 위한 소통에 있다.</p>
<p>제조업과 마찬가지로 소프트웨어의 80%는 유지보수로 이루어져있다.</p>
<p>코드는 요구사항을 표현하는 언어라는 사실을 명심하라. </p>
<p>나중은 결코 오지 않는다. 나쁜 코드는 개발 속도를 크게 떨어뜨린다.</p>
<p>유명한 프로그래머들은 깨끗한 코드에 대해 다음과 같이 설명한다.</p>
<ul>
<li>가독성 있고, 보기 즐거운 코드다.</li>
<li>한가지에 집중하고 간결하다.</li>
<li>명쾌한 추상화다.</li>
<li>타인이 수정하기 쉬운 코드다.</li>
<li>주의 깊게 작성한 코드다.</li>
<li>깨끗한 변수 이름, 깨끗한 함수, 깨끗한 클래스다.</li>
</ul>
<h2 id="💡-읽은-소감">💡 읽은 소감</h2>
<p>남자는 태어나 3번 운다는 말이 있다. 나는 클린코드를 읽기 전에 빈번하게 들은 말이 있다.</p>
<blockquote>
<p>이 책은 한번 읽고 끝이 아닌 신입 때, 주니어 때, 시니어 때 3번 읽어야 한다. 때마다 읽고 받아들이는 의미가 달라진다.</p>
</blockquote>
<p>책의 서문과 1장을 읽고 들은 생각은 아직 내가 읽기에는 적합하지 않는 책일 수 있다는 것이다. 서문에서도 언급하듯 클린 코드의 핵심과 이 책이 주는 교훈은 팀이나 공동체에서 동의하는 합리적인 원칙을 세우기 위한 소통이기 때문이다. 아무래도 사내 스터디에 어울릴만한 책이라고 감히 판단해 본다.</p>
<p>그렇지만 이 책은 대부분의 사람들이 납득할 만한 이유를 토대로 깨끗한 코드에 대한 중요성을 강조하고 있다. 아직 아마추어인 나도 공감 가는 내용들이 한가득인데, 프로들은 얼마나 흥미롭고 재밌는 책일까? 이 책을 완독하고, 오랜 시간이 지난 후에 프로의 씬에 진입한 내가 다시 읽고 난 뒤 감상이 궁금해진다.</p>
<p>1장은 코드가 지저분해지는 이유와 유명한 프로그래머들의 말을 인용하면서 깨끗한 코드가 무엇인지 독자에게 설명한다. 설명하는 내용과 그간 내가 본 깨끗한 코드에 대한 다른 글들에서 봤던 주장들과 비교해가며 읽으니 더욱 흥미로웠다.</p>
<p>특히 “깨끗한 코드는 타인이 수정하기 쉽고, 가독성 높다.”라는 부분에서 그동안 내가 추상화를 하면서 깨끗하게 보이는 것처럼 한곳에 쑤셔 박아왔던 것이 아닌가 반성한다. </p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/6dbbd20b-cfa7-47ae-a7eb-8b71634bed13/image.png" alt="토스 | SLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code"></p>
<p><em>(토스 | SLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code)</em></p>
<p>클린 코드는 원하는 로직을 빠르게 찾을 수 있는 코드 즉, 가독성 있는 코드라는 것을 명심하자.</p>
<h2 id="🤔-궁금한-내용">🤔 궁금한 내용</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[블로그 ISR도입기]]></title>
            <link>https://velog.io/@ateals_12/%EB%B8%94%EB%A1%9C%EA%B7%B8-ISR%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/%EB%B8%94%EB%A1%9C%EA%B7%B8-ISR%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Sun, 20 Aug 2023 12:36:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b0397fd6-d104-40de-a414-d1f1e4822c96/coding.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114820Z&X-Amz-Expires=3600&X-Amz-Signature=6fd995b4a9d1c1571c5e18721194ad1a7964301572e2e933f1a3c94b826a9106&X-Amz-SignedHeaders=host&x-id=GetObject" alt="coding.gif"></p>
<p>이번에 블로그를 다시 만들었다… <em>(블로그 최종_최최종__최최최종…)</em></p>
<p>이번 블로그의 가장 큰 핵심은 글을 발행할 때 재배포 하지 않도록 DB나 CMS를 이용해 실시간으로 블로그에 글을 업로드 하는 것과 마음에 들지 않는 블로그 디자인을 수정하는 것이다. <em>(디자인이 젤 어려워…)</em></p>
<p>DB를 이용하기에는 DB 클라우드 서비스를 이용해서 사용해야 하기 때문에 작업량이 많아질 것 같아서 전부터 봐왔던 Notion Api를 이용했다.</p>
<p>Notion을 이용해서 실시간으로 노션에서 새 글을 발행할 때마다 서버를 재배포하지 않고, 실시간으로 글을 발행할 수 있게 되었다.</p>
<p>이 과정에서 그동안 봐왔던 Next.js의 ISR을 사용하게 되었는데 이렇게 글로 남기게 되었다.</p>
<h1 id="야심찬-시작-실시간-글-발행">야심찬 시작, 실시간 글 발행</h1>
<hr>
<p>처음엔 단순히 노션에서 지원하는 Javascript SDK를 이용해서 개발했다.</p>
<p>역시 추상화되어 있는 SDK는 사용하기 편했고, Next.js의 SSR을 이용해서, 페이지 요청을 보낼 때 마다. 글을 불러와 MarkDown으로 만들어서 페이지에 표시해 주었다. 응답까지 대기 시간이 발생하기 때문에 Skeleton Ui를 구현해서 사용자가 로딩 중 임을 인식할 수 있게 만들었다. </p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b034870c-766f-48fe-9735-d12853a5ff77/skeletonUi.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114820Z&X-Amz-Expires=3600&X-Amz-Signature=a239e54c2dd8d1ec5763a5c9748d1eb10df5e52de8cbcd48d28acd86d313c4e4&X-Amz-SignedHeaders=host&x-id=GetObject" alt="skeletonUi.gif"></p>
<p>블로그를 만들고 포스트를 올리던 중 느낀 점은 내가 이전의 봤던 페이지도 다시 불러올 때마다 기다려야 한다는 점이었다. 그렇다고 페이지를 SSG로 만들자니 글을 수정할 때마다 재배포 해야 했기 때문에 원점으로 돌아가게 된다.</p>
<p>그러던 중 Next의 ISR이 떠오르게 되었다. 바로 적용해 보자!</p>
<h1 id="그래서-isr이-뭔데">그래서 ISR이 뭔데</h1>
<hr>
<p>Next.js 공식 문서에서는 ISR을 다음과 같이 설명하고 있다.</p>
<blockquote>
<p><strong>ISR(Incremental Static Regeneration)을 사용하면 전체 사이트를 재 구축할 필요 없이</strong> 페이지별로 정적 생성을 사용할 수 있습니다 . ISR을 사용하면 정적의 이점을 유지하면서 수백만 페이지로 확장할 수 있습니다.</p>
</blockquote>
<p>즉 정적으로 만들어 놓은 페이지 들도 필요시 업데이트할 수 있다는 것이다.</p>
<aside>
📌 Next 공식 문서에서 말하는 장점은 다음과 같다.

<p><strong>더 나은 성능: ISR을 통해 Vercel이</strong> <a href="https://vercel.com/docs/edge-network/overview">글로벌 에지 네트워크의</a> 모든 지역에서 생성된 페이지를 캐시 하고 파일을 내구성 있는 스토리지에 유지할 수 있으므로 정적 페이지는 일관되게 빠를 수 있습니다.</p>
<p><strong>백엔드 로드 감소:</strong> ISR은 캐시 된 콘텐츠를 사용하여 데이터 소스에 대한 요청을 줄임으로써 백엔드 로드를 줄이는 데 도움이 됩니다.</p>
<p><strong>더 빠른 빌드:</strong> 페이지는 방문자가 요청할 때 또는 빌드 중이 아니라 API를 통해 생성될 수 있으므로 애플리케이션이 커짐에 따라 빌드 시간을 단축할 수 있습니다.</p>
</aside>

<p>Next의 ISR 구현 방식은 현재 2가지가 있다.</p>
<h2 id="time-based-revalidation">Time-based Revalidation</h2>
<hr>
<p><strong>Time-based Revalidation (시간 기반 재검증</strong>)은 요청에 ravalidate Time을 설정해 일정 시간이 지난 후, 데이터를 재검증하는 방법이다.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2300bd7c-9d95-4c3d-838e-e22c0633ffef/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114820Z&X-Amz-Expires=3600&X-Amz-Signature=10b5c54f25ae93ae41bf6f03eba51b450d8f7009f02ab945abe01039098365a9&X-Amz-SignedHeaders=host&x-id=GetObject" alt="Untitled"></p>
<p>그림을 보면, 첫 번째 요청에서 cache가 없으므로 Data Source에 접근해서 응답 결과를 반환한다.</p>
<p>그다음 같은 요청을 보내면, cache에서 응답 결과를 반환한다.</p>
<p>이때 미리 지정해 둔 revalidate가 만료되면, 다음과 같이 작동한다.</p>
<ul>
<li>백그라운드에서 데이터 재검증을 시작한다.</li>
<li>데이터를 성공적으로 가져오면 Next.js는 새로운 데이터로 cache를 업데이트한다.</li>
<li>재검증이 실패하면 이전 데이터가 변경되지 않은 상태로 유지된다.</li>
</ul>
<h2 id="on-demand-revalidation">On-Demand Revalidation</h2>
<hr>
<p><strong>On-Demand Revalidation(온디맨드 재검증)</strong> 은 <strong>revalidatePath()</strong> 혹은 <strong>revalidateTag()</strong> 를 이용하여 요청을 재검증 한다.</p>
<p><strong>revalidatePath()</strong> 는 페이지의 경로 기반으로 재검증하는 방식이고, <strong>revalidateTag()</strong> 는 ****fetch요청시 option으로 보내준 tag를 기반으로 재검증하는 방식이다.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/c5ca1ee8-e137-4950-98b5-a9841d63ce6d/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114820Z&X-Amz-Expires=3600&X-Amz-Signature=f1d3324bba20328b0a88322e2b766fb74f59010773681182f00e4f23b1a4f062&X-Amz-SignedHeaders=host&x-id=GetObject" alt="Untitled"></p>
<p>사진은 <strong>revalidateTag()</strong> 를 사용한 예시를 보여준다. 첫번째 요청에서 tags를 포함해 요청을 보내주면, Data Source의 데이터를 가져와 cache에 저장한다.</p>
<p>이후 <strong>revalidateTag()</strong> 를 이용해 트리거 해주면, 트리거의 tag와 같은 <strong>tag를</strong> 가진 요청들이 캐시에서 제거된다.
다음에 같은 요청을 보내면, cache가 miss되어 다시 Data Source의 데이터를 가져와 cache에 저장한다.</p>
<p><strong>Time-based Revalidation (시간 기반 재검증</strong>) 은 데이터가 자주 변경되지 않고 신선도가 그다지 중요하지 않은 데이터에 유용하다.
<strong>On-Demand Revalidation(온디맨드 재검증)</strong> 은 가능한 한 빠르게 최신데이터를 표시하는 경우에 유용하다.</p>
<p>나는 블로그의 포스트가 최신화 되었을 때 데이터를 재검증 해주고 싶기 때문에, <strong>On-Demand Revalidation(온디맨드 재검증)</strong> 을 이용했다.</p>
<p><em>(공식문서도 친절하게 On-Demand방식의 예시로 CMS 콘텐츠 업데이트를 들고 있다…)</em></p>
<h1 id="코딩-시작">코딩 시작</h1>
<hr>
<p>나는 ISR의 <strong>revalidateTag()</strong>를 이용했는데<strong>, Tag</strong>를 이용한 이유는 단일 <strong>path</strong>로 최신화하는 것보다 <strong>tag</strong>로 직접 내가 그룹을 묶어서 캐시를 지워줄 수 있다고 생각했기 때문이다.</p>
<p>다는 두 가지 종류의 태그를 만들었다. 첫 번째는 글의 목록을 fetch 하는 “series” 두 번째는 글의 각각 id 값을 이용해서 글마다 최신화 상태에 대한 tag를 만들었다.</p>
<p>그렇다면 포스트 내용에 ISR을 적용한 코드를 보자</p>
<p><em>(이번 포스트는 ISR에 대한 내용이기 때문에 다른 코드는 참고로만 봐두자, 블로그 개발기는 시리즈로 계속 포스트 하겠다.)</em></p>
<p>이것은 내 글 본문을 만드는 코드다.</p>
<pre><code class="language-tsx">// app/post/[postId]/_components/PostBody.tsx

export default async ({ postId }: { postId: string }) =&gt; {
    const post = await notionPostData(postId);

    return (
        &lt;section className=&quot;flex justify-center&quot;&gt;
            &lt;section className=&quot;w-full dark:prose-invert prose prose-md prose-hr:mt-5 p-5 prose-headings:mt-10 prose-blockquote:border-l-deepblue prose-a:no-underline&quot;&gt;
                &lt;MDXComponent source={post} /&gt;
            &lt;/section&gt;
        &lt;/section&gt;
    );
};</code></pre>
<p><strong>notionPostData()</strong> 에 글의 id인 postId 파라미터를 보내주면 postId와 같은 notion의 글 내용을 불러와 MarkDown String으로 변환해 준다. <em>(이후 MDXComponent에서 source로 받은 문자열을 HTML로 렌더링 해준다.)</em></p>
<p><strong>notionPostData()</strong> 에서 Data fetching 해주는 코드는 다음과 같다.</p>
<pre><code class="language-tsx">const res = await (
        await fetch(url, {
            method: &quot;GET&quot;,
            headers: { accept: &quot;application/json&quot;, &quot;Notion-Version&quot;: &quot;2022-06-28&quot;, Authorization: `Bearer ${process.env.NOTION_KEY}` },
            next: { revalidate: false, tags: [id] },
        })
    ).json();</code></pre>
<p>Next.js의 확장된 fetch 옵션에 next에서 제공하는 옵션을 지정해 줬다.</p>
<pre><code class="language-jsx">next: { revalidate: false, tags: [id] },</code></pre>
<p>revalidate의 type으로는 false 와 number 가 있다.</p>
<p>false는 말 그대로 데이터를 자동으로 재 검증하지 않는 것이다. </p>
<p>number를 넣어주면, next는 number(초) 이후에 발생하는 같은 요청에 revalidate 트리거를 실행한다.</p>
<p>나는 포스트를 직접 트리거 하지 않을 때는 정적 페이지로 이용할 생각이기 때문에 false를 사용했다.</p>
<p>Next.js에서 tags는 배열로 받는다. 즉, 트리거 될 태그를 여러 개 지정해 줄 수 있다.</p>
<p>나는 tags에 post의 id를 지정해 주었다. 이렇게 하면 각각의 포스트가 최신화되었을 때 전체 포스트를 갱신하는 게 아니라 변경된 포스트의 cache만 을 지정해서 지워줄 수 있다.</p>
<p>이렇게 data fetching 설정을 마치고 나면,  트리거를 할 수 있도록 코드를 작성해야 한다. </p>
<p>Next에서  <strong>RevalidatePath()</strong> 혹은 <strong>RevalidateTag()을</strong> 이용하기 위해서는 <strong>Sever Action</strong>을 이용하거나 직접 <strong>API</strong>를 만들어주는 방법을 사용해야 한다.</p>
<p>나는 직접 API를 만드는 방식을 이용했다.</p>
<pre><code class="language-tsx">//app/api/revalidate/route.ts

import { NextRequest, NextResponse } from &quot;next/server&quot;;
import { revalidateTag } from &quot;next/cache&quot;;

export async function POST(request: NextRequest) {
    const tag = request.nextUrl.searchParams.get(&quot;tag&quot;);
    const secret = request.nextUrl.searchParams.get(&quot;secret&quot;);

    if (secret !== process.env.REVALIDATE_SECRET) return NextResponse.json({ message: &quot;Invalid secret&quot; }, { status: 401 });
    if (!tag) return NextResponse.json({ message: &quot;no Tag&quot; }, { status: 401 });

    revalidateTag(tag);

    return NextResponse.json({ revalidated: true, now: Date.now(), message: &quot;새로고침 성공&quot; });
}</code></pre>
<p>코드는 Next 공식 문서를 이용해서 작성했다.</p>
<p>해당 API는 Endpoint에 tag와 secret을  params로 받는다.</p>
<p>secret은 미리 지정해 둔 암호를 이용해서 무분별한 새로고침을 방지하기 위해 넣었다.</p>
<p><em>(지금은 임시방편으로 단순 문자열을 사용하고 있는데, 이런저런 블로그의 기능이 추가되면 변경해 보겠다.)</em> </p>
<p>tag가 없거나 secret이 다를 경우에는 early return을 이용해서 트리거 되지 않게 만들었다.</p>
<p>요청이 오면 해당 tag를 이용해 tag의 cache를 제거하고, 이후 요청에 새로운 cache를 생성하게 된다.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/0514f638-f0d3-4f3e-a08e-2f868498d1f7/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114823Z&X-Amz-Expires=3600&X-Amz-Signature=3fe32a910e1c5fba1ab3b55865fb9ce3ad6bb0fcf32aae8e96802231ed805cfe&X-Amz-SignedHeaders=host&x-id=GetObject" alt="Untitled"></p>
<p>이렇게 블로그에 해당 API 요청을 보내주는 버튼을 만들어서 글이 최신화되면, 눌러 트리거 되게 만들었다.</p>
<h1 id="마치며">마치며</h1>
<hr>
<p>당연한 이야기지만 SSR을 이용했을 때와 ISR을 이용했을 때의 속도 차이는 분명했다.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/13bc9185-9ada-441d-8bb3-ca7a5cd5e351/ssrvsisr.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114823Z&X-Amz-Expires=3600&X-Amz-Signature=036e9fd080c94ff6a8a3b1c9dcb9f102b25e9b2ecd5ffe80d8953d6e09a7511e&X-Amz-SignedHeaders=host&x-id=GetObject" alt="ssrvsisr.gif"></p>
<p><em>(와우 ISR 만세!)</em></p>
<p>이번에 블로그에 ISR을 도입하면서, Next의 ISR을 좀 더 잘 알게 되었고, 조금이나마 블로그 성능 개선도 할 수 있던 좋은 경험이었다.</p>
<p>역시 실제로 개발을 진행하면서 발생하는 문제를 해결하는 과정에서 얻는 경험과 지식이 책으로만 얻는 것보다 값진 것 같다. (물론 그렇다고 책으로 얻는 지식이 별로라는 말이 아니다.)</p>
<p>이번에 테스트하면서 알게 된 내용과 고민해 봐야 할 문제들이 생겼다.</p>
<ol>
<li>노션에서 글이 최신화되었을 때 따로 버튼을 누르지 않고 트리거 되도록 만들기
이건 내가 직접 서버와 DB를 이용한다면, DB를 최신화하는 과정에서 트리거 하는 API에 요청을 보내주면 되는 데, 지금은 notion을 CMS로 이용하고 있기 때문에 고민을 해봐야겠다.</li>
<li>현재 노션에서 블록이 많은 글을 불러올 때 속도가 너무 느리다.
지금 내 블로그에서 글 본문을 불러오는 방식은 글의 모든 블록을 가져온 다음 Markdown으로 변환하고, 렌더링하는 방식이다. 블록이 너무 많으면, 변환하는 과정에서 사용자는 긴 로딩 시간을 겪게 되는데, 블록을 각각의 작은 블록으로 만들어서 조금씩 렌더링 하는 방식을 생각해 봐야겠다. </li>
</ol>
<p><img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/9d7c64f0-2cac-47fe-8437-52409613ccb5/lol.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230820%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230820T114824Z&X-Amz-Expires=3600&X-Amz-Signature=d7834089f47a086950ddf6a055e9afc2a622a0786736ba10bf28539515c9490f&X-Amz-SignedHeaders=host&x-id=GetObject" alt="lol.gif"></p>
<p><em>(블로그 포스트는 걱정 안 해도 될 것 같다… 블로그도 만들고 개발 포스트도 만들고 1석 2조….)</em></p>
<p>내 블로그 - <a href="https://tealsblog.vercel.app/posts/a17fba55-be05-4589-bead-6f076f10093b">https://tealsblog.vercel.app/posts/a17fba55-be05-4589-bead-6f076f10093b</a></p>
<h1 id="참고-자료">참고 자료</h1>
<hr>
<p><strong><em><a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#on-demand-revalidation">https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#on-demand-revalidation</a></em></strong></p>
<p><strong><em><a href="https://nextjs.org/docs/app/building-your-application/caching#on-demand-revalidation">https://nextjs.org/docs/app/building-your-application/caching#on-demand-revalidation</a></em></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[아무 생각 없이 CRA만 하셨나요?]]></title>
            <link>https://velog.io/@ateals_12/%EC%95%84%EB%AC%B4-%EC%83%9D%EA%B0%81-%EC%97%86%EC%9D%B4-CRA%EB%A7%8C-%ED%95%98%EC%85%A8%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@ateals_12/%EC%95%84%EB%AC%B4-%EC%83%9D%EA%B0%81-%EC%97%86%EC%9D%B4-CRA%EB%A7%8C-%ED%95%98%EC%85%A8%EB%82%98%EC%9A%94</guid>
            <pubDate>Mon, 10 Jul 2023 07:53:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>네 맞습니다... React할때 무지성으로 CRA만 하던 사람 바로 접니다...</p>
</blockquote>
<p>CRA는 개발자 본인이 React를 위한 환경을 만들어 주지 않아도 자동으로 React 프로젝트를 시작할 수 있게 환경을 만들어 주는 개발도구 입니다.</p>
<p><code>npm create-react-app &quot;&quot;</code> 만 입력하면 자동으로 의존성들을 설치해주고 기본적인 보일러 프로젝트를 생성해 개발자는 작업만 할 수 있도록 만들어줍니다!</p>
<p>그치만 문제가 있죠..</p>
<ol>
<li>기술에는 항상 발전이 있습니다.</li>
<li>가볍게 시작하기에는 불필요한 의존성도 포함되어 있습니다.</li>
<li>어썸하지 않습니다?!</li>
</ol>
<p>사실 CRA만 이용해도 큰 문제는 없습니다. 저도 지금까지 CRA로 모든 react프로젝트를 진행했구요... 하지만, 이번에 새로운 React 프로젝트를 진행하기 위해서 tailwind를 설치하는 과정에서 이런 걸 봐버렸습니다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/8a3d327b-d84c-4d75-960d-5477d2cc7b47/image.png" alt=""></p>
<p><em>(엥? 아무튼 Vite를 쓰는게 좋다는데?)</em></p>
<p>Next.js를 사용하는 방법도 있었지만, 저는 이미 Next.js로 기능 구현을 해 보았으며 이번에는 React로 진행 해보고 싶던 와중에 Vite는 좋은 대안이 될 것 같았습니다. </p>
<h1 id="그래서-vite가-뭔데">그래서 Vite가 뭔데</h1>
<hr>
<blockquote>
<p>Vite는 현대적인 웹 개발을 위한 빠르고 경량화된 빌드 도구입니다. Vue.js의 창시자인 Evan You가 개발한 Vite는 Vue.js를 위해 만들어진 것이지만, 최근에는 React와 TypeScript를 지원하기도 합니다.</p>
</blockquote>
<p><em>(땡큐 Chat Gpt)</em></p>
<p>vite는 CRA와 같은 개발도구입니다. 처음에는 Vue를 위해 만들어 졌지만, React와 Typescript를 이용한 React 프로젝트를 지원합니다.</p>
<p>그리고 &quot;현대적인 웹 개발&quot; 이거 못참습니다.</p>
<p>GPT가 알려준 vite의 장점은 다음과 같습니다.</p>
<ol>
<li><p>빠른 개발 서버:HMR (Hot Module Replacement)을 지원하고 있어 코드 변경 시 실시간으로 변경 사항이 반영됩니다.</p>
</li>
<li><p>빠른 빌드: Vite는 ES 모듈을 사용하여 개별 파일을 로드하기 때문에 초기 빌드 속도가 매우 빠릅니다. 파일 단위로 캐싱하여 변경된 파일만 다시 빌드하므로 전체 애플리케이션을 다시 빌드할 필요가 없습니다.</p>
</li>
<li><p>더 작은 번들 크기: Vite는 개발 중에 ES 모듈을 그대로 유지하고, 프로덕션 빌드 시에만 번들링합니다. 이로 인해 번들 크기가 작아지고 초기 로딩 속도가 향상됩니다.</p>
</li>
<li><p>확장성: Vite는 TypeScript, CSS 전처리기 (Sass, Less), 테스트 프레임워크 (Jest 등) 등과 쉽게 통합됩니다. 다양한 플러그인과 기능을 제공하여 개발자가 프로젝트에 필요한 도구와 환경을 유연하게 구성할 수 있습니다.</p>
</li>
</ol>
<p>사실 CRA에서도 지원해주는 부분들이지만, 전체적으로 Vite가 React보다 더작은 번들의 크기와 더 빠른 속도라는 점에서 충분히 매력을 느낄 수 있는 것 같습니다.</p>
<p><em>(심지어 이름부터 프랑스어로 빠르다임)</em></p>
<h1 id="그래서-vite-어캐-하는-건데">그래서 Vite 어캐 하는 건데?</h1>
<hr>
<p>여러분이 CRA 하던것 처럼 Vite로 넘어가면 됩니다!!</p>
<p>먼저 <code>npm create vite@latest</code>를 작성합니다. 우리는 항상 최신버전을 사용하는 상남자니까 최신버전으로 진행하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/004d8f99-468e-4763-a208-142b513d953b/image.png" alt=""></p>
<p>프로젝트 이름까지 작성하고 나면 어떤 프로젝트를 진행할지 정할 수 있습니다.
<em>(뷰부터 리액트, 스벨트 까지.... 없는게 없네여)</em></p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/45647ab6-54cc-4c13-b482-1d494111be93/image.png" alt="">
리액트를 선택하면 다음과 같이 Typescript를 사용할 것인지 Javascript를 사용할 것인지 선택하는 창이 나옵니다. </p>
<p>그런데 여기서 처음보는 친구가 나옵니다 SWC? 그래서 검색을 해봤져 여러분의 시간은 소중하니까요.</p>
<blockquote>
<p>SWC는  Rust로 작성된 JavaScript 및 TypeScript 코드를 변환하는 고성능 JavaScript 컴파일러입니다.  주요 목표는 향상된 성능과 더 빠른 변환 속도를 제공하는 것입니다.</p>
</blockquote>
<p>아! Babel과 비슷한 친구였군요.
안그래도 vite가 빠른데 거기에 컴파일러도 더 빠른 속도? 참을 수 있습니까?</p>
<p>여기까지 설정하시면</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/7fd53a1d-4c06-42b3-bf7a-75df8a3d40a5/image.png" alt=""></p>
<p>어디서 많이 본듯한 폴더 구조가 생기죠?
CRA에 <code>index.tsx</code>가 <code>main.tsx</code>로, <code>index.html</code>이 폴더 가장 최상단에 있는 차이를 빼면, 동일한 React 프로젝트를 시작할 수 있습니다!</p>
<p>그럼 더 빨라진 React를 즐기러 가보겠습니다.</p>
<hr>
<p>Vite에서 환경변수를 사용할때  prefix를 <code>VITE_</code>로 적고 <code>import.meata.env.</code>로 접근할 수 있다고 합니다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useForm을 만들어보아요!]]></title>
            <link>https://velog.io/@ateals_12/useForm%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%95%84%EC%9A%94</link>
            <guid>https://velog.io/@ateals_12/useForm%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%95%84%EC%9A%94</guid>
            <pubDate>Sun, 25 Jun 2023 12:13:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://upload.wikimedia.org/wikipedia/ko/4/4a/%EC%8B%A0%EC%A7%B1%EA%B5%AC.png" alt=""></p>
<p>React에서 삽질을 하다가 Form을 만들면서 갑자기 든 생각이었다.</p>
<p>왜 input에 onchange를 사용해서 input 값을 가져올까? 그냥 submit에있는 event 안에 있는 target의 값을 가져오면 되지 않나?</p>
<p>그래서 한번 커스텀 훅을 만들어 보았다… <em>타스를 제대로 알지 못한채로 작성해서 코드가 맛이없을 수도 있습니다….</em></p>
<p>일단 전체 코드를 보자</p>
<h1 id="전체코드">전체코드</h1>
<pre><code class="language-tsx">export function useForm&lt;T&gt;(initialValues: T): [T, FormEventHandler&lt;HTMLFormElement&gt;] {
    const [data, setData] = useState&lt;T&gt;(initialValues);
    const onSubmit: React.FormEventHandler&lt;HTMLFormElement&gt; = (e) =&gt; {
        e.preventDefault();

        const inputs = [...e.currentTarget];
        const obj: T = inputs.reduce((acc: T, crr: Element) =&gt; {
            const { name, value } = crr as HTMLInputElement;
            return { ...acc, [name]: value };
        }, {} as T);

        setData(obj);
    };

    return [data, onSubmit];
}</code></pre>
<p>이게 내 useForm 훅의 전부이다.</p>
<p>내 useForm은 첫번째 인자로 input의 name을 가진 object를 받는다. 그 후 배열을 반환하는데 배열의 첫번째 값은 값에 접근할 수 있는 object(이때 타입은 useForm의 첫번째 인자와 동일하다.) 두번째는 form에 붙여줄 onSubmit 핸들러이다.</p>
<p>예시 코드를 작성해보자면 다음과 같다.</p>
<h1 id="사용-예시">사용 예시</h1>
<pre><code class="language-tsx">function App() {
    const [data, onSubmit] = useForm&lt;Info&gt;({ name: &quot;&quot;, age: &quot;&quot;, location: &quot;&quot; });
    return (
        &lt;&gt;
            &lt;form
                className=&quot;form&quot;
                onSubmit={onSubmit}
            &gt;
                &lt;input
                    name=&quot;location&quot;
                    type=&quot;text&quot;
                    placeholder=&quot;location&quot;
                /&gt;

                &lt;input
                    name=&quot;name&quot;
                    type=&quot;text&quot;
                    placeholder=&quot;name&quot;
                /&gt;
                &lt;input
                    name=&quot;age&quot;
                    type=&quot;number&quot;
                    placeholder=&quot;age&quot;
                /&gt;
                &lt;input type=&quot;submit&quot; /&gt;
            &lt;/form&gt;

            &lt;div&gt;
                &lt;p&gt;Name : {data.name}&lt;/p&gt;
                &lt;p&gt;age : {data.age}&lt;/p&gt;
                &lt;p&gt;location : {data.location}&lt;/p&gt;
            &lt;/div&gt;
        &lt;/&gt;
    );
}

export default App;</code></pre>
<p>보면 initialValues 안에 들어간 object의 프로퍼티 이름과 input의 name 이름이 동일한 것을 알 수 있다.</p>
<p>이렇게 해서 submit 버튼을 누르면 input의 name과 initialValues의 프로퍼티 값을 확인해서 값을 넣어준다.</p>
<p>그것이 내 훅의 동작 방법이 되는 것이다. 그럼 이제 코드를 하나씩 뜯어보도록 하자</p>
<pre><code class="language-jsx">const [data, setData] = useState &lt; T &gt; initialValues;</code></pre>
<p>데이터를 저장할 useState! 이 친구가 있기 때문에 data값이 form을 통해서 바뀌면 우리의 ui도 바뀔 것이다.</p>
<p>다음으로 onSubmit 핸들러를 보자</p>
<pre><code class="language-tsx">const onSubmit: React.FormEventHandler&lt;HTMLFormElement&gt; = (e) =&gt; {
    e.preventDefault();

    const inputs = [...e.currentTarget];
    const obj: T = inputs.reduce((acc: T, crr: Element) =&gt; {
        const { name, value } = crr as HTMLInputElement;
        return { ...acc, [name]: value };
    }, {} as T);

    setData(obj);
};</code></pre>
<p>일단 submit이벤트의 기본동작인 웹페이지 새로고침을 막아주자! <strong><em>e.preventDefault();</em></strong></p>
<p>다음으로 input들의 dom을 배열로 만들어 주었다.</p>
<pre><code class="language-jsx">const inputs = [...e.currentTarget];</code></pre>
<aside className="shadowBottom my-10 py-[20px] px-[10px] dark:bg-[#1E1E1E] bg-[#efefef] rounded-[10px]">
📌 event.target vs event.currentTarget

<p>처음에 js로 만들었을 때는 event.target으로 작성하고 잘 동작하는 것을 확인 했다. 하지만…. ts로 넘어와서 작업을 하면서 타입을 넣어주니까 안되길래 삽질끝에 찾아보니 currentTarget을 사용해야 코드가 정상적으로 작동했다.</p>
<p>그렇다면 둘의 차이는 무엇인가?
event.target은 이벤트를 트리거한 요소를 나타낸다. event.currentTarget는 이벤트 핸들러가 연결된 요소를 말한다.. 내 submit이벤트는 form에 연결되어 있으니까 currentTarget을 사용하는 건 이해가 되는데… 왜 target이 안되는지는 잘… 이해를 못하겠지만.. 아무튼 지식이 늘었다! (망할 ts….)</p>
</aside>

<pre><code class="language-jsx">const obj: T = inputs.reduce((acc: T, crr: Element) =&gt; {
            const { name, value } = crr as HTMLInputElement;
            return { ...acc, [name]: value };
        }, {} as T);</code></pre>
<p>다음으로 코테에서 많이 사용한 reduce함수를 이용하여 새로운 obj를 만들어 주었다.</p>
<p>input을 한개씩 가져와서 input 안에있는 name과 value를 가지고 initialValues와 같은 타입의 객체를 만들어 주었다.</p>
<p>이후 <strong>setData()</strong> 에 반환값으로 넘겨주어 data를 저장하고 랜더링한다.</p>
<h1 id="마치며">마치며…</h1>
<hr>
<p>이렇게 나만의 form 커스텀 훅을 만들어 보았다..</p>
<p>고집때문에 삽질하다가 ts와 절교할 뻔했지만… 무사히 넘어갔다.</p>
<p>form안에 input을 제어하는 코드를 onchange를 이용해서 만들길래 뭔가나는 굳이? 싶어서 이런식으로 만들어 봤다. <em>뭔가 onchange를 사용해야하는 이유가 있다면 알려주세여….</em></p>
<p>엄청 대단한 건 아니지만 뭔가 커스텀 훅을 만들었다고 생각하니까 큰일을 해낸거 같아서 기쁘다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[33] 7번째 등장 Symbol]]></title>
            <link>https://velog.io/@ateals_12/33-7%EB%B2%88%EC%A7%B8-%EB%93%B1%EC%9E%A5-Symbol</link>
            <guid>https://velog.io/@ateals_12/33-7%EB%B2%88%EC%A7%B8-%EB%93%B1%EC%9E%A5-Symbol</guid>
            <pubDate>Wed, 21 Jun 2023 03:54:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Symbol : ES6에서 도입된 7번째 데이터 타입으로 변경 불가능한 원시 타입</p>
</blockquote>
<p>심벌은 다른 값과 중복되지 않는 유일무이한 값이고, 주로 이름 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용합니다!</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/52dad9dc-69f4-4df5-bd2a-a1d4095404f1/image.png" alt=""></p>
<p><em>나를 괴롭히던 NEXT 13 Request 객체 프로퍼티 키들이 다 이놈이였구나..</em></p>
<h1 id="심벌-값의-생성">심벌 값의 생성</h1>
<hr>
<p>심벌 값은 Symbol 함수를 호출하여 생성할 수 있습니다.</p>
<p>심벌은 Symbol 함수 호출 없이는 생성할 수 없고, 생성된 심벌 값은 외부로 노출되지 않아 확인할 수 없으며, 다른 값과는 중복되지 않습니다.</p>
<pre><code class="language-jsx">const tag = Symbol();
console.log(typeof tag); // symbol

console.log(tag); // Symbol()</code></pre>
<p>Symbol함수는 선택적으로 문자열을 인수로 전달 할 수 있습니다.</p>
<p>이 문자열은 생성된 심벌 값에 대한 설명으로 디버깅 용도로만 사용 되고, 심벌 값에 어떠한 영향도 주지 않습니다.</p>
<p>고로, 이 문자열이 같아도 둘은 다른 심벌값이기 때문에 서로 간섭이 없죠.</p>
<pre><code>const tag = Symbol(&quot;name&quot;);
const list = Symbol(&quot;name&quot;);

console.log(tag === list); // false</code></pre><p>심벌값도 래퍼 객체를 생성합니다. description, toString 은 Symbol.prototype의 메서드입니다.</p>
<pre><code class="language-jsx">const tag = Symbol(&quot;name&quot;);

console.log(tag.description); // name
console.log(tag.toString()); // Symbol(name)</code></pre>
<p>심벌타입은 숫자 문자 타입으로 암묵적 변환이 일어나지 않습니다.</p>
<p>다만 불리언타입으로는 변환 되죠.</p>
<p><em>다행히 지옥의 암묵적 변환은 없…</em></p>
<h1 id="symbolfor--symbolkeyfor">Symbol.for() / Symbol.keyFor()</h1>
<hr>
<h2 id="symbolfor">Symbol.for()</h2>
<hr>
<p>Symbol.for 메서드는 인수로 받은 문자열을 키로 사용하여 키와 심벌 값들이 저장되어 있는 전역 심벌 레지스트리에서 해당 키와 일치하는 심벌값을 검색합니다.</p>
<ul>
<li>검색에 실패하면 새로운 심벌 값을 생성하여 Symbol.for() 메서드의 인수로 전달된 키로 전역 심벌 레지스트리에 저장한 후 생성된 심벌값을 반환</li>
<li>검색에 성공하면 검색된 심벌 값을 반환.</li>
</ul>
<pre><code class="language-jsx">const tag = Symbol.for(&quot;tag&quot;);
const tag2 = Symbol.for(&quot;tag&quot;);
console.log(tag === tag2); // true</code></pre>
<p>Symbol 과 Stmbol.for의 차이는 전역 심벌 레지스트리에 저장되어 검색할 수 있는 지 없는 지입니다.</p>
<h2 id="symbolkeyfor">Symbol.keyFor()</h2>
<hr>
<p>Symbol.keyFor()를 사용하면 심볼의 키를 추출 할 수 있다.</p>
<pre><code class="language-jsx">const tag = Symbol.for(&quot;name&quot;);

Symbol.keyFor(tag); // &quot;name&quot;</code></pre>
<h1 id="그렇다면-심볼을-어떤식으로-사용할까">그렇다면 심볼을 어떤식으로 사용할까?</h1>
<h2 id="값에-특별한-의미가-없고-상수-이름-자체에-의미가-있는-경우">값에 특별한 의미가 없고 상수 이름 자체에 의미가 있는 경우</h2>
<hr>
<pre><code class="language-jsx">const Direction = {
    UP: 1,
    DOWN: 2,
};

const myDirection = Direction.UP;

console.log(myDirection === Direction.UP);</code></pre>
<p>위의 코드에서 Direction에 존재하는 UP, DOWN 프로퍼티의 값은 중요하지 않습니다.</p>
<p>그저 UP을 표시하는 지, DOWN을 표시하는 지가 궁금할 뿐이죠.</p>
<p>하지만 상수 값은 변경이 가능하고 중복될 가능성이 있기 때문에 Symbol 값으로 바꾸는 것이 좋습니다.</p>
<pre><code class="language-jsx">const Direction = {
    UP: Symbol(&quot;up&quot;),
    DOWN: Symbol(&quot;down&quot;),
};

const myDirection = Direction.UP;

console.log(myDirection === Direction.UP);</code></pre>
<h2 id="심벌과-프로퍼티-키">심벌과 프로퍼티 키</h2>
<hr>
<p>객체의 프로퍼티 키로 심볼을 이용할 수 있습니다.</p>
<pre><code class="language-jsx">const obj = {
    [Symbol.for(&quot;num&quot;)]: 1,
};

console.log(obj[Symbol.for(&quot;num&quot;)]); //1</code></pre>
<p>이때 프로퍼티 키로 사용할 심벌 값에 대괄호를 사용해야 하고, 프로퍼티에 접근할 때도 마찬가지로 대괄호를 사용해야합니다.</p>
<h2 id="프로퍼티-은닉">프로퍼티 은닉</h2>
<hr>
<p>심벌 값을 프로퍼티 키로 사용하여 생성한 프로퍼티는 찾을 수 없습니다.</p>
<p>고로, 프로퍼티를 숨겨놓을 수 있겠네요..</p>
<p>이렇게 숨긴 프로퍼티는 for … of.. 와 같은 방법으로는 찾을 수 없습니다.</p>
<pre><code class="language-jsx">const obj = {
    [Symbol(&quot;num&quot;)]: 1,
};

console.log(obj[Symbol(&quot;num&quot;)]); //undefined</code></pre>
<p>하지만 완전히 숨길 수 있는 것은 아닙니다.</p>
<p>ES6에 추가된 Object.getOwnPropertySymbols() 메서드를 이용하면 심벌 값을 프로퍼티 키로 사용하여 찾을 수 있죠.</p>
<pre><code class="language-jsx">const obj = {
    [Symbol(&quot;num&quot;)]: 1,
};

console.log(obj[Object.getOwnPropertySymbols(obj)[0]]); //1</code></pre>
<h1 id="well-known-symbol">Well-known Symbol</h1>
<hr>
<p>자바스크립트가 기본적으로 제공하는 빌트인 심벌 값이 있습니다.</p>
<p>이런 값들을 ECMAScript에서는 Well-known Symbol이라 칭합니다.</p>
<p>예를 들어 빌트인 이터러블은 Well-known Symbol인 Symbol.iterator를 키로 갖는 메서드를 가지며, Symbol.iterator 메서드를 호출하면 이터레이터를 반환하도록 ECMAScript 사양에 규정되어 있습니다.</p>
<h1 id="🚪">🚪</h1>
<p>오늘은 심벌에 대해 알아봤습니다.</p>
<p>처음에 만났을 때는 꽤 당황했는데… 쉽게 유일무이한 원시 값이라고 생각하고 접근하니 괜찮은 것 같습니다.</p>
<p>이제 두려움 떨치고 사용해 봅시다… 심벌…</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[블로그 북마크 만들기]]></title>
            <link>https://velog.io/@ateals_12/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%B6%81%EB%A7%88%ED%81%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%B6%81%EB%A7%88%ED%81%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 18 Jun 2023 14:39:13 GMT</pubDate>
            <description><![CDATA[<h1 id="👋">👋</h1>
<p>이번에 next13이 어느정도 안정화되고… 여러 기능들을 읽어보니까 너무 사용하고 싶어서..</p>
<p>이거 저거 만져보고 지금 블로그도 13버전으로 마이그레이션 하다보니까.. 시간이 너무 부족했습니다.</p>
<p>새로운 환경이라 정보도 적고, 12버전에서 만들어 놓은 함수들과 페이지들을 전부 수정해야 하니 생각보다 시간이 많이 걸리더군요,…</p>
<p>+Typescript 를 추가했는데… ㅋㅋㅋ 타입 충돌이 많이 나더군요..</p>
<p>일단 만들어 놓은 북마크 기능을 보여드리도록 하죠</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/5442cc49-2205-4ff3-9ea1-35da0d6ef2b5/image.png" alt=""></p>
<p>사용자가 mdx파일에 <code>~~url~~</code> 형식으로 작성하면 페이지의 og테그 내용을 가져와 북마크로 만들어 줍니다</p>
<h1 id="시작">시작!</h1>
<p>og테그를 가져오기 위한 라이브러리는</p>
<p><a href="https://www.npmjs.com/package/open-graph">https://www.npmjs.com/package/open-graph</a></p>
<p>를 사용했습니다.</p>
<p>하지만 이 라이브러리를 next.js 서버컴포넌트에서 사용하려하니 net 모듈이 필요하더군요…</p>
<p>최대한 라이브러리 작업을 하지 않기 위해 다음과 같은 방법을 사용했습니다.</p>
<ol>
<li>api route를 만들어서 og태그를 받아오는 api를 만든다.</li>
<li>서버컴포넌트에서 api를 호출한다.</li>
<li>북마크를 랜더링한다.</li>
<li>MDXcomponents 에서 북마크 컴포넌트를 불러온다!</li>
</ol>
<p>처음엔 nextjs의 rewrite기능을 이용해서 임의로 만든 api키를 숨기는 방식을 사용하려 했지만..</p>
<p>왜인지 vercel에 배포하면 rewrite로 불러올때 header를 가져오는 과정에서 이전 url의 파라미터를 가져올 수 가 없었습니다…</p>
<p>그래서 서버에서 api를 호출 하는 방식으로 apikey를 숨겼습니다.</p>
<p>코드를 보시죠!</p>
<h2 id="api">api</h2>
<pre><code class="language-jsx">//app/api/getOg

import { NextRequest, NextResponse } from &quot;next/server&quot;;
import og from &quot;open-graph&quot;;

const getOg = (url: string) =&gt;
    new Promise((resolve, reject) =&gt; {
        og(url, function (err, meta) {
            if (err) {
                reject(err);
                return;
            }
            resolve(meta);
        });
    });

export async function GET(request: NextRequest) {
    const url = request.nextUrl.searchParams.get(&quot;url&quot;);
    const api_key = request.nextUrl.searchParams.get(&quot;api_key&quot;);

    if (api_key !== process.env.API_KEY) return NextResponse.json({ title: &quot;NOT MATCH API KEY&quot;, api_key });
    if (!url) return NextResponse.json({ title: &quot;NO URL&quot; });
    try {
        const data = await getOg(encodeURI(url));
        return NextResponse.json(data);
    } catch (err) {
        return NextResponse.json({ err });
    }
}</code></pre>
<p>api는 파라미터에 url과 api_key를 받아옵니다. api_key가 같은지 확인하고, 틀리다면 리턴합니다.</p>
<p>api키가 동일하다면, getOg함수를 이용하여 open-graph 태그를 가져옵니다.</p>
<p>가져온 태그를 json 형식으로 리턴합니다.</p>
<p>이렇게 받아온 코드를 bookmark 컴포넌트에서는 다음과 같은 방법으로 받아옵니다.</p>
<h2 id="bookmark">Bookmark</h2>
<pre><code class="language-tsx">import Link from &quot;next/link&quot;;
import { use } from &quot;react&quot;;

interface data {
    title: string;
    image: {
        url: string;
    };
    description?: string;
}
const getOgData = async (url: string) =&gt; {
    const baseUrl = process.env.NODE_ENV === &quot;development&quot; ? `http://localhost:3000` : &quot;https://tealslog.vercel.app&quot;;
    const data = await (await fetch(baseUrl + `/api/getOg?api_key=${process.env.NEXT_PUBLIC_API_KEY}&amp;url=${url}`)).json();
    return data;
};

export default ({ url }: { url: string }) =&gt; {
    const data: data = use(getOgData(url));

    return (
        &lt;&gt;
            {data &amp;&amp; (
                &lt;Link href={url}&gt;
                    &lt;div className=&quot;shadowBottom my-10 flex h-[200px] hover:scale-105&quot;&gt;
                        &lt;div className=&quot;h-full w-[30%] relative&quot;&gt;
                            &lt;img
                                src={data.image?.url}
                                alt=&quot;Image&quot;
                                className=&quot;h-full w-full object-cover &quot;
                            /&gt;
                        &lt;/div&gt;
                        &lt;div className=&quot;w-[70%] h-full relative flex flex-col justify-between p-4&quot;&gt;
                            &lt;div className=&quot;overflow-hidden&quot;&gt;
                                &lt;h1 className=&quot;text-[24px] font-bold mb-2 &quot;&gt;{data.title}&lt;/h1&gt;
                                &lt;p className=&quot;text-[12px]&quot;&gt;{data.description}&lt;/p&gt;
                            &lt;/div&gt;

                            &lt;small className=&quot;text-end overflow-clip text-[gray]&quot;&gt;{decodeURI(url)}&lt;/small&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Link&gt;
            )}
        &lt;/&gt;
    );
};</code></pre>
<p>Next 13 버전부터는 최상단에 “use client”를 작성하지 않은 컴포넌트는 모두 서버컴포넌트입니다.</p>
<p>서버컴포넌트에서는 fetch를 이용해서 데이터를 가져오죠</p>
<p>가져온 데이터를 컴포넌트에서 사용할땐 use()함수를 사용합니다.</p>
<p>Bookmark 컴포넌트는 url을 props로 받아서 api를 호출하고 받은 데이터를 북마크로 만들어줍니다.</p>
<p>이렇게 만든 컴포넌트는 스켈레톤 ui를 포함합니다.</p>
<pre><code class="language-jsx">export const SkeletonBookmark = () =&gt; {
    return (
        &lt;div className=&quot;shadowBottom my-10 flex h-[200px]&quot;&gt;
            &lt;div className=&quot;h-full w-[30%] bg-[gray] animate-pulse&quot;&gt;&lt;/div&gt;
            &lt;div className=&quot;w-[70%] h-full relative flex flex-col justify-between p-4&quot;&gt;
                &lt;div&gt;
                    &lt;h1 className=&quot;w-[40%] text-[24px] font-bold mb-2 bg-[gray] h-[30px] animate-pulse&quot;&gt;&lt;/h1&gt;
                    &lt;div className=&quot;w-[100%] text-[12px] h-[16px] animate-pulse flex items-center&quot;&gt;&lt;/div&gt;
                &lt;/div&gt;
                &lt;small className=&quot;text-end bg-[gray] h-[12px] animate-pulse&quot;&gt;&lt;/small&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
};</code></pre>
<p>mdx파일을 호출하면,</p>
<p>suspend에 fallback에 넘겨진 스켈레톤 ui를 먼저 보여준뒤,</p>
<p>데이터를 받으면 bookmark 컴포넌트를 렌더합니다.</p>
<h1 id="🚪">🚪</h1>
<p>이걸 만드는데 정말 많은 시행착오를 지난 것 같습니다..</p>
<p>막상 결과를 만들고 나니 정말 뿌듯하고 이쁜거 같네요 ㅎㅎㅎ</p>
<p>앞으로는 next 13을 사용해본 내용을 정리할거 같습니다</p>
<p>마지막으로 북마크를 만들면서 가장 많이 들었던 노래를 공유하면서 마치겠습니다!</p>
<p><a href="https://www.youtube.com/watch?v=hHD2GGRIJ9k">https://www.youtube.com/watch?v=hHD2GGRIJ9k</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[23]실행 컨텍스트]]></title>
            <link>https://velog.io/@ateals_12/23%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@ateals_12/23%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sun, 21 May 2023 12:29:59 GMT</pubDate>
            <description><![CDATA[<p>실행 컨텍스트는 자바스크립트의 동작 원리를 담은 핵심 개념입니다.</p>
<p>앞으로 다룰 클로저의 동작방식, 호이스팅 발생 이유, 태스크 큐와 함께 동작하는 이벤트 핸들러와 비동기 처리방식을 쉽고, 확실하게 이해하려면 실행 컨텍스트를 확실하게 이해하고 넘어가야 합니다..</p>
<p>쉽지 않고 내용도 많지만, 한번 해치워 보죠!</p>
<h1 id="소스코드의-타입">소스코드의 타입</h1>
<hr>
<p>ECMAScript는 소스코드를 4가지타입으로 구분합니다.</p>
<ul>
<li><p>전역 코드(Global code)</p>
<p>  전역에 존재하는 (전역에 정의된 함수, 클래스의 내부를 제외한)소스코드</p>
</li>
<li><p>함수 코드(Function code)</p>
<p>  함수 내부에 존재하는 (중첩된 함수, 클래스의 내부를 제외한) 소스코드</p>
</li>
<li><p>eval코드 (Eval code)</p>
<p>  빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스코드</p>
</li>
<li><p>모듈 코드 (Module code)</p>
<p>  모듈 내부에 존재하는 (모듈 내부 함수, 클래스를 제외한) 소스코드</p>
</li>
</ul>
<p>소스코드를 구분하는 이유는 </p>
<p>각 소스코드 타입에 따라 실행 컨텍스트를 생성하고 관리하는 과정이 다르기 때문입니다.</p>
<p>각 코드는 평가 이후 실행 컨텍스트가 생성됩니다.</p>
<h3 id="전역-코드">전역 코드</h3>
<p>전역 코드는 전역 스코프를 생성해 전역변수를 관리합니다.</p>
<p><strong>var</strong>키워드로 선언된 전역 변수와 함수 선언문으로 정의된 전역 함수를 전역 객체의 프로퍼티와 메서드로 바인딩 하고 참조하기 위해 전역 객체와 연결 되어야 합니다.</p>
<h3 id="함수-코드">함수 코드</h3>
<p>함수 코드는 지역 스코프를 생성하고, 지역 변수, 매개 변수, <strong>arguments</strong>객체를 관리해야합니다.</p>
<p>생성한 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결해야합니다.</p>
<h3 id="eval-코드">eval 코드</h3>
<p><strong>eval</strong>코드는 <strong>strict mode</strong>(엄격 모드)에서 자신만의 독자적인 스코프를 생성합니다.</p>
<h3 id="모듈-코드">모듈 코드</h3>
<p>모듈 코드는 모듈별로 독립적인 모듈 스코프를 생성합니다.</p>
<h1 id="소스코드의-평가와-실행">소스코드의 평가와 실행</h1>
<hr>
<p>자바스크립트 엔진은 소스코드를 소스코드의 평가와 소스코드의 실행 2개의 과정으로 나누어 처리합니다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/64003db1-618e-4fa8-a823-452d09f93ff0/image.png" alt=""></p>
<h3 id="소스코드-평가">소스코드 평가</h3>
<p>실행 컨텍스트를 생성하고 변수, 함수 선언문이 먼저 실행하여 생성된 변수나 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록합니다.</p>
<h3 id="소스코드-실행">소스코드 실행</h3>
<p>소스코드 평가과정이 끝나면 선언문을 제외한 소스코드가 순차적으로 실행됩니다. (런타임)</p>
<p>이때 실행에 필요한 정보(변수, 함수의 참조)는 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득합니다.</p>
<p>그리고 변수값의 변경과 같은 소스코드의 실행 결과는 다시 실행 컨텍스트가 관리하는 스코프에 등록됩니다.</p>
<pre><code class="language-jsx">const x = 1;
const y = 2;

function foo(a) {
    const x = 10;
    const y = 20;

    console.log(a + x + y);
}

foo(100);
console.log(x + y);</code></pre>
<p>위에 있는 코드로 예시를 들겠습니다!</p>
<h3 id="전역코드-평가">전역코드 평가</h3>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/7f57b37b-8683-4674-a94e-18c81b38dd94/image.png" alt=""></p>
<p>전역 코드 평가 과정을 거치며 선언문이 먼저 실행된다.</p>
<p>그결과 생성된 전역 변수와 전역 함수가 실행 컨텍스트가 관리하는 전역스코프에 등록된다.</p>
<p>전역 코드 평가 괒</p>
<h3 id="전역코드-실행">전역코드 실행</h3>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/2d51ed41-e33f-4719-b921-1709682e8401/image.png" alt=""></p>
<p>평가가 끝나면 런타임이 시작되어 전역 변수에 값이 할당되고 함수가 호출된다.</p>
<h3 id="함수코드-평가">함수코드 평가</h3>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/076bc985-3750-4612-8570-3fd36e48fe80/image.png" alt=""></p>
<p>함수 호출에 의해 코드 실행순서가 변경되어 함수 내부로 진입한다.</p>
<p>이때 함수 실행 이전에 함수코드를 평가하면서 매개변수와 지역 변수 선언문이 먼저 실행되고,</p>
<p>그 결과가 실행 컨텍스트가 관리하는 지역 스코프에 등록된다.</p>
<p>또한 함수 내부에서 지역 변수처럼 사용할 수 있는 <strong>arguments</strong> 객체가 생성되어 지역 스코프에 등록되고,</p>
<p><strong>this</strong> 바인딩도 결정된다.</p>
<h3 id="함수코드-실행">함수코드 실행</h3>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/f34212c8-da6d-44aa-bcb8-bddcd1a6f193/image.png" alt=""></p>
<p>런타임이 시작되고, 매개변수와 지역변수에 순차적으로 값이 할당된다.</p>
<p><strong>cosole.log</strong> 메서드를 호출하기 위해 <strong>console</strong>을 스코프 체인을 통해 검색한다.</p>
<p>이를 위해 함수코드의 지역 스코프는 상위스코프인 전역 스코프와 연결되어야 한다.</p>
<p>하지만 <strong>console</strong> 식별자는 스코프 체인에 등록되어 있지 않고 전역 객체에 프로퍼티로 존재한다.</p>
<p>다음으로 <strong>log</strong>메서드를 <strong>console</strong> 객체의 프로토타입 체인을 통해 검색한다.</p>
<p>다음 표현식 <strong>a+x+y</strong>를 평가한다.  </p>
<p>코드가 실행되려면 다음과 같은 스코프, 식별자, 코드 실행순서 등의 관리가 필요합니다.</p>
<ul>
<li>선언에 의해 생성된 모든 식별자를 스코프를 구분하여 등록하고 상태변화를 지속적으로 관리할 수 있어야 한다.</li>
<li>스코프는 중첩 관계에 의해 스코프 체인을 형성해야 한다. 즉, 스코프 체인을 통해 상위 스코프로 이동하여 식별자를 검색할 수 있어야 한다.</li>
<li>현재 실행중인 코드의 실행 순서를 변경할 수 있어야 하며 다시 되돌아갈 수도 있어야한다.</li>
</ul>
<p>실행 컨텍스트가 바로 이 모든 것들을 관리합니다.</p>
<aside>
📌 실행 컨택스트 

<p>소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역</p>
<p>식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로,
모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.</p>
<p>식별자와 스코프는 실행 컨텍스트의 렉시컬 환경으로 관리하고 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다.</p>
</aside>

<h1 id="실행-컨텍스트-스택call-stack">실행 컨텍스트 스택(Call Stack)</h1>
<hr>
<pre><code class="language-jsx">const x = 1;

function foo() {
    const y = 2;

    function bar() {
        const z = 3;
        console.log(x + y + z);
    }

    bar();
}

foo();</code></pre>
<p>위 코드는 전역 코드와 함수 코드로 이루어져 있습니다.</p>
<p>자바스크립트 엔진은 전역 코드를 평가하고 전역 실행 컨텍스트를 생성하고,</p>
<p>함수코드를 평가하고 함수 실행 컨텍스트를 생성합니다.</p>
<p>이때 생성된 실행 컨텍스트는 스택 자료구조로 관리하고 이를 실행 컨텍스트 스택(또는 Call Stack)이라고 부른답니다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/d15bccf1-4c43-4d21-96b6-5317c59251f5/image.png" alt=""></p>
<h1 id="렉시컬-환경">렉시컬 환경</h1>
<hr>
<p>렉시컬 환경은 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조이자,</p>
<p>실행 컨텍스트를 구성하는 컴포넌트입니다.</p>
<p><img src="https://velog.velcdn.com/images/niyu/post/591dbd1b-892f-4e2f-ade5-822c8055c28e/image.png" alt="https://velog.velcdn.com/images/niyu/post/591dbd1b-892f-4e2f-ade5-822c8055c28e/image.png"></p>
<p>렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프를 생성하여 식별자를 키로 등록하고 식별자에 바인딩 된 값을 관리합니다.</p>
<p>실행 컨텍스트는 <strong>Lexical Environment</strong> 컴포넌트와 <strong>Variable Environment</strong> 컴포넌트로 구성된다. </p>
<blockquote>
<p>모딥다에서는 정적모드와 eval, try/catch와 같은 특수한 상황은 제외하고, 
Lexical Environment와 Variable Environment 컴포넌트도 구분하지 않고, 
하나의 렉시컬 환경으로 통일해 설명한다.</p>
</blockquote>
<p>렉시컬 환경은 두개의 컴포넌트로 구성됩니다.</p>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/2d2c60aa-c460-4276-9d18-cf57ed0738d3/image.png" alt=""></p>
<h3 id="환경-레코드environment-record">환경 레코드(Environment Record)</h3>
<p>스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩 된 값을 관리하는 저장소,</p>
<p>환경 레코드는 소스코드의 타입에 따라 관리하는 내용에 차이가 있습니다.</p>
<h3 id="외부-렉시컬-환경에-대한-참조outer-lexical-environment-reference">외부 렉시컬 환경에 대한 참조(Outer Lexical Environment Reference)</h3>
<p>외부 렉시컬 환경에 대한 참조는 상위 스코프를 가리킵니다.</p>
<p>이때 상위 스코프란 외부 렉시컬 환경, 즉 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 상위 코드의 렉시컬 환경을 말합니다.</p>
<p>외부 렉시컬 환경에 대한 참조를 통해 단방향 링크드 리스트인 스코프 체인을 구현합니다.</p>
<h1 id="실행-컨텍스트의-생성과-식별자-검색-과정">실행 컨텍스트의 생성과 식별자 검색 과정</h1>
<hr>
<pre><code class="language-jsx">var x = 1;
const y = 2;

function foo(a) {
    var x = 3;
    const y = 4;

    function bar(b) {
        const z = 5;
        console.log(a + b + x + y + z);
    }

    bar(10);
}

foo(20);</code></pre>
<p>이 코드를 이용해서 실행 컨테스트의 전체적인 흐름을 알아봅시다!!</p>
<h2 id="전역-객체-생성">전역 객체 생성</h2>
<hr>
<p>전역 객체는 전역 코드가 평가되기 이전에 생성됩니다. </p>
<p>이때 전역 객체에는 빌트인 전역 프로퍼티와 빌트인 전역함수, 표준 빌트인 객체가 추가되며 동작환경 또는 특정 환경을 위한 호스트 객체를 포함합니다.</p>
<h2 id="전역-코드-평가">전역 코드 평가</h2>
<hr>
<aside>
📌 전역 코드 평가 순서

<ol>
<li><p>전역 실행 컨텍스트 생성</p>
</li>
<li><p>전역 렉시컬 환경 생성</p>
<p> 전역 환경 레코드 생성</p>
<ul>
<li><p>객체 환경 레코드 생성</p>
</li>
<li><p>선언적 환경 레코드 생성</p>
<p>this바인딩</p>
<p>외부 렉시컬 환경에 대한 참조 결정</p>
</li>
</ul>
</li>
</ol>
</aside>

<h3 id="생성된-전역-실행-컨텍스트와-렉시컬-환경">생성된 전역 실행 컨텍스트와 렉시컬 환경</h3>
<p><img src="https://blog.kakaocdn.net/dn/bkDB1q/btrxyiAVs4c/ue3xIpo1X18uWkNFaDGTbk/img.png" alt="https://blog.kakaocdn.net/dn/bkDB1q/btrxyiAVs4c/ue3xIpo1X18uWkNFaDGTbk/img.png"></p>
<h3 id="1-전역-실행-컨텍스트-생성">1. 전역 실행 컨텍스트 생성</h3>
<p>비어있는 전역 실행 컨텍스트를 생성하여 스택에 푸쉬합니다.</p>
<p>이때 전역 실행 컨텍스트는 스택의 최상위 (실행중인 실행 컨텍스트)가 됩니다.</p>
<h3 id="2-전역-렉시컬-환경-생성">2. 전역 렉시컬 환경 생성</h3>
<p>전역 렉시컬 환경을 생성하고 전역 실행 컨텍스트에 바인딩합니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/zD3su/btrxwY3LJ9Y/Ymu65b7I7NqRKuOrXmdpX1/img.png" alt="https://blog.kakaocdn.net/dn/zD3su/btrxwY3LJ9Y/Ymu65b7I7NqRKuOrXmdpX1/img.png"></p>
<h3 id="2-1-전역-환경-레코드-생성">2-1 전역 환경 레코드 생성</h3>
<p>전역 렉시컬 환경을 구성하는 컴포넌트인 전역 환경 레코드는 전역 변수를 관리하는 전역 스코프, 전역 객체의 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 제공합니다.</p>
<p>전역환경레코드는 객체 환경 레코드와 선언적 환경 레코드로 구성되어 있습니다.</p>
<h3 id="2-1-1-객체-환경-레코드-생성">2-1) 1. 객체 환경 레코드 생성</h3>
<p>전역 코드 평가 과정에서 <strong>var</strong> 키워드로 선언한 전역 변수와 함수 선언문으로 정의돈 전역 함수는 전역 환경 레코드의 객체 환경 레코드에 연결된 <strong>BindingObject</strong>를 통해 전역 객체의 프로퍼티와 메서드가 됩니다.</p>
<p>이때 등록된 식별자를 전역 환경 레코드의 객체 환경 레코드에서 검색하면 전역 객체의 프로퍼티를 검색후 반환합니다.</p>
<p>이것이 <strong>var</strong> 키워드로 선언된 전역변수와 함수 선언문으로 정의된 전역 함수가 전역 객체의 프로퍼티와 메서드가 되고, 전역 객체를 가리키는 식별자(<strong>window</strong>)없이 객체의 프로퍼티를 참조 할 수 있는 메커니즘입니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/b3p5Jr/btrxqady6jF/Kmr0HneAqEi8f9Rq5HECf0/img.png" alt="https://blog.kakaocdn.net/dn/b3p5Jr/btrxqady6jF/Kmr0HneAqEi8f9Rq5HECf0/img.png"></p>
<h3 id="호이스팅">호이스팅</h3>
<p><strong>var</strong>키워드로 선언한 변수와 <strong>var</strong>키워드로 선언한 변수에 할당한 함수 표현식은 코드 평가 단계에서 정의 및 초기화가 진행됩니다.</p>
<p>함수 선언문으로 정의한 함수가 평가되면 함수 이름과 동일한 이름의 식별자를 객체 환경 레코드에 바인딩 된</p>
<p><strong>BindingObject</strong>를 통해 전역 객체에 키로 등록하고 생성된 함수 객체를 즉시 할당합니다.</p>
<p>(그래서 호이스팅으로 선언된 함수는 선언문 이전에도 호출할 수 있는 거랍니다..)</p>
<h3 id="2-1-2-선언적-환경-레코드-생성">2-1) 2. 선언적 환경 레코드 생성</h3>
<p><strong>let</strong>, <strong>const</strong>로 선언한 전역 변수는 선언적 환경 레코드에 등록되고 관리됩니다.</p>
<p>(따라서 객체 환경 레코드에 선언된 다른 변수와는 다르게 <strong>window.y</strong>로 호출할 수 없습니다.)</p>
<p><img src="https://blog.kakaocdn.net/dn/KwJD3/btrxB4hPOnd/Y24OYqAbxIiRAhiQfzyqf0/img.png" alt="https://blog.kakaocdn.net/dn/KwJD3/btrxB4hPOnd/Y24OYqAbxIiRAhiQfzyqf0/img.png"></p>
<h3 id="일시적-사각지대">일시적 사각지대</h3>
<p><strong>const</strong>와 <strong>let</strong>키워드로 선언한 변수는 선언 단계와 초기화 단계가 분리되어 진행되기 때문에 소스코드 평가단계에서 선언적 환경 레코드에 해당 변수의 이름을 키값으로 한 프로퍼티가 등록되고, 초기화 되지 않아 아무런 값이 바인딩 되지 않았기 때문에 변수에 접근할 수 없습니다.</p>
<h3 id="2-2-this-바인딩">2-2. this 바인딩</h3>
<p>전역 환경 레코드의 <strong>[[GlobalThisValue]]</strong> 내부 슬롯에 <strong>this</strong>가 바인딩 됩니다.</p>
<p>일반적으로 전역 코드에서 <strong>this</strong>는 전역 객체이므로 <strong>[[GlobalThisValue]]</strong> 내부 슬롯은 전역 객체가 바인딩됩니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/HJ8p8/btrxqady64i/ktmNfhhR28CvbV9Djma1m0/img.png" alt="https://blog.kakaocdn.net/dn/HJ8p8/btrxqady64i/ktmNfhhR28CvbV9Djma1m0/img.png"></p>
<h3 id="2-3-외부-렉시컬-환경에-대한-참조">2-3. 외부 렉시컬 환경에 대한 참조</h3>
<p>외부 렉시컬 환경에 대한 참조는 현재 평가중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경 즉 상위 스코프를 가르킵니다.</p>
<p>하지만 전역코드를 포함하는 외부 소스코드는 없으므로 <strong>null</strong>이 할당됩니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/JfV3E/btrxrchSbue/8qrU2jFZ0EQEU69HRYkit1/img.png" alt="https://blog.kakaocdn.net/dn/JfV3E/btrxrchSbue/8qrU2jFZ0EQEU69HRYkit1/img.png"></p>
<h2 id="전역코드-실행-1">전역코드 실행</h2>
<hr>
<p>이제 전역코드가 순차적으로 실행되기 시작합니다.</p>
<p>변수 할당문이 실행되어 전역 변수 <strong>x,y</strong>에 값이 할당된다. 그리고 <strong>foo</strong>함수가 호출됩니다.</p>
<p>변수 할당문, 함수 호출문을 실행하려면 먼저 변수, 또는 함수 이름이 선언된 식별자인지 확인해야 합니다.</p>
<p>또한 같은 이름의 식별자가 다른 스코프에 존재할 수 있으므로 참조할 식별자를 구분해야 합니다.</p>
<p>이것을 <strong>식별자 결정</strong>이라고 합니다.</p>
<p>현재 실행중인 실행 컨텍스트는 전역 실행 컨텍스트이므로 전역 렉시컬 환경에서 식별자 <strong>x, y, foo</strong>를 검색하기 시작한다. 만약 실행중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색할 수 없으면 외부 렉시컬 환경에 대한 참조가 가리키는 렉시컬 환경(상위 스코프)으로 이동하여 식별자를 검색합니다.</p>
<p>아하! 이게 스코프체인의 동작 원리군요!</p>
<p>전역 렉시컬 환경은 스코프체인의 종점이므로 전역 렉시컬 환경에서 검색할 수 없는 식별자는 참조 에러를 발생시킵니다.</p>
<p>(그놈의 <strong>Reference Error</strong>…)</p>
<h2 id="함수-코드-평가">함수 코드 평가</h2>
<hr>
<pre><code class="language-jsx">var x = 1;
const y = 2;

function foo(a) {
    var x = 3;
    const y = 4;

    function bar(b) {
        const z = 5;
        console.log(a + b + x + y + z);
    }

    bar(10);
}

foo(20); &lt;- 호출 직전</code></pre>
<p><strong>foo</strong> 함수가 호출되면 전역 코드의 실행을 일시 중단하고 <strong>foo</strong>함수 내부로 코드의 제어권이 이동하고, 함수 코드를 평가하기 시작합니다. </p>
<aside>
📌 함수코드 평가 순서

<ol>
<li>함수 실행 컨텍스트 생성</li>
<li>함수 렉시컬 환경 생성</li>
</ol>
<p>함수 환경 레코드 생성</p>
<p>this바인딩</p>
<p>외부 렉시컬 환경에 대한 참조 결정</p>
</aside>

<h3 id="1-함수-실행-컨텍스트-생성">1. 함수 실행 컨텍스트 생성</h3>
<p>함수 실행컨텍스트를 생성하여 렉시컬 환경을 만든 후 실행 컨텍스트에 푸쉬합니다.</p>
<h3 id="2-함수-렉시컬-환경-생성">2. 함수 렉시컬 환경 생성</h3>
<p><strong>foo</strong> 함수의 렉시컬 환경을 생성하고 실행 컨텍스트에 바인딩 합니다.</p>
<p>함수의 렉시컬 환경에서도 마찬가지로 환경 레코드와 외부 렉시컬 환경에대한 참조 2개의 컴포넌트로 구성됩니다.</p>
<h3 id="2-1-함수-환경-레코드-생성">2-1 함수 환경 레코드 생성</h3>
<p>함수 환경 레코드는 매개변수, <strong>arguments</strong>객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리합니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/RWMWz/btrxqaEBlaV/clfkMriQfHjWiUcmEawMKk/img.png" alt="https://blog.kakaocdn.net/dn/RWMWz/btrxqaEBlaV/clfkMriQfHjWiUcmEawMKk/img.png"></p>
<h3 id="2-2-this-바인딩-1">2-2) this 바인딩</h3>
<p>함수 환경 레코드의 <strong>[[ThisValue]]</strong> 내부 슬롯에 <strong>this</strong>가 바인딩 된다. 이때 함수 호출 방식에 따라 결정됩니다.</p>
<p>여기서는 일반 함수를 호출 했으므로 전역 객체가 바인딩됩니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/qYTQX/btrxoHVvlMX/Z0mmmRC4ZclW0y3lVaRZr1/img.png" alt="https://blog.kakaocdn.net/dn/qYTQX/btrxoHVvlMX/Z0mmmRC4ZclW0y3lVaRZr1/img.png"></p>
<h3 id="2-3-외부-렉시컬-환경에-대한-참조-결정">2-3) 외부 렉시컬 환경에 대한 참조 결정</h3>
<p>해당 함수의 정의가 평가된 시점에 실행중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당됩니다.</p>
<p><strong>foo</strong>함수는 전역에 정의된 전역함수 이고, 전역 코드 평가 시점에 평가 되었기 때문에 평가 시점의 실행중인 실행 컨텍스트는 전역 실행 컨텍스트입니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/OkuFG/btrxkJTPaNi/ItW7ik1KJEic5mj8lfBHq0/img.png" alt="https://blog.kakaocdn.net/dn/OkuFG/btrxkJTPaNi/ItW7ik1KJEic5mj8lfBHq0/img.png"></p>
<blockquote>
<p>자바스크립트는 함수를 어디서 호출 했는지가 아닌 어디서 정의 했는지에 따라 상위 스코프를 결정한다. 
그리고 함수 객체는 자신이 정의된 스코프, 즉 상위 스코프를 기억한다.</p>
</blockquote>
<p>자바스크립트는 함수의 상위 스코프를 객체의 내부 슬롯 <strong>[[Environment]]</strong>에 저장합니다.</p>
<p>함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당되는 것은 </p>
<p>바로 상위 스코프를 가리키는 함수 객체의 내부 슬롯 <strong>[[Environment]]</strong>에 저장된 렉시컬 환경의 참조입니다.</p>
<h2 id="함수-코드의-실행">함수 코드의 실행</h2>
<hr>
<p>함수 코드 평가가 끝나면 런타임이 실행되어 순차적으로 모든 코드가 실행됩니다.</p>
<h3 id="1-console-식별자-검색">1. console 식별자 검색</h3>
<p><strong>cosole.log</strong> 메서드를 호출하기 위해 <strong>console</strong>을 스코프 체인을 통해 검색합니다.</p>
<p><strong>console</strong> 식별자는 객체 환경 레코드의 <strong>BingObject</strong>를 통해 전역 객체에서 찾을 수 있습니다.</p>
<h3 id="2-log-메서드-검색">2. log 메서드 검색</h3>
<p>이제 <strong>console</strong>식별자에 바인딩 된 객체에서 <strong>log</strong>메서드를 검색합니다.</p>
<h3 id="3-표현식-a--b--x--y--z의-평가">3. 표현식 a + b + x + y + z의 평가</h3>
<p>각 변수들을 스코프체인을 통해 검색하고, 참조합니다.</p>
<p><strong>a</strong>는 <strong>foo</strong> 함수 렉시컬 환경에서, </p>
<p><strong>b</strong>와 <strong>z</strong>는 <strong>bar</strong>함수 렉시컬 환경에서, </p>
<p><strong>x</strong>와 <strong>y</strong>는 <strong>foo</strong> 함수 렉시컬 환경에서 검색됩니다.</p>
<h2 id="함수-코드-실행-종료">함수 코드 실행 종료</h2>
<hr>
<p>더 이상 실행할 코드가 없다면 함수 코드의 실행이 종료됩니다.</p>
<p>이때 스택에서 해당 함수 실행 컨텍스트가 팝되어 제거되고 그 다음 스택에 최상위 실행 컨텍스트가 실행중인 실행 컨텍스트가 됩니다.</p>
<p>실행 컨텍스트가 스택에서 제거되었다고, 바로 함수 렉시컬 환경까지 소멸되는 것은 아닙니다.</p>
<p>렉시컬 환경은 실행 컨텍스트에 의해 참조 되지만 독립적인 객체이기 때문에 함수 렉시컬 환경이 더이상 참조되지 않을 때 까지 소멸하지 않습니다.</p>
<h2 id="전역-코드-실행-종료">전역 코드 실행 종료</h2>
<hr>
<p>모든 전역 코드의 실행이 종료된 후 전역 실행 컨텍스트도 종료되어 실행 컨텍스트 스택에서 팝되어 실행 컨텍스트 스택에는 아무것도 남지 않게 됩니다.</p>
<h1 id="실행-컨텍스트와-블록-레벨-스코프">실행 컨텍스트와 블록 레벨 스코프</h1>
<hr>
<p><strong>let</strong>, <strong>const</strong> 키워드로 선언한 변수는 모든 코드 블록을 지역 스코프로 인정하는 블록레벨 스코프를 따릅니다.</p>
<pre><code class="language-jsx">let x = 1;

if (true) {
  let x = 10;
  console.log(x); // 10
}

console.log(x); // 1</code></pre>
<p>위 코드에서 조건문이 실행되면 선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 전역 렉시컬 환경을 교체합니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/bnuLpV/btrxrb4lfA1/lJFpQWJpMTkbQez0ib8Y1K/img.png" alt="https://blog.kakaocdn.net/dn/bnuLpV/btrxrb4lfA1/lJFpQWJpMTkbQez0ib8Y1K/img.png"></p>
<p>코드 블록의 실행이 종료되면 원래의 렉시컬 환경으로 되돌립니다.</p>
<p><img src="https://blog.kakaocdn.net/dn/046tJ/btrxu8FORPS/PbHhxxkQinBAtbWM5Q14c1/img.png" alt="https://blog.kakaocdn.net/dn/046tJ/btrxu8FORPS/PbHhxxkQinBAtbWM5Q14c1/img.png"></p>
<p>이는 모든 블록문에 적용됩니다.</p>
<p><strong>for</strong>문의 변수 선언문에 <strong>let</strong>키워드를 사용하면, 반복해서 실행될 때마다 코드 블록을 위한 새로운 렉시컬 환경을 만듭니다.</p>
<p>만약 <strong>for</strong>문의 코드 블록 내에서 정의된 함수가 있다면 이 함수의 상위 스코프는 <strong>for</strong>문의 코드 블록이 생성한 렉시컬 환경입니다.</p>
<p>이때 함수의 상위 스코프는 <strong>for</strong>문의 코드블록이 반복해서 실행될 때마다 식별자의 값을 유지해야합니다.</p>
<p>이를 위해 <strong>for</strong>문의 코드 블록이 반복해서 실행될 때마다 독립적인 렉시컬 환경을 생성하여 식별자의 값을 유지합니다.</p>
<p>이는 나중에 나오는 클로져에서 더 다루어 보도록 하죠!</p>
<h1 id="🧹정리하기">🧹정리하기</h1>
<hr>
<p>후… 너무 어렵습니다…. 실행 컨텍스트</p>
<p>그래도 글을 쓰려고 3회독하고, 여러가지 강의도 보다 보니 어느정도 이해가 가네요.</p>
<p>이제 실행 컨텍스트 단어가 나와도 두렵지 않습니다!</p>
<p>그럼이만…</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개발] 블로그 글 시리즈 별로 나누기!]]></title>
            <link>https://velog.io/@ateals_12/%EA%B0%9C%EB%B0%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B8%80-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%B3%84%EB%A1%9C-%EB%82%98%EB%88%84%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/%EA%B0%9C%EB%B0%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B8%80-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%B3%84%EB%A1%9C-%EB%82%98%EB%88%84%EA%B8%B0</guid>
            <pubDate>Thu, 11 May 2023 09:47:46 GMT</pubDate>
            <description><![CDATA[<h1 id="발단">발단</h1>
<hr>
<pre><code>React를 어찌저찌 클론해보고, next.js에 혹한 뒤에 블로그 개발에 흥미가 생긴 나는 아무런 지식 없이 블로그를 만들기로 했다.</code></pre><p>다른 분들의 블로그 코드를 참조하고, 기본적인 페이지 생성은 next.js 블로그 라고 검색 했을 때 가장 먼저 발견한 <a href="https://miryang.dev/blog/build-blog-with-nextjs">Next.js로 나만의 블로그 만들기 with 정적 생성</a> 을 토대로 시작했다.</p>
<p>이대로 포스트를 만들다 보니 여타 다른 블로그나 벨로그에도 있는 시리즈 별로 글을 나누는 것이 너무 부러웠다.</p>
<p>글을 개발, 코딩테스트 연습, 일상, 회고 등등 나중에 확장하다 보면 분명 글을 파일별로 나누고 싶어질 것이기 때문에 미리 만들어 놓고 싶어졌다.</p>
<p>(이걸 하면서, 버그가 너무 많이 생겼다.....)</p>
<h1 id="과정">과정</h1>
<hr>
<p>시리즈를 나눈 코드는 <a href="https://bepyan.github.io/blog">bepyan</a>님의 코드를 참고했다!</p>
<p>우리는 마크다운 파일을 <a href="https://www.contentlayer.dev/docs/environments/nextjs">contentlayer</a>를 이용해서 html파일에 빌드한다.</p>
<p>이때 <strong>contentlayer</strong>에서 파일을 가져오는 방법은 <strong>config</strong>에서 설정한 디랙토리의 mdx파일을 불러오는 방식인데,</p>
<p>각 파일은 <strong>contentlayer</strong>에 의해 <strong>allPosts</strong> 배열에 <strong>json</strong>형식으로 저장되고, 해당 <strong>json</strong>파일에는 폴더 디랙토리가 나타나 있어 동적라우팅을 적용하기 편리했다.</p>
<p>내 블로그의 포스트를 나타내는 파일 경로는 다음과 같다.</p>
<pre><code>pages
|
|-&gt; posts
    |-&gt; [...slugs].jsx
    |-&gt;[slug].jsx
    |-&gt;index.jsx
</code></pre><h3 id="indexjs">index.js</h3>
<p><strong>posts</strong> 폴더의 가장 기본 경로이다. 이 페이지에는 모든 시리즈를 보여주는 페이지를 만들 것이다.</p>
<h3 id="slugjsx">[slug].jsx</h3>
<p>그렇다 동적라우팅 나는 이녀석 때매 애를 무지하게 먹었다.... 
이 파일에는 각 시리즈에 있는 글 목록을 나타내는 페이지를 만들 것이다.</p>
<h3 id="slugsjsx">[...slugs].jsx</h3>
<p>이 파일은 각각 글에 대한 페이지이다.  </p>
<blockquote>
<p>자자 나도 <strong>[slug]</strong>와 **[...slugs] **때매 애를 무지하게 먹었다. (버그의 대부분이 이 부분)</p>
</blockquote>
<p>여기서 나는 블로그 웹사이트 답게 서버에서 페이지를 정적으로 만들 것이다. (그 유명한 ssg)</p>
<p>시리즈의 라우트 경로 -&gt; posts/시리즈 이름
포스트의 라우트 경로 -&gt; posts/시리즈 이름/포스트 이름
이런 형식으로 정적 페이지가 미리 생성되고 랜더링 될텐데...</p>
<p>여기서 문제는 <strong>[...slugs]</strong>에 있다. 이 친구를 이용해서 slugs를 &quot;/&quot;단위로 분리해서 배열에 저장해서
포스트를 정적 페이지로 만들텐데, 이렇게 되면 &quot;posts/시리즈 이름&quot; 페이지는 <strong>[slug].jsx</strong>에서도 생성되고 <strong>[...slugs].jsx</strong>에서도 생성되어 빌드시 오류가 난다...  (그래서 나는 <strong>[...slugs].jsx</strong>에서 <strong>index.mdx</strong>파일을 제외하는 방식을 이용했다.)</p>
<h1 id="시작">시작!</h1>
<hr>
<p>글을 html페이지에 불러오는 건 참고자료에 나와있는 블로그와 구글에 검색하면 여러 포스트 들이 많으니 생략 하겠다.</p>
<h3 id="slugsjsx-1">[...slugs].jsx</h3>
<hr>
<pre><code class="language-jsx">
export default ({ post }) =&gt; {
    return (
        &lt;&gt;
            &lt;article className=&quot;post&quot;&gt;
                &lt;div className=&quot;log&quot;&gt;
                    &lt;MarkdownPost post={post.body.code} /&gt;
                &lt;/div&gt;
            &lt;/article&gt;
        &lt;/&gt;
    );
};

export const getStaticPaths = async () =&gt; {
    return {
        paths: allPosts.filter((i) =&gt; !i._raw.sourceFilePath.includes(&quot;/index.mdx&quot;)).map((p) =&gt; ({ params: { slugs: p._raw.flattenedPath.split(&quot;/&quot;) } })),
        fallback: &quot;blocking&quot;,
    };
};

export const getStaticProps = async ({ params }) =&gt; {
    const post = allPosts.find((p) =&gt; p._raw.flattenedPath === params.slugs.join(&quot;/&quot;));
    return {
        props: {
            post,
        },
    };
};
</code></pre>
<p>(<strong>import</strong>와 같은 코드는 생략하도록 하자!)</p>
<h3 id="i">I</h3>
<p>먼저 개시글 파일을 보면, <strong>getStaticPaths</strong>를 이용해서 <strong>route path</strong>를 빌드시점에 만들어준다.
이때, 아까 말한 두 파일에서 동일한 라우팅이 발생하는 <strong>index.mdx</strong>파일을 제외하고 만들어 주기 위해서,
<strong>allPosts</strong>배열에서 <strong>index.mdx</strong>를 포함하는 파일을 제거해 주었다.</p>
<p>제거해 준 각 아이템들은 <strong>params</strong>로 <strong>getStaticProps</strong>에 넘겨주었다.</p>
<h3 id="ii">II</h3>
<p>그 이후 <strong>getStaticProps</strong>을 이용해서 <strong>getStaticPaths</strong>에게 받은 파라미터와 동일한 <strong>path</strong>를 가진 파일을 찾아서 컴포넌트에 <strong>post prop</strong>을 보내주었다.</p>
<p>이때 <strong>slugs</strong>는 각 파라미터를 &quot;/&quot;기준으로 나누어 저장해둔 배열이기 때문에 &quot;/&quot;로 묶은 문자열로 치환해준다.</p>
<h3 id="slugjsx-1">[slug].jsx</h3>
<hr>
<p>이제 각 시리즈를 페이지를 만들어보자</p>
<pre><code class="language-jsx">
export default ({ collection }) =&gt; {
    console.log(collection);
    return (
        &lt;div&gt;
            {collection.map((item) =&gt; {
                return (
                    &lt;Link href={`/posts/${item._raw.flattenedPath}`}&gt;
                        &lt;h1&gt;{item.title}&lt;/h1&gt;
                        &lt;h1&gt;{item.description}&lt;/h1&gt;
                        &lt;h1&gt;{item.date}&lt;/h1&gt;
                        &lt;br /&gt;
                    &lt;/Link&gt;
                );
            })}
        &lt;/div&gt;
    );
};

export const getStaticPaths = async () =&gt; {
    return {
        paths: allPosts.map((p) =&gt; ({ params: { slug: p._raw.flattenedPath.split(&quot;/&quot;)[0] } })),
        fallback: false,
    };
};

export const getStaticProps = async ({ params }) =&gt; {
    // const collection = getCollactionItem(params.slug);
    const collection = allPosts.filter((i) =&gt; !i._raw.sourceFilePath.includes(&quot;/index.mdx&quot;)).filter((i) =&gt; i._raw.flattenedPath.includes(params.slug));
    return {
        props: {
            collection,
        },
    };
};
</code></pre>
<p>여기서 <strong>getStaticPaths</strong>를 보면 아까 그녀석과 조금 다를 것이다.
<strong>[slug]</strong>는 <strong>[...slug]</strong>와는 다르게 배열이 아닌 문자열 하나만 받는다.</p>
<p>따라서 파일에 있는 경로는 &quot;시리즈 이름 / 게시물 이름&quot;이므로 경로를 &quot;/&quot;로 분리한 배열 중 첫번째가 시리즈의 <strong>route</strong>가 된다.</p>
<p><strong>getStaticPaths</strong>에게 받은 <strong>params</strong>를 <strong>getStaticProps</strong>는 각 시리즈의 정보 저장 파일인 <strong>index.mdx</strong>를 제외하고 <strong>collection prop</strong>을 컴포넌트에 보내준다.</p>
<h3 id="indexjsx">index.jsx</h3>
<hr>
<pre><code class="language-jsx">export default ({ posts }) =&gt; {
    return (
        &lt;&gt;
            &lt;div className=&quot;p-[2em]&quot;&gt;
                {posts.map((post) =&gt; {
                    return (
                        &lt;BlogPost
                            key={post._id}
                            date={post.date}
                            title={post.title}
                            des={post.description}
                            slug={post._raw.flattenedPath}
                        /&gt;
                    );
                })}
            &lt;/div&gt;
        &lt;/&gt;
    );
};

export const getStaticProps = async () =&gt; {
    //allPosts =&gt; 해당 경로의 mdx파일을 배열에 담아서 전송해줌!
    const posts = allPosts.filter((i) =&gt; i._raw.sourceFilePath.includes(&quot;/index.mdx&quot;));

    return {
        props: {
            posts,
        },
    };
};
</code></pre>
<p>자 이제 시리즈 리스트를 보여주는 <strong>posts</strong>의 가장 메인 페이지이다.</p>
<p>여기는 쉽다. 페이지가 하나이기 때문에 <strong>getStaticPaths</strong>를 이용할 필요없고,
<strong>getStaticProps</strong>는 아까 <strong>[slug].jsx</strong>에서는 <strong>index.mdx</strong>파일만 분리해줬다면, 여기서는 <strong>index.mdx</strong>파일만 불러와 주면된다!</p>
<h1 id="완성">완성!</h1>
<hr>
<p>나는 이를 시도하는 도중에 갖은 버그를 만났다.
아까 언급한 동일한 페이지가 라우팅이 2번 되는 버그라던지, 빌드한 뒤에 페이지가 클라이언트 사이트 에러가 난다던지....</p>
<p>역시 부족한 지식덕분에 두들겨 맞으면서 만드는 중이다.
css도 없고 허접한 코드지만, 함수화 하고 컴포넌트를 분리해서 수정하고, 만들 생각이다.</p>
<p>그래도 시리즈를 나누어 놓으니 너무 뿌듯하고 행복하다!</p>
<p>다음에는 목차와 스크롤을 만들어 보고 싶은데..... 이거도 시도해 보니 쉽지 않다!
완성하면 공유해 보도록 하겠다...</p>
<h1 id="참고-자료">참고 자료</h1>
<hr>
<p><a href="https://miryang.dev/blog/build-blog-with-nextjs">Next.js로 나만의 블로그 만들기 with 정적 생성</a> - miryang.dev</p>
<p><a href="https://bepyan.github.io/blog">bepyan</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 블로그]우당탕탕 배포기]]></title>
            <link>https://velog.io/@ateals_12/Next.js-%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B0%ED%8F%AC%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/Next.js-%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B0%ED%8F%AC%EA%B8%B0</guid>
            <pubDate>Thu, 11 May 2023 08:52:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nextjs-blog-ateals.vercel.app/">내 블로그!</a></p>
<p>후... 우당탕탕 작업을 완료했다..</p>
<p>아직 css도 없고 그냥 텅 빈 글쪼가리 전달하는 웹페이지이지만</p>
<p>또.. 물론 계속 작업하면서 오류가 계속 나겠지만,,,,,,,,</p>
<p>버그를 수정하면서 성장하고 배우자 시작한 next.js이니까 공부하는셈 치자!</p>
<p>계속해서 업데이트한 내용들과 오류난 부분들에 대해서 velog와 내블로그에 공유할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[버그 리포트] 아무튼 클라이언트 어쩌구 버그]]></title>
            <link>https://velog.io/@ateals_12/%EB%B2%84%EA%B7%B8-%EB%A6%AC%ED%8F%AC%ED%8A%B8-%EC%95%84%EB%AC%B4%ED%8A%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%96%B4%EC%A9%8C%EA%B5%AC-%EB%B2%84%EA%B7%B8</link>
            <guid>https://velog.io/@ateals_12/%EB%B2%84%EA%B7%B8-%EB%A6%AC%ED%8F%AC%ED%8A%B8-%EC%95%84%EB%AC%B4%ED%8A%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%96%B4%EC%A9%8C%EA%B5%AC-%EB%B2%84%EA%B7%B8</guid>
            <pubDate>Wed, 10 May 2023 10:37:40 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/a8188c6e-4480-48c3-81a2-1d985791ba62/image.png" alt=""></p>
<p>이건 또 뭔가... 클라이언트 사이드에서 문제가 발생했단다...
일단 황급히 찾아보자</p>
<p>나는 정말 빌드 했을 때 버그나는게 끔찍한거 같다...
(내컴퓨터는 잘돌아가는데 왜! 왜?!!1!!!!!!!!!!!)</p>
<h1 id="해결">해결?</h1>
<hr>
<p>는 해결 못했다... 오류는 서버에서 프리랜더링 한 결과랑 클라이언트 랜더링 결과가 다르다곤 하는데...
제대로 공부안 하고 그냥 무작정 시작한 결과니까 받아들여야지...</p>
<p>그래서 test파일에 이전 커밋으로 복귀한 다음 함수나 컴포넌트 분리 없이 새로만들어서 빌드하니 해결되었다...
(해결이라기 보다는 회피....)</p>
<p>여기에서 조금 씩 커밋하면서 문제점을 찾아보자!</p>
<pre><code>2023 - 05 - 14</code></pre><p>오늘 다시 같은 오류가 나서 봤더니,, 대충 느낌이 왔다.
내가 getStaticProps를 사용한 페이지에 다른 변수를 이용해서 데이터를 랜더링 할때 생기는 문제 같은데...</p>
<p>역시 공부가 부족하다 더욱 공부해서 다음과 같은 실수를 줄이자!</p>
<pre><code>2023 - 05 - 15</code></pre><p>인고의 시간과 검색 끝에 알게 되었다.
나의 경우에는 클라이언트 오류가 json.parse에러였는데, next.js가 자체적으로 코드를 줄이던 중 파일이깨져서 오류가 나는 거였나보다...</p>
<p>next.config.js 에서 swcMinifty를 false로 하니까 눈물나게도 성공이다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[버그 리포트] Next.js getStaticPaths]]></title>
            <link>https://velog.io/@ateals_12/%EB%B2%84%EA%B7%B8-%EB%A6%AC%ED%8F%AC%ED%8A%B8-Next.js-getStaticPaths</link>
            <guid>https://velog.io/@ateals_12/%EB%B2%84%EA%B7%B8-%EB%A6%AC%ED%8F%AC%ED%8A%B8-Next.js-getStaticPaths</guid>
            <pubDate>Wed, 10 May 2023 10:36:01 GMT</pubDate>
            <description><![CDATA[<p>후... 어김 없이 버그가 발생했다.</p>
<pre><code>
[18:38:25.322] error - Conflicting paths returned from getStaticPaths, paths must be unique per page.
[18:38:25.323] See more info here: https://nextjs.org/docs/messages/conflicting-ssg-paths
[18:38:25.323] 
[18:38:25.324] path: &quot;/posts/JS&quot; from page: &quot;/posts/[...slugs]&quot; conflicts with path: &quot;/posts/JS&quot; from page: &quot;/posts/[slug]&quot; 
[18:38:25.324] path: &quot;/posts/life&quot; from page: &quot;/posts/[...slugs]&quot; conflicts with path: &quot;/posts/life&quot; from page: &quot;/posts/[slug]&quot; 
[18:38:25.324] 
[18:38:25.361] Error: Command &quot;next build&quot; exited with 1
[18:38:26.034] Deployment completed
[18:38:25.648] BUILD_UTILS_SPAWN_1: Command &quot;next build&quot; exited with 1</code></pre><p>자 버그 내용을 보자면, 내가 작성한 동적라우팅 두 곳에서 동일한 주소를 호출하는게 문제였다.</p>
<h1 id="해결">해결</h1>
<hr>
<p><a href="https://nextjs.org/docs/messages/conflicting-ssg-paths">https://nextjs.org/docs/messages/conflicting-ssg-paths</a></p>
<p>앞이 까마득 했지만
역시 갓갓 공식문서의 도움으로 </p>
<p>현제 내 폴더의 구조는 
posts
|
| -&gt;[slug]
|-&gt; [...slugs]</p>
<p>요로코롬이라 /posts/a라고 하면
두 폴더 동시에 라우팅?이 되는 문제였다.</p>
<p>그래서 두번째 폴더에서 해당되는 파일을 제거해 줬다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 블로그 개발기!]]></title>
            <link>https://velog.io/@ateals_12/Next.js-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/Next.js-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</guid>
            <pubDate>Mon, 08 May 2023 07:51:17 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs">Next.js</h1>
<hr>
<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATYAAACjCAMAAAA3vsLfAAAAh1BMVEUhISH///8AAAAeHh4aGhq4uLgEBAQpKSkUFBR4eHirq6v39/dOTk7Hx8csLCzy8vIMDAwXFxfs7Oy1tbVCQkKampq+vr4QEBA2NjaLi4vm5uaTk5PV1dXNzc0lJSVfX19/f39paWmioqI9PT0zMzNYWFh6enra2tpISEhlZWWXl5dvb2+GhobEP4i5AAAHeElEQVR4nO2d22KqOhCGmQxFKlVAFBRR29pWW33/59tJ6MFqDsSLJeme73ItjPCbmcwhpEFAEARBEARBEARBEARBEARBEARBEARBEARBEATxfwPneOtb8BDcr299Cx4SPuxpsjmD80N+63vwD1wvbn0LPhIvHsJb34N/VPfH7Nb34B/5a1Hd+h78A8ejKZmoM1n5SCbqDPu4Y7e+B/+o3usxBbquhC/pE60HzrDdkEzUGVbUOZmoK9Eb7CkXdSXfwzK69U14B1ZpU5GJusJWsKX0wBU2A0oPnKneYUSxhyvhNoE1OTZHEHdwTybqSlZASSbqSnaEFP+AiSJ2fAq00Gms+BXgqbIN9TVejxkMBusuMdR6PDAyFtXasWWs8GUCBcO1eah2vD43ncMHAEhe7D9t/AgWNnn1wccylrmzEdQVRiPbWIJZj9eN8CHhd9ghjIrv7bIFrARoDGMxrutrHPwV2ToE7UK2emigmGKAgwnAs1Y34djE/+bH8w/v+E93/m9PPa6QSNkSSKzlaSHbiEUGxAjVRkw7TdFWiNq0l519lBUAh/PBe6yalG1yBFjZzLSVzToeewaYaFZBbsLJg1IMdgdwH1sH7w9CthRLu5l2lA3zRncdWwAs1EU2P2UbjFNIBmYz7ShbkO+51R8V6uT8m1aaIpufso2zg9VMu8oWZCJSuaymYV5DqgtOPJUNI+6T34xm2lm2IOMa7C6uFF5f6wj8lG3AU5kJD3pNgWp32XBQX0Yh2RtAqf1dfJUtiHnkMDI1RbrLJsMz2PxSIZzzBVufi3grW8CG5tXUQbYgmvEo5HSJQcbj2YO+B++vbMi4mc71ZuoiG7Lmt0mKaK4wzGV/ZQtybloj/XRzkS0Ix9xMf/ZJCg8wCQ0Bjp+ytZV9MSX0ZuokW5CJxH/6mRHgOpEZvB6fZQtYDYm2fekmm6yF7D4nGFuZ8nt5hc+yidqbVhlH2YSr/NSKLQFq8ye9lk0+oCovEjjKxnMpEOXvz3DE0oP3W7aAr4A6M3WVTZYk05cQ13zaLS0f9FO24Eu2kOfhq0i55DnLJl3aiEWlKtU6v9Rv2YLoCLBQPmQrm0ufKdymfDCe2OvXmS+8lG3yI5vokajrFFK2fKxF0WcSUUiiKSP9xk/ZTuaKmCLKEpJswaRakmdFxCeqHt1qwr7LJstlKjO1da6GiikVvnAzBXUd/Bf+yxZkpbLX2XauCi2qpxZ5B3TZ8fEHZBONc4WZtkuCHsVDV0/tRPwf+DaOqJBftkrcA5BwzCO2mbVRL/BTtrPiRFQoSkhXxG0jgCaOGxG3Wbqwf0I2XNeXWxLcs4QFX3mnudg7aUnk/4hssj527pCuyknfos8a0sasiZ+yXbzFI5bAs+21zhWQGqBoKyA8ekvMbyH8EdnEQ5+ZqWu9jQtRt0sNZhPbR/+IbEE+PS1qCxyru3w1/n6pKt/bohA/ZVNFqjxw+LWauvUSBnAaxESLExFVmGRDJjpesYgN+7MvVSdbwHbQnE4QJ9lEf++06ypqSKb3IA2y4bpcVAE7rJJJMe/N3i2tbOGWz5cTM3WRTawo6emSgpios9avDxhkG8AdqxZQlyOY9OboLSmbsu/Llr/CewfZ2r2Bv1SQeZb+JQ6zbAVjab1m7ACqMstN0MsmKuSr+Pvn7S6bTKrOA1zR9E+15UqrbHxdr0J8mPZ/tsl3o37cemfZMBspyuCYcXe30rk3q2wrGB22LOuLaibZRIU8+V7/uu9vE8vm5a4IuV1wphnAJhtueWabNE+9iVGkbNptZ6MfM+0qm+zvPSq8v8hR4V3jDyyyBWG1+Wh6FNsZZQvn6beZdt27O9jpCpOiUa85MEUnWxbxAETMtizOM74kmN55+KcYZROb+ZJ9a28dZRPbMmv1LhkUS4VGUYVsOWI8LFg8gCHbN48M82pi7Rz+K8yyiRmyah1xN9nEpkntLpnqXWO/Ktmy4W7Lv/04L+C+ypLkMJ4P+/NijJRNH4eKGdK21DvJlk8Nfr8Ng5WlXoVsrObybydt5yt/SGVXojev/Vlkk3GqfFBbL4Hx1BGjxrjjHLNG3aJXyIbz15yvBPfLjbg+j56Wx2l/klKbbCJOlY7Y/qoaF/VZbvswfJso9SqONlL5NhTjYJx9lVGqqkdHYIjNWalJNsRammn8Zpft8LnJSI8s9V6eGCsW2be+BBddwG1xNzTesAjEuJmGr+WdkXLBhmVpO4In+7i7G1449upYlq89mkx2MGKW5SlaSn8UGh2b9G0ZY9YT2fg1iu+LGfNKtQ5gNYKPvgRMHiFKGnRiqTvcvfUmPvcJ7t5s+0gJBWxEJ4ddAWKyU+/pJUxw97bsSx7tE2z2/TIQ4QBrdjGZqTMYJB90eKk78YaOyr0GNtvRqa9XwBrbtkhCAY7rVzJTd6qnxvTuNqEheiYzvYa83JCZuhNOSzpq+Aqqw4yC3iuoFmSmV4DBbHDre/AR3D7SXwy6gnzjV0+zL4TvfT6Bub8gTbdrwPH81rfgJUiLKUEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEH4xn/0eG7Afh8+KAAAAABJRU5ErkJggg==" alt="nextjs"></p>
<blockquote>
<p>The React Framework for the Web
일부 세계 최대 기업에서 사용하는 Next.js를 사용하면 최신 React 기능을 확장하고 가장 빠른 빌드를 위한 강력한 Rust 기반 JavaScript 도구를 통합하여 전체 스택 웹 애플리케이션을 만들 수 있습니다.</p>
</blockquote>
<p>Next JS는 프론트와 백엔드를 쉽게 설계해주는(?) 웹 프레임워크 입니다.</p>
<p>사실 저도 next.js 잘모르고... 심지어 react에 발을 들인지도 얼마 되지 않았습니다.</p>
<p>그냥 막연하게 최신기술을 사용해서 나만의 프로젝트를 만들고 싶었는데,
그런 저의 향수를 자극한 것이 next.js와 블로그 만들기 였습니다.</p>
<p>여러가지 아티클도 많이 존재하고, 차근차근 따라쳐보고 배우면서 익숙해질 생각입니다!</p>
<p>어느정도 블로그의 틀이 잡히면, velog뿐 아니라 저만의 블로그에도 글을 올리겠습니다!</p>
<h1 id="👋">👋</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[노마드 코더 강의 수료]]></title>
            <link>https://velog.io/@ateals_12/%EB%85%B8%EB%A7%88%EB%93%9C-%EC%BD%94%EB%8D%94-%EA%B0%95%EC%9D%98-%EC%88%98%EB%A3%8C</link>
            <guid>https://velog.io/@ateals_12/%EB%85%B8%EB%A7%88%EB%93%9C-%EC%BD%94%EB%8D%94-%EA%B0%95%EC%9D%98-%EC%88%98%EB%A3%8C</guid>
            <pubDate>Wed, 22 Mar 2023 06:39:27 GMT</pubDate>
            <description><![CDATA[<h2 id="노마드코더-코코아톡-클론코딩-크롬앱-클론코딩을-마쳤습니다">노마드코더 코코아톡 클론코딩, 크롬앱 클론코딩을 마쳤습니다.</h2>
<p><img src="https://velog.velcdn.com/images/ateals_12/post/8c6f923c-a060-42bb-9c64-bab4c65a5714/image.png" alt=""></p>
<p>너무 재밌었습니다!</p>
<h3 id="코코아톡-클론코딩">코코아톡 클론코딩</h3>
<p>처음에 html, css 기초강의를 들을땐,
&quot;이미 알고 있는 내용을 내가 유료로 들을 필요가 있을까?&quot;라는 생각을 했습니다.</p>
<p>하지만 오산이였습니다.
니코쌤이 작성하는 코드를 보면서,
어떻게 코드를 관리하고 classname을 관리하는지 알 수 있었고,
지금까지 제가 얼마나 스파게티 코드를 짜는지 실감했습니다.</p>
<p>결과물을 완성하고 니코쌤이 주시는 숙제들을 하나씩 해가면서,
기본적인 css 레이아웃과 html, css파일을 어떻게 분리해서 작성하는지 배울 수 있는 좋은 시간이었습니다.</p>
<h2 id="바닐라-js로-크롬-앱-만들기">바닐라 JS로 크롬 앱 만들기</h2>
<p>일단 저에겐 꽤 쉬웠습니다.
워낙 Javascript를 많이 접하기도 했고, 기본적인 지식이 어느정도 있는 상태여서 이론은 어렵지 않았습니다.</p>
<p>이 강의에서는 니코쌤이 html, css, js를 어떤 식으로 동작시키는지, 
querySelector를 사용해서 class를 이용해 css타입을 바꾸는 방식이나.
Date.now()를 이용해 li에 id를 주는 방식을 알게 되었습니다.</p>
<h2 id="끝으로">끝으로</h2>
<p>역시 안다고 생각하는 게 가장 위험 한 것 같습니다.
다시 볼수록 새로운 걸 얻어가는 유익한 시간이었고,
다음 강의가 너무 기대됩니다.
저는 그럼 다음강의 들으러 이만.... 🤣</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이 사자처럼 11기]]></title>
            <link>https://velog.io/@ateals_12/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-11%EA%B8%B0</link>
            <guid>https://velog.io/@ateals_12/%EB%A9%8B%EC%9F%81%EC%9D%B4-%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-11%EA%B8%B0</guid>
            <pubDate>Tue, 21 Mar 2023 18:51:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ateals_12/post/ee10ad1a-030c-4a09-a756-864900532e45/image.png" alt=""></p>
<h2 id="멋쟁이-사자처럼-11기">멋쟁이 사자처럼 11기</h2>
<p>멋쟁이 사자처럼 11기에 붙었습니다.</p>
<p>휴학생이라 학교 동아리 참여에 고민이 많았는데,
기왕 학교쉬면서 프론트엔드 공부에 전념하는 김에 테크관련 연합동아리 중에서
웹에 가장 중심을 둔 멋쟁이 사자처럼에 지원하게 되었습니다.</p>
<p>아기사자가 된 김에 열심히 세션도 듣고,
오프라인 행사도 참여하고,
빨리 중앙 해커톤도 하고 싶네요!</p>
]]></description>
        </item>
    </channel>
</rss>