<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jaehyun_ground.log</title>
        <link>https://velog.io/</link>
        <description>미래 프론트 어쩌고</description>
        <lastBuildDate>Fri, 16 Jan 2026 13:01:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jaehyun_ground.log</title>
            <url>https://velog.velcdn.com/images/jaehyun_ground/profile/e48b4889-4d9f-46b4-a208-ed1e00fd1aee/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jaehyun_ground.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jaehyun_ground" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 나의 항해는 안녕했다! (2)]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EB%82%98%EC%9D%98-%ED%95%AD%ED%95%B4%EB%8A%94-%EC%95%88%EB%85%95%ED%96%88%EB%8B%A4-2</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EB%82%98%EC%9D%98-%ED%95%AD%ED%95%B4%EB%8A%94-%EC%95%88%EB%85%95%ED%96%88%EB%8B%A4-2</guid>
            <pubDate>Fri, 16 Jan 2026 13:01:40 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EB%82%98%EC%9D%98-%ED%95%AD%ED%95%B4%EB%8A%94-%EC%95%88%EB%85%95%ED%96%88%EB%8B%A4-1">신청 전 ~ 사전 스터디까지</a> 글에 이어 본과정 회고글 작성해보겠습니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/dec2636d-d6f3-4e11-9941-837d990cf4b3/image.jpeg" alt=""></p>
<p>사전 스터디를 잘 마무리하고 개강 첫 날.... 난 아직도 그 날의 우리팀 분위기를 잊을 수 없다... </p>
<p>이전 교육 과정이나 팀 활동을 할 때마다 팀운이 좋지 않아 힘들었던 나는 이번 항해 만큼은 좋은 팀원분들과 함께 하고 싶다는 열망이 컸다.</p>
<p>1팀을 처음 만나고 든 생각은 <code>아 또 망하겠는데</code> 였다. (지금은 너무 좋다 !!) 
사람이 6명 있는 방이 그렇게 조용할 줄은 몰랐고, 심지어 우리팀 학습 메이트님도 부재중이었는데... 태영님이 없었다면 큰일날 뻔했다. 또 사전 스터디를 함께 했던 분이 계셔서 다행이었다.</p>
<p>어쩌다보니 또 팀장으로 지목 당했다. 자신이 너무 없었지만 이왕 하게 된 거 제일 잘하고 싶었다. 항해를 들어오면서 했던 다짐 중 하나가 <code>항해 기간을 되돌아 봤을 때 후회하지 않도록 열심히 하자</code> 이었기 때문에....
많은 분들이 잘 했다며 칭찬 해주시는 것을 보면 나쁘지 않게 했나보다.</p>
<p>생각해보면 항해를 하면서 자신 없던 부분을 디벨롭할 수 있었던 것 같다. 팀장, 발표, 공개 페어 코딩 등 두려워하고 있던 영역에 많이 도전해보며 경험도 쌓고 한 단계 성장 할 수 있었다.</p>
<p>특히 공개 페어 코딩은 정말 하길 잘했다고 느낀다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/c0bb1372-d45a-4c15-a914-d77e1b39867d/image.png" alt=""></p>
<p>Q&amp;A 질문 중 페어 코딩을 해달라는 질문이 있었는데, 사실 그 질문은 학습 메이트분께서 하셨다고 한다. </p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/4b3f8341-d73a-4fc7-9d09-94a4bfa71bc8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/44ebe9fd-60e9-4c75-8750-f77497b512e2/image.png" alt=""></p>
<p>코치님께서 질문 주신 분을 찾는 순간 나보고 해보라는 메세지가 왔고, 처음에는 나의 형편 없는 코드를 다른 분들에게 보여주는 것이 너무 부끄러워 하기 싫다고 얘기를 하려다가 빅테크 기업에서 활동하고 있는 프론트엔드 개발자에게 피드백 받아볼 수 있는 기회가 흔치 않을 것이라는 생각 + 더 이상은 두려움 때문에 피하고 싶지 않아서 도전했다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/d89be877-9c2f-4c28-8fa3-1e65a6cb8a38/image.png" alt=""></p>
<p>정말 잘한 선택이었고, 해보지 않은 것에 도전하는 걸 꺼려했던 나는 이 경험을 기점으로 두려움을 피하지 않는 쪽으로 조금은 바뀔 수 있었다.</p>
<p>항해를 하면서 좋았던 또 다른 부분은 좋은 개발자 동료, 좋은 사람들을 많이 알아갈 수 있었다는 점이다. 항해를 하기 이전까지는 주변에 아는 개발자 동료가 없어 개발 이야기를 나눌 사람이 없었는데, 항해에서는 코드를 치다가 궁금한 부분이 있거나 막히는 부분이 있다면 바로바로 이야기를 나눌 수 있는 동료들이 많이 있었다.
초반에는 &quot;너무 기본적인, 쉬운 내용을 여쭤보는 것이 아닐까?&quot;, &quot;나만 모르는 것은 아닐까?&quot; 하며 물어보는 것을 주저했었다. 하지만 주변에서 재현님이 모르면 다 모른다며, 배우러 온 곳인데 질문 하는 것이 뭐가 무섭냐며 겁먹지 말고 질문을 해보라는 이야기를 많이 해주셨다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/13f29599-e5eb-49e2-b755-2a4ee446eca8/image.png" alt=""></p>
<p>그렇게 며칠을 고민했던 부분을 용기내어 질문을 올렸고, 늦은 시간까지 함께 고민해주고 좋은 자료도 공유 받을 수 있었다. 처음이 무섭지 한 번 해보고 난 뒤 부터는 겁 없이 질문 할 수 있었다.</p>
<p><strong>같이 항해를 했던 분들이 나를 정말 많이 바꿔주었다.</strong> 정말 항해하길 잘했다 :)</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/7f292cba-c64b-4227-9e48-0957e8de3be1/image.png" alt=""></p>
<p>항해를 하면서 힘들고 탈주를 하고 싶었던 적도 있었다. 내가 고민하지 못했던, 생각하지 못했던 부분들을 다른 분들은 당연하게 고민하고 생각하고 계셨다. 나만 뒤쳐지도 있다는 생각에 “아 왜 나는 이런 생각을 하지 못했지?” 라고 하며 자기 자신을 갉아먹고 있었고, 잠을 줄여가며 과제를 하기도 했다. 어느 순간부터는 학습이 아닌 pass를 위한 과제를 하고 있는 나를 발견할 수 있었다. 내가 항해를 하며 절대 패스에 집착하지 말자며 다짐하고 들어왔음에도 불구하고 말이다.
그렇게 3주차 쯤, 항해를 <strong>탈주</strong>하고 싶다는 생각이 강하게 들었다. 자존감은 너무나도 떨어져 있었고 남은 기간 동안 얼마나 더 암울해질지 가늠할 수가 없었기 때문이다.</p>
<p>나는 우리 팀 학습메이트님이 없었다면 항해를 탈주했을 것이다. 학습메이트님과 페이스 체크를 할 수 있는 기회가 생겼고 다른 분들과 비교하지 말고 배워보는 건 어떠냐는 말씀이 나에겐 엄청 큰 도움이 됐다.</p>
<p>항상 부정적으로 남들과 비교하던 내가 “이렇게도 생각할 수 있구나? 신기하다!” 와 같이 긍정적으로 생각할 수 있는 사람으로 조금은 바뀔 수 있었던 계기가 되었다. 부정적인 생각을 걷어내고자 노력하며 뭐가 됐든, 되든 안 되든 내가 할 수 있는 최선을 다해서 끝까지 해보자는 마음을 가지고 한주한주를 보내고자 했다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/4083211a-00d2-4011-901e-233cd5777c28/image.png" alt=""></p>
<p>누군가 나에게 항해를 하며 얻은 가장 큰 수확이 뭐냐고 물어본다면 나는 주저하지 않고 <strong>좋은 사람을 많이 알아가는 것</strong>이라고 말할 수 있다. 물론 FE 개발자로서 갖춰야 할 지식 키워드, 코치님과의 멘토링도 너무 좋았고 도움이 많이 됐지만 항해는 개발자 지인이 없던 나에게 좋은 개발자 동료를 만들어 주었고, 개발자를 떠나서 너무 좋은 사람들을 만날 수 있게 해주었다.</p>
<p>나에게 항해란 두려움을 극복하게 해주었고, 단점을 보완할 수 있게 해주었고, FE 개발자로서 어떤 지식을 쌓으며 나아가야 할 지 알려주었으며 좋은 사람들을 만나게 해주었다. 25년 제일 잘한 일이 아닐까? ㅎㅎ</p>
<p>나를 포함하여 함께한 항해 플러스 7기 분들이 앞으로도 좋은 일만 생기고 행운이 가득한 여정을 이어갔으면 좋겠다.</p>
<p>정말 고생 많으셨습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 나의 항해는 안녕했다! (1)]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EB%82%98%EC%9D%98-%ED%95%AD%ED%95%B4%EB%8A%94-%EC%95%88%EB%85%95%ED%96%88%EB%8B%A4-1</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EB%82%98%EC%9D%98-%ED%95%AD%ED%95%B4%EB%8A%94-%EC%95%88%EB%85%95%ED%96%88%EB%8B%A4-1</guid>
            <pubDate>Sat, 03 Jan 2026 10:18:38 GMT</pubDate>
            <description><![CDATA[<p>적다 보니 분량이 길어져 회고글은 시리즈로 나누어 작성할 예정입니다.<br>이번 글에서는 <strong>항해 플러스 신청 당시부터 사전 스터디까지</strong>의 이야기를 담았습니다.</p>
<hr>
<h2 id="이-제목을-정한-이유">이 제목을 정한 이유</h2>
<p>이 제목은 최종 발표 때 사용했던 제목이기도 합니다. 발표 제목은 <strong>「여러분의 항해는 안녕하신가요?」</strong>였습니다.</p>
<p>사실 이 문장은 과거 다른 교육 과정에서 본 글에서 인용한 것입니다. 프로그래머스 데브코스에 참여하던 시절, 다른 트랙의 한 분이 “여러분의 데브코스는 안녕하신가요?”라는 제목으로 상당히 강도 높은 비판 글을 남기고 떠났던 적이 있었습니다.</p>
<p>그 글이 워낙 인상 깊게 남아 있었고, 저는 반대로 <strong>항해에 대한 긍정적인 경험을 담아보고 싶어</strong> 이 제목을 선택하게 되었습니다.</p>
<hr>
<h2 id="항해-플러스를-신청하게-된-이유">항해 플러스를 신청하게 된 이유</h2>
<p>길고도 짧았던 10주 동안, 저는 <strong>항해 플러스</strong>라는 교육 과정에 참여했습니다.</p>
<p>항해에 신청한 이유는 단순했습니다. 회사에서 일이 많지 않았고, 입사 후 6~7개월 동안 스스로 <strong>성장하고 있다는 느낌을 받지 못했기 때문</strong>입니다.</p>
<p>정체된다는 감각은 곧 불안으로 이어졌고, 사이드 프로젝트나 개발 동아리를 찾아보기도 했습니다. 그러던 중 항해 플러스 후기 글을 접하게 되었습니다. 처음 든 생각은 이거였습니다.</p>
<blockquote>
<p>아, 이거 너무 빡세 보이는데?</p>
</blockquote>
<p>하지만 동시에, 묘하게 재밌어 보였습니다. 실력에 대한 자신감은 없고, 불안해하면서도 정작 열심히 하고 있지는 않았던 제게 이 과정이 <strong>하나의 전환점이 될 수 있겠다는 생각</strong>이 들었습니다. 왜인지는 모르겠지만 “이건 무조건 해야 한다”는 생각이 강하게 들었습니다.</p>
<p>결제 과정은 꽤나 우여곡절이 많았습니다. 항해 플러스 결제를 위해 신용카드를 처음 만들었고, 12개월 할부면 한도에서 매달 조금씩 빠져나가는 줄 알았다가 전액이 한 번에 한도에 잡힌다는 사실을 뒤늦게 알게 되기도 했습니다. 결국 한도가 더 높은 카드를 하나 더 만들게 되었고, 두 번째 카드 배송을 기다리던 중 스파르타 측에서 “신청서는 작성하셨는데 아직 결제가 안 되어 있어서요. 혹시 특별한 사유가 있으실까요?” 라는 전화를 받기도 했습니다. 지금 생각해보면 한 명의 수강생이라도 놓치지 않으려는 영업적인 이유였을지도 모르겠습니다. 하지만 당시의 저는 “와, 여기는 진짜 한 명 한 명을 신경 써주는구나”라며 괜히 감동을 받았던 기억이 납니다.</p>
<p>그렇게 저는 항해 플러스 7기에 합류하게 되었습니다.</p>
<hr>
<h2 id="사전-스터디-그리고-팀장이-되기까지">사전 스터디, 그리고 팀장이 되기까지</h2>
<p>정규 과정이 시작되기 전, 저는 <strong>사전 스터디부터 참여</strong>했습니다. 보통 스터디를 시작하면 팀장을 정하게 됩니다. 사전 스터디 채널이 생성되고 인삿말을 남기는 분위기였는데, 이상하게도 먼저 말을 꺼내면 팀장을 맡게 될 것만 같은 기분이 들었습니다. 그래서 일부러 인사를 미루고 있었습니다. 이유는 단순했습니다. <strong>팀장을 하고 싶지 않았기 때문입니다.</strong> 지금은 팀장을 잘 해줬다는 말을 종종 듣지만, 항해 이전의 저는 팀장이라는 역할에 대해 속이 메스꺼워질 정도의 거부감이 있었습니다.</p>
<h3 id="팀장을-피하게-된-이유">팀장을 피하게 된 이유</h3>
<p>대학교 재학 당시, 저는 <strong>멋쟁이사자처럼</strong>이라는 개발 동아리를 운영한 경험이 있습니다. 학교에 멋사가 처음 생겼던 1기에는 운영진으로 참여했고, 다음 기수에는 학교 대표를 맡게 되었습니다. 운영진으로 활동할 때는 비교적 수월했습니다. 주변에서 잘 도와주었고, 큰 부담을 느끼지 않았습니다.</p>
<p>문제는 대표가 된 이후였습니다. 신규 동아리라는 특성상 동아리의 모든 기반을 새로 만들어야 했고, 다음 기수부터는 조금 더 안정적으로 운영되길 바라는 마음에 운영진을 대폭 늘리는 결정을 내렸습니다. 제가 생각한 운영진 규모는 10명이었고, 공교롭게도 지원자 수 역시 정확히 10명이었습니다. 별도의 검증 과정 없이 “운영진 하시죠”라는 말로 모두 함께 시작하게 되었습니다. 시작부터 문제였습니다. 회의에 참석하지 않는 운영진이 있었고, 회의 자리에서는 대부분의 이야기를 제가 혼자 하고 있었습니다. 대답조차 돌아오지 않는 상황도 많았습니다. 결국 저는 이 사람들과는 제가 그리고 있던 방향의 활동을 함께할 수 없다고 판단했고, 운영과 기획의 대부분을 혼자 결정하고 진행하게 되었습니다. 대표라는 자리는 점점 버거워졌고, 동아리 이야기가 나오는 것 자체가 괴로워졌습니다. 
제일 힘들었던 순간은 중앙 해커톤이었습니다. 운영진 두 명이 사전 연락 없이 행사에 불참했고, 하필 그 두 사람은 같은 팀이었습니다. 그 팀의 부원들은 코드 한 줄 제대로 작성해보지 못한 채 밤을 새워야 했습니다.연락은 닿지 않았고, 제가 할 수 있는 일은 아무것도 없었습니다. 저는 저의 리더십에 굉장한 의구심을 품게 되었고, 나의 1년은 실패했나? 라는 의문에 나의 1년은 큰 실패였다고 확신해버린 순간이었습니다. 그 이후로 저는 팀장, 대표, 회장 같은 역할을 의식적으로 피하게 되었습니다. 또 실패할까 두려웠고, 다시 그런 감정을 겪고 싶지 않았기 때문입니다.</p>
<blockquote>
<p>나의 멋쟁이사자처럼은 안녕하지 않았다. 매우.</p>
</blockquote>
<p>다시 회고글로 돌아와보면, 이런 이유로 저는 사전 스터디 채널에서도 최대한 조용히 있으려고 했습니다. 킥오프 일정 이야기를 꺼내는 순간 팀장이 될 것 같다는 생각이 들었기 때문입니다. 하지만 이상하게도 아무도 킥오프 일정에 대해 이야기를 하지 않았습니다. 다음 주부터 스터디를 진행하려면 일정을 빨리 정해야 하는 상황이었는데도 말입니다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/ba75c76a-0d54-4632-a301-a07e2841d0d3/image.png" alt=""></p>
<p>결국 조바심이 나서 제가 먼저 킥오프 일정을 제안하게 되었습니다. 그리고 킥오프 당일, 예상했던 일이 현실이 되었습니다. “오늘 팀장을 정해야 하는데, 혹시 하고 싶으신 분 계신가요?” 라고 여쭤봤을 때</p>
<blockquote>
<p>“재현님이 하시면 되겠는데요…”</p>
</blockquote>
<p>그렇게 저는 팀장이 되었습니다.</p>
<p>미팅이 끝난 뒤, 불안함과 막막함이 한꺼번에 몰려왔습니다. 팀원들에 대한 불안이 아니라, <strong>내가 잘 해낼 수 있을까</strong>에 대한 두려움이었습니다. 하지만 이전의 경험과는 달리 사전 스터디는 굉장히 원활하게 진행되었습니다. 함께하는 분들이 모두 좋으신 분들이었고, 해야 할 일도 많지 않았습니다.</p>
<hr>
<h2 id="아쉬움으로-남은-한-가지">아쉬움으로 남은 한 가지</h2>
<p>한 가지 아쉬운 점이 있다면, 사전 스터디 기간 동안 팀원분들과 조금 더 일찍 이야기를 나누지 못했다는 점입니다.</p>
<p>스터디 진행 방식은 단순했습니다. 매주 랜덤으로 발표자를 정해 발표를 진행하고, 발표가 끝나면 바로 마무리했습니다.
저는 사실 대화를 좋아하는 편이라 스터디가 끝난 뒤에도 이런저런 이야기를 나누고 싶었지만, 시간이 늦기도 했고 직장을 다니는 분들께 부담이 될까 봐 항상 “수고하셨습니다”라는 말과 함께 바로 종료했습니다.</p>
<p>마지막 스터디 날, 그대로 끝내기엔 너무 아쉽다는 생각이 들었습니다. 이대로 친해지지 못하고 끝내버리기엔 우연한 이 인연이 소중했기에 용기를 내어 이야기를 꺼냈고, 놀랍게도 처음 대화를 나눈 게 맞나 싶을 정도로 대화는 자연스럽게 이어졌습니다. 1~2시간 가까이 이야기를 나눴던 것 같습니다. 중간에 팀원분들이 내가 대화하고 싶은게 느껴졌다는 이야기도 들었습니다...ㅎㅎ
애니 이야기를 많이 했는데, 그때 당시 귀멸의 칼날에 빠져있던 저는 귀멸의 칼날 이야기를 엄청 했던 기억이 납니다. 저는 애니를 많이 보지는 않았어서 애니 초보 취급을 받기도 했습니다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/9ee24541-520a-4d06-8bce-022e2ef3bbf5/image.jpeg" alt=""></p>
<p>그렇게 애니 고수분들의 추천작을 급하게 따라 적기도 했습니다. 저기서 아직 체인소맨 밖에 보지를 못했는데, 진격의 거인을 다 보고 나서 하나하나씩 봐야겠습니다.</p>
<hr>
<h2 id="마무리하며">마무리하며</h2>
<p>돌아보면 항해 플러스 시작부터 도전의 연속이었습니다. 제가 피하고 있던 감정과 역할을 다시 마주하게 만든 도전이었습니다. 이후 글에서 항해 플러스 정규 과정의 이야기를 정리해보려 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 7주차 - 디자인 패턴과 함수형 프로그래밍 그리고 상태 관리 설계]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-7%EC%A3%BC%EC%B0%A8-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EA%B3%BC-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-7%EC%A3%BC%EC%B0%A8-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EA%B3%BC-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sun, 07 Dec 2025 15:25:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/2b6b4e29-bf21-4a52-8142-1bf59069e375/image.png" alt=""></p>
<p>테오가 노리신 걸까요...??!? 👀</p>
<h1 id="들어가면서">들어가면서</h1>
<p>이번 주차에서는 <strong>디자인 패턴과 함수형 프로그래밍 그리고 상태 관리 설계</strong> 라는 주제를 가지고 학습을 진행했습니다. </p>
<p>이번 과제를 통해, <strong>응집된 코드를 역할과 관심사에 따라 분리하는 작업의 중요성</strong>을 체감했습니다. 약 1,100줄 규모의 서비스 코드를 리팩토링하며 계산 로직과 액션 로직을 분리하고, 도메인별 커스텀 훅을 구성해 로직을 모듈화했습니다. 또한 전역 상태 관리를 도입하여 props drilling 문제를 해소하는 과정에서, 전역 상태 관리가 필요한 이유에 대해 다시 고민할 기회를 얻었습니다.</p>
<p>처음 1,100줄 가량의 App.tsx 파일을 마주했을 때 가장 먼저 든 생각은 ‘파악하기 어렵다’는 점이었습니다. state, 함수, UI 로직이 한 파일에 모두 응집돼 있어 각 함수의 역할이나 UI가 어떤 state를 참조하는지 빠르게 이해하기 어려웠습니다. 이에 우선 <code>Header</code>, <code>Notification</code>, <code>Page</code>의 세 영역으로 코드를 분리한 뒤, Page 단에서 하위 컴포넌트를 세분화하는 방식으로 구조를 재정리했습니다.</p>
<p>프로젝트의 핵심 도메인을 <code>cart</code>, <code>product</code>, <code>coupon</code>, <code>notification</code> 네 가지로 정의하고, 각 도메인별 로직을 최대한 분리하는 방향으로 리팩토링을 진행했습니다. 도메인별 state와 액션 함수는 커스텀 훅으로 분리하고, 계산 로직은 유틸 함수로 이동시켜 함수 목적을 명확히 구분하고자 했습니다.</p>
<p>이러한 분리 작업을 통해 도메인별 비즈니스 로직과 UI 로직의 구조가 훨씬 명확해졌습니다. 특정 엔티티의 기능을 수정할 때 해당 영역의 파일만 확인하면 되기 때문에 유지보수 효율성 역시 크게 개선될 수 있음을 체감했습니다.</p>
<p>아울러 <strong>전역 상태 관리의 도입 이유에 대해서도 생각</strong>해볼 수 있었습니다. 과제를 할 때만 해도 전역 상태 관리를 props drilling 해소를 위한 수단으로만 인지하고 있었지만, 다른 분들의 PR을 보면서 핵심 목적이 <code>계층 분리</code>에 있다는 관점을 접하게 되었습니다. UI 컴포넌트가 도메인 props에 직접 의존하지 않고 전역 store에서 필요한 상태를 조회함으로써 UI와 도메인이 분리된다는 점이 인상적이었습니다. 또한 서버 상태와 클라이언트 상태를 구분해 관리하기 위해 전역 상태 관리를 도입한다는 의견도 살펴보며, 전역 상태 관리의 필요성에 대해 다양한 관점에서 고민할 수 있었습니다.</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>좋은 말 듣기</strong></p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/089a42a9-aa41-44ac-9862-c8b0825bfe16/image.png" alt=""></p>
<p>준일님께 좋은 말을 들었어요 !!!!!!!! 저는 항상 제 실력에 대해 의문을 가지고 있었고 불안함이 컸으며 다른 분들과 비교하며 &quot;나는 왜 이렇게 못할까?&quot;하는 생각을 정말 많이 했는데, 준일님께 저런 좋은 말을 들으니 &quot;내가 잘 하고 있는건가?&quot; 하는 생각을 조금은 할 수 있었습니다. 항상 주눅 들어 있던 저에게 조금은 희망찬 이야기를 들을 수 있어서 좋았어요 ㅎㅎ </p>
<p>다음에도 다른 분들께 좋은 피드백을 들을 수 있도록 열씸히 해야겠어요 !! 🔥</p>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>지치지 않기</strong></p>
<p>7주간 항해를 하면서 익숙해진 것인지 지친 것인지 잘은 모르겠지만, 이번주에 특히 무기력해지는 순간들이 많았습니다. 쉬는 시간을 주기적으로 가져가며 페이스를 잃지 않도록 해야겠어요</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>지식 공유회 발표 해보기?</strong></p>
<p>항해를 통해 스스로 부족하다고 느꼈던 부분을 보완하고자 했습니다.
지식 쌓기, 글 작성하기(문서, 회고 등등), 네트워킹 등 많은 부분을 달성했지만, 남은 목표 중 하나인 <strong>발표</strong>를 한 번 도전해볼까? 하는 생각이 듭니다. 
<code>내 발표가 다른 분들께 도움이 될까?</code>, <code>준비가 미흡하면 어떡하지?</code> 등과 같은 이유로 발표하는 것을 꺼려했는데.... 코치님들과 많은 분들이 강조해온 <code>회고</code>를 해볼 겸, <strong>“여러분들의 항해는 안녕하십니까?”</strong>라는 주제로 한 번 시도해볼까? 싶은 생각이 듭니다..!</p>
<p>7기 분들 모두 10주간의 항해 과정을 돌아보며, 스스로 만족했던 부분과 아쉬웠던 부분을 정리해보는 시간을 가져볼 수 있도록 하고 싶은 마음이 있답니다..ㅎㅎ<br>따라서 좋은 방향이든 나쁜 방향이든 <strong>“이번 항해가 정말 뜻깊었다”</strong>라는 생각으로 7기 활동을 마무리할 수 있도록 하는 발표를 조심스럽게 구상해보고 있어요....!!</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p>이번주는 항해분들과 함께했던 시간이 많았던 것 같습니다. 사전 스터디 팀원분들과 인생 곱창, 인생 슈붕을 배우며 모각코도 했고, 1팀과도 모각코를 했으며 금요일에는 1팀, 2팀분들과 맛있는 저녁도 먹고 밤샘 모각코도 했습니다...ㅋㅋㅋㅋ 적다보니 지칠만 했던 것 같네요😂 이정도면 모각코를 안 하면 죽는 병에 걸린 게 아닐까요? 
하지만 혼자 할 때보다 다른 분들과 모각코를 할 때 집중도 더 잘되고, 개발 이야기도 많이 할 수 있어서 유익한 시간이었다는 것은 분명해요 ㅎ.ㅎ </p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/e7c298bf-a672-47ab-916b-b498a2442652/image.png" alt=""></p>
<p>7팀에서 모모모(모르는 것을 모르는 사람들의 모임)라는 좋은 활동을 기획, 공유 해주셔서 과제에 대한 고민, 막히는 부분들 해결하는데 많은 도움이 되었습니다. 7팀 감사합니다 !!🔥</p>
<p>이제 항해도 3주밖에 남지 않았는데, 남은 3주는 정말 천천히 아주 천천히 지나갔으면 좋겠습니다.</p>
<p>항상 열심히 해왔던 것 처럼 이번주도 후회하지 않도록 빠이팅 나 자신🔥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 6주차 - UI 컴포넌트 모듈화와 디자인 시스템]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-6%EC%A3%BC%EC%B0%A8-UI-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%AA%A8%EB%93%88%ED%99%94%EC%99%80-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-6%EC%A3%BC%EC%B0%A8-UI-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%AA%A8%EB%93%88%ED%99%94%EC%99%80-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Sun, 30 Nov 2025 16:16:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/3849f610-5406-4f6f-89b5-7ceb08d48ff0/image.gif" alt=""></p>
<p>월요일이라 신난 저희의 모습입니다. 하핫 (저희의 모습을 남겨준 오스카이 감사합니다)
목요일에 밤샐 거라는 사실도 모른 채 해맑게 뛰어다니는 모습이 귀엽지 않나요?</p>
<p>이제 과제하는 것이 익숙해졌는지, 월요일이 오히려 즐거워졌습니다. 이거… 미쳐가는 걸까요?</p>
<h1 id="들어가면서">들어가면서</h1>
<p>6주차에는 <code>UI 컴포넌트 모듈화와 디자인 시스템</code>이라는 주제로 과제를 진행했습니다.</p>
<p>평소 관심이 있었던 분야인 <strong>디자인 시스템</strong>에 대해서 다뤄볼 수 있었고 키우고 싶었던 능력인 <strong>컴포넌트 분리</strong>에 대해서 고민해볼 수 있었습니다. 1주일이라는 시간이 너무나도 짧게 느껴졌던 만큼 나중에 과제를 다시 진행해보며 더 깊은 고민을 해보고 싶습니다 :)</p>
<h2 id="컴포넌트-분리에-대해">컴포넌트 분리에 대해</h2>
<pre><code class="language-typescript">
&lt;button onClick={onClose} className=&quot;alert-close&quot;&gt;
    ×
&lt;/button&gt;
</code></pre>
<p>버튼에 <code>alert-close</code> 클래스가 특정 위치에서만 사용되는 상황이라, 이를 공통 버튼 컴포넌트의 variant로 분리할지, 아니면 <code>AlertCloseButton</code>과 같은 전용 컴포넌트로 분리할지 고민이 되었습니다.</p>
<img width="96" height="75" alt="image" src="https://github.com/user-attachments/assets/9831973d-fee3-4a39-95ae-2727441ebdda" />

<p>모각코 참여자들과 코치님의 피드백을 들으면서, 이 고민에 정답은 없지만 제 나름의 컴포넌트 분리 기준을 세워볼 수 있었습니다.</p>
<p>저는 공통 버튼 컴포넌트를 기본으로 두고, 추가 기능이나 별도 디자인이 필요한 경우에는 새로운 컴포넌트를 생성해 공통 버튼을 래핑하는 방식을 선택했습니다. 공통 컴포넌트 내부에 ‘닫기’와 같은 특수 기능 옵션을 추가하면, 해당 컴포넌트가 정말 공통용으로 유지될 수 있을지 의문이 들었기 때문입니다.
따라서 공통 버튼은 최대한 범용적으로 유지하고, 기능·디자인이 확장되어야 하는 경우 별도 컴포넌트로 분리해 공통 컴포넌트의 재사용성과 유지보수성을 확보하는 방향을 택했습니다.</p>
<pre><code class="language-typescript">
import { cn } from &quot;@/lib/utils&quot;;
import { Button } from &quot;./button&quot;;

export const CloseButton = ({ ...props }: React.ComponentProps&lt;&quot;button&quot;&gt;) =&gt; {
  return (
    &lt;Button
      variant=&quot;none&quot;
      size=&quot;none&quot;
      className={cn([
        &quot;text-[24px] w-[24px] h-[24px] shrink-0 leading-1 outline-none focus:outline-none rounded-full&quot;,
        props.className,
      ])}
      {...props}
    &gt;
      {props.children || &quot;×&quot;}
    &lt;/Button&gt;
  );
};

</code></pre>
<h2 id="디자인-토큰에-대해">디자인 토큰에 대해</h2>
<p>제가 과제를 하면서 고민했던 부분 중 하나는 <code>디자인 토큰을 3단계 토큰 구조로 관리 해야할까?</code> 였습니다. 그 전에 3단계 토큰 구조에 대해서 간략하게 적어봅시다.</p>
<h3 id="primitive-tokens">Primitive Tokens</h3>
<p>: 색상값이나 크기값 등의 기본값을 가지는 가장 기본적인 토큰. 3단계 토큰 구조의 첫 번째 계층</p>
<pre><code class="language-css">--blue-200 : #1976d2;</code></pre>
<h3 id="semantic-tokens">Semantic Tokens</h3>
<p>: Primitive 토큰을 값으로 가지는 두 번째 계층. Primitive에 <strong>의미</strong>와 <strong>사용 목적</strong>을 부여한 계층. 의미론적 네이밍이 특징입니다. (브랜드 컬러 등등)</p>
<pre><code class="language-css">--color-primary: var(--blue-200);</code></pre>
<h3 id="component-specific-tokens">Component-Specific Tokens</h3>
<p>: 특정 컴포넌트에서만 사용하도록 범위를 더욱 좁힌 토큰. 이를 통해 디자인 토큰의 변화와 컴포넌트를 분리합니다.</p>
<pre><code class="language-css">--color-btn-primary: var(--color-primary);</code></pre>
<p>디자인 토큰을 3단계 레이어로 나누면, 브랜드 컬러 등 변경 사항이 생겼을 때 코드 전체를 수정할 필요 없이, 디자인 토큰 값만 변경하여 전체에 적용할 수 있습니다.</p>
<h3 id="디자인-토큰을-3단계-토큰-구조로-관리-해야할까">디자인 토큰을 3단계 토큰 구조로 관리 해야할까?</h3>
<p>저는 과제를 하면서 <code>디자인 토큰을 레이어화 해서 관리해야 할까?</code> 라는 의문이 들었습니다. 색상, 사이즈와 같은 단위의 Primitive 디자인 토큰만으로도 저는 충분히 프로젝트를 관리할 수 있을 것 같다는 생각이 들었습니다.</p>
<p>예를 들어</p>
<pre><code class="language-css">
// Primitive
--blue-200: #1976d2;

// Semantic
--color-primary: var(--blue-200);

// Component
--color-btn-primary: var(--color-primary);
</code></pre>
<p>라는 레이어 토큰이 있을 때 Primitive - Semantic 단계까진 이해할 수 있지만 Component 단계까지 쪼갠다면 너무 과하지 않을까? 라는 생각이 들었습니다. 컴포넌트 사용단에서 &quot;여기엔 primary 색상이 들어가야해!&quot; 라고 한다면 <code>bg-primary</code>, <code>bg-blue-200</code> 으로 하면 충분하지 않을까? 하는 생각이 자꾸만 들었습니다.
Primitive / Semantic / Component 레벨로 디자인 토큰을 관리할 때의 장점이 아직 명확하게 와닿지 않습니다. 디자인 시스템을 직접 만들거나 사용해본 경험이 없고, 규모가 큰 프로젝트를 해보지 못해서 생기는 경험 부족으로 인한 의문점이기도 한 것 같습니다.</p>
<p>과제 제출이 끝난 이후, 다른 분들의 PR을 보면서 어느정도 납득할 수 있었습니다. <a href="https://github.com/hanghae-plus/front_7th_chapter3-1/pull/21">참고 PR</a>
디자인 토큰을 나누는 이유는 <strong>프로젝트에서 사용하는 색상, 사이즈 등을 체계적으로 구조화하여, 변경이나 추가 사항이 발생할 때 원활하게 관리하기 위해서</strong>라고 느꼈습니다. 특히 규모가 크거나 장기적으로 진행되는 프로젝트에서 빛을 발할 수 있을 것 같았습니다. 다음에 회사에서 신규 프로젝트를 진행하게 된다면, 디자인 시스템을 구축해보는 경험을 쌓아보고 싶다는 생각이 들었습니다.</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>모각코 하기</strong></p>
<p>저는 혼자 코딩을 할 때보다, 같이 모여서 코딩을 할 때 집중이 더 잘 되는 것 같습니다. 과제에 대해 이야기도 나눠보면서 인사이트를 얻어가기도 하고, 많은 분들과 네트워킹도 할 수 있는 것 같아 모각코 하길 정말 잘한 것 같습니다.</p>
<p>초반에는 저희 팀끼리 모각코를 하려고 했지만, 어느새 제가 다른 팀 분들을 모셔오게 되면서 모각코가 7기 문화 중 하나로 자리 잡은 것 같습니다. &quot;이번주에는 모각코 안 하나요?&quot; 라는 질문도 들을 정도였어요.</p>
<p>항상 제가 먼저 저질러 놓고 팀원분들께 양해를 구하곤 했는데, 늘 긍정적으로 받아주셔서 정말 감사드립니다.
멀리서, 또는 다른 팀임에도 모각코에 참여해주시는 모든 분들께도 정말 감사드려요!
이번주, 다음주, 항해가 끝날 때까지 모각코 함께해요<del>!</del>!~! 😊</p>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>시간 분배 잘 하기</strong></p>
<p>이번에도 초반에는 고민할 시간을 가질 수 있었지만, 시간 분배에 실패하면서 후반부로 갈수록 AI 활용 빈도가 높아져 고민의 빈도가 많이 줄었습니다. 항해가 끝난 후에는 꼭 과제를 다시 풀어보는 스터디에 참여해야겠어요.</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>과제 할 일 분석하기</strong></p>
<p>과제 전체 내용을 파악하며, <code>이번 과제의 핵심 키워드는 무엇인가?</code>, <code>이번 과제에서 내가 해야 할 일은 무엇인가?</code>와 같이 시작 전 할 일을 명확히 정리하고 싶습니다.
이전에는 얼추 이해했다고 생각하고 과제를 시작하다 보면 어느 순간 내가 무엇을 하고 있는지 방향을 잃는 경우가 있었기 때문에, 다음 7주차에는 과제 분석 내용을 별도로 문서화하여 전체 그림을 보는 연습을 해보려 합니다.</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p>이번주 PR에서 정말 재밌는 <a href="https://github.com/hanghae-plus/front_7th_chapter3-1/pull/37">PR</a>을 발견했습니다. 소설을 써도 될 만큼의 필력을 가지고 계시더라구요. 소설보다 더 흥미롭게 읽었습니다. 저는 당돌한 신입 디자이너로 등장하더라구요...ㅎㅎ PR 내용뿐만 아니라 기술적으로도 많이 배울 수 있었던 과제 결과물이었던 것 같습니다. </p>
<p>항해 플러스 7기에는 실력 있는 분들이 정말 많은 것 같습니다. 초반에는 그들과 비교하며 <strong>“왜 나는 이렇게 부족하지?”</strong> 라는 생각에 저 자신을 갉아먹곤 했지만, 지금은 그들의 강점을 배우려는 방향으로 관점을 전환하려고 노력하고 있습니다. 많이 보고 적극적으로 질문하면서, 언젠가는 저도 항플 7기 분들처럼 뛰어난 실력을 갖춘 개발자로 성장하고 싶습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 5주차 - 나만의 React 만들기]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-5%EC%A3%BC%EC%B0%A8-%EB%82%98%EB%A7%8C%EC%9D%98-React-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-5%EC%A3%BC%EC%B0%A8-%EB%82%98%EB%A7%8C%EC%9D%98-React-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 24 Nov 2025 07:11:47 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서</h1>
<p>5주차엔 <code>나만의 React 만들기</code>라는 주제로 과제를 진행했습니다. React의 핵심 개념에 대해 직접 구현해보고 내부 동작을 학습할 수 있었던 주차였습니다.</p>
<p>저는 easy 단계를 선택했고, 이 단계에서 가상 DOM을 직접 구현하고 이벤트 위임 방식을 통한 이벤트 핸들러 적용, diff 알고리즘을 적용해 렌더링 성능을 개선하는 과정을 경험할 수 있었습니다.</p>
<p>추후에는 hard 단계도 도전하여 <code>react의 훅이 어떻게 이전의 상태를 관리하고 유지할 수 있는지</code>, <code>컴포넌트의 라이프 사이클을 어떻게 관리할 수 있는지</code>에 대해서 학습해보려 합니다.</p>
<h2 id="가상-dom">가상 DOM</h2>
<blockquote>
<p>DOM의 형태를 본따 만든 JS 객체</p>
</blockquote>
<p>실제 DOM이 다음과 같이 구성되어 있을 때</p>
<pre><code class="language-javascript">
&lt;li&gt;
  &lt;input type=&quot;checkbox&quot; class=&quot;toggle&quot; /&gt;
    todo list item 1
  &lt;button class=&quot;remove&quot;&gt;삭제&lt;/button&gt;
&lt;/li&gt;
</code></pre>
<p>가상 DOM은 아래와 같이 구성되어 있습니다.</p>
<pre><code class="language-javascript">
virtualDom(&#39;li&#39;, null,
    virtualDom(&#39;input&#39;, { type: &#39;checkbox&#39;, className: &#39;toggle&#39; }),
    &#39;todo list item 1&#39;,
    virtualDom(&#39;button&#39;, { className: &#39;remove&#39; }, &#39;삭제&#39;)
),

</code></pre>
<p>가상 DOM의 문제점은 <code>가독성</code>입니다. 따라서 이를 해결하기 위해 등장한 것이 <code>JSX</code>라고 합니다.</p>
<h2 id="이벤트-위임">이벤트 위임</h2>
<p>각 요소마다 이벤트를 붙이는 것이 아닌, 이벤트 위임 방식으로 이벤트를 등록하는 이유는 무엇일까요?</p>
<p>제가 내린 결론은 아래와 같습니다.</p>
<ul>
<li>각 요소마다 이벤트를 등록해야 하는 번거로움 + 메모리 낭비 방지 (메모리 사용을 줄이고 성능을 향상시키는 효과적인 방법)</li>
<li>새로운 요소가 추가 되더라도 매번 이벤트를 추가하지 않아도 됨 (동적으로 생성되는 요소들의 이벤트를 효율적으로 관리할 수 있음)</li>
</ul>
<h2 id="diff-알고리즘">diff 알고리즘</h2>
<blockquote>
<p>실제 DOM과 가상 DOM을 비교하여 변경된 부분만 찾아내어 실제 DOM에 반영하는 알고리즘</p>
</blockquote>
<p>oldNode와 newNode를 비교하여 변경된 부분만 찾아 실제 DOM에 반영함으로써 렌더링 성능을 향상시킨 것을 알 수 있었습니다.</p>
<p><code>updateElement(target, newNode, oldNode)</code>라는 업데이트 함수가 있을 때</p>
<ol>
<li>oldNode만 있는 경우</li>
</ol>
<ul>
<li>oldNode를 target에서 제거합니다.</li>
</ul>
<ol start="2">
<li>newNode만 있는 경우</li>
</ol>
<ul>
<li>newNode를 target에 추가합니다.</li>
</ul>
<ol start="3">
<li>oldNode와 newNode 모두 string 타입일 경우</li>
</ol>
<ul>
<li>oldNode와 newNode의 내용이 다르다면 newNode의 내용으로 교체합니다.</li>
</ul>
<ol start="4">
<li>oldNode와 newNode의 type이 다른 경우 (둘 중 하나가 string 타입이더라도 해당됩니다.)</li>
</ol>
<ul>
<li>oldNode를 제거하고 해당 위치에 newNode를 추가합니다.</li>
</ul>
<ol start="5">
<li>oldNode와 newNode의 type이 같은 경우</li>
</ol>
<ul>
<li>oldNode와 newNode의 attribute를 비교하여 변경된 부분만 반영합니다.<ul>
<li>oldNode attribute 중 newNode에 없는 것은 모두 제거</li>
<li>newNode의 attribute에서 변경된 내용만 oldNode의 attribute에 반영</li>
</ul>
</li>
</ul>
<p>이 과정을 자식 태그까지 반복하여 내용을 업데이트하게 됩니다.</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>과제의 핵심 키워드 파악하기</strong></p>
<p>지난 주차들과는 달리, 이번 주차 과제를 진행하면서 <code>과제에서 핵심 키워드는 무엇일까?</code>에 대한 고민을 계속 하려고 했습니다. 무작정 코드를 치는 것이 아닌, 과제의 목적을 파악하고 키워드에 대해 학습하려는 노력을 많이 했던 것 같습니다.</p>
<p>지난 주차의 아쉬움이었던 <code>과제 통과를 위한 무작정 코딩</code>에서 벗어나, 과제 핵심 키워드에 대해 고민해봄으로써 어제보다 나은 오늘의 내가 되었다는 점에서 의미 있는 한 주였습니다.</p>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>목요일 모각코 금지 🚫</strong></p>
<p>이번주는 화요일, 목요일 총 2번의 모각코를 진행했는데요... 목요일 모각코는 하면 안되겠다는 생각이 들더라구요...ㅎㅎ 
화요일은 집중도 잘되고 시간 대비 효율이 잘 나와 만족스러웠는데 목요일은 밤샘이 강제되는 느낌 + 마감 전날이라 오히려 집중이 잘 안되는 느낌이랄까요... 조금은 힘들었던 것 같습니다.</p>
<p>과제의 압박이 덜한 월, 화에 모각코하기 !</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>hard 도전하기</strong></p>
<p>easy 단계를 수행한 만큼 hard 과제도 진행하며 코치님께서 제공해주신 모든 학습 키워드 학습하기 !!!! 🔥🔥🔥🔥</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p>이번주는 중간 네트워킹이 진행되었던 주차이기도 했는데요 !! 
좋은 자리를 만들어주신 매니저님과 학습 메이트님들께 감사의 말씀을 전합니다 👍 너무 재미있었고 유익했던 시간이었어요 ㅎ.ㅎ 
좋은 기회로 4팀과 크라임씬 약속도 다녀올 수 있었는데 너무너무너무너무너무너무너무 재밌었습니다 😁 (다음에 또 초대해주세요 !)</p>
<p>학습뿐만 아니라 많은 네트워킹 기회를 가질 수 있다는 점도 항해 플러스의 큰 장점인 것 같습니다. 함께하는 분들을 알아가며 많은 자극을 받을 수 있어 좋은 것 같습니다. 
(항해 플러스 하길 너무너무 잘했다 !!!! 🔥)</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/f3b722bd-8b8f-44a0-9765-9461d78137d7/image.png" alt=""></p>
<p>모각코 모집 토크 중인 저희의 모습을 마지막으로 5주차 회고도 마무리 해보겠습니다 😊<br>(우리 제법 귀여울지도....?? 이와중에 머리에 싸커킥 날리는 영코....ㅠ)</p>
<hr>
<h1 id="참고-자료">참고 자료</h1>
<ul>
<li><a href="https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Virtual-DOM/#_4-diff-%E1%84%8B%E1%85%A1%E1%86%AF%E1%84%80%E1%85%A9%E1%84%85%E1%85%B5%E1%84%8C%E1%85%B3%E1%86%B7-%E1%84%8C%E1%85%A5%E1%86%A8%E1%84%8B%E1%85%AD%E1%86%BC">https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Virtual-DOM/#_4-diff-%E1%84%8B%E1%85%A1%E1%86%AF%E1%84%80%E1%85%A9%E1%84%85%E1%85%B5%E1%84%8C%E1%85%B3%E1%86%B7-%E1%84%8C%E1%85%A5%E1%86%A8%E1%84%8B%E1%85%AD%E1%86%BC</a></li>
<li><a href="https://devocean.sk.com/blog/techBoardDetail.do?ID=165601&amp;boardType=techBlog">https://devocean.sk.com/blog/techBoardDetail.do?ID=165601&amp;boardType=techBlog</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 4주차 - 프레임워크 없이 SPA 만들기]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-4%EC%A3%BC%EC%B0%A8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%97%86%EC%9D%B4-SPA-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-4%EC%A3%BC%EC%B0%A8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%97%86%EC%9D%B4-SPA-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 23 Nov 2025 16:01:24 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서</h1>
<p>4주차는 <strong>프레임워크 없이 SPA 만들기</strong> 라는 주제를 가지고 과제를 진행했습니다. 바닐라 JS로 SPA의 기능을 구현해보면서 리액트가 해결하고자 하는 문제를 이해하고 해결해보는 과제였습니다.</p>
<p>이번 과제에서 핵심 키워드는 <strong>렌더링 라이프 사이클</strong>, <strong>Router</strong>, <strong>Store</strong>를 구현해보는 것이었습니다.</p>
<p>이번 주차의 전략은 <strong>일단 돌아가게 구현 후 리팩토링 하면서 라이프 사이클, 라우터, 스토어를 구현</strong>하는 것이었으나, 일단 돌아가게 구현하는데 시간을 오래 사용하여 과제의 핵심 키워드에 대해 제대로 된 학습을 하지 못했습니다. 하지만 과제 주차가 끝났다고 해서 다시 못하는 것도 아니고, 항해 과정이 끝나고 나서도 과제를 다시 해볼 수 있기 때문에 추후에 핵심 키워드에 대해 충분히 고민하며 다시 진행해보려고 합니다.</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>스스로 압박하지 않기</strong></p>
<p>과제를 완벽하게 해내야 한다는 압박감, 다른 분들께서 과제에 대해서 이야기 하는 걸 들으면서 &quot;나는 왜 이런 생각을 못해냈지?&quot; 하는 무능함 등 3주차까지는 이러한 요소들로 인해 자신감이 많이 떨어져있던 상태였습니다. 오죽하면 항해 플러스를 하차해야하나 싶을 정도로 멘탈이 좋지 않았어요. 다행히 학습 메이트님과 페이스 체크를 할 수 있는 기회가 생겼고, 이러한 고민들을 털어 놓으면서 조금은 부담감을 내려놓을 수 있었습니다. 이번 주가 끝나면 과제 내용을 다시 안 볼 것도 아닐 뿐더러 나는 왜 못하지? 보단 &quot;이러한 생각도 할 수 있구나 ~&quot; 하며 다른 분들의 지식을 흡수해가는 것이 더욱 바람직한 방향이라는 것을 깨달을 수 있었습니다. 학습 메이트님 뿐만 아니라 코치님들께서도 1주 만에 모든 내용을 흡수하기는 쉽지 않다며 너무 부담감을 가지는 것보단 자신의 속도에 맞게 진행하며 학습은 언제든지 할 수 있는 것이니 부담을 가지지 말라는 조언을 많이 해주셨습니다.</p>
<blockquote>
<p>어제보다 더 나은 오늘의 내가 되었다면 그것으로 충분하다 !!</p>
</blockquote>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>시간 분배 잘 하기</strong></p>
<p>이번 주차에서 아쉬웠던 점은 시간이 부족하여 핵심 키워드에 대해 많은 고민을 해보지 못한 것이었습니다.
남은 주차에서는 요일 별 계획을 세워 주차 별 핵심 키워드에 대해 고민을 해볼 수 있는 시간을 확보해보려고 합니다 !</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>의미있는 내용 공유 해보기</strong></p>
<p>과제 리뷰를 하며 아쉬웠던 점은 <strong>많은 고민을 해보지 못해 생산성 있는 이야기를 많이 하지 못하는 것</strong>입니다. 각자가 겪은 고민에 대해 이야기해보고 같은 고민을 한 사람들과 생산성 있는 이야기를 해보는 것이 목표입니다 :)</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p>무작정 사용해왔던 리액트의 내부 원리에 대해서 알아볼 수 있었던 주차였습니다. 리액트가 어떤 문제를 해결하고자 했고 어떤 방식으로 해결해냈는지 생각해보며 <strong>문제 해결을 수월하게 해낼 수 있는 개발자</strong>로 한 걸을 내딛을 수 있었습니다. </p>
<p>꼭 과제를 다시 진행해보며 핵심 키워드에 대해 충분히 고민해보고 그 결과를 블로그 등의 문서로 남겨보는 것을 목표로 남겨두며 4주차 회고도 마무리 해봅니다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 3주차 - 완성도 있는 테스트 전략 수립하기]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-3%EC%A3%BC%EC%B0%A8-%EC%99%84%EC%84%B1%EB%8F%84-%EC%9E%88%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84%EB%9E%B5-%EC%88%98%EB%A6%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-3%EC%A3%BC%EC%B0%A8-%EC%99%84%EC%84%B1%EB%8F%84-%EC%9E%88%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84%EB%9E%B5-%EC%88%98%EB%A6%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 16 Nov 2025 13:57:24 GMT</pubDate>
            <description><![CDATA[<p>밀린 3주차 쓰러 후다닥...</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/a0d8ebbe-e07d-407f-9fb6-19c0ed3c0270/image.jpg" alt=""></p>
<hr>
<h1 id="들어가면서">들어가면서</h1>
<p>3주차엔 <code>완성도 있는 테스트 전략 수립하기</code> 이라는 주제를 가지고 과제를 진행했습니다.
Playwright를 활용한 e2e 테스트, Storybook + Chromatic을 활용한 시각적 회귀 테스트를 작성해보는 경험을 할 수 있었고, 테스트 코드를 도입해야 하는 상황에서 주어진 상황에 따른 최적의 테스트 전략을 선택할 수 있는 능력을 기를 수 있었습니다.
또한 FE 개발에서 사용되는 테스트의 종류에 대해서 배울 수 있었습니다.</p>
<h2 id="단위-테스트">단위 테스트</h2>
<ul>
<li>정의 : 함수나 메서드 등 작은 단위를 독립적으로 검증하는 테스트 (주어진 input에 대해 올바른 output을 내는지)</li>
<li>장점 : 빠른 실행 속도</li>
<li>단점 : 모듈 간 상호작용 or 전체 시스템에 대한 검증이 어려움</li>
</ul>
<h2 id="통합-테스트">통합 테스트</h2>
<ul>
<li>정의 : 여러 모듈, 컴포넌트가 올바르게 상호작용 하는지 확인, 핵심 비지니스 기능의 로직 검증</li>
<li>장점 : 모듈 간 검증 가능</li>
<li>단점 : 모킹에 의존하게 되면 신뢰도가 낮아질 수 있다</li>
</ul>
<h2 id="e2e-테스트">E2E 테스트</h2>
<ul>
<li>정의 : 사용자 관점에서 시나리오 기반 전체 워크플로우를 검증</li>
<li>장점 : 사용자 시나리오 기반 테스트이다 보니, 핵심 비지니스 기능에 대한 시각적 확인 가능</li>
<li>단점 : 실제 API 사용을 위해선 백엔드와 협의가 필요, 테스트 실행 시간 증가</li>
</ul>
<h2 id="시각적-회귀-테스트">시각적 회귀 테스트</h2>
<ul>
<li>정의 : 공통 컴포넌트 등의 UI의 변경 사항을 시각적으로 검증</li>
<li>장점 : 직접 개발 환경을 실행하지 않아도 작업 내용을 실시간으로 확인 가능</li>
<li>단점 : 비용과 관리에 대한 부담이 존재</li>
</ul>
<h2 id="스냅샷-테스트">스냅샷 테스트</h2>
<ul>
<li>정의 : HTML DOM 구조를 직렬화하여 이전 스냅샷과 비교 검증</li>
<li>장점 : 빠른 컴포넌트 구조 변경 사항 확인</li>
<li>단점 : 의도된 변경 사항인지 매번 확인 필요, 스냅샷 업데이트 남발 가능성</li>
</ul>
<p>각 프로젝트의 특성과 상황을 분석하여 적합한 테스트 전략을 세우는 것이 중요하다는 것을 이번 과제를 하면서 배울 수 있었습니다 :)</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>최적의 테스트 코드 전략에 대해 고민해보기</strong></p>
<p>이전까지는 무작정 테스트 코드를 도입하는게 좋을 것 같다는 막연한 생각을 해왔지만, Chapter 1을 마무리하면서 든 생각은 <code>상황에 맞는 최적의 테스트 전략을 세워 효율을 극대화 하는 것이 중요하다</code>는 것을 배울 수 있었습니다. </p>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>테스트 코드를 손으로 다 작성해야 한다 vs 테스트 코드 작성은 AI에게 맡긴다</strong></p>
<p>이번 과제를 진행하면서 들었던 생각 중 하나는 <code>내가 직접 하나하나 테스트 코드를 치기엔 시간이 오래 걸릴 것 같고, AI에게 위임하자니 실력 향상 안됨 + 100% 신뢰가 가능할까?</code> 입니다. <code>실력 향상 안됨</code>은 현재 배우고 있는 입장이기 때문에 든 생각인 것 같기도 합니다. 
<code>테스트 코드 작성법(문법 + 메서드)을 전부 다 외워두는게 좋을까 ?</code> 라고 생각해보면 뭔가 아닌거 같고, 그렇다고 AI에게 위임을 하자니 내가 검토는 하지만 테스트 코드를 작성할 때마다 매번 AI에게 시키는 것이 과연 바람직한 방식인지 고민이 많이 됩니다.</p>
<p>아마도 제가 생각하는 이상적인 개발자가 <code>도움 안받고 내 머리만으로 코드를 뚝딱뚝딱 작성하는 개발자</code> 라고 생각해서 위와 같은 고민이 드는 것 같습니다. 예전에는 구글링, 지금은 AI를 활용하여 많은 개발자들이 개발할 때 도움을 받곤 하는데 제가 너무 구시대적인 발상을 가지고 있는 것인지도 모르겠습니다. 욕심 때문일까요 ? 강박 때문일까요 ? <code>정답을 보고 코드를 작성하는 건 내 성장에 도움이 안돼. 내 머리로 직접 생각해서 직접 작성해야 진정한 내 것이야</code> 라는 생각이 자꾸만 저 스스로를 압박하는 느낌입니다.</p>
<p>이에 대한 질문을 PR에 남겼고, 코치님과 팀원분들이 좋은 말씀을 해주셔서 고민을 조금은 덜 수 있었습니다.</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>사고력 기르기</strong></p>
<p>개발자에게 <strong>기록 + 사고력 기반의 프로젝트 파악 능력</strong>은 정말 중요하다는 것을 느낄 수 있었습니다. 
과제 수행을 위한 방법을 찾고 있던 저와는 달리, 과제 시작 전 프로젝트 전체 구조를 파악하고 설계해나가는 팀원분들의 모습을 보면서 많이 배울 수 있었습니다.
과제 마무리에 급급하여 코치님께서 의도한 학습 목표에 부합하지 못하는 모습을 보이는 것 같은데, 마무리 보다는 배움에 초점을 맞춰 마음의 여유를 가지고 과제를 할 수 있도록 노력해야겠습니다... </p>
<p>(과제의 의도를 파악하기, 무작정 코드부터 치지말고 과제 내용 파악 + 진행 방향성 설계해보기, 머리로만 생각하지 말고 내용, 구조가 엉망이더라도 문서로 기록하기 등등..)</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/8a3b442e-1d06-42d9-9d18-af7e261d9c40/image.png" alt=""></p>
<p>이번 주차에 운 좋게 BP를 받았습니다. 제가 한 고민들이 유의미했다는 점에서 보람도 있었지만, 동시에 걱정도 됩니다. 과제 수행에서 학습이 우선되어야 하는데, 무의식적으로 &#39;통과&#39;를 목표로 코드를 작성하고 있어 고민이 되는 부분입니다.</p>
<p>블랙 배지를 향한 욕심이 학습의 본질을 흐리게 만들까 우려됩니다. 이 부분은 제가 스스로 조절해야 한다는 것을 알고 있지만, 저는 아직까지 저를 다루는 것이 익숙하지 않은 듯 합니다.</p>
<p>남은 주차도 학습의 본질을 잊지 않고 항해 여정을 이어가야겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 2주차 - AI와 테스트를 활용한 안정적인 기능 개발]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-AI%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%88%EC%A0%95%EC%A0%81%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-AI%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%95%88%EC%A0%95%EC%A0%81%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Mon, 03 Nov 2025 08:17:32 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서</h1>
<p>이번 주차에선 <code>AI와 테스트를 활용한 안정적인 기능 개발</code> 이라는 주제로 과제를 진행했습니다.</p>
<p>TDD의 개념을 이해하고 AI를 활용한 TDD 방식을 통해 기존 서비스에 새로운 기능을 점진적으로 추가하는 과정을 경험하며 TDD의 장점을 알 수 있었고, AI를 잘 활용하는 방법에 대해 생각해볼 수 있었습니다. 또한 어렵게만 느껴졌던 <code>AI 에이전트</code>라는 주제에 대한 두려움을 없애주었던 주차였습니다.</p>
<h2 id="tdd란">TDD란?</h2>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/0c579c9a-1281-4df5-8a68-17324af1b67a/image.webp" alt=""></p>
<p>TDD(<code>Test-Driven Development, 테스트 주도 개발</code>)는 실제 코드를 작성하기 전에 테스트 코드를 먼저 작성하여 기능 개발을 하는 소프트웨어 개발 방법론 중 하나입니다.</p>
<p>실패하는 테스트 코드를 작성하는 단계를 <code>RED</code>, 테스트 코드를 통과하는 최소한의 기능 코드를 작성하는 단계를 <code>GREEN</code>, 기능 코드에 대한 리팩토링을 진행하는 단계를 <code>REFACTOR</code> 단계라고 합니다.</p>
<p>즉, TDD는 반복적인 <code>RED -&gt; GREEN -&gt; REFACTOR</code> 단계를 거쳐 기능을 구현하는 방법론으로 이해할 수 있습니다.</p>
<h2 id="ai-에이전트란">AI 에이전트란?</h2>
<p>AI 에이전트란, <code>LLM</code>을 핵심 엔진으로 사용하여 행동을 수행하는 &#39;실행자&#39;입니다. AI 에이전트는 LLM, 계획, 도구, 피드백 등의 구성 요소를 가질 수 있습니다.</p>
<p>즉, 사용자의 지시를 받아 LLM을 통해 답변을 생성하고 답변을 생성하는 과정에서 정의된 계획 or 도구 등을 활용하는 것을 의미합니다. 마지막엔 피드백 or 평가 단계를 두어 AI 에이전트가 스스로 적절한 결과물인지 판단하도록 할 수도 있습니다.</p>
<h2 id="과제에선-ai-에이전트를-어떻게-구성했는가">과제에선 AI 에이전트를 어떻게 구성했는가</h2>
<p>이번 과제에서 저는 총 6개의 AI 에이전트를 만들었습니다. (학습 메이트님들 좀 부려먹었어요 ㅎ.ㅎ)</p>
<ul>
<li>Doeun : 사용자로부터 입력받은 기능에 대한 기능 상세 문서를 만드는 에이전트</li>
<li>Taeyoung : 기능 상세 문서를 바탕으로 세세한 작업 단위(story)를 나누는 에이전트</li>
<li>Haneul : story 별 테스트 코드를 설계하고 작성하는, 즉 TDD RED 단계를 담당하는 에이전트</li>
<li>Yeongseo : 작성된 테스트 코드를 통과하는 기능 코드를 작성하는, 즉 TDD GREEN 단계를 담당하는 에이전트</li>
<li>Junhyeoung : 작성된 기능 코드를 리팩토링하는, 즉 TDD REFACTOR 단계를 담당하는 에이전트</li>
<li>Jaehyun : 5개 에이전트를 총괄하는 오케스트레이션 에이전트</li>
</ul>
<p>각각의 에이전트를 개별적으로 실행했을 때는 만족스러운 결과물을 얻을 수 있었지만, 오케스트레이션 에이전트를 활용했을 때는 문제가 발생했습니다.</p>
<p>오케스트레이션 에이전트가 각 에이전트에게 업무를 분배하고, 각자가 역할에 맞게 작업을 수행하는 자동화된 협업 프로세스를 구축하고 싶었지만, 오케스트레이션 에이전트가 다른 에이전트를 호출하지 않고 스스로 모든 작업을 처리하려는 문제가 발생했습니다.</p>
<p>아직 오케스트레이션 구조를 어떻게 설계해야 할지 명확히 감이 오진 않지만, 팀원분들과 학습 메이트의 노하우를 공유받아 제 에이전트를 고도화해볼 계획입니다.</p>
<hr>
<h1 id="👍-keep">👍 Keep</h1>
<p><strong>두려워하지 말고 적극적으로 나서기</strong></p>
<p>이번 과제를 하면서 코치님과 페어 프로그래밍을 하는 경험을 했습니다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/f3867fe2-24d7-4bbe-a9ff-9b5d92f31c37/image.gif" alt=""></p>
<p>예전의 저였다면 
<code>내 코드가 공개되는 게 너무 부끄럽다…</code>, <code>내가 짠 코드가 형편없으면 어떡하지?</code> 
이런 생각들에 사로잡혀 아예 시도조차 하지 못했을 겁니다.</p>
<p>두려움을 극복한 결과로 너무나도 소중한 경험을 할 수 있었습니다. 기회를 만들어주신 분들께 감사함을 전합니다🙇</p>
<p>다음에도 좋은 기회가 주어진다면, <strong>주저하지 않고 적극적으로 참여하여 또 다른 귀중한 경험을 쌓고 싶습니다.</strong></p>
<hr>
<h1 id="️-problem">‼️ Problem</h1>
<p><strong>모든 걸 AI에게 위임하고자 한 것</strong></p>
<p>과제 초반에는 &quot;나보다 AI가 더 똑똑하니깐 ~&quot; 라는 생각으로, 에이전트 설계부터 기능 구현까지 모든 과정을 AI에게 위임했습니다.
하지만 그 결과, AI가 결과물을 만들어내긴 했으나 그 결과가 올바른지 스스로 판단하기 어려운 문제가 발생했습니다.
또한 결과물의 작성 포맷을 명확히 정해주지 않다 보니 매번 다른 형태의 결과물이 생성되었고, 이로 인해 결과의 정확성을 평가하기 어려웠습니다.</p>
<p>이 경험을 통해 <strong>중간중간 인간의 개입이 반드시 필요하다는 것</strong>을 깨달았습니다. 결과물에 대한 판단이 이루어지지 않으면 결과물이 산으로 갈 수 있기 때문입니다. 또한 입력(input)과 출력(output)에 대한 템플릿을 제공하고, 에이전트의 작업 워크플로우를 명시적으로 설계함으로써 매번 유사한 구조와 품질의 결과물을 도출하는 것이 중요하다는 점을 배웠습니다.</p>
<hr>
<h1 id="🔥-try">🔥 Try</h1>
<p><strong>과제 제출 보단 학습에 의미를 두기</strong></p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/989f496e-6f85-4cd4-8393-bd521872d6e4/image.gif" alt=""></p>
<p>욕심이 많아서일까요… 자꾸만 과제를 pass 받고 싶다는 생각에 자꾸만 무리를 하게 되는 것 같습니다. 
사실 과제를 통과하지 못하더라도 <strong>조금이라도 배운 것이 있다면</strong> 혹은 <strong>어제보다 더 나은 오늘의 내가 됐다면</strong> 그 자체만으로 충분히 의미가 있는데, 막상 과제 pass를 받지 못하면 실패라고 느껴져, 스스로를 괜히 옥죄이게 되는 것 같습니다. 
그런 마음 때문에 오히려 스스로에게 지나친 부담을 주고, 무리하게 되는 것 같습니다.</p>
<p>과제 pass에 대한 압박에서 벗어나, 양질의 지식을 흡수하고 그것을 내 것으로 만드는 데 집중해야겠습니다.</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/e21d26b8-3441-4bc0-9ecb-c7f88cee087a/image.jpeg" alt=""></p>
<p>지난주 과제에서 0.5 BP를 받았습니다 😊
예상치 못한 결과라 더욱 기뻤고, 수료하기 전에는 BP 1번을 받아보고 싶다는 의지도 생겼습니다 ㅎ.ㅎ</p>
<p>아직 항해 초반이지만, 이전과 달리 <strong>변화된 부분도 있고 얻는 것도 많아</strong>, 항해 플러스를 신청하기 잘 했다는 생각이 듭니다. 남은 8주 동안에도 지금처럼 <strong>열심히 참여하여 많은 것을 배우고 얻어가기 위해 노력해야겠습니다.</strong>🔥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 1주차 - 프론트엔드 테스트 코드 익숙해지기]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-1%EC%A3%BC%EC%B0%A8-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9D%B5%EC%88%99%ED%95%B4%EC%A7%80%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-1%EC%A3%BC%EC%B0%A8-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9D%B5%EC%88%99%ED%95%B4%EC%A7%80%EA%B8%B0</guid>
            <pubDate>Sun, 26 Oct 2025 08:34:30 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서</h1>
<p>1주차에는 <code>프론트엔드 테스트 코드</code>에 대해서 다룬다. </p>
<p>과제를 진행하기 전까진 &quot;테스트 코드는 작업 분량만 2배 늘려.&quot;, &quot;이점이 뭔지 모르겠어.&quot; 와 같은 생각이 지배적이었는데, 과제를 진행하면서 생각이 바뀌게 됐다. 특히 <code>테스트 코드는 명세서의 역할을 할 수 있다.</code>는 말이 가장 크게 와닿았고, 2주차 발제 내용 중 하나의<code>TTD</code>의 개념에 대해 이해하고 나니 테스트 코드의 중요성을 깨달을 수 있었다.</p>
<p>과제를 하면서 테스트 코드를 처음 작성해본 탓에 초반에는 방향조차 잡지 못했지만, <code>[테스트 환경 설정 -&gt; 테스트 상황 발생 -&gt; 상황에 대해 검증]</code> 라는 틀을 잡고 작성하니 조금은 수월해졌다. 아직까지 <code>description을 명확하게 작성하기</code>, <code>중복 코드 줄이기</code> 와 같이 테스트 코드를 작성하는데 부족함이 많지만 테스트 코드의 중요성을 깨달을 수 있었다는 점에서 의미있는 1주차를 보낸 것 같다 :)</p>
<h1 id="👍-keep">👍 Keep</h1>
<p>: 현재 만족하고 계속 유지할 부분</p>
<p><strong>포기하지 않기</strong></p>
<p>항해에 지원하면서 다짐했던 것 중 하나는 <strong>아무리 힘들고 어려워도 포기하지 않기</strong>였다.</p>
<p>나는 항상 무언가를 하고 나면 후회하는 경우가 많았다. 멋사를 운영했을 때, 데브코스에 참여했을 때 등등 무언가가 마무리되고 그것을 되돌아 봤을 때 후회를 한 적이 많았다. <strong>&quot;이렇게 했다면 더 좋은 결과를 낼 수 있지 않았을까?&quot;</strong> 와 같은 후회를 가장 많이 하곤 했는데, 항해 활동을 되돌아봤을 땐 똑같은 후회를 하고 싶지 않았다.</p>
<p>1주차 과제부터 접해보지 못한 영역에 대해 다뤘다보니 “쉬운 단계로 내려갈까?” 하는 생각이 들기도 했지만, 배우고 성장하기 위해 항해에 참여한 만큼 이러한 어려움은 당연한 과정이라 생각하며 회피하지 않기로 다짐했다. </p>
<p>과제를 완벽하게 수행했다고는 할 수 없지만, 115개의 테스트 케이스를 모두 통과했고 남은 질문에도 나름의 답변을 작성했다. 학습 메이트 태영님이 말씀하신 <strong>“어제보다 더 나은 오늘의 내가 됐는가?”</strong>라는 질문에 그렇다고 자신 있게 답할 수 있기에, 만족스러운 1주차였다고 생각한다.</p>
<h1 id="️-problem">‼️ Problem</h1>
<p>: 개선이 필요하다고 생각하는 문제점</p>
<p><strong>나약한 나의 집중력</strong></p>
<p>과제를 진행하며 나의 고질적인 문제인 집중력 부족을 다시 한 번 느꼈다. 테스트 코드를 연속적으로 작성해야 하는데, 하나를 마무리할 때마다 집중이 흐트러지고, 다시 마음을 잡아 또 하나를 작성하면 집중이 깨지는 상황이 반복되었다. 분명 개선이 필요한 부분이다. 과제 마감일이 되어서야 집중해 남은 작업들을 마무리했지만, 평소에 꾸준히 집중해서 진행했다면 마지막 날 그렇게 고생하지는 않았을 것이다. (집중하자 나 자신...)</p>
<p><a href="https://www.youtube.com/watch?v=lyeLYUCalNw">https://www.youtube.com/watch?v=lyeLYUCalNw</a></p>
<p>신기하게 이 플레이리스트를 들으면 집중이 잘 된다... (다른 분들도 들어주셨으면 좋겠는데 맨날 까인다...)</p>
<h1 id="🔥-try">🔥 Try</h1>
<p>: 문제점을 해결하기 위해 시도해야 할 것</p>
<p><strong>요일 별 할 일 정리</strong></p>
<p>P 성향이 90% 이상인 나에게 가장 어려운 일은 요일별로 할 일을 정리하는 것이다. 그러나 계획 없이 진행하다 보니 마지막 날에 몰아서 처리하는 경우가 많아져, 이제는 할 일 정리가 필수라는 것을 느꼈다.</p>
<h1 id="마무리하면서">마무리하면서</h1>
<p>테스트 코드는 서비스의 명세서 역할을 할 수 있기에, 나중에 새로운 개발자가 프로젝트에 합류하게 됐을 때 기능 파악에 많은 도움을 줄 수 있다. 또한 테스트 코드가 작성되어 있으면 마음 편히 기능 수정을 할 수 있겠는 것을 체감할 수 있었다.</p>
<p>회사 업무에도 테스트 코드를 도입해보고 싶었지만 귀찮음 때문에 포기했었다. 그러나 이번 경험을 통해 테스트 코드의 중요성을 깨달았고, 테스트 코드에 대한 생각이 바뀐 만큼 업무에 도입하는 것을 제안해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 4주차 - React]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8-React</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8-React</guid>
            <pubDate>Tue, 14 Oct 2025 15:14:04 GMT</pubDate>
            <description><![CDATA[<h2 id="redux-toolkit">Redux Toolkit</h2>
<h3 id="flux-패턴">Flux 패턴</h3>
<p>: React의 상태 관리 아키텍처 패턴. 데이터 흐름을 단방향으로 유지해 어플리케이션 상태를 예측 가능하게 만든다. (중앙 집중식 상태 관리)</p>
<ul>
<li>Action -&gt; Dispatcher -&gt; Store(Model) -&gt; View<ul>
<li>Action : 데이터를 변경하는 행위. Dispatcher에게 전달되는 객체</li>
<li>Dispatcher : 데이터 흐름을 관리하는 중앙 허브</li>
<li>Store : 상태 저장소. 상태와 상태를 변경할 수 있는 메서드</li>
<li>View : 리액트 컴포넌트</li>
</ul>
</li>
</ul>
<p>Flux 패턴을 사용하는 대표적인 상태 관리 라이브러리 : Redux</p>
<blockquote>
<p>반대되는 개념인 <code>Atomic 패턴</code>을 사용하는 대표적인 상태 관리 라이브러리에는 <code>zustand</code> 등이 있다.</p>
</blockquote>
<h3 id="redux-toolkit-1">Redux Toolkit</h3>
<p>: Redux를 개량한 것. 코드는 더 적게, Redux를 더 편리하게 사용하기 위해 만든 라이브러리</p>
<hr>
<h2 id="axios">axios</h2>
<h3 id="instance">instance</h3>
<p>: <code>axios.create()</code>로 생성하는 커스텀 axios 객체</p>
<p>공통 설정(baseURL, headers 등)을 사전 정의해두고, API 요청 시 재사용하기 위한 용도로 사용한다. (interceptor 설정 가능)</p>
<pre><code class="language-javascript">
import axios from &quot;axios&quot;;

export const api = axios.create({
  baseURL: &quot;https://api.example.com&quot;,
  timeout: 5000,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
  },
});

// 사용 예시
import { api } from &quot;./axiosInstance&quot;;

const getUser = async () =&gt; {
  const res = await api.get(&quot;/users/1&quot;);
  return res.data;
};
</code></pre>
<h3 id="interceptor">interceptor</h3>
<p>: 요청 혹은 응답을 가로채어 로직을 삽입할 수 있는 기능</p>
<ul>
<li>요청 헤더 추가 및 공통 헤더 설정</li>
<li>인증 관리</li>
<li>로그 관련 로직 삽입</li>
<li>에러 핸들링</li>
</ul>
<p>등의 작업을 인터셉터를 통해 처리할 수 있다.</p>
<pre><code class="language-typescript">
// 요청 가로채기
api.interceptors.request.use(
  (config) =&gt; {
    const token = localStorage.getItem(&quot;accessToken&quot;);
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) =&gt; Promise.reject(error)
);

// 응답 가로채기
api.interceptors.response.use(
  (response) =&gt; {
    // 응답 데이터 구조 단일화
    return response.data;
  },
  (error) =&gt; {
    const { status } = error.response;
    if (status === 401) {
      // 토큰 만료 처리 등
      console.warn(&quot;인증 만료 - 로그아웃 필요&quot;);
    }
    return Promise.reject(error);
  }
);

</code></pre>
<pre><code class="language-typescript">
// api/client.ts
import axios from &quot;axios&quot;;

const client = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 7000,
});

client.interceptors.request.use((config) =&gt; {
  const token = sessionStorage.getItem(&quot;accessToken&quot;);
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

client.interceptors.response.use(
  (res) =&gt; res.data,
  (err) =&gt; {
    if (err.response?.status === 401) {
      // refreshToken 로직 수행 가능
    }
    return Promise.reject(err);
  }
);

export default client;

</code></pre>
<hr>
<h2 id="thunk">Thunk</h2>
<p>: Redux에서 비동기를 다루기 위해 사용되는 리덕스 미들웨어</p>
<p>Thunk 함수를 반환하는 함수, 즉 객체가 아닌 함수를 반환할 수 있도록 허용해주는 미들웨어</p>
<ul>
<li>dispatch를 할 때 객체가 아닌 함수를 dispatch 할 수 있게 해준다.<ul>
<li>따라서 중간에 하고자 하는 작업을 함수를 통해 넣을 수 있게 된다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>dispatch(함수) → 함수실행 → 함수안에서 dispatch(객체)</p>
</blockquote>
<hr>
<h2 id="custom-hook">Custom hook</h2>
<p>: 기본 hook 만으로는 중복되는 로직을 효율적으로 관리하기 어렵기 때문에, 개발자가 직접 Hook을 만들어서 사용하게 되는데, 이를 <code>Custom hook</code> 이라고 한다.</p>
<ul>
<li>React 기본 hook (useState, useEffect 등)을 조립, 확장해서 만든 사용자 정의 함수</li>
<li>상태 로직을 컴포넌트 바깥으로 추출해 재사용하기 위한 구조</li>
<li><code>재사용 가능 로직</code>을 구현하기 위해 사용됨.</li>
<li><code>use</code> 로 시작해야하며 hook 규칙을 준수해야 한다. (컴포넌트 최상위에서 호출 등등)</li>
</ul>
<hr>
<h2 id="throttling--debouncing">throttling &amp; debouncing</h2>
<p>: 짧은 시간 동안 연속해서 같은 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하기 위한 기법</p>
<blockquote>
<p>구현 시 <code>setTimeout</code>, <code>setInterval</code>, <code>클로저</code>를 사용하는데, 이때 정리(clean up 함수 작성)가 제대로 이루어지지 않으면 <code>메모리 누수</code>가 발생할 수 있다.</p>
</blockquote>
<ul>
<li>메모리 누수 : 필요하지 않은 메모리를 계속 점유하고 있는 현상</li>
</ul>
<h3 id="throttling">throttling</h3>
<p>: 짧은 시간 동안 연속해서 같은 이벤트가 발생했을 때, 이벤트들을 <code>일정 시간 단위</code>로 그룹화하여 그룹의 처음 or 마지막 핸들러만 호출되도록 하는 것. 일정 시간 간격으로 이벤트 핸들러 호출</p>
<blockquote>
<p>무한 스크롤에 많이 사용</p>
</blockquote>
<h3 id="debouncing">debouncing</h3>
<p>: 짧은 시간 동안 연속해서 같은 이벤트가 발생했을 때, 마지막 이벤트로부터 일정 시간이 경과한 후에 한 번만 호출하도록 하는 것</p>
<blockquote>
<p>resize 이벤트, 입력값 실시간 검색에 많이 사용</p>
</blockquote>
<hr>
<h2 id="인증인가">인증/인가</h2>
<ul>
<li><code>인증(Authentication)</code> : 서비스를 이용하려는 유저가 등록된 유저인지 확인하는 절차</li>
<li><code>인가(Authorization)</code> : 특정 리소스에 접근할 수 있는 권한이 있는지 확인하는 절차</li>
</ul>
<blockquote>
<p>http 프로토콜 통신의 특징</p>
</blockquote>
<ul>
<li>무상태 (stateless) : 서버는 클라이언트의 상태를 기억하지 않으므로, 각 요청마다 서버에서 요구하는 모든 상태 정보를 담아서 요청해야 한다.</li>
<li>비연결성 (connectionless) : 서버와 클라이언트는 연결되어 있지 않다. 서버 입장에서는 매번 새로운 요청이다. 따라서 최소한의 서버 자원으로 서버를 유지할 수 있게 해준다.</li>
</ul>
<h2 id="쿠키-세션-토큰">쿠키, 세션, 토큰</h2>
<ul>
<li><p><code>쿠키(cookie)</code> : 서버와 클라이언트 간의 지속적인 데이터 교환을 위해 만들어진 key-value 형식의 저장소.</p>
<ul>
<li>별도로 삭제하거나 유효기간이 만료되지 않는 이상 서버와 통신할 때 자동으로 주고 받게 된다</li>
<li>서버에 특정 API 요청을 했을 때 서버가 응답 시 header 안에 set-cookie 속성으로 쿠키 정보를 담아주면 응답을 받은 브라우저는 쿠키를 브라우저에 자동으로 저장한다.</li>
<li>서버에 http 요청을 할 때마다 브라우저에 저장되오 있는 쿠키는 자동으로 서버에 보내진다. (동일한 Origin 혹은 CORS를 허용하는 Origin에만 쿠키를 보냄)</li>
</ul>
</li>
<li><p><code>세션(sesstion)</code> : 사용자와 서버 간의 <code>연결이 활성화된 상태</code>를 의미한다. (인증이 유지되고 있는 상태</p>
</li>
<li><p><code>토큰(token)</code> : 클라이언트에서 보관하는 <code>암호화된 인증 정보</code>를 의미한다.</p>
<ul>
<li>서버에서 사용자 인증 정보를 보관할 필요가 없기 때문에 서버 부담을 줄일 수 있다.</li>
<li>웹에서 인증 수단으로 사용되는 토큰은 <code>JWT (JSON Web Token)</code> 이 있다.</li>
</ul>
</li>
</ul>
<h2 id="jwt">JWT</h2>
<p>: JSON Web Token. 웹에서 인증 수단으로 사용되는 토큰</p>
<ul>
<li>header, payload, signature 형식 3가지 데이터로 구성되어 있다.</li>
<li>암호회된 토큰을 누구나 복호화하여 payload를 볼 수 있다. (토큰의 용도는 인증정보(payload)에 대한 보호가 아니라 <code>위조 방지</code>)</li>
<li>정보(payload)를 토큰화할 때 signature에 secret key가 필요하고, secret key는 복호화가 아닌 토큰이 유효한지 검증하는 데 사용됨.</li>
</ul>
<blockquote>
<p>리소스 접근 인가를 받기 위해 사용되는 토큰을 <code>Access Token</code>, <code>Refresh Token</code>으로 만료된 Access Token을 재발급. 
Refresh Token도 만료된 상태라면 재인증 필요</p>
</blockquote>
<hr>
<h2 id="s3">S3</h2>
<p>: Amazon S3는 확장성, 데이터 가용성, 보안 및 성능을 제공하는 스토리지 서비스. 쉽게 말해 구글 드라이브처럼 파일 저장 서비스</p>
<ul>
<li>FTP 서버처럼 단순 파일 저장 영역으로 사용하거나 AWS 서비스의 사용 로그 저장, 정적 웹 사이트 호스팅에 사용할 수 있음.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 3주차 - React]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8-React</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8-React</guid>
            <pubDate>Tue, 07 Oct 2025 08:00:57 GMT</pubDate>
            <description><![CDATA[<h2 id="react-router-dom">react-router-dom</h2>
<h3 id="react-router-dom이란">react-router-dom이란?</h3>
<p>: React 어플리케이션에서 클라이언트 사이드 라우팅을 구현하기 위한 라이브러리. 여러 페이지 이동을 가능하게 하는 라이브러리</p>
<h3 id="사용법">사용법</h3>
<ol>
<li>페이지 컴포넌트 생성</li>
<li><code>Router.js</code> 작성 - url 1개당 페이지 컴포넌트 연결</li>
<li><code>App.js</code>에 Router 적용</li>
</ol>
<pre><code class="language-typescript">
// Router.js
&lt;BrowserRouter&gt;
  &lt;Routes&gt;
    &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
    &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
    &lt;Route path=&quot;contact&quot; element={&lt;Contact /&gt;} /&gt;
    &lt;Route path=&quot;works&quot; element={&lt;Works /&gt;} /&gt;
  &lt;/Routes&gt;
&lt;/BrowserRouter&gt;
</code></pre>
<h3 id="react-router-dom-주요-hooks">react-router-dom 주요 hooks</h3>
<ul>
<li><code>useNavigate</code> : 코드를 통한 path 이동</li>
<li><code>useLocation</code> : 현재 path 정보 확인</li>
<li><code>Link</code> : <code>a</code> 태그를 대체하는 태그<ul>
<li>JSX에서 a 태그를 사용해야 한다면 <code>Link</code> 태그를 사용해서 구현해야 함. a 태그를 사용하면 페이지가 이동하면서 브라우저가 새로고침 되기 때문</li>
</ul>
</li>
</ul>
<h3 id="children">children</h3>
<p>: 컴포넌트 태그 사이에 들어가는 내용 (props.children)</p>
<ul>
<li>하위 요소로 컴포넌트를 받고 싶을 때 사용</li>
<li>layout에 적용하면 공통 UI (header, footer 등) + 페이지 요소 구조로 만들 수 있다.</li>
</ul>
<hr>
<h2 id="dynamic-route">Dynamic Route</h2>
<h3 id="dynamic-route-동적-라우팅이란">Dynamic Route (동적 라우팅)이란</h3>
<p>: path에 유동적인 값을 넣어 특정 페이지로 이동하도록 구현하는 방법</p>
<ul>
<li><code>path/:id</code> 로 설정하여 동적 페이지 이동 가능</li>
<li><code>:id</code>는 url에 따라 바뀌는 값. <code>useParams</code> hook으로 조회 가능</li>
</ul>
<pre><code class="language-javascript">&lt;Route path=&quot;page/:id&quot; element={&lt;Page /&gt;} /&gt;</code></pre>
<h3 id="query-parameter-조회">query parameter 조회</h3>
<pre><code class="language-javascript">
// Works.js

import { Link } from &#39;react-router-dom&#39;;

const data = [
  { id: 1, todo: &#39;리액트 배우기&#39; },
  { id: 2, todo: &#39;노드 배우기&#39; },
];

function Works() {
  return (
    &lt;div&gt;
      {data.map((work) =&gt; (
        &lt;div key={work.id}&gt;
          &lt;Link to={`/works/${work.id}`}&gt;{work.todo}&lt;/Link&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre>
<pre><code class="language-javascript">
// Work.js

import { useParams } from &#39;react-router-dom&#39;;

const data = [
  { id: 1, todo: &#39;리액트 배우기&#39; },
  { id: 2, todo: &#39;노드 배우기&#39; },
];

function Work() {
  const { id } = useParams(); // useParams hook을 통해 쿼리 파라미터 추출
  const work = data.find((item) =&gt; item.id === parseInt(id));
  return &lt;div&gt;{work?.todo}&lt;/div&gt;;
}
</code></pre>
<pre><code class="language-javascript">
// 라우터 단

&lt;Routes&gt;
  &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
  &lt;Route path=&quot;works&quot; element={&lt;Works /&gt;} /&gt;
  &lt;Route path=&quot;works/:id&quot; element={&lt;Work /&gt;} /&gt;
&lt;/Routes&gt;
</code></pre>
<hr>
<h2 id="비동기-프로그래밍">비동기 프로그래밍</h2>
<h3 id="동기-vs-비동기">동기 vs 비동기</h3>
<ul>
<li>동기 : 현재 실행중인 코드가 끝나야 다음 코드가 실행되는 방식.</li>
<li>비동기 : 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식. 순서 보장 X. <code>setTimeout</code>, <code>addEventListener</code>, api 서버 통신 로직이 대표적인 비동기 코드</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/48eb9c30-d06a-48ed-9390-9594208af08b/image.png" alt=""></p>
<pre><code class="language-javascript">
console.log(&quot;1&quot;);
setTimeout(() =&gt; console.log(&quot;2&quot;), 3000); // 비동기 코드
console.log(&quot;3&quot;);
// 실행 순서 → 1 → 3 → 2
</code></pre>
<h3 id="콜백-지옥">콜백 지옥</h3>
<p>: 콜백 함수 중첩이 많아져 들여쓰기가 깊어지고 가독성이 안좋아지는 문제</p>
<ul>
<li>이벤트 처리, 서버 통신과 같은 비동기적 작업을 수행할 때 자주 발생</li>
<li>Promise, async/await 로 해결할 수 있다</li>
</ul>
<pre><code class="language-javascript">
setTimeout(() =&gt; {
    console.log(&#39;아메리카노&#39;)
      setTimeout(() =&gt; {
        console.log(&#39;밍밍한 아메리카노&#39;)
        ...
    }, 500)
}, 500)
</code></pre>
<h3 id="proimse">Proimse</h3>
<p>: 비동기 작업을 표현하는 객체, 3가지 상태를 가짐</p>
<ul>
<li>pending : 대기</li>
<li>fulfilled : 성공</li>
<li>rejected : 실패</li>
</ul>
<h3 id="then-catch-finally">then, catch, finally</h3>
<pre><code class="language-javascript">
axios.get(&quot;/api&quot;)
  .then(res =&gt; console.log(&quot;성공:&quot;, res))   // 성공 시 실행
  .catch(err =&gt; console.log(&quot;실패:&quot;, err)) // 실패 시 실행
  .finally(() =&gt; console.log(&quot;항상 실행&quot;)); // 무조건 실행
</code></pre>
<h3 id="asyncawait">async/await</h3>
<ul>
<li>await 는 Promise가 처리될 때까지 기다렸다가 결과를 리턴</li>
<li>에러는 try ~ catch로 처리</li>
</ul>
<pre><code class="language-javascript">
const getWeather = async () =&gt; {
    try {
        const response = axios.get(&#39;/api&#39;);
        console.log(&#39;성공&#39; + response);
    } catch (error) {
        console.log(&#39;실패&#39; + error);
    }
}
</code></pre>
<hr>
<h2 id="rest-api">REST API</h2>
<h3 id="rest-api란">REST API란</h3>
<p>: 어떤 자원에 대해 CRUD를 진행할 수 있게 HTTP method를 사용하여 요청을 보내는 것. REST 원칙을 적용하여 설계한 API</p>
<ul>
<li>REST<ul>
<li>자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것
HTTP URL을 통해 자원을 명시, HTTP 메소드(GET, POST, PUT, PATCH, DELETE)를 통해 해당 자원에 대한 CRUD를 적용함.</li>
<li>전반적인 웹 어플리케이션에서 상호작용 하는데 사용되는 웹 아키텍쳐 모델. 즉 자원을 주고받는 웹 상에서의 통신 체계에 있어서 범용적인 스타일을 규정한 아키텍쳐</li>
</ul>
</li>
<li>API
응용 프로그램을 통해 데이터를 제공받거나, 기능을 사용하고자 할 때 사용하는 인터페이스 및 규격</li>
</ul>
<h3 id="rest의-특징">REST의 특징</h3>
<ul>
<li>균등한 인터페이스 (Uniform Interface)<ul>
<li>HTTP의 표준만 따른다면 어떠한 기술이던지 접목하여 사용할 수 있다. 플랫폼이나 언어의 제약에 구애받지 않는다.</li>
<li>REST API 정의를 JSON 방식으로 많이 함. XML도 가능</li>
</ul>
</li>
<li>무상태성 (Stateless)<ul>
<li>서버는 클라이언트의 상황을 고려하지 않고 API 요청에 대해서만 처리하기 때문에 이를 “상태가 없다”고 표현한다.</li>
<li>클라이언트를 고려하지 않아도 되기 때문에 구현이 간결해진다.</li>
</ul>
</li>
<li>캐싱 가능 (Cacheable)<ul>
<li>REST는 HTTP 표준을 기반으로 만들어졌기 때문에 HTTP의 특징인 캐싱을 사용할 수 있다.</li>
<li><code>GET</code> 메소드를 <code>Last-Modified</code> 값과 함께 보낼 경우, 컨텐츠의 변화가 없을 때 캐시된 값을 사용하게 된다. 이렇게 되면 네트워크의 응답시간 뿐만 아니라 API 서버에 요청을 발생시키지 않기 때문에 부담이 덜하다는 장점을 가지게 된다.</li>
</ul>
</li>
<li>자체 표현성 (Self-Descriptiveness)<ul>
<li>REST API 자원 명시 규칙 및 메소드는 <strong>그 자체로 의미를 지니기 때문에 어떠한 요청에 있어서 그 요청 자체로 어떤 것을 표현하는지 알아보기 쉽다.</strong></li>
<li>API 문서를 제공하더라도 요청하는 방식만으로 어떠한 의미인지 알 수 있어야 좋은 REST API라고 할 수 있다.</li>
<li>좋은 REST API는 요청만 보더라도 무슨 행동을 하는지 알 수 있어야 한다.</li>
</ul>
</li>
<li>클라이언트 - 서버 구조 (Client-Server Architecture)<ul>
<li>REST 서버가 API를 제공하는 방식이기 때문에 클라이언트에서 처리하는 부분과 독립적으로 동작한다.</li>
<li>서로간의 의존성이 줄어들고 클라이언트와 서버를 최대한 독립적으로 개발할 수 있도록 도와준다.</li>
</ul>
</li>
<li>계층형 구조 (Layered System)<ul>
<li>클라이언트는 계층형 구조가 불가능하지만 REST 서버의 경우, 보안/로드 밸런싱/암호화 등을 추가할 수 있고 Proxy 및 게이트웨이 등 중간 매체를 사용할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="rest-api의-핵심">REST API의 핵심</h3>
<ul>
<li>URL은 리소스(자원)를 표현해야 한다.<ul>
<li>리소스 명은 동사가 아닌 명사를 사용한다</li>
<li>리소스는 Collection과 Document로 표현할 수 있다.</li>
</ul>
</li>
<li>리소스에 대한 행위는 HTTP Method로 표현해야 한다.<ul>
<li>GET : 리소스 조회</li>
<li>POST : 리소스 생성</li>
<li>PUT : 리소스 업데이트 (전체)</li>
<li>PATCH : 리소스 업데이트 (부분)</li>
<li>DELETE : 리소스 삭제</li>
</ul>
</li>
</ul>
<h3 id="http-상태코드">HTTP 상태코드</h3>
<ul>
<li>2xx : 성공 관련 (200 Ok, 201 Created)</li>
<li>3xx : 리다이렉션 관련 (304 Not Modified)</li>
<li>4xx : 클라이언트 에러 관련 (400 Bed Request, 401 Unauthorized)</li>
<li>5xx : 서버 에러 관련 (500 Internal Server Error)</li>
</ul>
<h3 id="path-variable-vs-query-parameter">Path Variable vs Query Parameter</h3>
<ul>
<li>Path Variable : <code>/users/10</code> 와 같이 경로에 값을 포함하는 경우</li>
<li>Query Parameter : <code>/users?id=10</code> 와 같이 <code>?key-value</code> 형태로 정렬, 필터링 같은 조건 검색에 적합함</li>
</ul>
<hr>
<h2 id="json">JSON</h2>
<h3 id="json이란">JSON이란</h3>
<p>: JavaScript Object Notation. 자바스크립트 객체 문법을 토대로 문자 기반의 데이터 교환 형식</p>
<ul>
<li>문자열, 숫자, boolean, 배열, 객체 모두 표현 가능</li>
<li>JS 객체와 유사하지만 같진 않다<ul>
<li>작은 따옴표 X, 큰 따옴표만 허용</li>
</ul>
</li>
</ul>
<h3 id="json-method">JSON method</h3>
<ul>
<li>JSON.stringify() : JS 객체를 JSON 문자열로 변환할 때 사용</li>
</ul>
<pre><code class="language-javascript">
console.log(JSON.stringify({ x: 5, y: 6 }));
// &quot;{&quot;x&quot;:5,&quot;y&quot;:6}&quot;
</code></pre>
<ul>
<li>JSON,parse() : JSON 문자열을 JS 객체로 변환할 때 사용</li>
</ul>
<pre><code class="language-javascript">
const json = &#39;{&quot;result&quot;:true, &quot;count&quot;:42}&#39;;
const obj = JSON.parse(json);

console.log(obj.count);
// 42

console.log(obj.result);
// true
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 4주차 - TS]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8-TS</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%A3%BC%EC%B0%A8-TS</guid>
            <pubDate>Tue, 07 Oct 2025 06:08:15 GMT</pubDate>
            <description><![CDATA[<h2 id="정확한-선택적-프로퍼티-타입">정확한 선택적 프로퍼티 타입</h2>
<p>: 컴파일러 플래그 <code>--exactOptionalPropertyTypes</code>을 사용하면 암시적으로 undefined를 허용하는 프로퍼티에 대한 undefined 할당이 더 이상 허용되지 않는다.</p>
<ul>
<li><code>name?: string</code> 대신 <code>name : string | undefined</code>와 같이 명시적으로 undefined를 허용해야한다.</li>
</ul>
<hr>
<h2 id="awaited-타입-및-promise-개선">Awaited 타입 및 Promise 개선</h2>
<p>: <code>Awaited&lt;&gt;</code> 유틸리티 타입은 무한하게 중첩된 Promise에서 값을 추출한다.</p>
<pre><code class="language-typescript">
type P1 = Awaited&lt;string&gt;; // string
type P2 = Awaited&lt;Promise&lt;string&gt;&gt;; // string
type P3 = Awaited&lt;Promise&lt;Promise&lt;string&gt;&gt;&gt;; // string
</code></pre>
<h2 id="import-문에-대한-type-키워드">Import 문에 대한 type 키워드</h2>
<p>: <code>Import type</code>가 아닌 import 문에서 type 키워드를 사용하여 하나로 결합하여 불러올 수 있다.</p>
<pre><code class="language-typescript">// 이전
import { something } from &#39;./file&#39;;
import type { SomeType } from &#39;./file&#39;;

// 새로운 방식
import { something, type SomeType } from &#39;./file&#39;;
</code></pre>
<h2 id="const-단언">const 단언</h2>
<p>: 상수를 정의할 때 <code>as const</code>를 사용하여 리터럴 타입으로 정확하게 타입 지정을 할 수 있다. 또한 객체와 배열을 <code>readonly</code>로 만들어서 상수 객체의 변경을 방지한다.</p>
<pre><code class="language-typescript">
// 이전 방식
const obj = { name: &#39;foo&#39;, value: 9, toggle: false }; // { name: string; value: number; toggle: boolean; }
// 값은 일반적으로 입력되므로 어떤 값이라도 지정할 수 있습니다.
obj.name = &#39;bar&#39;;

const tuple = [&#39;name&#39;, 4, true]; // (string | number | boolean)[]
// 길이와 정확한 타입은 타입에서 확인할 수 없습니다. 모든 값은 어디에나 지정할 수 있습니다.
tuple[0] = 0;
tuple[3] = 0;

// 새로운 방식
const objNew = { name: &#39;foo&#39;, value: 9, toggle: false } as const; // { readonly name: &quot;foo&quot;; readonly value: 9; readonly toggle: false; }
// &quot;foo&quot;로 정의되어 있고 읽기 전용이므로 값을 할당할 수 없습니다.
objNew.name = &#39;bar&#39;; // type error: Cannot assign to &#39;name&#39; because it is a read-only property.

const tupleNew = [&#39;name&#39;, 4, true] as const; // readonly [&quot;name&quot;, 4, true]
// 이제 길이와 정확한 타입이 정의되었으며 리터럴로 정의되어 읽기 전용이므로 아무 것도 할당할 수 없습니다.
tupleNew[0] = 0; // type error: Cannot assign to &#39;0&#39; because it is a read-only property.
tupleNew[3] = 0; // type error: Index signature in type &#39;readonly [&quot;name&quot;, 4, true]&#39; only permits reading.
</code></pre>
<h2 id="클래스-내-메서드에-대한-코드-스니펫-완성">클래스 내 메서드에 대한 코드 스니펫 완성</h2>
<p>: 클래스가 메서드 유형을 상속할 때 에디터에서 해당 메서드가 코드 스니펫으로 제안된다.</p>
<hr>
<h2 id="인덱싱된-액세스-추론-개선-사항">인덱싱된 액세스 추론 개선 사항</h2>
<p>: 키로 타입을 직접 인덱싱할 때 같은 객체에 있는 경우, 타입이 더 정확해진다.</p>
<pre><code class="language-typescript">
interface AllowedTypes {
  &#39;number&#39;: number;
  &#39;string&#39;: string;
  &#39;boolean&#39;: boolean;
}

// Record는 허용된 타입 중에서 종류와 값 타입을 지정합니다
type UnionRecord&lt;AllowedKeys extends keyof AllowedTypes&gt; = { [Key in AllowedKeys]:
{
  kind: Key;
  value: AllowedTypes[Key];
  logValue: (value: AllowedTypes[Key]) =&gt; void;
}
}[AllowedKeys];

// logValue 함수는 Record 값만 허용합니다.
function processRecord&lt;Key extends keyof AllowedTypes&gt;(record: UnionRecord&lt;Key&gt;) {
  record.logValue(record.value);
}

processRecord({
  kind: &#39;string&#39;,
  value: &#39;hello!&#39;,
  // 암시적으로 string | number | boolean 타입을 갖는 데 사용되는 값입니다.
  // 이제 string으로만 올바르게 추론됩니다.
  logValue: value =&gt; {
    console.log(value.toUpperCase());
  }
});
</code></pre>
<h2 id="타입스크립트-추적-분석기">타입스크립트 추적 분석기</h2>
<p>: <code>-generateTrace &lt;Output folder&gt;</code> 옵션은 타입스트립트 CLI에서 타입 검사 및 컴파일 프로세스에 관한 세부 정보가 포함된 파일을 생성하는 데 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 3주차 - TS]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8-TS</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%A3%BC%EC%B0%A8-TS</guid>
            <pubDate>Wed, 01 Oct 2025 03:00:39 GMT</pubDate>
            <description><![CDATA[<h3 id="제네릭">제네릭</h3>
<p>타입 파라미터를 사용하여 코드 작성 시점에서는 구체적인 자료형을 정하지 않고, <strong>사용 시점에 타입을 지정</strong></p>
<ul>
<li><code>&lt;T&gt;</code> 형태로 많이 작성</li>
<li>타입을 매개변수처럼 다루는 문법</li>
<li>타입을 유동적으로 받기 때문에 코드 재사용성이 높아진다.</li>
<li>제네릭으로 허용되는 타입은 <code>extends</code>를 사용하여 제한할 수도 있다. (인터페이스 확장의 extends와 반대됨)</li>
</ul>
<h3 id="유틸리티-타입">유틸리티 타입</h3>
<h4 id="partial">Partial</h4>
<p>: 타입의 모튼 프로퍼티를 옵셔널하게 만든다. (부분 집합 느낌)</p>
<pre><code class="language-javascript">interface Test {
  name: string;
  age: number;
}

type TestPartial = Partial&lt;Test&gt;;</code></pre>
<h4 id="required">Required</h4>
<p>: 타입의 모든 프로퍼티를 필수로 만든다</p>
<pre><code class="language-javascript">interface Test {
  name?: string;
  age?: number;
}

type TestRequired = Required&lt;Test&gt;;</code></pre>
<h4 id="readonly">Readonly</h4>
<p>: 타입의 모든 프로퍼티를 읽기 전용으로 만든다.</p>
<pre><code class="language-javascript">interface Test {
  name: string;
  age: number;
}

type TestReadonly = Readonly&lt;Test&gt;; // { readonly name: string; readonly age: string }</code></pre>
<h4 id="record">Record</h4>
<p>: Record&lt;K, T&gt; =&gt; K 키 집합으로부터 T 값의 객체 생성</p>
<h4 id="pick">Pick</h4>
<p>: 지정된 프로퍼티만 가져온다</p>
<pre><code class="language-javascript">interface Test {
  name: string;
  age: number;
}

type TestPick = Pick&lt;Test, &#39;name&#39;&gt;; // {name: string}</code></pre>
<h4 id="omit">Omit</h4>
<p>: 지정된 타입을 무시한다</p>
<pre><code class="language-javascript">interface Test {
  name: string;
  age: number;
}

type TestOmit = Omit&lt;Test, &#39;name&#39;&gt;; // {age: number}</code></pre>
<h4 id="parameters">Parameters</h4>
<p>: 함수 타입의 매개변수를 가져온다.</p>
<pre><code class="language-javascript">function doSmth(value: string, anotherValue: number): string {
  return &#39;test&#39;;
}
type Params = Parameters&lt;typeof doSmth&gt;; // {value: string, anotherValue: number}</code></pre>
<h4 id="returntype">ReturnType</h4>
<p>: 함수의 반환 타입을 가져온다.</p>
<pre><code class="language-javascript">type Return = ReturnType&lt;typeof doSmth&gt;; // string
</code></pre>
<h3 id="조건부-타입">조건부 타입</h3>
<p>: 어떤 타입이 다른 타입과 일치하거나 확장하는지에 따라 타입을 조건부로 설정하는 것</p>
<pre><code class="language-javascript">
type A = number extends number ? &#39;Yes&#39; : &#39;No&#39;; 
// &#39;Yes&#39;
type B = string extends number ? &#39;Yes&#39; : &#39;No&#39;; 
// &#39;No&#39;
</code></pre>
<h3 id="조건부--infer-타입-추론">조건부 + infer (타입 추론)</h3>
<p>: 조건부 타입 안에서 infer 키워드를 써서 새로운 타입 변수 선언이 가능하다</p>
<pre><code class="language-javascript">
type Return&lt;T&gt; = T extends (...args: any[]) =&gt; infer R ? R : never;

type R1 = Return&lt;() =&gt; number&gt;; // number
type R2 = Return&lt;(x:string) =&gt; Promise&lt;boolean&gt;&gt;; // Promise&lt;boolean&gt;
</code></pre>
<h3 id="튜플-옵셔널-요소와-나머지">튜플 옵셔널 요소와 나머지</h3>
<p>: <code>?</code>를 사용하여 옵셔널한 요소를 튜플로 선언하고 나머지는 <code>...</code>을 사용하여 다른 타입에 따라 선언할 수 있다.</p>
<pre><code class="language-javascript">
// 만약 튜플의 길이를 정확히는 모르지만 적어도 1 이상인 경우, `?`를 사용하여 선택적인 타입을 지정할 수 있습니다.
const list: [number, number?, boolean?] = [];
list[0] // number
list[1] // number | undefined
list[2] // boolean | undefined
list[3] // Type error: Tuple type &#39;[number, (number | undefined)?, (boolean | undefined)?]&#39; of length &#39;3&#39; has no element at index &#39;3&#39;.

// 기존 타입을 기반으로 튜플을 만들 수도 있습니다.
// 만약 배열의 시작 부분을 패딩하고 싶다면, rest 연산자 `...`를 사용하여 패딩할 수 있습니다.
function padStart&lt;T extends any[]&gt;(arr: T, pad: string): [string, ...T] {
  return [pad, ...arr];
}

const padded = padStart([1, 2], &#39;test&#39;); // [string, number, number]</code></pre>
<blockquote>
<p>튜플 : TS에서 길이와 각 원소의 타입이 고정된 배열</p>
</blockquote>
<h3 id="추상-클래스와-메소드">추상 클래스와 메소드</h3>
<p>: 클래스와 그 안의 메서드는 추상적으로 선언하여 인스턴스화되지 않도록 할 수 있다.</p>
<pre><code class="language-javascript">abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log(&#39;roaming the earth...&#39;);
  }
}

// 확장 시 추상 메서드를 구현해야 합니다.
class Cat extends Animal {} // Compile error: Non-abstract class &#39;Cat&#39; does not implement inherited abstract member &#39;makeSound&#39; from class &#39;Animal&#39;.

class Dog extends Animal {
  makeSound() {
    console.log(&#39;woof&#39;);
  }
}

// 추상 클래스는 인터페이스처럼 인스턴스화할 수 없으며 추상 메서드도 호출할 수 없습니다.
new Animal(); // Compile error: Cannot create an instance of an abstract class.

const dog = new Dog().makeSound(); // &quot;woof&quot;</code></pre>
<h3 id="생성자-시그니처">생성자 시그니처</h3>
<p>: 클래스 선언 외부에서 생성자 타입을 정의. 대부분의 경우 사용해서는 안된다.</p>
<h3 id="constructorparameters-유틸리티-타입">ConstructorParameters 유틸리티 타입</h3>
<p>: 클래스가 아닌 생성자 타입에서 생성자 매개변수를 가져오는 타입스크립트 헬퍼 함수이다.</p>
<hr>
<h2 id="typescript-40">TypeScript 4.0</h2>
<h3 id="가변-튜플-타입">가변 튜플 타입</h3>
<p>: 튜플의 나머지 요소가 제네릭 일 수 있는 것. 여러 개의 rest 요소 사용이 허용된다.</p>
<pre><code class="language-javascript">declare function concatNew&lt;T extends Arr, U extends Arr&gt;(arr1: T, arr2: U): [...T, ...U];</code></pre>
<h3 id="레이블링된-튜플-요소">레이블링된 튜플 요소</h3>
<p>: 튜플 요소의 이름을 <code>[start: number, end: number]</code>와 같이 지정할 수 있다. 요소 중 하나에 이름이 지정된 경우, 모든 요소에 이름을 지정해야 한다.</p>
<pre><code class="language-javascript">type Foo = [first: number, second?: string, ...rest: any[]];

// 이렇게 하면 여기에서 인자의 이름을 올바르게 지정할 수 있으며 에디터에도 표시됩니다.
declare function someFunc(...args: Foo);</code></pre>
<h3 id="생성자로부터-클래스-프로퍼티-추론">생성자로부터 클래스 프로퍼티 추론</h3>
<p>: 생성자에서 프로퍼티를 설정하면 타입을 유추할 수 있으므로 수동으로 설정할 필요가 없다.</p>
<pre><code class="language-javascript">class Animal {
  // 생성자에서 타입을 할당할 때 타입을 설정할 필요가 없습니다.
  name;

  constructor(name: string) {
    this.name = name;
    console.log(this.name); // string
  }
}</code></pre>
<h3 id="jsdoc-deprecated-지원">JSDoc @deprecated 지원</h3>
<p>: JSDoc, TSDoc <code>@deprecated</code> 태그는 타입스크립트에서 인식된다</p>
<pre><code class="language-javascript">/** @deprecated Use `NewType` instead */
type OldType = string;

type NewType = string;

const a: OldType = &quot;hello&quot;; // IDE에서 경고: OldType은 deprecated됨</code></pre>
<hr>
<h2 id="typescript-41">TypeScript 4.1</h2>
<h3 id="템플릿-리터럴-타입">템플릿 리터럴 타입</h3>
<p>: 템플릿 리터럴을 사용하여 문자열 리터럴 타입을 조합/변형할 수 있는 기능</p>
<pre><code class="language-javascript">type ex = `Hello ${string}`;

// ex는 Hello로 시작하는 모든 문자열 타입</code></pre>
<p>유니온 타입과 결합하여 조합이 가능하다.</p>
<pre><code class="language-javascript">type Lang = &quot;ko&quot; | &quot;en&quot;;
type Path = `/api/${Lang}`;

// 결과: &quot;/api/ko&quot; | &quot;/api/en&quot;</code></pre>
<p>제네릭, 다른 유틸리티와 결합이 가능하다.</p>
<pre><code class="language-javascript">type EventName&lt;T extends string&gt; = `on${Capitalize&lt;T&gt;}`;
type E = EventName&lt;&quot;click&quot; | &quot;hover&quot;&gt;;
// &quot;onClick&quot; | &quot;onHover&quot;

// Capitalize : 문자열 리터럴 타입의 첫 글자를 대문자로 바꿔주는 유틸리티</code></pre>
<h3 id="매핑된-타입에서의-키-리매핑">매핑된 타입에서의 키 리매핑</h3>
<p>: <code>as</code> 키워드를 사용해서 매핑된 타입의 키 이름을 다시 지정할 수 있다.</p>
<pre><code class="language-javascript">const obj = { value1: 0, value2: 1, value3: 3 };
const newObj: { [Property in keyof typeof obj as `_${Property}`]: number }; // { _value1: number; _value2: number; _value3: number; }</code></pre>
<h3 id="재귀적인-조건부-타입">재귀적인 조건부 타입</h3>
<p>: 자기 자신을 참조하여 타입을 점진적으로 변환하거나 분해하는 방식, 자기 자신을 다시 호출하여 복잡한 타입 연산을 수행하는 기법</p>
<pre><code class="language-javascript">type Last&lt;T extends any[]&gt; = 
  T extends [...infer Rest, infer L] 
    ? Last&lt;Rest&gt; extends never 
      ? L 
      : Last&lt;Rest&gt;
    : never;

// 결과
type A = Last&lt;[1, 2, 3]&gt;; // 3
type B = Last&lt;[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]&gt;; // &#39;d&#39;

/// Last는 배열을 Rest와 마지막 L로 쪼갠 뒤, 재귀적으로 들어가면서 최종 L을 반환
</code></pre>
<h3 id="jsdoc-see-태그를-지웒는-에디터">jsDoc @see 태그를 지웒는 에디터</h3>
<p>: 에디터에서 <code>@see variable/type/link</code> 태그가 지원된다.</p>
<blockquote>
<p>@see : 참고 장보를 제공할 때 사용됨</p>
</blockquote>
<pre><code class="language-typescript">
/**
 * @see User
 */
type UserId = string;

interface User {
  id: UserId;
  name: string;
}

// UserId 볼 때 User 타입도 봐라</code></pre>
<h3 id="tsc--explainfiles">tsc -explainFiles</h3>
<p>: ts 컴파일러에서 <code>tsc -explainFiles</code> 을 사용할 수 있다.</p>
<p><code>tsc -explainFiles</code> : 컴파일에 포함된 파일과 그 이유를 설명</p>
<h3 id="분해된-변수는-명시적으로-사용하지-않는-것으로-표시-가능">분해된 변수는 명시적으로 사용하지 않는 것으로 표시 가능</h3>
<p>: 구조 분해할 때 밑줄(<code>-</code>)을 사용하여 변수를 사용하지 않을 것이라고 표시할 수 있음.</p>
<pre><code class="language-typescript">const [_first, second] = [3, 5];
console.log(second);

const [_, value] = [3, 5];
console.log(value);</code></pre>
<hr>
<h2 id="typescript-43">TypeScript 4.3</h2>
<h3 id="프로퍼티에서-쓰기-타입-분리">프로퍼티에서 쓰기 타입 분리</h3>
<p>: <code>getter</code>와 <code>setter</code>의 타입을 다르게 설정 가능</p>
<ul>
<li>getter 반환 타입은 setter 파라미터 타입의 supertype이어야 함 (읽을 때는 넓은 타입, 쓸 때는 좁은 타입 가능)</li>
</ul>
<pre><code class="language-typescript">class Person {
  private _name: string = &quot;&quot;;

  get name(): string {
    return this._name;
  }

  set name(value: string | undefined) {
    this._name = value ?? &quot;&quot;;
  }
}

const p = new Person();
p.name = undefined; // OK (setter 허용)
console.log(p.name.toUpperCase()); // OK (getter는 string 반환)


// 읽을때는 무조건 string로 다루고, 쓸 때는 undefined도 받을 수 있도록</code></pre>
<h3 id="override">override</h3>
<p>: 클래스 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의할 때 사용</p>
<ul>
<li>상속 시 부모 클래스의 메서드를 그대로 가져오되, 동작을 바꾸고 싶을 때 override 사용</li>
<li>override 사용 시, 부모 클래스에 동일한 메서드가 없으면 에러 발생</li>
</ul>
<pre><code class="language-typescript">
class Animal {
  makeSound() {
    console.log(&quot;Some sound&quot;);
  }
}

class Dog extends Animal {
  override makeSound() {
    console.log(&quot;Bark!&quot;);
  }
}

const dog = new Dog();
dog.makeSound(); // Bark!

</code></pre>
<h3 id="정적-인덱스-시그니처">정적 인덱스 시그니처</h3>
<p>: <code>static [propsName: string] : string</code> 를 사용하여 정적 인덱스 시그니처를 설정할 수 있다.</p>
<ul>
<li>일반 인덱스 시그니처와 달리, 타입 수준에서 특정 집합의 키만 허용하고, 그 값의 타입을 지정하는 형태</li>
<li>정해진 키 집합에 대한 값 타입을 제한</li>
</ul>
<pre><code class="language-typescript">type Roles = &quot;admin&quot; | &quot;editor&quot; | &quot;viewer&quot;;

type RolePermissions = {
  [R in Roles]: boolean; // 각 역할은 boolean 값
};

const perms: RolePermissions = {
  admin: true,
  editor: false,
  viewer: true,
  // superAdmin: false, // ❌ Roles에 없는 키는 에러
};

// [R in Roles] =&gt; 정적 인덱스 시그니처
// 키 집합이 미리 정의</code></pre>
<blockquote>
<p>인덱스 시그니처 : 객체가 어떤 키를 가질 수 있는지, 값의 타입이 무엇인지 동적으로 정의하는 방법
<code>[key : string] : number</code> =&gt; 모든 문자열 키에 대해 number 타입 값 허용</p>
</blockquote>
<h3 id="jsdoc-link-태그-지원">jsDoc @link 태그 지원</h3>
<p>: jsDoc, tsDoc에서 <code>@link</code> 태그 지원</p>
<p><code>@link</code> : 코드나 문서, URL 등 외부 참조를 연결할 때 사용 (하이퍼링크 연결)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 2주차 - React]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-React</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-React</guid>
            <pubDate>Wed, 24 Sep 2025 08:29:23 GMT</pubDate>
            <description><![CDATA[<h2 id="state">state</h2>
<p>React에서 state는 컴포넌트 내에서 데이터와 UI 상태를 관리하는 변수. 컴포넌트 내부에서 바뀔 수 있는 값</p>
<p>state의 특징</p>
<ul>
<li>값이 바뀌면 해당 컴포넌트가 리렌더링됨</li>
<li>불변성 유지 : state를 수정하기 위해선 직접 수정하는 것이 아닌 set 함수를 통해 변경해야 함</li>
</ul>
<p>React에서 state를 정의할 땐 <code>useState()</code> hook을 사용</p>
<pre><code class="language-javascript">
const [value, setValue] = useState(초기값);
</code></pre>
<h2 id="불변성">불변성</h2>
<p>불변성(immutability): 한 번 생성된 데이터는 직접 변경할 수 없다는 의미</p>
<ul>
<li>원시 데이터(숫자, 문자열, 불리언 등)는 불변<ul>
<li>값이 바뀌면 새로운 메모리에 저장되고, 변수는 새 값을 참조</li>
</ul>
</li>
<li>참조형 데이터(객체, 배열, 함수)는 기본적으로 가변<ul>
<li>값이 바뀌면 기존 메모리를 직접 수정함</li>
</ul>
</li>
</ul>
<h3 id="react와-불변성">React와 불변성</h3>
<p>React는 이전 state와 새로운 state의 <strong>메모리 주소</strong>를 비교하여 변경 여부를 감지</p>
<ul>
<li>기존 state를 직접 수정하면 메모리 주소가 변하지 않아 변화 감지 실패 → UI 갱신 X</li>
<li>따라서 state 업데이트 시 <strong>불변성을 유지</strong>해야 함<ul>
<li>배열 → <code>[...prev, newItem]</code></li>
<li>객체 → <code>{ ...prev, key: value }</code></li>
</ul>
</li>
</ul>
<h2 id="순수-함수">순수 함수</h2>
<p>같은 입력값에 대해 항상 같은 출력값을 반환하고, 외부 상태나 변수에 영향을 미치치 않는 함수</p>
<pre><code class="language-javascript">
// 순수 함수
function add(a, b) {
  return a + b; // 입력(a, b)에 따라 항상 같은 결과
}


// 순수 함수 X
let count = 0;

function increment() {
  count += 1; // 외부 상태(count)를 변경 → 부작용
  return count;
}

</code></pre>
<ul>
<li>React 컴포넌트와 state 업데이트 함수는 순수 함수로 작성해야 예측 가능, 디버깅 쉬워짐</li>
<li>외부 상태를 직접 수정하거나 다른 side effects(HTTP 요청, 쿠키 조작 등)를 발생 시킨다면 UI 예측이 어려워지고 버그 발생 가능성 높아짐</li>
</ul>
<h2 id="컴포넌트-렌더링">컴포넌트, 렌더링</h2>
<p>컴포넌트: UI를 구성하는 단위체
렌더링 : 현재 props와 state 상태에 따라 UI를 그리는 작업</p>
<p>렌더링이 발생하는 조건은</p>
<ul>
<li>초기 렌더링</li>
<li>state가 변경 되었을 때</li>
<li>props가 변경 되었을 때</li>
<li>상위 컴포넌트가 렌더링 되면 하위 컴포넌트도 렌더링 됨</li>
<li>context 값 변경 (context API 사용 시)</li>
</ul>
<blockquote>
<p>명령형 프로그래밍 vs 선언형 프로그래밍</p>
</blockquote>
<ul>
<li>명령형 프로그래밍 : 무엇을 할지 뿐만 아니라, 어떻게 할지를 구체적으로 기술하는 방식</li>
<li>선언형 프로그래밍 : 무엇을 할지를 기술하고, 어떻게 할지는 시스템이나 언어가 알아서 처리하는 방식</li>
</ul>
<pre><code class="language-javascript">// 명령형

const numbers = [1, 2, 3, 4];
const squared = [];
for (let i = 0; i &lt; numbers.length; i++) {
  squared.push(numbers[i] * numbers[i]);
}
console.log(squared); // [1, 4, 9, 16]


// 선언형

const numbers = [1, 2, 3, 4];
const squared = numbers.map(n =&gt; n * n);
console.log(squared); // [1, 4, 9, 16]

</code></pre>
<h2 id="react-hooks">React Hooks</h2>
<h3 id="usestate">useState</h3>
<p>컴포넌트의 상태 관리를 가능하게 하는 hook. </p>
<ul>
<li>컴포넌트가 리렌더링 되더라도 이전 값을 유지. </li>
<li>state를 업데이트 하기 위해선 직접 수정하는 것이 아닌 setState 함수를 통해 수정해야 함</li>
<li>useState hook으로 원시 타입이 아닌 데이터를 수정 시 불변성을 유지해야함</li>
<li>state 초기값으로 모든 타입이 올 수 있음. 초기값 계산이 필요한 경우 함수형 초기값 사용 가능</li>
</ul>
<pre><code class="language-javascript">
const [count, setCount] = useState(0)

// state를 변경하는 방법
setCount(1)
setCount((prev) =&gt; prev + 1)
</code></pre>
<blockquote>
<p>일반 업데이트 방식과 함수형 업데이트 방식의 차이는?</p>
</blockquote>
<ul>
<li>일반 업데이트 방법은 setState 함수가 여러개 있더라도 일괄적으로 처리 (불필요한 렌더링을 줄이기 위해 한번에 처리)</li>
<li>함수형 업데이트 방식은 setState 함수를 호출한 만큼 로직이 실행됨</li>
</ul>
<p>함수형 업데이트 방식이 안정적인 것 같음</p>
<h3 id="useeffect">useEffect</h3>
<p>부수 효과(side effect)를 관리하기 위해 제공되는 hook</p>
<ul>
<li>컴포넌트가 마운트, 언마운트 됐을 때 실행하고자 하는 함수를 제어할 수 있음</li>
<li>의존성 배열을 통해 hook callback 함수의 실행 조건을 제어할 수 있음</li>
<li>useEffect hook을 한 번만 실행하고자 할 때는 의존성 배열을 빈 배열로</li>
</ul>
<blockquote>
<p>부수 효과(side effect
: UI를 그리는 순수 연산이 아닌, 외부 환경에 영향을 주거나 외부 상태를 참조/변경하는 작업</p>
</blockquote>
<h3 id="useref">useRef</h3>
<p>DOM 요소에 접근할 수 있도록 하는 hook, 데이터를 담아둘 때도 사용함</p>
<ul>
<li>ref에 설정된 값 (current)은 컴포넌트가 계속 렌더링디어도 unmount 전까지 값을 유지함<ul>
<li>제어 컴포넌트(state), 비제어 컴포넌트(ref)</li>
</ul>
</li>
</ul>
<h3 id="usecontext">useContext</h3>
<p>context API를 사용할 때 사용되는 hook</p>
<blockquote>
<p>Provider에서 제공한 value가 달라진다면 해당 context를 구독하고 있는 모든 컴포넌트가 리렌더링 됨.</p>
</blockquote>
<h3 id="usememo-usecallback-reactmemo">useMemo, useCallback, React.memo</h3>
<p>최적화를 위한 hook. 불필요한 렌더링이 발생하지 않도록 하여 최적화를 도움</p>
<ul>
<li>useMemo : 값을 메모이제이션</li>
<li>useCallback : 함수를 메모이제이션</li>
<li>memo : 컴포넌트를 메모이제이션</li>
</ul>
<h3 id="라이프-사이클">라이프 사이클</h3>
<p>리액트 컴포넌트가 생성되고 제거될 때 까지의 과정을 의미 <code>Mount - Update - Unmount</code></p>
<ul>
<li><strong>Mount</strong> : 컴포넌트가 생성될 때를 의미 (constructor(), getDerivedStateFromProps(nextProps, prevState), render(), componentDidMount())</li>
<li><strong>Update</strong> : 컴포넌트가 갱신될 때를 의미 (getDerivedStateFromProps(nextProps, prevState), shouldComponentUpdate(), render(), getSnapshotBeforeUpdate(), componentDidUpdate())</li>
<li><strong>Unmount</strong> : 컴포넌트가 DOM에서 제거될 때를 의미 (componentWillUnmount)</li>
</ul>
<h3 id="dom-virtual-dom">DOM, Virtual DOM</h3>
<p>DOM : 페이지를 구성하는 엘리먼트(컴포넌트)를 tree 형태로 표현한 것</p>
<ul>
<li>tree 요소 하나하나를 &#39;노드&#39; 라고 표현</li>
</ul>
<p>Virtual DOM : 가상의 DOM, 실제 DOM 구조와 동일한 복사본</p>
<ul>
<li>Virtual DOM 작동 원리<ul>
<li>실제 DOM과 동일한 가상의 DOM을 만들어 변경 사항을 반영. 그 후 실제 DOM과 가상 DOM을 비교하여 바뀐 부분만 실제 DOM에 적용.</li>
<li>브라우저의 렌더링 부담을 줄여줄 수 있음. (메모리에서 가상 DOM으로 변경 사항 선 반영 후 바뀐 부분만 실제 DOM에 업데이트 하기 때문)</li>
</ul>
</li>
</ul>
<ul>
<li>Virtual DOM을 사용하는 이유<ul>
<li>사용자 인터렉션이 일어날 때마다 DOM을 조작하게 된다면, 매번 브라우저 렌더링이 실행되고 이는 성능에 많은 영향을 끼치게 됨
따라서 가상 DOM을 이용해 DOM 처리 횟수를 최소화하고 효율적으로 진행하게 해줌.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 2주차 - TS]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-TS</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-TS</guid>
            <pubDate>Mon, 22 Sep 2025 02:47:52 GMT</pubDate>
            <description><![CDATA[<h1 id="es2021">ES2021</h1>
<h2 id="stringreplaceallsearchvalue-replacevalue">String.replaceAll(searchValue, replaceValue)</h2>
<p>문자열 내에서 특정 문자열 또는 정규식 패턴과 일치하는 모든 항목을 새 문자열로 교체하는 메서드. 교체된 새 문자열을 반환</p>
<ul>
<li>searchValue에는 찾는 문자열, 전역 플래그 <code>g</code>가 있는 정규식 객체가 들어올 수 있음</li>
<li>replaceValue에는 교체할 문자열, 매치된 값에 따라 동적으로 결과를 생성하는 함수가 들어올 수 있음</li>
</ul>
<p>하나만 바꾸고 싶을 땐 replace, 모두 바꾸고 싶을 땐  replaceAll 사용</p>
<pre><code class="language-javascript">const text = &quot;apple apple&quot;;

// replace (첫 번째만 치환)
console.log(text.replace(&quot;apple&quot;, &quot;orange&quot;));
// &quot;orange apple&quot;

// replaceAll (전체 치환)
console.log(text.replaceAll(&quot;apple&quot;, &quot;orange&quot;));
// &quot;orange orange&quot;
</code></pre>
<h2 id="promiseany">Promise.any</h2>
<p>여러 개의 프로미스 중에서 가장 먼저 성공한(fullfilled) 프로미스를 반환하는 메서드. 모든 프로미스가 실패(rejected) 했을 때만 <code>AggregateError</code>와 함께 rejected됨</p>
<pre><code class="language-javascript">
// 가장 먼저 성공한 결과 반환
const p1 = new Promise((_, reject) =&gt; setTimeout(() =&gt; reject(&quot;fail1&quot;), 100));
const p2 = new Promise((resolve) =&gt; setTimeout(() =&gt; resolve(&quot;success&quot;), 200));
const p3 = new Promise((resolve) =&gt; setTimeout(() =&gt; resolve(&quot;another success&quot;), 300));

Promise.any([p1, p2, p3])
  .then(result =&gt; console.log(result))  // &quot;success&quot;
  .catch(error =&gt; console.log(error));


// 모든 Promise가 rejected일 때 =&gt; AggregateError 발생
const p1 = Promise.reject(&quot;fail1&quot;);
const p2 = Promise.reject(&quot;fail2&quot;);

Promise.any([p1, p2])
  .then(result =&gt; console.log(result))
  .catch(error =&gt; {
    console.log(error instanceof AggregateError); // true
    console.log(error.errors); // [&quot;fail1&quot;, &quot;fail2&quot;]
  });

</code></pre>
<h2 id="nullish-coalescing-할당-">Nullish coalescing 할당 (??=)</h2>
<p>왼쪽 피연산자가 nullish(null or undefined)일 때, 오른쪽 피연산자 값을 할당</p>
<pre><code class="language-javascript">
let a = null;
a ??= 10;
console.log(a); // 10

let b = 0;
b ??= 20;
console.log(b); // 0

let c;
c ??= &quot;default&quot;;
console.log(c); // &quot;default&quot;

</code></pre>
<h2 id="논리적-and-할당-">논리적 AND 할당 (&amp;&amp;=)</h2>
<p>왼쪽 피연산자가 truthy일때 오른쪽 피연산자 값을 할당</p>
<pre><code class="language-javascript">
let a = 5;
a &amp;&amp;= 20;
console.log(a); // 20

let b = 0;
b &amp;&amp;= 30;
console.log(b); // 0
</code></pre>
<h2 id="논리적-or-할당-">논리적 OR 할당 (||=)</h2>
<p>왼쪽 피연산자가 falsy일때 오른쪽 피연산자 값을 할당</p>
<pre><code class="language-javascript">
let a = 0;
a ||= 10;
console.log(a); // 10

let b = &quot;hi&quot;;
b ||= &quot;hello&quot;;
console.log(b); // &quot;hi&quot;
</code></pre>
<h2 id="weakref">WeakRef</h2>
<ul>
<li>객체를 변수에 저장할 때 강한 참조로 관리됨. 누군가 참조하고 있는 한 GC가 해당 객체를 제거하지 않음</li>
<li><code>WeakRef</code>로 감싼 객체를 변수에 저장하면 약한 참조로 관리되며, 객체에 대한 다른 강한 참조가 모두 사라지면 GC에 의해 메모리에서 제거됨</li>
</ul>
<blockquote>
<p>주의점</p>
</blockquote>
<ul>
<li>GC 타이밍은 예측 불가, 메모리에서 바로 제거 되는 것이 아님</li>
<li>캐싱, 메모리 관리 등의 상황에서만 사용 권장 (일반적인 데이터 저장 용도로는 적합하지 않음)</li>
</ul>
<h2 id="숫자-리터럴-구분자">숫자 리터럴 구분자</h2>
<p>숫자의 가독성을 높이기 위해 <code>_</code>를 사용하여 숫자를 구분</p>
<pre><code class="language-javascript">const number = 1_000_000;</code></pre>
<hr>
<h1 id="es2022">ES2022</h1>
<h2 id="top-level-await">Top level await</h2>
<p>ES 모듈의 최상위 수준에서 <code>await</code> 키워드를 사용할 수 있게 됨. async 래퍼 함수가 필요하지 않아 오류 처리가 개선됨</p>
<h2 id="private">#private</h2>
<p><code>#</code> 로 시작하는 이름을 지정하여 클래스 멤버(프로퍼티, 메서드)를 비공개로 설정. 클래스 자체에서만 접근할 수 있으며 삭제 및 동적 할당 불가능
TS 프로젝트에선 이 방법을 권장하지 않음. 기존 private 키워드 사용하자</p>
<h2 id="정적-클래스-멤버">정적 클래스 멤버</h2>
<p>클래스 멤버를 정적으로 표시 (static 키워드)</p>
<h2 id="클래스에서의-정적-초기화-블록">클래스에서의 정적 초기화 블록</h2>
<p>클래스가 초기화될 때 실행되는 블록, 정적 멤버의 생성자</p>
<h2 id="import-assertions">Import Assertions</h2>
<p><code>import ... from ... assert { type: &#39;json&#39; }</code>와 같이 가져온 모듈의 타입을 단언하는 방법</p>
<h2 id="정규식-매치-인덱스">정규식 매치 인덱스</h2>
<p>정규식 일치 및 캡처 그룹에 대한 시작 및 끝 인덱스를 가져옴. <code>RegExp.exec()</code>, <code>String.match()</code> 및 <code>String.matchAll()</code>에서 동작</p>
<h2 id="음수-인덱싱-at-1">음수 인덱싱 (.at(-1))</h2>
<p>배열, 문자열 인덱싱 시 at()을 이용하여 끝부터 인덱싱 가능 <code>arr[arr.length - 1]</code>과 동일 (할당은 불가능)</p>
<h2 id="objecthasownobj-property">Object.hasOwn(obj, property)</h2>
<p><code>obj.hasOwnProperty(property)</code> 와 같이 객체가 특정 프로퍼티를 가지고 있는지 판별할 때 사용하는 메서드</p>
<blockquote>
<p>객체 자체가 hasOwnProperty를 덮어쓴 경우, hasOwn 메서드를 통해 안전하게 판별할 수 있음</p>
</blockquote>
<h2 id="error-cause">Error cause</h2>
<p>error에 선택적인 원인을 지정할 수 있음.</p>
<hr>
<h2 id="auto-accessor">Auto-Accessor</h2>
<p>자동으로 프로퍼티를 비공개로 설정하고 해당 프로퍼티에 대한 get/set 접근자를 생성</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 1주차 - React]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-React</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-React</guid>
            <pubDate>Wed, 17 Sep 2025 08:34:15 GMT</pubDate>
            <description><![CDATA[<h1 id="react-소개">React 소개</h1>
<ul>
<li>A JavaScript library for building user interfaces, 웹 or 앱 어플리케이션의 UI를 담당하는 JavaScript 라이브러리</li>
<li>SPA(Single Page Application) 기반 프론트엔드 개발 라이브러리</li>
</ul>
<h2 id="spa-single-page-application">SPA (Single Page Application)</h2>
<p>한 개의 페이지로 이루어진 어플리케이션. MPA (Multi Page Application)과 상반된 개념</p>
<h3 id="spa-vs-mpa">SPA vs MPA</h3>
<p><strong>SPA</strong></p>
<ul>
<li>한 개의 페이지로 이루어진 어플리케이션</li>
<li>전체 화면에서 일부분이 바뀌는 식으로 동작</li>
<li>최초 로딩은 느리지만 이후 로딩은 빠름</li>
<li>CSR 방식 사용 시, SEO에 불리 (초기엔 빈 HTML이기 때문)</li>
</ul>
<p><strong>MPA</strong></p>
<ul>
<li>여러 개의 페이지로 이루어진 어플리케이션</li>
<li>페이지 중 일부만 변하더라도 페이지 전체가 리렌더링</li>
<li>매번 새로운 페이지를 렌더링 하기 때문에 로딩이 느림</li>
<li>SEO에 유리 (서버로부터 만들어진 HTML을 받기 때문)</li>
</ul>
<h2 id="왜-react를-많이-사용할까">왜 React를 많이 사용할까?</h2>
<ul>
<li>facebook(현 meta)에서 개발한 기술 스택, 꾸준한 유지보수</li>
<li>방대한 커뮤니티, 문제 발생 시 관련 자료 찾기가 쉬움</li>
<li>React에 익숙해지면 RN에도 도전해볼 수 있음</li>
</ul>
<h2 id="컴포넌트-맛보기">컴포넌트 맛보기</h2>
<p>컴포넌트란? 쉽게 말하면 페이지를 이루는 단위 (Header, Footer, Contents 등)
<strong>React에서는 컴포넌트를 조립해서 페이지를 만든다 !</strong></p>
<ul>
<li>SPA는 컴포넌트 단위로 변경사항을 반영하기 때문에 화면 깜빡임 X</li>
<li>MPA는 바뀐 내용이 반영된 HTML을 다시 불러오기 때문에 화면 깜빡임 O</li>
</ul>
<hr>
<h1 id="react에서-자주-사용되는-필수-es6-문법">React에서 자주 사용되는 필수 ES6 문법</h1>
<h2 id="상수와-변수">상수와 변수</h2>
<p>이전에는 var 키워드로 변수를 선언했다면 ES6부턴 let, const 키워드를 사용해 변수, 상수를 선언한다.</p>
<blockquote>
<ul>
<li>var : 재할당 가능, 재선언 가능, 함수 레벨 스코프</li>
</ul>
</blockquote>
<ul>
<li>let : 재할당 가능, 재선언 불가, 블록 레벨 스코프</li>
<li>const : 재할당 불가, 재선언 불가, 블록 레벨 스코프</li>
</ul>
<h3 id="함수-레벨-스코프-블록-레벨-스코프">함수 레벨 스코프, 블록 레벨 스코프</h3>
<ul>
<li>함수 레벨 스코프 : 함수 내부에서 선언된 변수는 함수 내에서만 유효하며 함수외부에서는 참조할 수 없다.</li>
<li>블록 레벨 스코프 : 함수를 포함한 모든 코드 블록(함수, if문, for문, while문, try/catch문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. </li>
</ul>
<h2 id="object-선언-단축-속성-객체-복사">Object 선언, 단축 속성, 객체 복사</h2>
<p>객체는 key-value 쌍으로 가짐</p>
<pre><code class="language-javascript">
// 객체 선언 방법
const a = {
    name: &#39;abc&#39;,
      age: 1
}

const name = &#39;Jaehyun&#39;
const b = {
    name, // 객체 키와 동일한 이름을 가진 변수를 적용할 때, 단축 속성을 사용할 수 있음
      age: 1
}
</code></pre>
<h3 id="얕은-복사-깊은-복사">얕은 복사, 깊은 복사</h3>
<p>객체나 배열을 복사할 때 참조까지 복사하느냐의 차이</p>
<p>얕은 복사</p>
<ul>
<li>객체/배열의 1차원 값은 복사</li>
<li>객체/배열 참조는 주소만 복사 -&gt; 원본과 복사본이 같은 참조를 공유</li>
</ul>
<blockquote>
<p>변수에 객체, 배열, 함수와 같은 참조 타입 데이터를 담을 때, 변수에는 데이터가 담기는 것이 아닌, 데이터의 메모리 주소(참조값)가 저장됨</p>
</blockquote>
<pre><code class="language-javascript">
const user = {
  name: &quot;재현&quot;,
  address: { city: &quot;Seoul&quot; }
};

// 얕은 복사 (스프레드 연산자)
const copy = { ...user };

copy.name = &quot;길동&quot;;
copy.address.city = &quot;Busan&quot;;

console.log(user.name);        // &quot;재현&quot; (독립적)
console.log(user.address.city); // &quot;Busan&quot; (같이 바뀜!)

// name은 기본값이라 독립적이지만, address는 객체가 참조값을 공유 =&gt; 원본도 같이 변경됨
</code></pre>
<p>깊은 복사</p>
<ul>
<li>중첩된 객체/배열까지 모두 새로운 메모리에 복사</li>
<li>원본과 복사본이 독립적</li>
</ul>
<pre><code class="language-javascript">
const user = {
  name: &quot;재현&quot;,
  address: { city: &quot;Seoul&quot; }
};

// 깊은 복사 (JSON 방법)
const deepCopy = JSON.parse(JSON.stringify(user));

deepCopy.name = &quot;길동&quot;;
deepCopy.address.city = &quot;Busan&quot;;

console.log(user.name);        // &quot;재현&quot;
console.log(user.address.city); // &quot;Seoul&quot; (안 바뀜!)


// 두 객체는 독립적이므로 서로 영향을 주지 않음
</code></pre>
<h2 id="템플릿-리터럴">템플릿 리터럴</h2>
<p>백틱 (``)을 사용하여 문자열을 선언하는 방식.</p>
<ul>
<li>일반 문자열 사이에 데이터를 삽입할 수 있음</li>
<li>멀티 라인 작성 가능</li>
</ul>
<h2 id="배열객체-구조-분해-할당">배열/객체 구조 분해 할당</h2>
<pre><code class="language-javascript">
// 객체 구조 분해 할당

const person = {
    name: &#39;Jaehyun&#39;,
    age: 1
}


const {name: userName, age} = person
console.log(`${userName}님, ${age}살이시네요!`); // Jaehyun님, 1살이시네요!


function hello({name, age}) {
    console.log(`${name}님, ${age}살이시네요!`);
}
hello(person); // Jaehyun님, 1살이시네요!


// 배열 구조 분해 할당

const testArr = [1, 2, 3, 4, 5];
const [val1, val2, val3, val4, val5] = testArr;

console.log(val1); // 1

let [name] = [&quot;Tom&quot;, 10, &quot;Seoul&quot;];
let [, age] = [&quot;Tom&quot;, 10, &quot;Seoul&quot;]; // Tom은 무시됨
let [name, age, region] = [&quot;Tom&quot;, 10, &quot;Seoul&quot;];
let [name, age, region, height] = [&quot;Tom&quot;, 10, &quot;Seoul&quot;]; // height는 undefined
let [name, age, region, height = 150] = [&quot;Tom&quot;, 10, &quot;Seoul&quot;];
</code></pre>
<h2 id="전개-연산자-spread-operator">전개 연산자 (Spread Operator)</h2>
<p>스프레드 연산자(...)는 배열, 객체, 이터러블의 요소를 펼치는 연산자. 배열, 객체, 함수 호출 등의 상황에서 자주 쓰임</p>
<pre><code class="language-javascript">
const arr1 = [1, 2, 3];
const arr2 = [4, 5];

const merged = [...arr1, ...arr2]; 
console.log(merged); // [1, 2, 3, 4, 5]

// 복사
const obj1 = { name: &quot;Jaehyun&quot;, age: 1 };
const obj2 = { region: &quot;Seoul&quot;, age: 3 };

const merged = { ...obj1, ...obj2 };
console.log(merged); // { name: &quot;Jaehyun&quot;, age: 3, region: &quot;Seoul&quot; }
// 나중에 오는 속성이 앞에 있는 속성을 덮어씀
</code></pre>
<pre><code class="language-javascript">
function sum(...args) {
  return args.reduce((acc, cur) =&gt; acc + cur);
}

console.log(sum(10, 20, 30)); // 60
console.log(sum(1, 2, 3, 4, 5)); // 15

// 스프레드 연산자 사용으로 가변 인자를 전달할 수 있음
</code></pre>
<h2 id="화살표-함수">화살표 함수</h2>
<ul>
<li>함수 선언 시 function 키워드 대신 <code>=&gt;</code> 사용</li>
<li>파라미터가 1개면 () 생략 가능</li>
<li>return 생략 가능</li>
<li>생성자 함수로 사용 불가 (new 키워드 사용 불가)</li>
<li>this 바인딩 차이<ul>
<li>일반 함수 : 호출 방식에 따라 this가 동적으로 결정됨</li>
<li>화살표 함수 : this가 정의된 시점의 상위 스코프로 고정됨</li>
</ul>
</li>
</ul>
<pre><code class="language-javascript">
// 일반 함수 표현식
const add = function(a, b) {
  return a + b;
};

// 화살표 함수
const add = (a, b) =&gt; {
  return a + b;
};

// 생성자 함수로 사용 불가 예시
const Person = (name) =&gt; {
  this.name = name;
};

const p = new Person(&quot;abc&quot;); // ❌ TypeError
</code></pre>
<hr>
<h1 id="react-component">React Component</h1>
<h2 id="컴포넌트란">컴포넌트란</h2>
<ul>
<li>React 어플리케이션에서 UI를 구성하는 독립적인 단위 (재사용 가능한 HTML + 로직 조각)</li>
<li>재사용 가능하고, 독립성을 가지며, UI 표현 중심(props, state를 활용해 UI 출력)</li>
</ul>
<h2 id="컴포넌트-종류">컴포넌트 종류</h2>
<h3 id="함수형-컴포넌트">함수형 컴포넌트</h3>
<ul>
<li>React 16.8 이후 등장한 hooks 사용 가능, 라이프 사이클(생명주기) 메서드를 hooks를 통해 사용</li>
<li>클래스형 컴포넌트에 비해 간결한 구조를 가지고 있어 많이 사용</li>
</ul>
<h3 id="클래스형-컴포넌트">클래스형 컴포넌트</h3>
<ul>
<li>hooks가 등장하기 이전에 주로 사용하던 방식, 라이프 사이클 메서드 (mount, update, unmount) 사용 가능</li>
</ul>
<h2 id="컴포넌트-합성-부모---자식-컴포넌트">컴포넌트 합성 (부모 - 자식 컴포넌트)</h2>
<ul>
<li>컴포넌트는 다른 컴포넌트를 포함할 수 있음</li>
</ul>
<hr>
<h2 id="jsx">JSX</h2>
<p>JSX(JS + XML) :JS를 확장한 문법으로 JS 파일 내부에서 HTML 문법을 사용있는 문법</p>
<ul>
<li>조건문은 삼항 연산자로 사용 (return 문 안에서 if 못쓰니깐)</li>
<li>반복문은 map() 메서드 사용</li>
<li>1개의 엘리먼트만 반환 (return 문 안에 최상위 태그는 1개만 존재)</li>
</ul>
<hr>
<h2 id="props">Props</h2>
<p>부모 컴포넌트가 자식 컴포넌트로 넘겨준 데이터를 <code>props</code>라고 함 (컴포넌트 간 state 공유 방법)</p>
<ul>
<li>props는 읽기 전용 데이터</li>
<li>props를 받은 컴포넌트는 props를 직접 수정할 수 없다. (핸들러 함수를 같이 props로 넘겨주어 상위 컴포넌트에서 관리되고 있는 props 데이터를 변경하도록)</li>
</ul>
<h3 id="props-drilling">props drilling</h3>
<p>최상위 컴포넌트에서 최하위 컴포넌트로 props를 넘겨주어야 할 때 중간에 의미 없이 <code>props를 받아 그대로 넘기는</code> 현상이 많아지는 것을 말함</p>
<ul>
<li>props drilling가 심해질 수록 데이터 흐름 파악이 힘들고 컴포넌트 간 결합도가 높아짐 =&gt; 유지보수 헬</li>
<li>props drilling를 방지하기 위해 react의 context API, 전역 상태 라이브러리를 사용할 수 있음</li>
</ul>
<h3 id="children">children</h3>
<p>컴포넌트 태그 사이에 들어가는 내용을 의미, </p>
<ul>
<li>부모가 자식을 감쌀 때 전달 됨</li>
<li>ReactNode 타입</li>
<li>Layout 컴포넌트를 만들 때 자주 사용</li>
</ul>
<pre><code class="language-javascript">
const Card = ({children}) =&gt; {
  return &lt;div className=&quot;card&quot;&gt;{children}&lt;/div&gt;;
}

// 사용 예시
&lt;Card&gt;
  &lt;h1&gt;제목&lt;/h1&gt;
  &lt;p&gt;내용&lt;/p&gt;
&lt;/Card&gt;
</code></pre>
<h3 id="구조-분해-할당으로-props-추출하기">구조 분해 할당으로 props 추출하기</h3>
<pre><code class="language-javascript">
const Card = (props) =&gt; {
  return &lt;div&gt;{props.name}&lt;/div&gt;;
}

// 사용 예시
&lt;Card name=&#39;Ahn&#39; /&gt;
</code></pre>
<p>구조 분해 할당을 적용해보면</p>
<pre><code class="language-javascript">
const Card = ({name}) =&gt; {
  return &lt;div&gt;{name}&lt;/div&gt;;
}

// 사용 예시
&lt;Card name=&#39;Ahn&#39; /&gt;
</code></pre>
<p>로 사용 가능</p>
<h3 id="defaultprops">defaultProps</h3>
<p>컴포넌트에 props가 전달되지 않았을 때 사용할 기본값을 지정하는 개념</p>
<pre><code class="language-javascript">
const Card = ({name}) =&gt; {
  return &lt;div&gt;{name}&lt;/div&gt;;
}

Card.defaultProps = {
    name: &#39;Ahn&#39;
}
</code></pre>
<ul>
<li>클래스/함수형 모두 사용 가능</li>
<li>TS와 호환성 문제가 있을 수 있고 최근에는 점점 안 쓰이는 추세</li>
</ul>
<pre><code class="language-javascript">
const Card = ({ name = &#39;Ahn&#39; }) =&gt; {
  return &lt;div&gt;{name}&lt;/div&gt;;
}
</code></pre>
<ul>
<li>함수형 컴포넌트에서만 사용 가능</li>
<li>TS와 자연스럽게 호환</li>
<li>가장 일반적이고 간단한 방법</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스 프론트엔드 7기] 사전 스터디 1주차 - TS]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-TS</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-7%EA%B8%B0-%EC%82%AC%EC%A0%84-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-TS</guid>
            <pubDate>Mon, 15 Sep 2025 09:47:54 GMT</pubDate>
            <description><![CDATA[<h1 id="과거-현재도-유효한-방식들">과거 (현재도 유효한 방식들)</h1>
<h2 id="템플릿-리터럴">템플릿 리터럴</h2>
<p>백틱(``)을 사용하여 문자열을 선언하는 방식. ES6부터 백틱을 사용하여 문자열을 정의할 수 있게 됨</p>
<pre><code class="language-javascript">
const name = &#39;Jaehyun&#39;;

console.log(&#39;My name is&#39; + name + &#39;. nice to meey you&#39;); // My name is Jaehyun. nice to meet you

// 백틱 사용
console.log(`My name is ${name}. nice to meey you`); // My name is Jaehyun. nice to meet you
</code></pre>
<h2 id="템플릿-리터럴-태그">템플릿 리터럴 태그</h2>
<p>템플릿 리터럴 앞에 함수 이름을 붙이면 템플릿 리터럴과 템플릿 값의 일부가 전달됨.</p>
<ul>
<li>템플릿 리터럴로 선언한 문자열 앞에 함수를 붙여 기능을 붙이는 느낌</li>
</ul>
<pre><code class="language-javascript">
// values 길이는 2라고 가정 (템플릿 리터럴 안에 number 타입의 변수(${...})가 2개)
// reduce를 통해 식을 작성하는 것이 보통. (values의 길이를 단정지을 수 없기 때문에) 편의를 위해 아래와 같이 예시를 작성함
const formatNumbers = (strings: TemplateStringsArray, ...values: number[]) : string =&gt; {
    return strings[0] + values[0].toFixed(2) + values[1].toFixed(2) + strings[1];
}

console.log(formatNumbers`This is value: ${0}, ${4}, it&#39;s important.`) // This is value: 0.00, 4.00, it&#39;s important.

</code></pre>
<h2 id="심볼symbols">심볼(Symbols)</h2>
<p>객체의 고유한 키값 </p>
<pre><code class="language-javascript">Symbol(&#39;foo&#39;) === Symbol(&#39;foo&#39;) // false
Symbol.for(&#39;a&#39;) === Symbol.for(&#39;a&#39;) // true</code></pre>
<blockquote>
<p>Symbol() 과 Symbol.for()의 차이는?</p>
</blockquote>
<ul>
<li>Symbol() : 매번 고유한 심볼을 생성. 해당 심볼의 description은 식별에 도움을 주지 않음.</li>
<li>Symbol.for() : 전역 심볼 레지스트리에 등록된 심볼을 찾거나 생성. 동일한 key가 있으면 동일한 심볼을 재사용</li>
</ul>
<hr>
<h1 id="es2020">ES2020</h1>
<h2 id="옵셔널-체이닝-">옵셔널 체이닝 (?.)</h2>
<p><code>?.</code> 앞의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환</p>
<pre><code class="language-javascript">
const user = {
  name: null
}

console.log(user.name.first) // 타입 에러
console.log(user.name?.first) // undefined
</code></pre>
<p>인덱싱 및 함수에도 사용할 수 있음. (ex. array?.[3], func?.())</p>
<h2 id="널-병합-연산자-">널 병합 연산자 (??)</h2>
<p>||과 함께 조건부 할당에 사용할 수 있는 연산자</p>
<blockquote>
<p>|| vs ?? </p>
</blockquote>
<ul>
<li>|| : 모든 falsy 값에 적용</li>
<li>?? : undefined, null에만 적용</li>
</ul>
<h2 id="import">import()</h2>
<p>동적 import, <code>정적 임포트인 import ... from &quot;...&quot;</code>와 비슷하지만 런타임에 동작하고 Promise를 반환하며 변수를 사용한다는 차이가 있음</p>
<h2 id="stringmatchall">String.matchAll</h2>
<p>루프를 사용하지 않고 캡처 그룹을 포함하여 정규식의 여러 개의 일치 항목을 가져오는 메서드, <code>str.matchAll(regexp)</code> 와 같은 형태로 사용. (regexp는 g 플래그 필수)</p>
<ul>
<li>캡쳐 그룹 : 정규식에 ()로 묶은 부분, 매치된 값이 따로 저장됨</li>
</ul>
<pre><code class="language-javascript">const text = &quot;cat, bat, rat&quot;;
const regex = /(\w)at/g;

console.log(text.match(regex));
// [&quot;cat&quot;, &quot;bat&quot;, &quot;rat&quot;]

console.log([...text.matchAll(regex)]);

/*
[
  [&quot;cat&quot;, &quot;c&quot;, index: 0, input: &quot;cat, bat, rat&quot;, groups: undefined],
  [&quot;bat&quot;, &quot;b&quot;, index: 5, input: &quot;cat, bat, rat&quot;, groups: undefined],
  [&quot;rat&quot;, &quot;r&quot;, index: 10, input: &quot;cat, bat, rat&quot;, groups: undefined]
]
*/
</code></pre>
<pre><code class="language-javascript">
const text = &quot;2025-09-15, 2024-12-31&quot;;
const regex = /(\d{4})-(\d{2})-(\d{2})/g;

for (const match of text.matchAll(regex)) {
  const [full, year, month, day] = match;
  console.log({ full, year, month, day });
}

// { full: &quot;2025-09-15&quot;, year: &quot;2025&quot;, month: &quot;09&quot;, day: &quot;15&quot; }
// { full: &quot;2024-12-31&quot;, year: &quot;2024&quot;, month: &quot;12&quot;, day: &quot;31&quot; }

</code></pre>
<h2 id="promiseallsettled">Promise.allSettled()</h2>
<p>Promise 배열 중 1개라도 실패하면 즉시 reject 처리하는 <code>Promise.all</code>과 달리 모든 Promise가 끝날 때 까지 기다리며 실패하더라도 결과 배열에 <code>rejected</code>로 저장함</p>
<ul>
<li>또한, <code>Promise.all</code>은 실패한 Promise 1개만은 반환하여 이후 Promise의 성공/실패 유무를 판단하기 까다로움 + 성공한 promise에 접근할 수 없음</li>
<li>모든 Promise의 결과가 필요할 때 유용하게 사용 가능</li>
</ul>
<pre><code class="language-javascript">
const promises = [
  Promise.resolve(42),
  Promise.reject(&quot;에러 발생&quot;),
  new Promise((resolve) =&gt; setTimeout(() =&gt; resolve(&quot;done&quot;), 1000))
];

Promise.allSettled(promises).then((result) =&gt; {
    console.log(result);
})

/*
[
  { status: &quot;fulfilled&quot;, value: 42 },
  { status: &quot;rejected&quot;, reason: &quot;에러 발생&quot; },
  { status: &quot;fulfilled&quot;, value: &quot;done&quot; }
]
*/
</code></pre>
<h2 id="bigint">BigInt</h2>
<p>매우 큰 정수를 표현하기 위해 도입된 원시 타입, <code>Number.MIN_SAFE_INTEGER ~ Number.MAX_SAFE_INTEGER</code> 범위를 넘어가면 정확도가 낮아질 수 있음</p>
<ul>
<li>BigInt() 생성자 사용 시, 오류 방지를 위해 문자열과 함께 사용하는 것이 좋음.</li>
<li>BigInt와 Number 타입은 연산 시 같이 사용 불가. BigInt 타입 끼리 연산, Number 타입 끼리 연산 가능</li>
</ul>
<h2 id="globalthis">globalThis</h2>
<p>전역 객체를 가리키는 표준화된 식별자, 환경에 따라 동일하게 전역 객체에 접근할 수 있도록 하는 메서드</p>
<pre><code class="language-javascript">
console.log(globalThis); // 브라우저면 window, Node면 global
</code></pre>
<h2 id="importmeta">import.meta</h2>
<p>ES 모듈 환경에서 모듈 자체에 대한 메타데이터를 담고 있는 객체</p>
<ul>
<li>import.meta.url : 현재 모듈 파일의 url을 반환</li>
</ul>
<h2 id="export--as--from-">export * as ~~ from ...</h2>
<p>ES 모듈에서 다른 모듈을 통째로 재추출 할 때 사용하는 방식</p>
<ul>
<li>여러 유틸 함수들을<code>~~.</code> 로 사용할 수 있게끔 묶어서 재추출 할 때 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[FEconf 2025 후기]]></title>
            <link>https://velog.io/@jaehyun_ground/FEconf-2025-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/FEconf-2025-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 25 Aug 2025 02:03:56 GMT</pubDate>
            <description><![CDATA[<p>평소 가보고 싶었던 컨퍼런스인 <a href="https://2025.feconf.kr/">FEconf 2025</a>에 다녀왔다.
신청 일자를 놓친 탓에 이번에도 못 갈뻔 했지만, 다행히 양도를 받아 참여할 수 있었다.</p>
<p>첫 FEconf는 아쉬움이 많이 남았다. </p>
<p>주제에 대한 이해도가 부족한 경우가 있어 내용을 제대로 흡수하지 못한 경우도 있었고, 이전 후기들을 통해 다양한 기업 부스 체험을 기대했지만 작년보다 기업 부스 갯수가 확연하게 줄어 입장 시작 1시간 만에 부스 사은품이 다 떨어진 탓에 스티커만 받아왔다. 부스 사은품이 다 떨어져 사람들이 모이지 않자 관리자분들이 자리를 비우는 경우도 있었고, 부스에 가도 아무런 액션을 취하지 않는 경우도 있었다. 여러 기업 부스에 참여해 굿즈도 받고 채용 관련 상담도 받고 싶었지만 그러지 못한 것이 아쉬웠다.</p>
<p>또 하나 느낀 점은, 혼자보단 지인들과 함께 왔다면 더 좋았을 것 같다는 생각이 들었다. 이번 FEconf는 네트워킹에 중점을 두었다는 느낌을 많이 받았는데 낯가림이 있는 나는 네트워킹 행사를 많이 즐기지 못했다. 지인과 함께 있었다면 조금은 수월하게 네트워킹에 참여할 수 있지 않았을까? 하는 아쉬움이 남았다.</p>
<hr>
<p>나는 Lightning Talk 세션에 많이 참여했다. 다른 세션은 유튜브에 업로드 된다는 이야기를 듣기도 했고, 아래 사진처럼 자리를 잡지 못하면 발표 자료가 잘 안보였기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/d1ba2e71-4f46-49f3-a8a6-49c99823bcd5/image.jpeg" alt=""></p>
<p>Lightning Talk 세션은 다른 세션보단 가벼운 주제지만, 유익한 내용을 많이 들을 수 있었다.</p>
<p>기억에 남는 세션은 </p>
<ul>
<li>비IT기업에서 개발자로 살아남기</li>
<li>킵올빌런 은퇴식</li>
<li>UI 코드 생성 자동화를 프로덕션까지: 우리 팀은 Figma MCP + Cursor(AI)와 함께 일합니다</li>
</ul>
<p>이다. </p>
<p>AI와 관련된 세션이 생각보다 많았고, 특히 Figma MCP + Cursor 환경을 구축해 UI 요소를 빠르게 개발할 수 있는 환경을 구축한 사례를 주제로 한 세션을 들었을 땐 나도 적용해보고 싶다는 생각이 들 정도로 흥미롭게 발표를 들었던 것 같다.</p>
<p>킵올빌런의 은퇴식도 직관할 수 있었다. (기억할게요 킵올빌런)
<img src="https://velog.velcdn.com/images/jaehyun_ground/post/eb214c77-ac2c-42f7-8293-8234ba384fd9/image.jpeg" alt=""></p>
<p>비IT기업에서 개발자로 일하고 있는 내가 취해야 할 자세도 배울 수 있었다. (티를 팍팍 내자)
<img src="https://velog.velcdn.com/images/jaehyun_ground/post/7137dc27-552e-4f19-b132-617d17628103/image.jpeg" alt=""></p>
<hr>
<p>메인 세션의 발표 내용을 제대로 흡수하지 못한 것은 아쉽지만, Lightning Talk 세션에서 유익한 정보와 내용을 많이 얻을 수 있었다.</p>
<p>다음 FEconf가 열리기 전까지 많이 성장해서 내년에는 메인 세션의 내용도 잘 흡수할 수 있었으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[☁️ 하나의 맥북에서 GitHub 개인 & 회사 계정 함께 쓰기]]></title>
            <link>https://velog.io/@jaehyun_ground/%ED%95%98%EB%82%98%EC%9D%98-%EB%A7%A5%EB%B6%81%EC%97%90%EC%84%9C-GitHub-%EA%B0%9C%EC%9D%B8-%ED%9A%8C%EC%82%AC-%EA%B3%84%EC%A0%95-%ED%95%A8%EA%BB%98-%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@jaehyun_ground/%ED%95%98%EB%82%98%EC%9D%98-%EB%A7%A5%EB%B6%81%EC%97%90%EC%84%9C-GitHub-%EA%B0%9C%EC%9D%B8-%ED%9A%8C%EC%82%AC-%EA%B3%84%EC%A0%95-%ED%95%A8%EA%BB%98-%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Fri, 28 Mar 2025 01:48:25 GMT</pubDate>
            <description><![CDATA[<p>회사와 개인 프로젝트를 하나의 맥북에서 함께 작업하다 보면,<br><strong>커밋 계정 꼬임</strong>이나 <strong>푸시 오류</strong>, <strong>퍼블릭/프라이빗 접근 이슈</strong>가 자주 생긴다.</p>
<p>이 글에서는 GitHub <strong>회사 계정</strong>과 <strong>개인 계정</strong>을 각각 SSH 키로 구분해서 사용하는 방법을 정리한다.</p>
<hr>
<h1 id="📁-ssh-폴더가-없다면-직접-만들어주기">📁 .ssh 폴더가 없다면? 직접 만들어주기</h1>
<p>일부 환경에서는 ~/.ssh 폴더가 기본적으로 없을 수도 있다.
이 경우 SSH 키를 저장할 수 있도록 먼저 폴더를 만들어야 한다.</p>
<pre><code class="language-bash">mkdir -p ~/.ssh</code></pre>
<p>-p 옵션은 이미 폴더가 존재해도 에러 없이 넘어가게 해줌</p>
<p>SSH 키는 항상 이 폴더 안에 저장됨</p>
<h2 id="1️⃣-ssh-키-각각-생성-회사--개인">1️⃣ SSH 키 각각 생성 (회사 / 개인)</h2>
<p>터미널에서 각각 아래 명령어를 입력하여 SSH 키를 생성한다.</p>
<h3 id="🔹-개인-계정-키-생성">🔹 개인 계정 키 생성</h3>
<pre><code class="language-bash">ssh-keygen -t ed25519 -C &quot;your_personal_email@example.com&quot;</code></pre>
<p>프롬프트가 뜨면 아래처럼 입력:</p>
<pre><code>~/.ssh/id_ed25519_personal</code></pre><p>→ 엔터 2번 (패스프레이즈(비밀번호)는 생략 가능)</p>
<pre><code class="language-bash">|#*+ .o==+        |
|== .  .=o        |
| o.E .           |
|. o   .. .       |
|oo   .. S        |
|o .  o.  o o     |
|    o o.  *      |
|   + +o.oo o     |
|ooo.+o=+oo.      |</code></pre>
<p>이런 이모지 나오면 성공</p>
<hr>
<h3 id="🔹-회사-계정-키-생성">🔹 회사 계정 키 생성</h3>
<pre><code class="language-bash">ssh-keygen -t ed25519 -C &quot;your_work_email@company.com&quot;</code></pre>
<p>프롬프트가 뜨면 아래처럼 입력:</p>
<pre><code>~/.ssh/id_ed25519_work</code></pre><p>→ 엔터 2번 (패스프레이즈(비밀번호)는 생략 가능)</p>
<pre><code class="language-bash">|#*+ .o==+        |
|== .  .=o        |
| o.E .           |
|. o   .. .       |
|oo   .. S        |
|o .  o.  o o     |
|    o o.  *      |
|   + +o.oo o     |
|ooo.+o=+oo.      |</code></pre>
<p>이런 이모지 나오면 성공</p>
<hr>
<h3 id="📝-키-이름은-자유롭게-정해도-됨">📝 키 이름은 자유롭게 정해도 됨</h3>
<p>예시:</p>
<pre><code class="language-bash">~/.ssh/id_github_personal
~/.ssh/id_github_work</code></pre>
<hr>
<h2 id="2️⃣-ssh-키-확인">2️⃣ SSH 키 확인</h2>
<pre><code class="language-bash">ls ~/.ssh</code></pre>
<p>아래 4개가 보이면 OK:</p>
<pre><code>id_ed25519_personal
id_ed25519_personal.pub
id_ed25519_work
id_ed25519_work.pub</code></pre><hr>
<h2 id="3️⃣-github에-공개-키-등록">3️⃣ GitHub에 공개 키 등록</h2>
<pre><code class="language-bash">개인 github

cat ~/.ssh/id_ed25519_personal.pub</code></pre>
<pre><code class="language-bash">회사 github

cat ~/.ssh/id_ed25519_work.pub</code></pre>
<p>명령어 입력 후 나오는 값을 복사하여
[GitHub → Settings → SSH and GPG Keys]에서 각각의 계정에 등록.  </p>
<hr>
<h2 id="4️⃣-ssh-설정-파일-수정-sshconfig">4️⃣ SSH 설정 파일 수정 (<code>~/.ssh/config</code>)</h2>
<pre><code class="language-bash">nano ~/.ssh/config</code></pre>
<p>아래 내용 추가:</p>
<pre><code class="language-ssh"># 개인 계정
Host personal
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_personal
  IdentitiesOnly yes

# 회사 계정
Host work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes</code></pre>
<hr>
<h3 id="📝-host-이름도-자유롭게-지정-가능">📝 Host 이름도 자유롭게 지정 가능</h3>
<p>예: `gh-personal`, `gh-work`, `github-jaehyun`, `office`, `home` 등<br>중요한 건 나중에 clone 또는 remote 설정 시 `git@<Host>:...` 형태로 쓸 거라는 점!</p>
<hr>
<h2 id="5️⃣-ssh-연결-테스트">5️⃣ SSH 연결 테스트</h2>
<pre><code class="language-bash">ssh -T git@personal</code></pre>
<pre><code class="language-bash">ssh -T git@work</code></pre>
<p>결과:</p>
<pre><code>Hi your-username! You&#39;ve successfully authenticated, but GitHub does not provide shell access.</code></pre><p>개인 or 회사 github 계정의 이름이 나오면 성공</p>
<hr>
<h2 id="6️⃣-💻-로컬에-이미-있는-프로젝트-설정">6️⃣ 💻 로컬에 이미 있는 프로젝트 설정</h2>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/fb7434a3-906d-4d74-9a5a-10bfbc73b576/image.png" alt=""></p>
<p>레포 클론 시, SSH 탭을 눌렀을 때 나오는 url 복사 후 </p>
<pre><code>
  git@github.com:~~/~~.git
</code></pre><p>  을</p>
<pre><code>
  git@personal:~~/~~.git
</code></pre><p>  로 바꿔 준다고 생각하면 됨. (회사 프로젝트라면 personal 대신 work가 들어가야함.)</p>
<h3 id="📁-개인-프로젝트">📁 개인 프로젝트</h3>
<pre><code class="language-bash">git remote set-url origin git@personal:username/repo.git
git config user.name &quot;your user name&quot;
git config user.email &quot;your_personal_email@example.com&quot;</code></pre>
<hr>
<h3 id="🏢-회사-프로젝트">🏢 회사 프로젝트</h3>
<pre><code class="language-bash">git remote set-url origin git@work:orgname/repo.git
git config user.name &quot;Jaehyun Ahn&quot;
git config user.email &quot;your_work_email@company.com&quot;</code></pre>
<hr>
<h2 id="7️⃣-📦-새로-clone-해올-때">7️⃣ 📦 새로 clone 해올 때</h2>
<h3 id="📁-개인-계정에서-clone">📁 개인 계정에서 clone</h3>
<pre><code class="language-bash">git clone git@personal:username/repo.git
cd repo
git config user.name &quot;your name&quot;
git config user.email &quot;your_personal_email@example.com&quot;</code></pre>
<hr>
<h3 id="🏢-회사-계정에서-clone">🏢 회사 계정에서 clone</h3>
<pre><code class="language-bash">git clone git@work:orgname/repo.git
cd repo
git config user.name &quot;your name&quot;
git config user.email &quot;your_work_email@company.com&quot;</code></pre>
<hr>
<h2 id="✅-마무리-체크리스트">✅ 마무리 체크리스트</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>개인용</th>
<th>회사용</th>
</tr>
</thead>
<tbody><tr>
<td>SSH 키</td>
<td>`id_ed25519_personal`</td>
<td>`id_ed25519_work`</td>
</tr>
<tr>
<td>공개 키 등록</td>
<td>개인 GitHub 계정</td>
<td>회사 GitHub 계정</td>
</tr>
<tr>
<td>`~/.ssh/config` 설정</td>
<td>`Host personal`</td>
<td>`Host work`</td>
</tr>
<tr>
<td>clone 주소</td>
<td>`git@personal:username/...`</td>
<td>`git@work:orgname/...`</td>
</tr>
<tr>
<td>커밋 사용자</td>
<td>개인 이메일 &amp; 이름</td>
<td>회사 이메일 &amp; 이름</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-참고">🧠 참고</h2>
<ul>
<li><code>git remote -v</code> 로 현재 remote 주소 확인 가능</li>
<li>커밋 계정 확인: <code>git config user.name</code>, <code>git config user.email</code></li>
<li>전역 설정은 <code>~/.gitconfig</code>, 레포별 설정은 <code>.git/config</code>에 저장됨</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Script 컴포넌트는 서버 환경에서 동작할까? (Feat. 서버 사이드 렌더링)]]></title>
            <link>https://velog.io/@jaehyun_ground/Next.js-Script-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C%EB%8F%84-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@jaehyun_ground/Next.js-Script-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C%EB%8F%84-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Thu, 20 Mar 2025 06:53:17 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">Intro</h2>
<p>최근 회사에서 내가 맡은 업무 중 하나는 Next.js 프로젝트에 <a href="https://velog.io/@yjinhann/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Presentation-Container"><strong>Presentation &amp; Container 디자인 패턴</strong></a>을 적용해 UI와 로직을 나누고, <strong>Storybook + Chromatic</strong> 조합으로 시각적 회귀 테스트를 진행하는 것이었다.</p>
<p>최근 프로젝트에 단위 테스트를 도입하면서 코드 리팩토링까지 겸하여 진행 중인데, 이 과정에서 예기치 못한 UI 변동을 방지 하기 위해 시각적 회귀 테스트를 도입하게 되었다.</p>
<blockquote>
<p>시각적 회귀 테스트란?
코드 변경 전/후의 스크린샷을 비교해 차이를 감지하고 예기치 못한 오류를 확인하여 <strong>UI의 시각적 일관성을 제공할 수 있도록 하는 테스트</strong>이다. <a href="https://techblog.woowahan.com/17081/">참고</a></p>
</blockquote>
<p>진행 도중, 사수분께서 나에게 한 가지 요청을 하였다.</p>
<blockquote>
<p>Next.js의 Script 컴포넌트가 서버 사이드에서 작동이 안되는지 확인 해줄래요?</p>
</blockquote>
<p>나도 모르는 내용이었기에, 검증을 위한 테스트 코드를 작성하며 클라이언트 환경과 서버 환경을 확인하는 작업이 필요했다.</p>
<h2 id="테스트-해보자🔥">테스트 해보자🔥</h2>
<h3 id="script-컴포넌트가-뭔데">Script 컴포넌트가 뭔데?</h3>
<p>검증하기 전, Script 컴포넌트가 어떤 역할을 하는 컴포넌트인지 알 필요가 있었다.</p>
<p><a href="https://nextjs.org/docs/app/api-reference/components/script">Script 컴포넌트</a>란 <strong>HTML의 script 태그를 최적화</strong> 해주는 컴포넌트다. 보통 script 태그는 GA를 연동하거나, 다른 웹사이트 or 기능과 상호작용이 필요할 때 사용하게 된다.
스크립트를 언제 어떻게 불러올 지 정하려면 일반 script 태그를 사용하는 것이 아닌 Next.js에서 제공하는 Script 컴포넌트를 사용하는 것이 좋다.</p>
<p>Script 컴포넌트는 <code>strategy</code> 속성을 사용할 수 있는데 속성 값은 아래 4가지를 사용할 수 있다.</p>
<ul>
<li>beforeInteractive : 페이지를 다 불러와서 상호작용 하기 전에 스크립트를 불러오는 것</li>
<li>afterInteractive (기본값) : 페이지를 다 불러온 다음 스크립트를 불러오는 것.</li>
<li>lazyOnload : 스크립트를 다른 모든 데이터나 소스들을 불러오고 나서 불러오는 전략</li>
<li>worker : 실험적 속성 값으로, 아직 정식으로 사용되진 않는다.</li>
</ul>
<p>서버 환경에서 렌더링 준비를 마친 HTML과 JS 파일을 클라이언트에 보낸 뒤, 클라이언트 환경에서 HTML과 JS 코드를 매칭시키는 과정인 <strong>Hydrate</strong>(하이트레이트) 순서와 연관지어 사용할 수 있는 속성이라고 볼 수 있다.</p>
<h3 id="테스트-진행-결과는">테스트 진행 결과는..</h3>
<p>테스트 코드는 아래와 같이 작성했다.</p>
<pre><code class="language-javascript">// app/page.tsx
import Script from &#39;next/script&#39;;

export default function Page() {
  console.log(&#39;app/page.tsx 렌더링 중...&#39;);

  return (
    &lt;div&gt;
      &lt;h1&gt;Script 컴포넌트 테스트&lt;/h1&gt;
      &lt;Script src=&quot;/test-script.js&quot; /&gt;
    &lt;/div&gt;
  );
}

// public/test-script.js
console.log(&#39;🔥 script 실행됨&#39;);</code></pre>
<p>서버 사이드 환경에서 Script 컴포넌트가 동작되는지 확인하기 위해 <code>page.tsx</code>를 서버 컴포넌트로 만든 뒤 Script 컴포넌트를 통해 <code>test-script.js</code> 파일을 가져오도록 코드를 작성했다.</p>
<p>이후 터미널에서 프로젝트르 실행해보면 서버 로그를 확인할 수 있다.</p>
<p>터미널과 브라우저 콘솔 창을 확인해보면 아래 사진과 같은 결과를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/1c2466be-4b1f-4924-8ad9-6f1373e97e11/image.png" alt="">
<img src="https://velog.velcdn.com/images/jaehyun_ground/post/446d0931-2061-4b94-a5af-329cffac4a63/image.png" alt=""></p>
<p>위는 서버 환경에서의 로그, 아래는 클라이언트 환경에서의 로그를 나타낸다. 이를 통해 알 수 있는 내용은,</p>
<ol>
<li><code>page.tsx</code>는 서버 컴포넌트로서 서버 환경에서 렌더링됐다.</li>
<li>script는 클라이언트 환경에서 실행됐다. </li>
</ol>
<p>Script 컴포넌트를 서버 컴포넌트 내에 작성하면, 서버에서 렌더링은 되지만 실행은 클라이언트에서 되는 것을 알 수 있다.</p>
<p>왜 이런 결과가 나왔는지는 SSR(서버 사이드 렌더링) 과정을 살펴보면 유추해볼 수 있다.</p>
<h3 id="ssr-서버-사이드-렌더링">SSR (서버 사이드 렌더링)</h3>
<p>SSR (서버 사이드 렌더링)이란, 서버에서 페이지를 그려 클라이언트(브라우저)로 보낸 후, 화면에 표시하는 기법을 의미한다.</p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/f64942f9-c444-4167-aae3-12315ff59d50/image.png" alt=""></p>
<p>위 사진처럼, 사용자가 SSR 페이지에 접속한다면, 서버에서는 렌더링 준비를 마친 HTML과 JS 코드를 클라이언트에 전달하게 된다. </p>
<p><img src="https://velog.velcdn.com/images/jaehyun_ground/post/43532442-0d2a-4c66-a854-668d097fa6aa/image.png" alt=""></p>
<p>따라서 사용자는 페이지에 접속하면 빠르게 서비스 화면을 볼 수 있게 된다.
하지만, JS 코드는 클라이언트에서 실행되기 때문에 JS 코드 다운로드가 마무리되기 전까진 버튼을 클릭하는 등의 동작은 실행되지 않는다.</p>
<h2 id="결론은-">결론은 ?</h2>
<p>따라서 Script 컴포넌트를 서버 컴포넌트 내에 작성하면, 서버에서 렌더링은 되지만 실행은 클라이언트에서 되는 이유를 SSR과 연관지어 유추해본다면</p>
<p><strong>서버 사이드 렌더링 과정이 서버에서 완성된 HTML과 JS 파일이 클라이언트로 전송되어 JS 적용은 클라이언트에서 되기 때문에, <code>strategy</code> 속성과 관계 없이 script는 클라이언트 환경에서만 실행된다.</strong></p>
<p>로 결론 지을 수 있다.</p>
<hr>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://techblog.woowahan.com/17081/">https://techblog.woowahan.com/17081/</a></li>
<li><a href="https://nextjs.org/docs/app/api-reference/components/script">https://nextjs.org/docs/app/api-reference/components/script</a></li>
<li><a href="https://hahahoho5915.tistory.com/52">https://hahahoho5915.tistory.com/52</a></li>
<li><a href="https://dev-ellachoi.tistory.com/28">https://dev-ellachoi.tistory.com/28</a></li>
<li><a href="https://hyunseo-fullstackdiary.tistory.com/305">https://hyunseo-fullstackdiary.tistory.com/305</a></li>
<li><a href="https://helloinyong.tistory.com/315">https://helloinyong.tistory.com/315</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>