<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>naro-Kim.log</title>
        <link>https://velog.io/</link>
        <description>멘티를 넘어 멘토가 되는 그날까지 파이팅</description>
        <lastBuildDate>Sat, 02 Mar 2024 08:04:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>naro-Kim.log</title>
            <url>https://velog.velcdn.com/images/naro-kim/profile/0b845a2f-82bc-4bba-a18c-cac205e3cb26/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. naro-Kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/naro-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2024년 1월과 2월을 돌아보며 (ft.생애 첫 과제테스트)]]></title>
            <link>https://velog.io/@naro-kim/2024-%EC%B2%AB-%EB%8B%AC%EA%B3%BC-%EB%91%90%EB%B2%88%EC%A7%B8-%EB%8B%AC%EC%9D%98-%EB%82%98%EB%8A%94-ft.%EC%83%9D%EC%95%A0-%EC%B2%AB-%EA%B3%BC%EC%A0%9C%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@naro-kim/2024-%EC%B2%AB-%EB%8B%AC%EA%B3%BC-%EB%91%90%EB%B2%88%EC%A7%B8-%EB%8B%AC%EC%9D%98-%EB%82%98%EB%8A%94-ft.%EC%83%9D%EC%95%A0-%EC%B2%AB-%EA%B3%BC%EC%A0%9C%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sat, 02 Mar 2024 08:04:56 GMT</pubDate>
            <description><![CDATA[<p>어느덧 2024년 3월에 가까워지는 시기입니다. 드디어 길고 길었던 학부에서의 시간을 떠나, 복수학위증도 받고 사회로 첫걸음 하는 시기가 왔어요. 그리고 여러 기업에 이력서를 준비해 실패와 성공도 경험하며 면접 경험도 더 업그레이드 해왔습니다! 작년에 준비했던 취준이 일반 게임이었다면, 이번 년도의 취준은 랭겜으로 접어들었습니다 🔥🔥🔥</p>
<h2 id="✅-올해의-첫-달과-두번째-달은-어떻게-보냈나요">✅ 올해의 첫 달과 두번째 달은 어떻게 보냈나요?</h2>
<h3 id="1월에는--">1월에는 -</h3>
<p>1월에는 부트캠프(독서실)에 잠깐 다니며 이력서와 포트폴리오를 다듬는 시간을 가졌습니다. 개인적으로 부트캠프가 필요하진 않았지만 독서실 대신 다니면서 교육 장소에서 9to9으로 공부하며 공부 습관에 익숙해지고 취준의 마음가짐을 다잡고자 했어요. </p>
<p>확실히 혼자 공부하는 것보다 동료들과 동고동락하면서 서로 멘탈도 잡아주고 스트레스 푸는 경험이 좋았어요. 또, 다양한 백그라운드를 가진 사람들과 서로 경험과 생각을 나누며 소통할 수 있어 열정을 주고 받을 수 있었고 취뽀 후 다시 어셈블하기로 약속했어요! 👍👍</p>
<p>그래서 1월의 활동들을 요약하자면..</p>
<blockquote>
<ol>
<li>Winter wonderland 프로젝트 리팩토링 및 기능 추가 (O)</li>
<li>코테풀이 100 solve (O)</li>
<li>이력서, 포트폴리오 초안 제작 (O)</li>
<li>중견기업 최종 합격 (O)</li>
<li>모의면접으로 진행하는 컴퓨터 네트워크 스터디 참여 (O)</li>
<li>공부 습관 들이기 (O)</li>
<li>블로그 습관 들이기 (O)</li>
</ol>
</blockquote>
<p>이렇게, 1월은 차근차근 공부 습관을 들이는 기간이었던 것 같아요. 특히 모의면접 스터디로 학부 수업에서 배웠던 CS 지식도 다시 복습하며 <strong>왜 이런 내용을 배웠고, 어떻게 활용해야 할지</strong> 감잡을 수 있었어요. CS 지식을 실제 기술에 적용하는 것은 별개의 이야기겠지만, 소프트 스킬에서 팀원들의 많은 도움을 받을 수 있었어요. 특히 모의 면접을 거치며 시선 처리나 발성, 답변의 호흡을 처리하는 방법도 체득할 수 있었어요. 아직 기술 면접에서 네트워크와 관련해 깊은 질문을 받진 않았지만 앞으로의 면접에 자신감을 얻게된 계기였어요.</p>
<p>또, 포트폴리오 작성을 병행하면서 프로젝트의 트러블 슈팅 과정을 돌아보았어요. 다이어그램을 그려가며 상태 관리의 흐름도 되짚어보고, 당시에 선택한 방법이 최적이었는지 돌아보았어요. 그리고 만일 문제 해결을 위해 블로그만 따라했었다면 왜 그런 해결 방법이 제시된 것인지 근본적인 논리로 돌아가 공부할 수 있었어요. 이때 작성한 포트폴리오는 사실 타인이 보기엔 바로 기술적인 면을 이해하기 어려운 다이어그램들로 이루어져있었는데요, 스스로 도입의 이유와 근거를 정리하며 더욱 탄탄한 이력서를 만드는데 큰 도움이 되었어요. 포트폴리오는 3월에 웹으로 개편하기로 계획했습니다!</p>
<h3 id="아쉬웠던-것들">아쉬웠던 것들</h3>
<p>아쉬웠던 점들은 대부분 서류 제출과 관련한 것들이었어요. </p>
<p>특히 제가 관심있었던 에듀테크 스타트업이 있는데, 포트폴리오의 일부 링크가 깨진 상태로 제출되었어요. 노트북과 데탑의 OS도 다르기 때문에 pdf viewer로 아크로뱃과 웹 등 다양하게 체크했음에도 불구하고 로컬 파일과 다르게, 제출했던 pdf를 웹에서 다운받으면 link outline이 생기는 문제가 발생했었어요. </p>
<p>이미 제출한 뒤라 당황했지만 채용담당자님께 메일을 드려 수정한 파일을 메일로 보내드렸어요. 그런데 또 똑같은 문제가 발생해, 수정된 파일 역시 깨진 채 다운로드 되는 걸 확인했고.. 해당 스타트업에선 서류 탈락해버렸습니다.</p>
<p>그 이후로 링크를 아예 다 지우고 새로 생성하여 export하니 해결되어서, 지금은 무사히 이력서를 돌리고 있지만, 제출 전에 체크와 테스트를 반복해야 한다는 걸 뼈저리게 느낀 경험이었습니다.</p>
<h3 id="배운-것들">배운 것들</h3>
<p>한 달동안 이런 활동들을 하면서 기업마다 선호하는 이력서 양식이 다르다는 느낌을 받았어요.</p>
<blockquote>
</blockquote>
<p>대기업 : 프로젝트의 나열과 간략한 소개, 짧은 역할 소개 
스타트업 : 프로젝트의 나열과 해당 프로젝트에서 경험한 이슈 트래킹, 트러블 슈팅</p>
<p>이런 큰 분류를 나누고 프로젝트를 나열 시 기여한 부분은 커밋 단위로 상세히 적고, 역할 소개에서도 성과의 수치를 계산해 기입하고자 노력했어요. 이력서를 다루는 것에 어느정도 익숙해져서, 자기소개서 제출이 필수가 아닌 곳을 위주로 서류를 지원해보기 시작했습니다.
그리고 이렇게 만든 이력서를 다른 사람들로부터 피드백 받고자 부트캠프 사람들에게도 공유했지만, 2월엔 프론트엔드 현직자분들에게 피드백을 받고자 계획했습니다.</p>
<h3 id="2월에는-그래서">2월에는 그래서..</h3>
<p>2월에는 부트캠프를 그만뒀어요. 해당 부트캠프 과정이 프론트엔드 개발과 거리가 더욱 더 멀어졌고, 공부 습관도 이미 잡힌데다 최종 합격한 곳이 생기면서 취준에 보다 더 집중하고 싶다 생각했어요. 첫 취뽀한 곳도 좋은 곳이었지만, 이력서와 포트폴리오를 완전히 완성하지 않았어서 완성한 상태라면 어떤 기업에 갈 수 있을까?라는 궁금증도 들고, 스스로 가능한 곳까지 더 준비해보자는 욕심을 내봤어요.</p>
<p>그래서 2월 동안의 세웠던 계획과 활동은 아래와 같습니다.</p>
<blockquote>
<ol>
<li>원티드 프리온보딩 FE 2월 챌린지 (O)</li>
<li>프론트엔드 현직자들과의 커피챗 (O)</li>
<li>링크드인 네트워킹 (O)</li>
<li>이력서 완성 (O)</li>
<li>면접 피드백 (O)</li>
</ol>
</blockquote>
<p>이 목표들을 위해서 2월의 첫째 주, 둘째 주는 진짜 누가봐도 바쁘게 지냈어요. 원티드 프리온보딩 챌린지에서는 기존에 알고있던 개념을 다시 돌아보며, 면접에 대응할 수 있도록 복기하는 시간이었어요. 강사님께서 프론트엔드 개발의 큰 개념과 개괄을 짚어주고 수강생들끼리 채팅으로 개발 과정에서 경험했던 어려움을 나누기도 하면서 기술적인 문제들을 하나씩 짚어갈 수 있었어요. 여기서 정리했던 내용들은 블로그 포스트로 정리해 공유할 계획입니다.</p>
<p>특히, debounce 훅을 만들면서 프리온보딩 디스코드에서 도움을 받아 문제를 해결하고 FormElement도 다시 찾아 공부했습니다. 알고 있다 생각했던 엘리먼트나 개념들을 다시 점검해야함을 배운 기회였어요.</p>
<p>그리고 온라인으로 네트워킹을 하는 것이 익숙치 않았는데 이번 챌린지에서 아래처럼 &#39;이력서 리뷰 채널&#39;을 잠시 운영하면서 다른 개발자분들과 소통하고, 서로 아는 기술 지식도 공유했습니다. 제 이력서를 점검하는 기회이기도 했지만, 정말 다양한 이력과 놀라운 활동을 하신 분들이 다양해서, 에너지를 받을 수 있었습니다. 저또한 다른 분들의 이력서에서 디자인적인 요소와 구성에서 최대한 도움을 드리고자 노력했습니다.</p>
<img src="https://velog.velcdn.com/images/naro-kim/post/41c8f8f2-ec26-4c4d-8904-ad01e4b4a06e/image.png" width="50%" height="50%">

<p>프로젝트를 인사담당자에게 어떻게 쉽게 읽히게 할까?를 고민했던 인사이트를 주고 받아 수정했어요! 이외에도 사람인의 커리어매치 기능으로 카카오 계열사 현직자분과 이력서 첨삭을 진행하기도 하고 서류의 완성도를 높여갔습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/af029115-99d1-4ebc-b69a-676b7e6dea0f/image.png" alt="">
그리고, 자기소개서도 준비하기 위해 현직자분과 링크드인 네트워킹을시도해보았습니다.
링크드인으로 누군가에게 메세지를 보내는 것은 처음이었는데, 지원하고자 하는 회사의 현직자분께 먼저 연락드리는 용기를 내어보기도 했어요! 직접 만나뵐 수 있었다면 더 좋았겠지만, 사실 현직자시니까 바쁘실텐데 시간을 내주신 것 자체에 감사의 마음을 담아 커피를 보내드렸습니다. 취뽀를 이루고나면 다시 한 번 좋은 소식으로 연락드리기로 ..</p>
<p>그리고 벨로그를 통해 커피챗 신청도 받으면서 또 새로운 개발자 분과 인사이트를 공유했습니다. 이전까지, 프론트엔드 분야에서 직접 나서 먼저 연락을 취한다던가 네트워킹을 하지않았었는데 이번 달엔 큰 도전을 하며 한 걸음 성장할 수 있었습니다! </p>
<h3 id="2월의-성과">2월의 성과</h3>
<p>마지막으로, 원티드 챌린지 강의를 진행해주셨던 현직자님의 파이널 첨삭으로 이력서를 완성했어요! 2월 15일 부터는 13개의 스타트업 기업에 서류를 제출하였고, 6개 기업에서 서류 합격 결과를 받았어요. 약 50%에 달하는 합격률을 보면서, 1월과 2월의 노력이 헛된게 아닌 걸 느꼈어요. 나도 할 수 있구나!라는 성취감에 긴 시간 준비하면서도 지치지 않았던 같아요.</p>
<img src="https://velog.velcdn.com/images/naro-kim/post/6a455cfe-94ff-4e5e-aeed-6b97ca91964e/image.png" width="50%" height="50%">

<hr>
<h2 id="💡-스타트업-사전-인터뷰를-봤어요">💡 스타트업 사전 인터뷰를 봤어요</h2>
<p>그리고, 얼마전 서류 합격을 받은 스타트업 중 한 곳에서 &#39;사전 인터뷰&#39;를 진행했어요. 사실, 경력 채용 공고가 올라와있던 곳에 지원한터라 스스로도 자신이 없었어요. 하지만, 결과적으로는 이 사전 인터뷰를 통해서 저의 강점을 돌아보고 자신감을 얻을 수 있었어요.</p>
<p>원티드 서류 합격이 이뤄지자, 해당 스타트업의 HR팀에서 먼저 전화를 주셨어요. 그리고 <strong>사전 인터뷰 &gt; 기술 인터뷰 &gt; 리더십 인터뷰</strong>로 이루어질 과정에 응시할 것인지 여부를 여쭤보셨어요. 이전에 사용해본 프로덕트이기도 하고 경험을 쌓을 좋은 기회라 판단한 저는 당연히 사전 인터뷰에 응하기로 했어요!</p>
<h3 id="진행-과정">진행 과정</h3>
<p>사전 인터뷰는 2월 23일, 정오부터 1시간 동안 비대면으로 진행했습니다. 집에 고양이가 있어 인터뷰에 방해를 받을 수 있기 때문에 장소는 스터디룸을 빌려 준비했어요. 인터뷰 과정 자체는 이력서의 내용들과 지원자가 회사에 대해 어떻게 알고있고 무슨 생각을 하고있는지 서로 확인하는 시간이었어요. 
면접관 님은 회사의 서비스 팀 리더분이셨고 주로 해당 회사의 도메인과 관련한 경험을 갖고있는지 질문을 받았어요. </p>
<p>그리고, 디자인 주전공, 컴퓨터공학 복수전공이기에 CS 지식 면에서 스스로 생각하는 부족한 점은 없는지 여쭈어 보셨어요. 이외에 프로젝트에서 타 직군과 협업에 있어 어려웠던 점들도 여쭤보셨습니다. 이외에도 회사의 개발 문화와 제가 잘 맞을 수 있을지, 이력을 돌아보며 확인하는 시간이었습니다.</p>
<p>이어서는 제가 회사에 대해 궁금한 점들을 여쭙는 시간이 이어졌어요. 제가 드렸던 질문 중에 가장 중요했다고 생각한 질문은 <strong>어떠한 이유로 경력직 채용 공고에서 신입인 저의 이력서를 보고 연락을 주셨는지?</strong>라고 생각합니다. 사전 인터뷰 여부에 대해 여쭤보실 때 부터 머리에 맴돌던 생각인데, 회사의 생각을 알고 싶었고 회사에서 <strong>저에게 어떠한 기대</strong>를 품고있는지 확인하려는 의도였습니다. 그리고, 이 질문을 통해서 저도 주도적으로 회사 채용의 구조를 파악하고자 노력한다는 것을 어필하고 싶었어요.</p>
<p>질문에 대해서는 다음과 같이 답변을 받았습니다. &quot;회사에서 빠르게 서비스 프로덕트를 개발해야 하기에 채용할 사람의 실력도 중요하지만 <strong>컬쳐핏</strong>이 중요하다 느꼈기에, 서류에서 에너지와 열정이 보인 아현님이 궁금했다&quot;</p>
<p>이 한마디에 제 활동이 인정받는 느낌을 받았습니다.. (감동x100) 그리고 이후에는, 부족하더라도 신입과 인턴 포지션으로도 채용할 생각을 가지고 계신지 여쭈어 보았고 면접관님께서 현재 신입과 인턴 TO는 없지만, 채용 프로세스가 마무리된 이후에 내부 판단에 따라 해당 포지션으로 오퍼가 갈 수도 있다고 말씀해주셨어요.</p>
<h3 id="배운-점">배운 점</h3>
<blockquote>
<p>서류가 붙었다면 그 회사가 나를 뽑을 충분한 이유가 있다는 것이고 경력직을 뽑더라도 내가 먼저 인턴 혹은 신입 포지션을 역제안할 수 있다.</p>
</blockquote>
<p>물론 진리의 회바회가 존재하므로 지원하는 기업의 문화에 맞춰 제안해야겠죠! 그리고, 경력에 지원하고 신입 포지션을 말씀드린다면 오히려 자신감이 떨어져보일 수도 있으니 면접의 분위기에 따라 조심히 제안해야 할 것 같습니다.</p>
<p>이런 이야기를 주고받은 이후에는 짧게 팀 문화와 회사 생활도 여쭤보고 주로 사용하고 계시는 프레임워크, 스타일시트, 디자인 패턴 등등.. 취준하면서 프론트엔드 방법론에서 궁금했던 기술적인 것들을 잔뜩 여쭈어봤습니다. 사실 기술적인 면에서도 이거저거 더 묻고 여쭙고 싶은 것들도 많았지만 스터디룸 예약을 1시까지 해둬서 빠르게 나올 수 밖에 없었어요.</p>
<p>특히 여쭙고 싶었던 것은 도메인에서 web audio API를 활용할 때, 객체형 프로그래밍을 사용하신다 하셨는데 함께 실무를 진행하게 된다면 어떤 책 혹은 문서를 공부하고 오면 도움이 될지 여쭙고 싶었어요. 이 부분은 audio 관련 학부연구생을 했던 친구의 도움을 받아 짧게 web audio API를 사용하며 다음 면접을 준비해보고 있습니다.</p>
<hr>
<h2 id="나의-생애-첫-과제테스트">나의 생애 첫 과제테스트</h2>
<p>그리고 2월의 마지막 주엔 처음으로 <code>과제테스트</code>를 경험했습니다. 테스트는 제가 지정하는 날짜부터 시작이었고, 졸업식과 약속이 있는 2월 24일 이후인 2월 25일 ~ 2월 27일 3일간 진행했어요. 앞서 사전 인터뷰를 본 회사와는 다른 회사였지만, 도메인에 대한 궁금증이 있었고 무엇보다 과제테스트를 경험하고 싶어 지원했던 회사였어요.</p>
<p>사실, 서류가 통과할지도 긴가민가했는데 합격에 과제테스트 전형에 응시할 수 있어 뿌듯했어요! 과제테스트의 요구사항과 과제 파일은 메일로 전달받았고, 테스트 응시 후 Git repository에 담당자를 초대한 뒤 리뷰받는 형태였어요. 주어진 요구사항들을 Figma와 Notion을 통해 확인하고 Storybook을 통해 해당 회사가 가진 디자인 시스템에도 접근해볼 수 있었어요. </p>
<p>차근차근 요구사항을 살펴보고 UI 구현 &gt; API data fetch 구현 &gt; 애니메이션 구현의 순서로 개발하고자 했어요. 그리고 과제 테스트에 응시하며 가장 신경썼던 점은 <strong>관심사 별로 로직을 분리하는 것</strong>이었습니다. 컴포넌트를 그리는 로직은 컴포넌트 로직대로, 상태관리와 관련된 로직은 상태관리 로직대로 나누고자 했지만 사실 아직도 어떤 방식으로 분리하고 나눌지는 고민이에요. 그리고 무엇보다 일단 요구사항을 구현하고 이후에 관심사에 따른 파일 분리를 진행하며 완성까지 시간이 더욱 지체됐던 경험이라 생각합니다.</p>
<h3 id="아쉬웠던-점">아쉬웠던 점</h3>
<blockquote>
</blockquote>
<ul>
<li>debounce 처리 없음</li>
<li>하드코딩 스타일링 : px단위를 theme 파일이나 token으로 관리하지 않았어요.</li>
<li>반응형 대응 없음 : 요구사항에도 없었고 디자인 상에 반응형 시안이 존재하지 않았기에 mediaQuery 적용을 미루었다 반응형 대응을 하지 못해 아쉬웠어요.</li>
<li>테스트 코드 미작성</li>
<li>컴포넌트 스토리 미작성</li>
<li>gitignore에 yarn 추가 깜빡한거 ! : yarn cache를 깃에 몽땅 올려버린 실수.. 충분히 인지하던 점인데 너무 아쉽다. </li>
</ul>
<p>그 외에도 여러 부분들이 마음에 걸리던 과제 테스트였습니다.
Notion으로 전달받은 기능 중 일부가 Figma 디자인에 존재하지 않는다는 걸 늦게 알아차렸어요. 이 부분을 Git issue 혹은 채용담당자님과 커뮤니케이션했어야 하지 않았을까 생각해요. 실무에서도 이런 누락이 존재할 수도 있는데, 어떻게 대응할 것인지 판단할 수 있는 항목이었던 것 같아요.</p>
<p>그리고 다음으로 고민이 된 부분은 전역 상태관리 도입의 기준 판단이었습니다. 테이블 컴포넌트를 만들면서 서버로부터 총합에 대한 값을 받아야 하는데 해당 값을 NavBar 컴포넌트와 Detail Page의 테이블에도 사용해야 했어요. 이런 데이터는 전역 상태관리를 통해 관리하면 편리하겠지만, 제출까지 4시간도 안남았던 터라 Readme 파일 정리, 기능 요구사항 재확인, 애니메이션 적용 등등 마무리해야할 작업들이 많이 남아있던 상태였습니다.</p>
<p>그래서 데이터를 Query hook으로 일단 받아두었는데 끝까지 전역상태로 리팩토링하고 마무리하지 못해 아쉬웠어요. 만일 빠르게 판단했더라면 데이터 변경이 일어나지 않는 값이었으므로 contextAPI를 사용해 관리했을 수 있었을 거라 생각합니다.</p>
<h3 id="배운-점-1">배운 점</h3>
<blockquote>
<p>1년 전과 비교하면, 나 많이 성장했구나! 앞으로 공부해야할 것들이 무엇인지 확실히 알게 되엇따</p>
</blockquote>
<p>그럼에도 이 테스트를 거치면서, 설계부터 구현 과정의 순서를 되짚어 보았어요.
이전까지 <strong>&quot;관심사 별로 분리한다&quot;</strong>는 개념이 이해하기 어려웠는데, 다양한 아티클과 세미나 영상을 보면서 직접 코드로 개념을 이해해보는 시도를 했어요. 비즈니스 로직과 컴포넌트 로직을 나누어 보기도 하고 Data fetch와 전역 상태를 분리하기도 하고, hook에 상태관련 데이터 로직을 모아보기도 하고..</p>
<p><a href="https://www.youtube.com/watch?v=HYgKBvLr49c&amp;t=759s">FEConf KR, 컴포넌트 다시 생각하기</a>
<a href="https://www.youtube.com/watch?v=fR8tsJ2r7Eg&amp;t=17s">Effective component 지속가능한 컴포넌트 </a>
<a href="https://www.youtube.com/watch?v=edWbHp_k_9Y&amp;t=490s">Toss/SLASH 21 - 실무에서 바로 쓰는 Frontend Clean code</a></p>
<p>위의 세미나를 보며 안티 패턴과 좋은 패턴을 직접 짜고 반성하는 시간이었어요. 특히 TOSS SLASH는 FE에게 교과서나 다름없다 생각하는데, 여기서 진유림님께서 말씀하신 클린 코드의 정의에 공감하고 따르려 노력했어요.</p>
<blockquote>
<p>클린 코드는 <strong>찾고 싶은 로직을 빠르게 찾을 수 있는 코드</strong>이다</p>
</blockquote>
<p>내가 짠 코드가 이 규칙을 지키는지 반복해서 돌아보고, 다음 프로젝트에선 설계 단에서 부터 큰 그림을 쌓는 개발자가 되어보겠습니다. ❌ 안티패턴 멈춰! ❌ </p>
<hr>
<h2 id="🧭-그럼-앞으로는">🧭 그럼 앞으로는?</h2>
<h3 id="취준은-결국-나를-찾는-기간">취준은 결국 &#39;나를 찾는 기간&#39;</h3>
<p>과연 회사에게 있어 채용하고 싶은 프론트엔드 개발자는 어떤 사람일까요? 여러 요인이 있겠지만, 취준하면서 느낀 점은 먼저 회사가 원하는 인재상을 분석하고, 제가 가진 강점을 최대한 회사 입장에서 매력적으로 보이게 보여한다는 점이에요.</p>
<p>따라서 3월 부터는 자기소개서를 정리하며 제가 가진 강점을 생각하고 스토리를 작성할 예정이에요.
제 강점의 큰 흐름은 아래와 같다 생각했습니다.</p>
<blockquote>
<p> 나의 강점은 직관과 논리를 융합할 수 있다는 것 </p>
</blockquote>
<p>제가 공부해왔던 디자인은 직관이 크게 작용하는 영역입니다.
제품이 갖고있는 Form을 만드려면 코너를 얼마나 깎아야 하는가, 레이아웃 배치에 있어 사람이 읽는 방향은 어떻게 되는가, warn을 나타내는 red컬러의 색 대비는 다른 컬러시스템들과 차이를 두는가 등등.. 공식 Docs를 통해 정리된 문법보다도 경험과 시각적 맥락에서 직관에 의해 결정되는 사항들이 큰 영역이었어요. NN group 리서치 자료나 Microsoft의 MUI, Apple Human Interface Guidelines가 공식 Docs라고 생각하면 됩니다.</p>
<p>그런 반면 제가 경험한 개발은 논리가 크게 작용하는 영역입니다. 하드웨어를 구성하는 회로 단위에서 시작해 언어, 프로그램 등이 큰 체계와 논리에 따라 구성되어 있습니다. 그래서 개발자가 새로운 기술을 익힐 때에도, 단순히 기능 구현할 때에도 모두 논리가 자리잡고 있습니다. 현재 서비스의 트래픽과 팀 구조 등에 따라 시스템 아키텍쳐가 결정됩니다. </p>
<p>두 분야의 경험을 합칠 수 있다는 것에서 시작해, 디자인에서 논리를 발휘해 성과를 만든 경험과 개발에서 직관을 발휘해 성취를 만든 경험을 적어내려 스토리를 쓰려해요. </p>
<h3 id="3월부터-취준은">3월부터 취준은,</h3>
<p>3월과 4월은 상반기 공채가 시작되는 시즌이에요.
그리고 당장 다음 주인 3월 초엔 기술 면접이 잡혀 있어요. 이전에 흥미롭게 사용했던 프로덕트들을 서비스하고 있어 합격할 수 있도록 리트코드를 통해 코드 테스트에 대비할 예정입니다.</p>
<h3 id="프로젝트는요">프로젝트는요</h3>
<p>지금은 원티드 3월 프리온보딩 사전과제를 만들었어요. 기존에 사용했던 styled-component 개념도 다시 잡고 <a href="https://velog.io/@naro-kim/Styled-Components-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">styled-components 기반 디자인 시스템</a>도 만들었어요.</p>
<p>앞으로 남은 3~4월 기간에는 이전에 멈춰두었던 포트폴리오 웹 제작을 마무리하려해요. 다른 프론트엔드 포트폴리오랑 차별점으로 뽑을 수 있는 3d 인터랙션을 가져가면서도 국내의 &#39;포트폴리오&#39; 규격에 부합하는 UI는 무엇일지 고민하고 있습니다.</p>
<p>정리하면 3월의 목표는 아래와 같습니다.</p>
<blockquote>
<ol>
<li>자소서 스토리 짜기</li>
<li>블로그 공부 기록하기 (프레임워크 및 라이브러리 개념 정리 및 성능 비교 등등..)</li>
<li>포트폴리오 웹 개발 완료하기</li>
</ol>
</blockquote>
<h3 id="끊임없는-공부도-함께">끊임없는 공부도 함께!</h3>
<p>그리고 공부에 집중할 수 있다면 제일 좋겠지만, 코테와 면접에 대비하면서 공부도 병행해야 해요
공부하고자 하는 주제는 다양한데, &quot;결합도와 응집성&quot; &quot;css 라이브러리 성능 비교&quot; 등이 있어요
이번 상반기에 좋은 소식을 전할 수 있도록 화이팅해보겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[styled-components 개념과 디자인 시스템 알아보기]]></title>
            <link>https://velog.io/@naro-kim/Styled-Components-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@naro-kim/Styled-Components-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 17 Feb 2024 05:01:21 GMT</pubDate>
            <description><![CDATA[<p>여러분들은 <code>styled-components</code>에 대해서 얼마나 아시나요? 저는 지난 <a href="https://velog.io/@naro-kim/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EC%BB%A4%EB%AE%A4%EB%8B%88%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">멋사 커뮤니티 제작 프로젝트</a>와 해커톤에서 다루어 보았었는데요, 사실 코드 활용법만 알고 있었고 그 개념과 동작 원리는 잘모르고 있었어요. 그래서 해커톤 협업 과정에서 디자인 시스템에 적응할 때 약간의 문제를 겪기도 했었죠.</p>
<p>그렇기 때문에 오늘 글에서는 styled-components의 개념과 동작 원리, 디자인 시스템 코드 작성까지 다루어보려 해요. 먼저, styled-components는 <code>css-in-js</code> 기술을 활용한 리액트 라이브러리라고 합니다. 그럼 css-in-js가 styled-components와 어떤 연관이 있는지 그 개념을 짧게 소개하고 이해해보도록 합시다.</p>
<h1 id="🖋️-css-in-js의-개념-이해하고-넘어가기">🖋️ CSS-in-js의 개념 이해하고 넘어가기</h1>
<h2 id="개념">개념</h2>
<p>css-in-js는 이름 그대로, 자바스크립트 코드로 css를 작성하는 방식을 말합니다. css의 복잡성 문제를 해결하기 위한 아이디어로 등장했습니다. css-in-js를 제안한 <a href="https://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html">vjeux</a>의 자료를 따라 요약하자면 기존의 css에서 해결한 문제점들은 아래와 같습니다.</p>
<blockquote>
<ol>
<li>Global namespace: 글로벌 공간에 선언된 이름의 명명 규칙 필요</li>
<li>Dependencies: CSS 간의 의존 관계를 관리</li>
<li>Dead Code Elimination: 미사용 코드 검출</li>
<li>Minification: 클래스 이름의 최소화</li>
<li>Sharing Constants: JS와 CSS의 상태 공유</li>
<li>Non-deterministic Resolution: CSS 로드 우선순위 이슈</li>
<li>Isolation: CSS와 JS의 상속에 따른 격리 필요 이슈</li>
</ol>
</blockquote>
<p>어떻게 이런 문제들을 해결할 수 있었을까요? css-in-js는 js runtime에서 스타일시트를 생성, 관리하며 프로그래밍 언어의 동적인 특징을 이용할 수 있도록 설계되었습니다. </p>
<p>따라서 CSS와 자바스크립트 변수나 상태를 공유하고, 조건식에 따라 스타일에 변화를 줄 수도 있었습니다. 장점과 함께 문제점을 어떻게 해결했는지 짧게 살펴보겠습니다.</p>
<h2 id="장점">장점</h2>
<blockquote>
</blockquote>
<ul>
<li>gloabl namespace: 신경쓸 필요가 없습니다. JS로 표현된 것을 CSS로 컴파일할 때, 컴파일러가 고유한 이름을 생성합니다.</li>
<li>dependencies : 모든 스타일을 runtime에 주입하므로, CSS간의 의존관계 관리의 문제가 발생하지 않습니다.</li>
<li>minification : 자동 이름 생성으로 클래스 이름을 최소화합니다.</li>
<li>sharing Constnats : JS와 CSS가 변수나 상태를 공유할 수 있습니다.</li>
<li>런타임에 스타일시트가 처리되기 때문에 CSS 로드 우선순위 이슈가 없습니다.</li>
<li>css-in-js는 문서레벨이 아니라 컴포넌트 레벨로 CSS모델을 추상화하고, 중복 및 의존성을 줄여 유지보수를 용이하게 합니다.</li>
</ul>
<p>하지만 css-in-js에 단점 역시 존재합니다.</p>
<h2 id="단점">단점</h2>
<blockquote>
<ul>
<li>js 해석과정이 추가로 실행되기 때문에 페이지 전환등의 속도가 느려집니다. <a href="https://www.samsungsds.com/kr/insights/web_component.html">해석 시간 비교 참고 자료</a></li>
</ul>
</blockquote>
<p>위 참고자료에서 확인할 수 있듯이, <code>CSS-in-JS</code>를 <code>CSS-in-CSS</code>와 비교하면, 같은 페이지 임에도 358ms vs 90.5ms라는 속도 차이를 확인할 수 있습니다.</p>
<p>따라서 css-in-js를 선택하고자 고민한다면 css 모듈 방식과 페이지 로드 속도에 대한 요구사항 비교 후 라이브러리 선택이 중요합니다.</p>
<h1 id="📄-템플릿-리터럴template-literal이란">📄 템플릿 리터럴(Template Literal)이란?</h1>
<h2 id="사용법">사용법</h2>
<p>다음으로 살펴볼 주제는 <strong>Template Literal</strong>입니다.
styled-components는 Tagged Template Literal 문법을 토대로하고 있습니다. 기존의 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals">Template Literal</a>은 내장된 표현식을 허용하는 <strong>문자열 리터럴</strong>입니다. 백틱(`) 기호를 사용하며, 플레이스 홀더를 이용해 표현식을 넣을 수 있습니다. 이는 기호 $와 중괄호를 조합해 아래(`${expression}`)처럼 표현할 수 있습니다.</p>
<pre><code class="language-javascript">const name = &#39;react&#39;;
const message = `hello ${name}`;

console.log(message);
// &quot;hello react&quot;</code></pre>
<p>위와 같이 작성하면 플레이스 홀더안의 표현식과 그 사이 텍스트는 한 번에 함께 함수로 전달됩니다. 기본 함수는 이를 단일 문자열로 concat시켜 줍니다. </p>
<p>하지만 만약에 Template Literal을 사용할 때 표현식에 일반 문자열이나 숫자가 아닌 <strong>객체</strong>를 넣는다면, 아래와 같은 결과를 얻을 수 있습니다.</p>
<pre><code class="language-javascript">const object = { a: 1 };
const text = `${object}`
console.log(text);
// &quot;[object Object]&quot;</code></pre>
<p>또는 <strong>함수</strong>를 넣는다면 그 결과는 아래와 같습니다.</p>
<pre><code class="language-javascript">const fn = () =&gt; true
const msg = `${fn}`;
console.log(msg);
// &quot;() =&gt; true&quot;</code></pre>
<p>그리고 이런 구조는 응용하면 아래와 같이 사용할 수 있습니다.</p>
<pre><code class="language-javascript">const red = &#39;빨간색&#39;;
const blue = &#39;파란색&#39;;
function favoriteColors(texts, ...values) {
  console.log(texts);
  console.log(values);
}
favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.`</code></pre>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/d7e0ac92-7875-4db9-8f9a-a825baf490b8/image.png" alt=""></p>
<p>이렇게 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals#tagged_templates">Tagged Template Literal</a>을 사용하면, 내부에 넣은 JS value를 조회하면 함수에 prop으로 넘겼던 값들을 확인할 수 있습니다. 여기까지, <a href="https://react.vlpt.us/styling/03-styled-components.html">벨로퍼트와 함께하는 모던 리액트</a>의 styled-components 파트의 Template Literal 문법 예제들을 확인해 보았는데요, 이제 응용 문법인 Tagged Template Literal의 예제를 분석해보도록 하겠습니다.</p>
<h2 id="tagged-template-literal">Tagged Template Literal</h2>
<p>태그를 사용하면, Template Literal을 함수로 파싱하는 것인데요, 태그 함수의 첫번째 인수는 문자열 배열을 포함합니다. 나머지 인수는 표현식을 포함합니다. </p>
<p>위의 예제로 쓰인 </p>
<pre><code class="language-javascript">favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.`</code></pre>
<p>Tagged Template Literal에서는 첫번째 인수로 texts 문자열 배열에 <code>[&quot;제가 좋아하는 색은&quot;, &quot;과&quot;, &quot;입니다.&quot;]</code>를, 나머지 인수로 <code>[&quot;빨간색&quot;, &quot;파란색&quot;]</code>을 넣었습니다. 특히, 함수 파라미터의 나머지 인수 부분에서 rest 문법이 사용되어 호출 시 인수를 분해해 호출할 수 있음을 볼 수 있습니다.</p>
<p>만약 이를 reduce 함수와 조합한다면 아래와 같은 작업을 할 수 있습니다.</p>
<pre><code class="language-javascript">const red = &#39;빨간색&#39;;
const blue = &#39;파란색&#39;;
function favoriteColors(texts, ...values) {
   return texts.reduce((result, text, i) =&gt; `${result}${text}${values[i] ? `&lt;b&gt;${values[i]}&lt;/b&gt;` : &#39;&#39;}`, &#39;&#39;);
}
favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.`
// 제가 좋아하는 색은 &lt;b&gt;빨간색&lt;/b&gt;과 &lt;b&gt;파란색&lt;/b&gt;입니다.</code></pre>
<p>이런 복잡한 함수를 바로 사용하진 않겠지만, styled-components 에서는 이러한 문법을 사용해 컴포넌트의 props를 읽어 옵니다.</p>
<pre><code class="language-javascript">function sample(texts, ...fns) {
  const mockProps = {
    title: &#39;안녕하세요&#39;,
    body: &#39;내용은 내용내용 입니다.&#39;
  };
  return texts.reduce((result, text, i) =&gt; `${result}${text}${fns[i] ? fns[i](mockProps) : &#39;&#39;}`, &#39;&#39;);
}
sample`
  제목: ${props =&gt; props.title}
  내용: ${props =&gt; props.body}
`
/*
&quot;
  제목: 안녕하세요
  내용: 내용은 내용내용 입니다.
&quot;
*/</code></pre>
<p>sample이란 tagged template literal의 texts 배열에는 <code>[&quot;제목:&quot;, &quot;내용:&quot;]</code>이 들어있고, fns 배열에 <code>[props=&gt; props.title, props=&gt;props.body]</code>가 들어있을 거에요.
<img src="https://velog.velcdn.com/images/naro-kim/post/3a79e79f-d6e9-4280-8c1a-460889807880/image.png" alt=""></p>
<p>때문에 <code>fns[i](mockProps)</code>는 배열의 i번째 요소인 <code>mockProps.title</code>과 <code>mockProps.body</code>를 리턴하게 됩니다.
복잡한 구조지만 하나씩 천천히 뜯어보니 이해할만 합니다. styled-components에선 이러한 Tagged Template Literal의 문법적 특성을 활용해서, 스타일링에 비구조화 할당을 응용하기도 합니다.</p>
<p>이제 styled-components의 개념으로 넘어가보도록 하겠습니다.</p>
<h1 id="🎨-styled-component의-개념-이해하기">🎨 styled-component의 개념 이해하기</h1>
<p>그래서 여기까지 css-in-js와 템플릿 리터럴의 개념을 살펴보았어요. 그럼 이제 본격적으로 styled-component의 개념에 대해 알아봅시다.</p>
<h2 id="개념-1">개념</h2>
<p>앞서 다뤄본 내용처럼, styled-components는 내부 작동을 위해 Tagged Template Literal이라는 문법을 활용합니다. 그리고 이를 이용해 컴포넌트와 스타일링 간의 매핑을 지우고, 마치 평범한 리액트 컴포넌트를 작성하듯 스타일을 만들어낼 수 있습니다. 무엇보다, React component system의 스타일링을 위해 CSS를 대체할 방법으로 개발되었습니다.</p>
<h2 id="기본-문법">기본 문법</h2>
<h2 id="조건부-스타일링">조건부 스타일링</h2>
<p>조건부 스타일링은 가변 스타일링이라고도 불리는데요, 간단한 Button 컴포넌트 예제와 함께 살펴보겠습니다.</p>
<p>아래와 같은 버튼 컴포넌트를 App에서 호출하였다고 가정하겠습니다.</p>
<pre><code class="language-javascript">//src/component/Button.js

import React from &quot;react&quot;
import styled, { css } from &quot;styled-components&quot;

const StyledButton = styled.button`
  padding: 0.375rem 0.75rem;
  border-radius: 0.25rem;
  font-size: 1rem;
  line-height: 1.5;
  border: 1px solid lightgray;

  ${(props) =&gt;
    !props.primary &amp;&amp;
    css`
      color: black;
      background: gray;
      border-color: gray;
    `}

  ${(props) =&gt;
    props.primary &amp;&amp;
    css`
      color: white;
      background: navy;
      border-color: navy;
    `}
`

const Button = ({ name, ...props }) =&gt; {
  return &lt;StyledButton {...props}&gt;{name}&lt;/StyledButton&gt;
}

export default Button;</code></pre>
<hr>
<pre><code class="language-javascript">import React from &quot;react&quot;
import Button from &quot;./Component/Button&quot;

const App = () =&gt; {
    return (
        &lt;&gt;
            &lt;Button name=&quot;버튼 1&quot; /&gt;
            &lt;Button name=&quot;버튼 2&quot; primary/&gt;
        &lt;/&gt;
    );
};

export default App;</code></pre>
<p>Button 컴포넌트는 name 속성에 이름을 전달할 수 있는데요, 지금 App에서 보다시피 버튼 2는 primary 속성도 넘겨주고 있습니다.</p>
<p>따라서 버튼 1은 </p>
<pre><code class="language-javascript">  ${(props) =&gt;
    !props.primary &amp;&amp;
    css`
      color: black;
      background: gray;
      border-color: gray;
    `}</code></pre>
<p>위 부분에 <code>!props.primary</code> 조건을 만족해 조건부 스타일링으로 gray 바탕의 black 색상 텍스트를 갖게 됩니다.</p>
<p>반면, 버튼 2는</p>
<pre><code class="language-javascript">  ${(props) =&gt;
    props.primary &amp;&amp;
    css`
      color: white;
      background: navy;
      border-color: navy;
    `}</code></pre>
<p>위 부분의 <code>props.primary</code>를 만족하게 되어 navy 바탕의 white 색상 텍스트를 표현하게 됩니다.</p>
<h2 id="객체-지향-프로그래밍의-특성---상속">객체 지향 프로그래밍의 특성 - 상속</h2>
<p>또 다른 특징으로, styled-component는 객체지향 프로그래밍과 비슷한 모습을 보입니다.</p>
<pre><code class="language-css">const Box = styled.div`
  background-color: ${(props) =&gt; props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled(Box)`
  border-radius: 50px;
`;
</code></pre>
<p>위와 같이 코드를 작성하면, Circle component들은 Box component의 style attribute들을 상속하여 사용하게 됩니다. 객체지향 프로그래밍의 상속의 개념과도 같은 모습이죠.</p>
<h1 id="👩🎨-디자인-시스템-구성하기">👩‍🎨 디자인 시스템 구성하기</h1>
<p>여기까지 살펴본 styled-components의 기본적인 문법과 특성을 토대로 디자인 시스템을 구성해봅시다. 디자인 시스템은 프로젝트 UI 전반에서 공통적으로 쓰이는 layout과 typography, theme등으로 구성할 수 있는데요, 이번엔 layout 구성 예제를 살펴보겠습니다.</p>
<h2 id="layout">Layout</h2>
<h4 id="layoutsflexts">layouts/Flex.ts</h4>
<pre><code class="language-typescript">import styled, { css } from &quot;styled-components&quot;;
import { BgColor, Border, Flex, BoxStyle } from &quot;./layout.types&quot;;
import { getStyle, toMarginPaddingString } from &quot;./layout.utils&quot;;

/**
 * @prop {number} `rounded` border radius
 * @prop {number} `m`, `p` margin, padding
 * @prop {number} `mv`, `pv` vertical
 * @prop {number} `mh`, `ph` horizontal
 * @prop {number} `ml`, `mr`, `mt`, `mb`, `pt`, `pb`, `pl`, `pr` allowed
 * @prop {number | string} `w` width
 * @prop {number | string} `h` height
 * @prop {&#39;flex-start&#39; | &#39;flex-end&#39; | &#39;center&#39;} `alignItems` align-items
 * @prop {&#39;flex-start&#39; | &#39;flex-end&#39; | &#39;center&#39; | &#39;space-between&#39; | &#39;space-evenly&#39;} `justifyContent` justify content
 * @prop {ColorKeys} `bgColor` background color
 */
export const LayoutBase = styled.div&lt;BgColor &amp; Flex &amp; BoxStyle &amp; Border&gt;`
  ${({
    theme,
    p, ph, pv, pt, pr, pb, pl,
    m, mh, mv, mt, mr, mb, ml,
    w, h,
    flex,
    rounded,
    z,
    outline,
    alignItems = &quot;flex-start&quot;,
    bgColor = &quot;TRANSPARENT&quot;,
    justifyContent = &quot;flex-start&quot;,
    cursor,
  }) =&gt; css`
    ${getStyle(&quot;padding&quot;, toMarginPaddingString(p, ph, pv, pt, pr, pb, pl))}
    ${getStyle(&quot;margin&quot;, toMarginPaddingString(m, mh, mv, mt, mr, mb, ml))}
    ${getStyle(&quot;width&quot;, w)}
    ${getStyle(&quot;height&quot;, h)}
    ${getStyle(&quot;flex&quot;, flex)}
    ${getStyle(&quot;border-radius&quot;, rounded)}
    ${getStyle(
      &quot;border-color&quot;,
      outline &amp;&amp; outline in theme ? theme[outline] : outline
    )}
    ${getStyle(&quot;z-index&quot;, z)}
    ${typeof rounded === &quot;number&quot; ? &quot;overflow: hidden;&quot; : &quot;&quot;}
    ${typeof outline === &quot;string&quot;
      ? &quot;border-width: 1px; border-style: solid;&quot;
      : &quot;&quot;}
    display: flex;
    flex-direction: column;
    align-items: ${alignItems};
    justify-content: ${justifyContent};
    background-color: ${bgColor &amp;&amp; bgColor in theme ? theme[bgColor] : bgColor};
    cursor: ${cursor};
  `}
`;

export const FlexRow = styled(LayoutBase)`
  flex-direction: row;
`;

export const FlexCol = styled(LayoutBase)`
  flex-direction: column;
`;</code></pre>
<p>위 코드는 &#39;flex&#39; property를 가진 layout을 위한 코드입니다. 먼저 <code>LayoutBase</code>를 <code>FlexRow</code>와 <code>FlexCol</code> 컴포넌트에서 상속받고 있는 모습을 확인할 수 있어요. LayoutBase는 theme, 패딩, 마진, flex 속성, width, heigth, border-radius 등을 props로 받을 수 있습니다. </p>
<h4 id="layoutslayoututillsts">layouts/layout.utills.ts</h4>
<pre><code class="language-typescript">import { DefaultOrNumber } from &#39;./layout.types&#39;;

export const toPx = (value: &#39;default&#39; | number) =&gt;
  value === &#39;default&#39; ? 20 : value;

export const toMarginPaddingString = (
  ...[all = 0, h = 0, v = 0, t = 0, r = 0, b = 0, l = 0]: (
    | DefaultOrNumber
    | undefined
  )[]
) =&gt; {
  const top = toPx(t) || toPx(v) || toPx(all);
  const right = toPx(r) || toPx(h) || toPx(all);
  const bottom = toPx(b) || toPx(v) || toPx(all);
  const left = toPx(l) || toPx(h) || toPx(all);
  return `${top}px ${right}px ${bottom}px ${left}px;`;
};

export const getStyle = (key: string, value: number | undefined | string) =&gt; {
  if (value === undefined) return &#39;&#39;;
  if ([&#39;z-index&#39;, &#39;flex&#39;].includes(key)) return `${key}: ${value};`;

  if (typeof value === &#39;number&#39;) return `${key}: ${value}px;`;
  return `${key}: ${value};`;
};</code></pre>
<p>이 유틸함수에선 Pixel 단위를 사용하고 있어요. 따라서, rem 단위로 활용하려면 base font size를 지정한 뒤 rem 단위로 변환해주는 함수가 필요해요.</p>
<pre><code class="language-typescript">// Define your base font size in pixels
const baseFontSizeInPx = 16; // Example, 16px

export const pxToRem = (pxValue: number) =&gt; {
  // Convert pixels to rems based on the base font size
  const remValue = pxValue / baseFontSizeInPx;
  // Return the value with &#39;rem&#39; unit
  return `${remValue}rem`;
};
</code></pre>
<p>혹은 유틸함수 단위 자체를 처음부터 rem으로 작성할 수도 있습니다. 이럴 경우, 프로젝트 팀원들과 어떠한 단위를 사용할지 상의하고 디자인 가이드 문서도 확인하시길 바랍니다!</p>
<h4 id="layoutslayouttypests">layouts/layout.types.ts</h4>
<pre><code class="language-typescript">export type DefaultOrNumber = &quot;default&quot; | number;
export type Margin = {
  m?: DefaultOrNumber;
  mh?: DefaultOrNumber;
  mv?: DefaultOrNumber;
  mt?: DefaultOrNumber;
  mb?: DefaultOrNumber;
  ml?: DefaultOrNumber;
  mr?: DefaultOrNumber;
};
export type Padding = {
  p?: DefaultOrNumber;
  ph?: DefaultOrNumber;
  pv?: DefaultOrNumber;
  pt?: DefaultOrNumber;
  pb?: DefaultOrNumber;
  pl?: DefaultOrNumber;
  pr?: DefaultOrNumber;
};
export type Border = {
  rounded?: number;
  outline?: string;
};
export type BoxStyle = {
  z?: number;
  w?: string | number;
  h?: string | number;
} &amp; Margin &amp;
  Padding;

export type BgColor = {
  bgColor?: string;
};

type FlexType = &quot;center&quot; | &quot;flex-start&quot; | &quot;flex-end&quot;;

export type Flex = {
  flex?: number;
  alignSelf?: FlexType;
  alignItems?: FlexType;
  justifyContent?: FlexType | &quot;space-between&quot; | &quot;space-evenly&quot;;
  cursor?: &quot;pointer&quot; | &quot;grab&quot; | undefined;
};</code></pre>
<p>그리고 위 코드는 layout component props에 사용된 type들을 정의해둔 코드입니다. 예시로 Flex가 들어있지만, Grid에 맞춰 커스텀해 쓸 수 있어요. 특히 FlexType이란 유니온 타입을 확장해서 justifyContent을 만들어두었는데, 이렇게 하나의 <code>layout.types.ts</code> 문서를 통해 디자인시스템에서 사용할 props를 편리하게 관리할 수 있습니다.</p>
<pre><code class="language-typescript">import { L } from &quot;@/design-system&quot;;

const someComponent = () =&gt; {
  return(
      &lt;&gt;
      &lt;L.FlexCol w={&quot;100%&quot;} h={&quot;100%&quot;} justifyContent={&quot;space-between&quot;}&gt;
          {/* inner content */}
      &lt;/L.Flexcol&gt;
    &lt;/&gt;
  )
};</code></pre>
<p>이를 종합하면 위의 코드처럼, &#39;L&#39;이라는 약어로 레이아웃 컴포넌트 모듈을 import해서 쉽게 스타일링에 활용할 수 있습니다. 만약 이런 디자인시스템을 활용한다면, 팀원끼리 css를 수정할 상황이 발생했을 때  소통하는 시간이 줄어들고 코드 가독성도 높아질 것입니다.</p>
<p>여기까지, styled-components의 등장 배경이 된 css-in-js와 핵심 문법인 Tagged Template Literal, 디자인 시스템 사용 예시까지 살펴보았습니다.</p>
<h3 id="느낀점">느낀점</h3>
<p>이전까지 프로젝트에선 tailwindCSS를 주로 쓰고 styled-components를 주의깊게 살펴볼 기회가 없었는데요, nextjs에서 tailwind를 권장해서 써오긴 했지만 이번 글을 정리하며 styled-components의 개발 가치관과 토대가 된 기술을 확인할 수 있었습니다.</p>
<p>개념적으론 이런 부분들을 공부할 수 있었고, <strong>잘 만들어진 디자인 시스템</strong>을 목표로 한다면 라이브러리를 어떻게 활용해야 할까?또한 고민해볼 수 있었습니다. 특히나 프로젝트에서는 <strong>유지보수</strong>가 중요한 부분인데, <strong>수정하기 쉽고 재사용하기 편한 코드</strong>를 만들기 위해선 디자인 시스템 코드에 대한 이해도를 더욱 더 높여야겠다고 생각했습니다. </p>
<p>앞으로는 vanilla-extractCSS나 tailwindCSS로 구성한 디자인 시스템 코드도 소개하고 다루어보아야 겠어요. 그리고 각각의 CSS 라이브러리가 왜 어떻게 도입된건지 개념을 이해해 적재적소에 활용해서 편리한 서비스를 만들도록 노력해보겠습니다!</p>
<p>다들 화이팅!</p>
<h3 id="참고">참고</h3>
<p><a href="https://styled-components.com/">공식문서</a> 
<a href="https://react.vlpt.us/styling/03-styled-components.html">velopert styled-components</a>
<a href="https://github.com/suekim3028/path-finder/tree/feature/camera-view/src/design-system">styled-components 디자인 시스템 참고</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자세히 알아보자, 네트워크 계층]]></title>
            <link>https://velog.io/@naro-kim/%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@naro-kim/%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Thu, 08 Feb 2024 13:18:38 GMT</pubDate>
            <description><![CDATA[<h1 id="네트워크-계층">네트워크 계층</h1>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/88dac9c1-b198-4f37-8dba-67077cf11cde/image.png" alt=""></p>
<h2 id="forwarding">Forwarding</h2>
<p>packet이 라우터의 input link에 도달했을 때, 라우터는 패킷을 적절한 output link로 전송시켜야 한다.</p>
<h2 id="routing">Routing</h2>
<h1 id="router-라우터란">Router 라우터란?</h1>
<p>Router에는 여러개의 인터페이스가 존재한다. 각각의 Interface의 IP 주소의 subnet은 각기 다 다르다. 즉 다른 말로, subnet의 교집합이라고 말할 수 있다. Router의 구조도는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/16941b1d-67a5-40d5-bc79-39fa5107fdc1/image.png" alt=""></p>
<ul>
<li><code>Routing processor</code> : Forwarding table을 만들고 각각의 input port에 table을 저장한다. </li>
<li><code>Input port</code> : 만들어져있는 table을 읽고 들어온 input  data forward</li>
</ul>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6c3b666a-9c2f-409d-843f-66c39377ebb7/image.png" alt=""></p>
<p>Input port를 자세히 들여다보면 위 그림과 같다. 들어온 data를 들여다보고, data forward를 위한 <strong>entry matching</strong>이 일어나게 된다. 이때, data entry가 forward 처리속도 보다 빠르다면, 어쩔 수 없이 queuing이 발생한다.
이를 최대한 방지하기 위해, 독립적이로 만들어진 Forward table들이 Input port마다 저장되어 병렬적으로 빠르게 실행한다.</p>
<h1 id="ip-address">IP Address</h1>
<h2 id="ipv4-datagram">IPv4 Datagram</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8268da03-00f0-4894-89d6-e77aa7dc54bd/image.png" alt=""></p>
<h2 id="ip-주소-계층화">IP 주소 계층화</h2>
<p>계층화의 장점 : prefix가 같으면 같은 네트워크에 속한다. 즉, 라우터에 들어가는 forwarding table이 같아지면서 단순화되어 매칭도 쉬워진다.</p>
<ul>
<li>Network ID를 24bit로 제한했을 때의 단점
: 2^8인 256개만이 Host ID를 서포트할 수 있다. 따라서, 호스트 크기가 큰 경우 prefix를 줄이고 hostID 크기를 키우기도 한다.</li>
</ul>
<p>즉, prefix (networkID)와 hostID는 고정되어있지 않고 유연하게 지원할 수 있다.</p>
<h2 id="ip-fragmentation">IP fragmentation</h2>
<p>링크 계층 프레임이 전달할 수 있는 최대 데이터 양을 MTU(Maximum Transmission Unit)라고 한다. MTU 제한에 따라, IP packet이 링크로 전달될 수 있도록 단편화가 일어난다. 이때, IP header에 fragment 분할 정보를 같이 전송하여, 추후 reassemble이 가능하도록 한다. </p>
<p>IP header에 fragement 단편화를 위해 아래와 같은 field들이 존재한다.</p>
<ul>
<li><code>ID, 16bits</code> : 각 조각이 동일한 데이터그램에 속하면 같은 일련번호를 공유함</li>
<li><code>flag, 3bits</code> : 분열의 특성을 나타내는 플래그<ul>
<li>첫번 째 bit : 미사용 (항상 0)</li>
<li>두번 째 bit : DFbit (Don&#39;t Fragment, 0이면 Fragmentation 가능 / 1은 불가능)</li>
<li>세번 째 bit : MF bit (More Fragment)<ul>
<li>현재의 조각이 마지막이면 0</li>
<li>더 많은 조각이 뒤에 계속 있으면 1</li>
</ul>
</li>
</ul>
</li>
<li><code>offset, 13 bits</code> : 8 Byte 단위로 최초의 fragment로부터 어떤 곳에 붙여야하는지 위치를 나타냄</li>
</ul>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5007bcbc-4f39-4cf5-84ab-c5bf16c21c4e/image.png" alt=""> </p>
<p>예시로, 4000Byte의 Datagram이 존재하고 MTU가 1500Byte라고 하자. 그렇다면 실제 data payload 크기는 <code>4000 - 20 (header) = 3980 Byte</code>일 것이다. 단편화 과정을 나타내면 아래와 같다.</p>
<blockquote>
<ol>
<li>MTU가 1500 이므로, 동일한 헤더를 가진 3개의 Fragment로 분할하자.</li>
<li><code>20(header)Byte + 1480(data) Byte</code> , <code>20(header)Byte + 1480(data) Byte</code> , <code>20(header)Byte + 1020(data) Byte</code> 세 가지의 Fragment가 분할된다.</li>
<li>reassemble 과정에서 변환할 수 있도록, 원래 데이터에서의 시작지점인 offset field를 작성한다.</li>
</ol>
</blockquote>
<h2 id="ipv6">IPv6?</h2>
<h2 id="transitioning-ipv4-to-ipv6">Transitioning IPv4 to IPv6</h2>
<h1 id="subnet">Subnet</h1>
<h2 id="subnet과-subnet-mask">Subnet과 Subnet mask?</h2>
<blockquote>
<p>Subnet : 같은 networkID를 가진 인터페이스의 집합. 혹은 라우터를 거치지 않고도 연결될 수 있는 인터페이스들의 집합.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/459704c6-0db1-4815-a0a5-8bd01ab875ee/image.png" alt=""></p>
<h2 id="cidr-classess-interdomain-routing">CIDR (Classess Interdomain Routing)</h2>
<h3 id="class-a">Class A</h3>
<h3 id="class-b">Class B</h3>
<h3 id="class-c">Class C</h3>
<h1 id="nat-network-address-translation">NAT (Network address translation)</h1>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/534a4a2e-ed54-415b-9bfa-86d691377151/image.png" alt=""></p>
<ul>
<li>IP 주소뿐 아니라 포트번호까지 바꾼 이유?
  : IP주소는 내부적으로 유일한 번호. 그러나, 포트번호는 겹칠 수 있음. 중첩되지않도록 유일하게 테이블을 작성하게 됨</li>
</ul>
<h1 id="dhcp-dynamic-host-configuration-protocol">DHCP (Dynamic Host Configuration Protocol)</h1>
<p>IP 주소 배정 프로토콜로, 
<img src="https://velog.velcdn.com/images/naro-kim/post/fb0e727f-2e50-4b37-8c6c-7b024daa87ba/image.png" alt=""></p>
<h3 id="dhcp-discover">DHCP Discover</h3>
<h3 id="dhcp-offer">DHCP Offer</h3>
<h3 id="dhcp-request">DHCP Request</h3>
<h3 id="dhcp-ack">DHCP ACK</h3>
<hr>
<p><strong>참고</strong>
<a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워크 하향식 접근 8판</a>
<a href="http://www.kocw.net/home/cview.do?cid=6b984f376cfb8f70">컴퓨터네트워크, 한양대학교 이석복 교수님 강의</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 기본 개념 돌아보기]]></title>
            <link>https://velog.io/@naro-kim/Javascript-core-concept-review</link>
            <guid>https://velog.io/@naro-kim/Javascript-core-concept-review</guid>
            <pubDate>Tue, 06 Feb 2024 08:27:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/bde9dd85-b70f-48ad-8194-7b32c4d70a85/image.png" alt=""></p>
<p>보통 이렇게, 신입에게 원하는 자격 요건에 &#39;비동기 프로그래밍, 클로저, ES6&#39; 지식이 들어있다. 실제 면접에서도, 화이트보드 코딩 시험으로 클로저 함수를 주고, 바르게 동작하려면 어떻게 코드를 손봐야하는지 시험을 봤다. 특히나 비동기나 클로저는 개념과 실제 코드를 연결짓기 힘든데, 그 점에 유의해서 항상 내가 비동기를 어떻게 제어하고 있는지 돌아보며 코드를 작성하자.</p>
<h1 id="자바스크립트">자바스크립트</h1>
<h2 id="호이스팅이란">호이스팅이란?</h2>
<p>js 인터프리터가 함수의 선언, 할당, 실행을 나누었으나 선언이 코드의 최선두로 끌어올려진 것처럼 동작하는 현상. </p>
<ul>
<li>var = 호이스팅 (O)</li>
</ul>
<p>코드 상단에서 var 변수를 선언하고 하단에서 a=1;라고 하였다면 상단에서 참조 에러 대신 undefined를 console을 찍게 된다.</p>
<ul>
<li>let = 호이스팅 동작 (X)</li>
</ul>
<p>let 변수는 선언과 할당을 별도로 실행한다. 따라서 호이스팅 되더라도 할당되지 않아 메모리에 변수가 선언되지 않으며  호출 시 reference 에러가 발생한다.</p>
<h2 id="var-let-const의-개념과-차이">var, let, const의 개념과 차이</h2>
<h3 id="var">var</h3>
<blockquote>
<p>전역적으로 선언 되어 함수 밖에서 변수가 호이스팅 되도록 허용한다. 즉, 변수가 선언 되기 전에 전역 스코프에서 참조될 수 있다. 재선언도 가능하다.</p>
</blockquote>
<h3 id="let">let</h3>
<blockquote>
<p>블록 스코프로 선언되어 변수가 선언 되기 전에나 정의된 블록 외부에서 해당 변수를 참조할 수 없다. 재선언할 수 없다. 값의 변경은 가능하다.</p>
</blockquote>
<h3 id="const">const</h3>
<blockquote>
<p>블록 스코프로 선언되어 변수가 선언 되기 전이나 정의된 블록 외부에서 참조할 수 없다. 값을 재할당하거나 재선언할 수 없다.</p>
</blockquote>
<p>성장하기 위해 공부하자!</p>
<h2 id="프로토타입과-상속">프로토타입과 상속</h2>
<p>모든 js 객체는 다른 객체에 대한 “참조”인 <code>__proto__</code> 프로퍼티를 가지고 있다. 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메소드를 프로토타입에 미리 구현해 놓음으로써 자산을 공유하여 사용할 수 있다.</p>
<h2 id="비동기와-동기">비동기와 동기</h2>
<p>비동기 통신이 등장하게 된 배경은, MPA 환경에서 SPA환경으로 프론트엔드와 백엔드가 분리되며 동적인 브라우저 렌더링이 발전했기 때문이다.</p>
<h3 id="ajax">AJAX?</h3>
<p>가장 처음 등장한 서버와 브라우저가 비동기적으로 데이터를 교환하는 통신
동적 웹 어플리케이션을 만들 수 있어 페이지 일부만 렌더링도 가능하다. 즉, 필요한 부분의 리소스만 로드하여 갱신하므로 빠르고 부드러운 화면 효과로 사용자 경험 증대</p>
<h3 id="promise와-callback의-차이점과-장단점">Promise와 Callback의 차이점과 장단점</h3>
<p>프로미스와 콜백 모두 js에서 비동기 처리를 위해 사용하는 패턴이다.</p>
<p>콜백의 경우 함수 처리 순서를 보장하기 위해, 중첩의 중첩이 쌓이는 ‘콜백헬’이 발생하는 단점과 에러 처리가 어려운 단점이 있습니다.</p>
<p>이 문제를 해결하기 위해 ES6부터 Promise를 도입했습니다.</p>
<p>Promise는 생성자 함수를 통해 인스턴스화 하며, 작업 수행 성공 시 resolve 메소드를 호출해 비동기 처리 결과를 후속 처리 메소드로 리턴한다. </p>
<p>비동기 작업 실패시 reject 메소드를 호출해 후속처리 메소드로 전달한다.</p>
<p>여기서 후속처리 메소드는 <code>then</code> 과 <code>catch</code>가 있으며 두 메소드 모두 Promise 객체를 반환한다. Promise의 <strong>then의 메소드 체이닝을 통해 콜백헬 문제를 해결</strong>할 수 있다.</p>
<h3 id="async-await이란">Async, Await이란?</h3>
<p>async와 await는 Promise를 더욱 쉽게 사용하기 위한 ES2017(ES8) 문법입니다. </p>
<p><code>async</code> 키워드는 function에 붙여 비동기적인 동작을 하도록 합니다. 이 키워드가 붙은 함수는 항상 Promise 객체를 반환합니다. </p>
<p><code>await</code> 키워드는 Promise 객체의 앞에 붙여 처리를 기다립니다.</p>
<p>두 키워드를 사용하면 코드가 간결해집니다. 하지만 에러처리를 위해선 try catch를 사용해야합니다. 또한 이 두 키워드를 이용해 동기적 코드 제어가 가능합니다. </p>
<h3 id="async-await-promise의-차이는">Async, Await, Promise의 차이는?</h3>
<p>async와 await을 사용해 Promise.then을 단축할 수 있습니다. 비동기 제어를 원하는 함수를 async 키워드로 정의하고, 내부에서 await을 사용합니다.</p>
<h1 id="클로저란">클로저란?</h1>
<p>클로저란 내부 함수가 외부로 반환된 이후에도 Life-cycle을 유지하는 것이라고 합니다. 주로, <strong>클로저 안에 정의된 함수는 만들어진 환경을 기억한다</strong>고 표현합니다.</p>
<p>클로저를 이용해 class 선언 없이 자바나 다른 언어들에서 쓰이는 <code>private</code> 키워드를 흉내낼 수 있습니다. 그럼, 클로저를 언제, 어떻게 이용하는지 간단히 살펴보겠습니다.</p>
<h2 id="private-접근-제어">Private 접근 제어</h2>
<p>javascript private 프로퍼티는 js class와 함께 실제 스펙에 추가된지 얼마 안 된 문법이라고 합니다. 따라서, 근본적으로 js에선 &#39;_&#39; 키워드를 써서 private으로 취급한다는 컨벤션을 쓰기도 했었습니다. 하지만 이는 필드에 컨벤션을 썼을 뿐 실제로는 public으로 작동하는 문제점이 있었습니다. </p>
<p>따라서 제안된 방법이 클로저를 사용한 캡슐화와 은닉화였습니다. 클로저는 어떤 환경과 그 환경을 조작하는 함수를 연관시켜 주고, 객체지향 프로그래밍과 같은 맥락에서 사용할 수 있습니다.</p>
<p>클로저를 사용하면 그 특성에 따라 스코프 접근을 제안할 수 있습니다.
예시로, count 변수를 숨기며 값을 제어할 수 있도록 만들 수 있습니다.</p>
<pre><code class="language-javascript">function counter(){
  let count = 0;
  return {
   add: function() {
       count++;
     console.log(count);
   }
  }
}

const countHandler = counter();
countHandler.add(); // 1
countHandler.add(); // 2</code></pre>
<h2 id="함수형-프로그래밍">함수형 프로그래밍</h2>
<p>함수형 프로그래밍의 목적 중 하나는, &quot;사이드 이펙트를 최대한 일으키지 않는다&quot;</p>
<h2 id="클로저-스코프-체인과-커링">클로저 스코프 체인과 커링</h2>
<p>모든 클로저에는 세가지 스코프(체인)이 있다고 합니다.</p>
<blockquote>
</blockquote>
<ul>
<li>지역 범위 (Local scope)</li>
<li>전역 범위 (Global scope)</li>
<li>포함하는 범위 (Enclosing scope) : 블록 함수 또는 모듈 범위</li>
</ul>
<h1 id="자바스크립트-이벤트루프">자바스크립트 이벤트루프</h1>
<h2 id="싱글스레드">싱글스레드</h2>
<hr>
<p><strong>참고</strong>
<a href="https://simplejs.gitbook.io/olaf/">Simple js</a>
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures">mdn web docs/closure</a>
<a href="https://ui.toast.com/weekly-pick/ko_20200312">은닉을 향한 자바스크립트의 여정</a>
<a href="https://blog.shiren.dev/2016-06-27-%ED%81%B4%EB%A1%9C%EC%A0%80,-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%BA%A1%EC%8A%90%ED%99%94%EC%99%80-%EC%9D%80%EB%8B%89%ED%99%94/">클로저, 그리고 캡슐화와 은닉화</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아한 형제들과 "우아한 타입 스크립트 with 리액트"]]></title>
            <link>https://velog.io/@naro-kim/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%83%80%EC%9E%85-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-with-%EB%A6%AC%EC%95%A1%ED%8A%B8</link>
            <guid>https://velog.io/@naro-kim/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%83%80%EC%9E%85-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-with-%EB%A6%AC%EC%95%A1%ED%8A%B8</guid>
            <pubDate>Tue, 06 Feb 2024 07:46:03 GMT</pubDate>
            <description><![CDATA[<h1 id="읽게된-계기">읽게된 계기</h1>
<p>감수자의 말을 읽어보면, 이 책에 대한 소개로 &quot;주니어 개발자들에게 제시된 <strong>어떻게 성장할 수 있을까?</strong>&quot;라는 질문에 대답 중 하나라고 말하고 있습니다. </p>
<p>제가 신입으로 지원하고 싶은 다수의 회사들이 지원 자격에 <strong>&quot;타입스크립트에 대한 지식이 있으신 분&quot;</strong>을 적어두고 있어요. 그리고 여러 프로젝트들을 진행하면서 타입스크립트를 사용하긴 했지만 스스로 타입 시스템에 대한 깊은 이해는 부족하다 생각하고 있었어요. 이런 상황에서, 프로젝트와 스터디에서 공부한 <strong>타입스크립트를 복습하고 실무 관점에서 성장</strong>하기 위해 이 책을 읽었습니다.</p>
<p>특히 우아한 형제들에서의 타입스크립트 실무 활용 사례를 살펴보면서 헷갈리는 개념들을 바로잡고 지식을 채워나가길 기대하고 있어요.</p>
<h1 id="책의-구성">책의 구성</h1>
<p>이 책의 목차는 다음과 같습니다. </p>
<blockquote>
</blockquote>
<ul>
<li>1장, 들어가며</li>
<li>2장, 타입</li>
<li>3장, 고급 타입</li>
<li>4장, 타입 확장하기 좁히기</li>
<li>5장, 타입 활용하기</li>
<li>6장, 타입스크립트 컴파일</li>
<li>7장, 비동기 호출</li>
<li>8장, JSX에서 TSX로</li>
<li>9장, 훅</li>
<li>10장, 상태관리</li>
<li>11장, CSS-in-JS</li>
<li>12장, 타입스크립트 프로젝트 관리</li>
<li>13장, 타입스크립트와 객체 지향</li>
</ul>
<p>저는 하루에 한 챕터씩 읽고 정리하는 계획으로 책을 공부했어요. 이전에 공부했더라도, 정확히 그 개념과 활용을 다시 짚어보자는 목표를 세웠습니다.
그럼, 가볍게 책을 소개하는 포스트니까 읽었던 1장의 내용 요약과 2장 도입부를 소개하며 글을 진행하겠습니다.</p>
<h2 id="1장-들어가며">1장, 들어가며</h2>
<p>1장은 자바스크립트의 역사와 한계를 알아보며 <strong>타입스크립트의 등장 배경</strong>을 살피는 챕터입니다.
자바스크립트의 특징과 한계를 살피고 자바스크립트와 브라우저에서 발생했던 문제점들을 해결하기 위한 방법들이 무엇이 있었는지 살펴봅니다. </p>
<h3 id="자바스크립트의-등장-배경">자바스크립트의 등장 배경</h3>
<p>1995년, 넷스케이프의 <strong>브랜든 아이크(Brenden Eich)</strong>가 웹 컨텐츠를 표현하기 위해 이미지, 플러그인 요소등을 쉽게 조합할 수 있는 <strong>자바스크립트</strong>를 만들었습니다. 이 때, 자바스크립트를 웹 대응 언어로서 완벽한 해결책으로 제시하기 보다 빠르게 시장 반응을 확인할 수 있는 프로토타입 언어로 출시하였다고 해요. </p>
<p>당시 사용자에게 유의미한 편의성을 제공하진 못했지만, 아래와 같은 특징들을 지녔었습니다.</p>
<ul>
<li>C, Java와 유사한 기본 문법</li>
<li>객체 지향 언어인 Self의 <strong>프로토타입 기반 상속</strong><a href="https://ko.javascript.info/prototype-inheritance">1</a> 개념을 차용</li>
<li>Lisp 계열 언어 중 하나인 Scheme의 <strong>일급 함수</strong><a href="https://developer.mozilla.org/ko/docs/Glossary/First-class_Function">2</a> 개념을 차용</li>
</ul>
<h3 id="자바스크립트의-확산">자바스크립트의 확산</h3>
<p>초기 자바스크립트는 브라우저 생태계를 고려해 작성한 것이 아니고, 새로운 기능이 추가되는 형태로 발전했다 합니다. 이에 브라우저와 JS 발전 속도가 차이가 벌어지고 <strong>폴리필</strong><a href="https://toss.tech/article/smart-polyfills">3</a>과 <strong>트랜스파일</strong><a href="https://javascript.info/polyfills#transpilers">4</a>같은 개념이 등장했습니다.</p>
<blockquote>
<ul>
<li><code>폴리필(polyfoll)</code> : 브라우저가 지원하지 않는 코드를 브라우저가 사용할 수 있도록 변환한 코드 혹은 플러그인</li>
</ul>
</blockquote>
<ul>
<li><code>트랜스파일(transpile)</code> : 최신 버전의 코드를 예전 버전 코드로 변환하는 과정</li>
</ul>
<p>이렇게 새로운 개념과 라이브러리들이 유행하였지만, 언제 유지보수가 멈출지 모르는 라이브러리에 크로스브라우징 이슈들을 온전히 기대어 해결할 순 없었다고 합니다. 그래서 시간이 지날수록 모든 브라우저에서 동일하게 동작하는 <strong>표준화된 자바스크립트의 필요성</strong>이 커져 갔습니다.</p>
<p>이에 넷스케이프는 컴퓨터 시스템 표준을 관리하는 &#39;Ecma 인터내셔널 (국제 표준화 기구)&#39;에 자바스크립트 기술 규격을 제출했습니다. 그리고 이는 <strong>ECMAScript</strong>라는 이름으로 공식 자바스크립트 표준화가 되었어요. 이렇게 JS가 표준화되자, 정적 웹사이트들에서 동적 웹애플리케이션의 전환이 가속화되었습니다.</p>
<h3 id="동적-웹-애플리케이션">동적 웹 애플리케이션?</h3>
<p>그럼 정적 웹사이트와 동적 웹애플리케이션의 차이는 무엇일까요? 이책에서는 웹의 발전 형태와 각각을 지칭하는 이유를 말합니다. </p>
<ul>
<li><code>웹사이트</code> : 단방향으로 정보를 제공. 사용자와 인터랙션 x, 동적인 컨텐츠 업데이트가 없다. HTML 문서에 링크가 연결된 웹페이지 모음</li>
<li><code>웹 애플리케이션</code> : 쌍방향 소통 웹. 사용자와의 인터랙션으로 컨텐츠를 업데이트. 검색, 댓글, 채팅 등 웹 페이지 내부에 다양한 애플리케이션이 존재</li>
</ul>
<p>기존 HTML 문서 기반 웹사이트가 쌍방향 소통을 시작하고, 서비스의 규모가 커지면서 웹 애플리케이션의 형태로 변화했습니다. 특히, 구글 지도는 이런 웹 애플리케이션의 서막을 연 서비스 중 하나로 평가받고 있습니다. 기존의 지도 서비스가 브라우저에 이미 만들어진 지도를 다운받아 쓰는 형태였다면 구글 지도는 길을 찾기 위해 사용자가 직접 출발지와 목적지를 입력하는 등의 인터랙션과 쌍방향 소통이 가능해졌습니다.</p>
<p>이렇게 대규모 웹 서비스 개발의 필요성이 커지고 사용자가 PC뿐 아닌 태블릿, 모바일 등 다양한 디바이스로 서비스를 경험하게 되면서, 하나의 웹 페이지를 통으로 개발하는 것이 아닌 <strong>컴포넌트 기반 개발 방법론(Component Base Development)</strong>이 등장했습니다. CBD는 서비스에서 데이터를 구분하고 그에 맞는 UI를 표현할 수 있게 컴포넌트 단위로 개발하는 접근 방식입니다. </p>
<blockquote>
<p><code>컴포넌트 베이스 개발 (Component Based Development, CBD)</code>
: 재사용할 수 있는 컴포넌트를 개발하거나 조합해 하나의 애플리케이션을 만드는 방법론</p>
</blockquote>
<p>컴포넌트는 모듈과 유사하게, 독립된 기능을 재사용하기 위한 코드 묶음입니다. 또한 런타임 환경에서 독립적으로 배포, 실행될 수 있는 단위입니다. 이때, 컴포넌트는 <strong>다른 컴포넌트와의 의존성을 최소화</strong>하는 것이 중요합니다. </p>
<h3 id="자바스크립트의-한계점">자바스크립트의 한계점</h3>
<blockquote>
<ol>
<li>동적 타입 언어로, 런타임에 타입이 결정되어 개발자의 의도와는 다른 결과를 야기할 수 있다.</li>
<li>약타입 언어로, 명시적으로 타입을 변환하지 않았을 때 에러를 일으키는 대신 연산을 진행시켜 예기치 못한 오류가 발생할 수 있다.</li>
</ol>
</blockquote>
<p>위와 같은 한계점들을 극복하기 위한 해결방안으로 JS 개발 생태계에선 <code>자바스크립트 인터페이스</code>의 해결 방안들이 나타났습니다.
바로 <code>JSDoc</code>, <code>propTypes</code>, <code>Dart</code>입니다. 이들은 모두 의미있는 시도였지만 자바스크립트 스스로의 인터페이스가 아닌 문서 생성 도구와 속성, 새로운 언어이기에 근본적인 문제를 해결할 수 없었습니다.</p>
<p>이윽고, 자바스크립트 스스로가 인터페이스를 기술할 수 있는 언어로 거듭나야 한다는 목소리가 커졌습니다.
그렇게, 타입스크립트가 등장했습니다.</p>
<h3 id="타입스크립트란">타입스크립트란?</h3>
<p>마이크로소프트사는 Javascript의 superset 언어인 TypeScript를 공개했습니다. Dart와 다르게 JS 코드를 그대로 사용할 수 있었으며 기존의 많은 단점들을 극복할 수 있었습니다. 타입스크립트로 해결한 단점들은 대표적으로 아래와 같습니다.</p>
<blockquote>
<ol>
<li>안정성 보장</li>
<li>개발 생산성 향상</li>
<li>협업에 유리</li>
<li>JS에 점진적 적용 가능</li>
</ol>
</blockquote>
<p>따라서 개발자들은 이렇게 속시원히 JS의 문제들을 해결해 준 TS 도입을 환영했다 합니다. 이제 타입스크립트가 어떻게 위의 단점들을 극복해나갔는지는 다음에 이어질 장들을 통해 더 자세히 확인해봅시다.</p>
<h2 id="2장-타입">2장, 타입</h2>
<blockquote>
<p>2장은 정적 타이핑을 위해 타입이 무엇인지, 다른 언어에서 타입은 어떻게 동작하는지 살펴보고 타입스크립트에서 활용하는 방법을 다룹니다.</p>
</blockquote>
<p>자바스크립트에서 다루는 타입과 타입스크립트가 다루는 타입은 어떻게 다를까요? 이를 구분하기 위해 먼저, 프로그래밍언어에서 <strong>변수를 선언하는 것</strong>부터 살펴봅니다. 변수란, 값을 저장할 수 있는 공간이자 값을 가리키는 상징적 이름입니다.</p>
<p>컴퓨터 메모리의 공간은 한정적이기 때문에, 효율적으로 공간을 활용하려면 해당 변수가 메모리 공간에서 차지할 값 크기를 알아야 합니다. 따라서 값의 크기를 명시해두면, 컴퓨터가 값을 참조할 때 한 번에 메모리 크기를 알 수 있어 훼손없이 가져오게 됩니다. 예를 들어, 메모리에 &#39;숫자 타입&#39; 값이 할당 되어 있으면 JS 엔진은 이를 숫자로 인식해 8byte 단위로 값을 읽어올 것입니다.</p>
<p>하지만 변수에 저장할 수 있는 값의 종류는 프로그래밍 언어마다 다른데요, 최신 ECMAScript 표준을 따르는 JS라면 아래와 같은 7가지 데이터 타입을 정의한다 합니다.</p>
<ul>
<li>undefined</li>
<li>null</li>
<li>Boolean</li>
<li>String</li>
<li>Symbol</li>
<li>Numeric (Number와 BigInt)</li>
<li>Object</li>
</ul>
<p>이를 <strong>자료형으로서의 타입</strong>이라고 부릅니다. js 애플리케이션에서 메모리를 효율적으로 관리하고 싶다면 이러한 타입을 통해 값을 명시하는 과정이 필요합니다. 이후 2장의 뒷 내용과 다른 챕터들은 이런 타입을 이해해 효율적이고 안정적인 타입스크립트를 사용하도록 예제와 함께 개념을 설명하고 있어요.</p>
<h1 id="읽고난-감상">읽고난 감상!</h1>
<p><del>우리는 어떤 민족입니까? 배달의 민족</del>
이 책을 읽으면서, 각 파트의 개념을 익히고 예시가 필요할 때마다 친숙한 서비스와 그 서비스 속 UI 컴포넌트에 적용된 TS코드 예시를 살펴볼 수 있었습니다. 이전까지 공부 방법이 공식 문서로 Playground 따라가기, Typechallenge 도전하기 등이었는데 공식 문서는 번역하여 이해하다 보니 어색하게 받아들이게 된 개념들이 있었어요. </p>
<p>그런데 이 책은 배민을 예제로 하기 때문에 한국화된 코드들로 아주 이해하기 쉬웠습니다. 특히, 열거형 타입의 예시를 들 때 &#39;배송 현황&#39; 컴포넌트의 코드를 볼 수 있는데, 다른 언어들과 유사하면서도 TypeScript만이 가지는 멤버값의 특성을 이해하기 쉬웠어요.</p>
<p>만약 주니어 혹은 신입 프론트엔드 개발자가 타입스크립트를 공부해야 할때, 개념이 헷갈린다면 한 번 쯤은 읽어보길 추천하는 책입니다.</p>
<hr>
<h3 id="용어-참고">용어 참고</h3>
<ol>
<li><a href="https://ko.javascript.info/prototype-inheritance">프로토타입 기반 상속</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Glossary/First-class_Function">일급 함수</a></li>
<li><a href="https://toss.tech/article/smart-polyfills">폴리필</a></li>
<li><a href="https://javascript.info/polyfills#transpilers">트랜스파일</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[자세히 알아보자, 전송계층과 TCP와 UDP]]></title>
            <link>https://velog.io/@naro-kim/%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EC%A0%84%EC%86%A1%EA%B3%84%EC%B8%B5%EA%B3%BC-TCP%EC%99%80-UDP</link>
            <guid>https://velog.io/@naro-kim/%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-%EC%A0%84%EC%86%A1%EA%B3%84%EC%B8%B5%EA%B3%BC-TCP%EC%99%80-UDP</guid>
            <pubDate>Thu, 01 Feb 2024 14:34:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/b141f921-eb32-423f-94bc-ed5cff778936/image.png" alt=""></p>
<p>Reliable service과 Best-Effort service에 대해 알아봅시다. TCP 프로토콜은 전송 계층에서 연결을 유지하며 session을 트래킹합니다. 반면에 UDP 프로토콜은 신뢰성을 덜 신경씁니다. 연결을 유지할 필요도 없습니다. 오직 source port와 destination port만 있다면 바로 데이터그램을 전송할 수 있습니다.</p>
<h2 id="📌-tcp">📌 TCP</h2>
<p>이전 블로그까지 TCP 프로토콜을 간략하게 설명하고 넘어갔었습니다. TCP 프로토콜은 인터넷 계층에서 패킷 혹은 데이터그램을 주고받습니다. 이번 글을 통해 TCP의 핵심 개념인 핸드쉐이크와 Congestion control, Flow control에 대해 더 자세히 알아보겠습니다.</p>
<p>먼저, TCP는 Transimission Control Protocol(전송 제어 프로토콜)로 두 호스트를 연결하고 데이터 스트림을 연결하는 전송 프로토콜입니다. TCP의 특징은 대표적으로 아래와 같습니다.</p>
<blockquote>
<ol>
<li>신뢰할 수 있고 정확한 데이터를 전송한다.</li>
<li>연결형 통신을 사용하는 프로토콜이다.</li>
<li>데이터를 패킷 단위로 분할하여 전송한다.</li>
</ol>
</blockquote>
<p>그럼 TCP가 데이터를 전송하기 위해 필요한 handshake 과정에 대해 더 자세히 알아봅시다.</p>
<h3 id="tcp의-신뢰성-보장을-위한-방법들">TCP의 신뢰성 보장을 위한 방법들</h3>
<h3 id="3-way-handshake란">3 way handshake란?</h3>
<p>3-way-handshake는 TCP의 연결을 초기화하기 위한 단계입니다. 신뢰성있는 데이터 전송을 보장하기 위해 3번의 패킷 전송을 통해 상대방 컴퓨터와의 세션 연결을 초기화하는 과정을 의미합니다.</p>
<p>3 way handshake를 쉽게 이해하기 위해, 실생활의 예시를 들어보면 &#39;길 물어보기&#39;와 비슷합니다. 지나가는 사람에게 &#39;실례합니다. 혹시 잠시 길을 여쭈어도 될까요?&#39;라고 인사를 건네고, 상대가 &#39;아, 네. 어디 가시는데요?&#39;라고 응답이 오면 &#39;00를 찾고있는데 알고 계시나요?&#39;라고 다시 물어보면 이제 우린 상대에게 &#39;00를 찾는다&#39;는 정보를 전달할 수 있습니다. </p>
<p>즉, 이 과정은 <strong>acknowledges request</strong>와 <strong>send own request</strong>, <strong>acknowledge host</strong>라는 세 번의 과정안에서 이루어집니다. 이를 그림으로 나타내면 아래와 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/788027eb-6204-449d-980d-e6b5fe545a0a/image.png" alt=""></p>
<p>과정을 컴퓨터 네트워크 용어로 다시 짚어보면 아래와 같습니다.</p>
<blockquote>
<ol>
<li><strong>SYN</strong> : Client A로부터 Server B에 SYN을 보내며 연결을 요청합니다. B는 SYN을 받고, A에게 요청을 받았다는 신호를 보내야 합니다. 이때, SYN bit가 1로 활성화된 세그먼트가 보내집니다.</li>
<li><strong>SYN + ACK</strong> : B는 A에게 전송에 대한 허가를 받기 위해 연결 요청의 SYN을 보냅니다. 또한 이전 요청을 받았다는 신호인 ACK도 함께 보냅니다. 이 과정에서 SYN bit == 1, ACK == 1인 세그먼트가 보내집니다.</li>
<li><strong>ACK</strong> : A는 B에게 연결 요청을 확인했고 허가한다는 신호인 ACK를 보냅니다. ACK bit == 1인 세그먼트가 B로 보내집니다.</li>
</ol>
</blockquote>
<h3 id="4-way-handshake란">4 way handshake란?</h3>
<p>4-way-handshake는 TCP의 연결을 종료하기 위한 단계입니다. 이 과정에선 FIN과 ACK를 사용합니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ded55ccb-851e-41fd-b11d-4f370350c73b/image.png" alt=""></p>
<blockquote>
<ol>
<li><strong>FIN</strong> : Client A로부터 Server B에 FIN을 보내며 연결 종료를 요청합니다. B는 FIN을 받고, A에게 요청을 받았다는 신호를 보내야 합니다. 이때, FIN bit가 1로 활성화된 세그먼트가 보내집니다.</li>
<li><strong>ACK</strong> : B는 A에게 연결 종료에 대한 응답인 ACK를 보냅니다. 이 과정에서 ACK == 1인 세그먼트가 보내집니다.</li>
<li><strong>FIN</strong> : 또한 B도 A에게 연결 종료 요청을 위해 FIN을 보냅니다. FIN == 1인 세그먼트가 보내집니다.</li>
<li><strong>ACK</strong> : A는 B에게 연결 종료에 대한 응답인 ACK를 보냅니다. ACK bit == 1인 세그먼트가 B로 보내집니다.</li>
</ol>
</blockquote>
<h3 id="tcp-빠른-재전송-fast-retransmit">TCP 빠른 재전송 (Fast retransmit)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5cd5c273-aabd-4d07-8edd-ada0e1a295fe/image.png" alt=""></p>
<p>segment에 대한 중복된 3개의 ACK 신호를 받았을 때, segment를 다시 전송하는 것을 말합니다. 발생하는 원인은, 재전송 타이머값이 종종 길어지며 손실된 패킷의 재전송 전에 지연시간이 커지게 되기 때문입니다. 이런 상황이 발생했음을 감지하고 해결하고자 중복된 ACKs를 통해 손실된 세그먼트를 검출합니다.</p>
<h3 id="congestion-control">Congestion control</h3>
<p>TCP 의 congestion control은 sender 가 receiver 에게 보낼 데이터 양을 제한함으로써 이루어집니다.
sender가 패킷을 전송할 때, 패킷 로스가 일어나기 전까지 rate를 점진적으로 증가시킵니다. 패킷 로스는 특정한 라우터가 보낼 수 있는 rate 보다 datastream 전송이 빠를 때 발생하며, TCP는 이런 패킷 로스를 congestion으로 생각합니다. 따라서, 패킷 로스의 결과로 rate가 느려집니다.</p>
<h3 id="flow-control">Flow control</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8a3cc2d0-617a-42c1-ad86-a34e3d2548c5/image.png" alt="">
TCP 프로토콜은 데이터 교환을 위해 Flow Control(흐름 제어)를 사용합니다. 만약 Receiver의 버퍼가 꽉 찼다면, Not Ready 상태가되어 Sender의 전송이 멈춥니다. 시간이 지나고 Receiver의 세그먼트 프로세스가 마무리 되었다면 Buffer가 ready 상태가 되어 Sender의 데이터 전송이 재개됩니다. 따라서, 네트워크나 클라이언트에 이상이 생긴다면 self-throttle을 관리합니다.</p>
<h2 id="📌-udp">📌 UDP</h2>
<p>이전까지, UDP가 User Datagram Protocol의 약자이며 전송 계층에서 사용되는 프로토콜임을 배웠습니다. 또한 TCP와 다르게 sequencing과 관련한 기능을 제공하지 않습니다. 짧게 UDP의 특징을 복습하고 넘어가면 아래와 같습니다.</p>
<blockquote>
<ol>
<li>비연결형으로 신뢰성 없는 전송 프로토콜이다.</li>
<li>데이터를 데이터그램 단위로 쪼개며 전송하는 전송 계층에 속한다.</li>
<li>체크섬을 통해 수신한 패킷의 오류 여부 정도만을 알 수 있다.</li>
<li>연결과 신뢰성 확인등의 기능이 없기 때문에 TCP에 비해 적은 오버헤드로 전송이 가능하다.</li>
<li>일정한 전송 속도 제한이 있거나 데이터 손실을 허용하는 애플리케이션에 적합하다.</li>
</ol>
</blockquote>
<p>따라서, 빠른 전송이 필요한 Video application들이나 VoiceOver IP(음성 인터넷 프로토콜)가 UDP를 사용합니다. </p>
<h3 id="udp-header">UDP header</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/361c295d-8575-420c-ae4c-f9a12e411f5f/image.png" alt=""></p>
<p>위 이미지는 UDP header를 나타난 그림입니다. 데이터 세그먼트에 단순히 몇 가지만 더해집니다. 바로 Source port와 Destination port, UDP Length, UDP checksum입니다. 이때, 대표적인 Destination Port인 DNS의 경우 53번 포트로 연결합니다.</p>
<h3 id="udp-checksum">UDP Checksum</h3>
<p>위 헤더 이미지에서 UDP Checksum을 확인할 수 있었습니다. 4 계층의 Checksum은 수학적으로 segment가 트래픽에서 오류가 발생했는지를 확인할 수 있습니다. 2 계층에서도 동일하게 사용할 수 있는 방법으로 하나의 전체 패킷에서 사라진 비트가 있는지 확인하는 방식입니다.</p>
<blockquote>
</blockquote>
<p>체크섬은 송신할 세그먼트를 16비트 단위로 나누고, 모두 더한 다음 1의 보수를 취해서 만들어 집니다.
이제 이 체크섬을 세그먼트와 같이 전송 합니다.</p>
<blockquote>
</blockquote>
<p>수신자는 수신된 세그먼트에 대해 동일한 방식으로 체크섬을 만들고 헤더의 체크섬과 일치 하는지 비교함으로써신된 세그먼트의 오류를 검출할 수 있습니다.</p>
<p>마지막으로 TCP와 UDP를 비교했을 때, UDP의 장단점에 대해 정리해보겠습니다.</p>
<blockquote>
<p>** UDP의 장단점**</p>
</blockquote>
<ul>
<li>장점<ul>
<li>비연결형 서비스이므로 TCP에 비해 전송 속도가 빠르며 네트워크 오버헤드가 적습니다.</li>
<li>1:1, 1:N, N:N 통신이 가능합니다.<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>단점<ul>
<li>데이터의 신뢰성을 보장할 수 없습니다.</li>
</ul>
</li>
</ul>
<h2 id="📌-신뢰적-데이터-전송의-원리">📌 신뢰적 데이터 전송의 원리</h2>
<h3 id="전송후-대기-프로토콜이란">전송후 대기 프로토콜이란?</h3>
<p>전송후 대기 프로토콜은 패킷을 전송하고 그 패킷에 대한 수신 확인 응답을 받고나서,다음 패킷을 전송하는 방식 입니다. 이러한 방식은 네트워크 링크 이용률이 낮아 속도가 느리다는 단점이 있습니다.</p>
<h3 id="파이프라인-프로토콜이-뭘까요">파이프라인 프로토콜이 뭘까요?</h3>
<p>파이프라이닝 프로토콜은 전송한 패킷에 대한 수신 확인 응답을 받지 않고도, 여러 개의 패킷을 연속으로 전송하여 링크 이용률과 전송 속도를 높이는 프로토콜 입니다.</p>
<hr>
<p>[ 참고 자료 ]</p>
<p><a href="https://better-together.tistory.com/134">TCP/IP 전송 계층(트랜스포트 계층)과 포트(Port) 번호 [변계사 Sam의  테크 스타트업!:티스토리]</a>
<a href="https://youtu.be/LyDqA-dAPW4?si=s43AivrxAEKnQfLO">What Is a Three-Way Handshake in TCP?</a>
<a href="https://www.youtube.com/watch?v=VjBDgcNno-Q">Connectionless Transport:UDP - JimKurose</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP와 HTTPS, DNS 프로토콜]]></title>
            <link>https://velog.io/@naro-kim/HTTP%EC%99%80-HTTPS-DNS-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</link>
            <guid>https://velog.io/@naro-kim/HTTP%EC%99%80-HTTPS-DNS-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</guid>
            <pubDate>Fri, 19 Jan 2024 08:19:32 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에선 지난 시간에 다뤘던 HTTP 프로토콜을 좀 더 상세히 살펴보고 그 다음으로 HTTP에서 보안이 강화된 HTTPS를 알아보겠습니다. 마지막으로 DNS에 대해 다루겠습니다.</p>
<h1 id="http-프로토콜이란">HTTP 프로토콜이란?</h1>
<blockquote>
<p><code>HTTP (HyperText Transfer Profocol)</code>는 인터넷상에서 데이터를 전송하기 위한 프로토콜로, TCP/IP 4계층에서 응용 계층에 속한다. 주로 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜이다. 주로 TCP를 사용하고, HTTP/3부터는 UDP를 사용하며 80번 포트를 사용한다.</p>
</blockquote>
<p><a href="https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EA%B3%84%EC%B8%B5">지난 포스트</a>에서 이야기한 것처럼 웹에서 이뤄지는 모든 데이터 교환의 기초로, 클라이언트 - 서버 프로토콜이라고도 합니다. 이 때 클라이언트-서버 프로토콜은 수신자 측에 의해 requerst가 초기화되는 프로토콜을 의미합니다.</p>
<p>주요 역할은 <strong>클라이언트와 서버 간에 웹 페이지(HTML 파일), 이미지, 동영상 등의 리소스를 주고 받는 것입니다.</strong> HTTP는 요청-응답 프로토콜로, 클라이언트가 HTTP 요청을 서버에 보내면, 서버는 요청을 처리하고 결과를 HTTP 응답으로 돌려줍니다. 이 프로토콜은 웹에서 데이터를 주고 받는 방식을 정의하고 있습니다.</p>
<h3 id="http의-역할"><strong>HTTP의 역할</strong></h3>
<ol>
<li><p>리소스와 독립적인 인터페이스(uri, url)를 제공함으로써 서비스의 직접적인 구현 방식을 클라이언트로부터 숨깁니다. 이를 바탕으로 <strong>상호작용의 복잡성을 줄이고</strong> 서버와 클라이언트의 <strong>독립성</strong>을 높입니다. </p>
</li>
<li><p>중간 계층 프로토콜로서 proxy나 gateway가 non-HTTP 정보 시스템을 <strong>보다 보편적인 인터페이스로 변환</strong>할 수 있게 해줍니다.</p>
</li>
</ol>
<h2 id="http의-요청과-응답이란">HTTP의 요청과 응답이란?</h2>
<p>클라이언트와 서버는 데이터 스트림과는 대조적으로, 개별적인 메세지 교환에 의해 통신합니다. 보통 브라우저인 클라이언트에 의해 전송되는 메세지를 <code>요청(requests)</code>라 부르며, 그에 대해 서버에서 응답으로 전송되는 메세지를 <code>응답(responses)</code>이라고 부릅니다.</p>
<p>HTTP request와 response의 구조는 서로 닮아있습니다. 
아래 이미지와 같이 총 4 파트로 나누어볼 수 있습니다.
<img src="https://velog.velcdn.com/images/naro-kim/post/ebebe385-8a1b-4796-adf5-33f15ec135be/image.png" alt=""></p>
<blockquote>
<p><strong>HTTP request와 response의 구조</strong></p>
</blockquote>
<ol>
<li><code>start line</code> : 실행되어야 할 request 혹은 request 수행에 따른 성공 또는 실패가 기록된다. 항상 1줄로 끝난다.</li>
<li><code>HTTP headers</code> : request 설명 혹은 message 본문에 대한 설명</li>
<li><code>blank line</code> :  메타 정보가 모두 보내졌음을 알리는 빈 줄</li>
<li><code>body</code> : request 관련 내용(HTML 폼 컨텐츠 등)이 옵션으로 들어가거나 response 관련 문서가 들어간다. body의 유무는 1번과 2번에 명시된다. </li>
</ol>
<p>이 구조에서 HTTP 요청 메세지의 <strong>start line</strong>과 <strong>header</strong>를 묶어서 <code>request head</code>라고 부릅니다. 대조적으로 HTTP 메세지의 payload는 <code>body</code>라고 부릅니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8dfaf62e-efdd-46ee-a1cd-48dfa49e5b73/image.png" alt=""></p>
<ul>
<li>startline : <code>Method / URL / Protocol Version</code></li>
<li>Headers : <code>Request headers / Response headers / General headers / Representation headers</code> </li>
</ul>
<h3 id="http-headers">HTTP Headers</h3>
<p>HTTP 헤더는 앞서 구조에서 설명했던 내용처럼 HTTP message 본문에 대한 부가적인 설명을 전송할 수 있게 해줍니다. 
그 종류는 <code>Request headers / General headers / Representation headers / Entity headers</code> 이렇게 4가지로 나뉩니다. </p>
<blockquote>
<ul>
<li><code>General Headers</code> : HTTP header에서 request/response message 모두에 사용될 수 있습니다. 다만, 컨텐츠에 적용되지 않으며 현재 버전의 HTTP 명세에선 분류하고 있지 않습니다.</li>
</ul>
</blockquote>
<ul>
<li><strong>Cache-Control</strong> : request/response의 directive를 다루는 헤더 필드입니다. 캐시의 max-age나 private directive 등을 다룹니다. </li>
<li><strong>Connection</strong> : keep-alive 혹은 close를 받아 현재 전송이 완료된 후 네트워크 접속을 유지할지 말지 제어합니다. </li>
</ul>
<blockquote>
<ul>
<li><code>Request headers</code> : HTTP request에서 request 컨텍스트에 관한 정보를 전달하여 서버가 response를 처리할 수 있도록 합니다.</li>
</ul>
</blockquote>
<ul>
<li><strong>Cookie</strong> : reponse로부터 받았던 Set-Cookie 헤더 혹은 Document.cookie로 저장한 HTTP Cookie를 담고있는 필드입니다.<ul>
<li><strong>Host</strong> : request가 보내질 서버의 port 넘버나 host를 특정합니다. 기본 적으로 HTTP URL에 대해선 80번, HTTPS URL에 대해선 443번이 매칭됩니다.</li>
<li><strong>User-Agent</strong> : 서버와 네트워크 피어들이 request를 보낸 user agent(ex.브라우저)나 app과 os, vendor 를 식별할 수 있게 해주는 문자열입니다.</li>
<li><strong>Accept-*</strong> : response의 적절한 포맷을 나타냅니다. </li>
<li><strong>Authorization</strong> : 서버의 보호된 리소스에 대한 접근을 허용하며 user agent에 신뢰 권한을 부여하는 헤더입니다.</li>
</ul>
</li>
</ul>
<blockquote>
<ul>
<li><code>Response headers</code> : HTTP response에서 사용되는 헤더로, 메세지 컨텐츠와 연관은 없습니다.</li>
</ul>
</blockquote>
<ul>
<li><p><strong>Age</strong> : delta-seconds 타입으로, 객체가 proxy cache 내에 머문 시간을 나타냅니다.</p>
</li>
<li><p><strong>Location</strong> : 응답 후 redirect할 새로운 리소스의 URL을 나타냅니다. 3xx나 201 상태 코드에서만 의미가 있습니다.</p>
</li>
<li><p><strong>Server</strong>  : request를 처리한 origin server의 필드입니다. 보안 취약점으로 자주 걸리게 되므로 response header에서 숨길 수 있습니다.</p>
</li>
<li><p><strong>Set-Cookie</strong> : 서버로부터 user agent에게 cookie를 보내기 위해 쓰이는 헤더 필드입니다. user agent는 추후 api 요청 시 cookie를 다시 서버로 보냅니다.</p>
<blockquote>
<ul>
<li><code>Representation headers</code> : HTTP message body에서 보내진 리소스를 표현하는 헤더입니다. 예를 들어, 몇몇 데이터는 XML이나 JSON과 같은 특정한 타입으로 포맷되거나 지역별 언어로 변경됩니다. 또한 전송을 위해 압축되거나 인코딩될 수도 있습니다. 또한 Representation headers는 Entity headers를 포함합니다.</li>
</ul>
</blockquote>
<ul>
<li><strong>Content-Type</strong> : resource의 media type을 나타냅니다. response 내에서는 클라이언트에 반환될 컨텐츠 유형이 무엇인지 나타냅니다. request 내에서는, 서버에게 전송 데이터의 타입을 나타냅니다. </li>
<li><strong>Content-Language</strong> : 응답자에 따라 표현할 언어를 나타내는 헤더입니다. </li>
<li><strong>Content-Encoding</strong> : media type을 압축하기 위해 사용하는 헤더입니다. 개체의 원본 포맷의 형태를 알려주어 디코딩과 인코딩이 가능하게 합니다.</li>
</ul>
</li>
</ul>
<h2 id="http-요청의-리소스란">HTTP 요청의 리소스란?</h2>
<p>HTTP의 요청의 대상을 <code>리소스</code>라고 부릅니다. 이 리소스는 문서나 사진 등 다양한 형식을 지원합니다. 각 리소스는 식별을 위해서 URI(Uniform Resource Identifier)라는 식별자로 식별할 수 있습니다. </p>
<ul>
<li><p><code>URI</code>를 더 자세히 설명하도록 하겠습니다. 가장 일반적인 URI는 <strong><code>웹주소</code></strong>로 알려진 URL(Uniform Resource Locator)입니다.  예시로는 “<a href="https://velog.io/@naro-kim/posts%E2%80%9D">https://velog.io/@naro-kim/posts”</a> 등이 있습니다.</p>
</li>
<li><p>이러한 URL을 사용자가 브라우저 주소에 입력하면 해당 URL과 연결되는 페이지 리소스를 로드할 수 있습니다.</p>
</li>
</ul>
<h2 id="http-메서드의-종류">HTTP 메서드의 종류</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/49622e8b-5d3d-4d0d-a301-ad99c72e72ab/image.png" alt=""></p>
<h3 id="get과-post의-차이점">GET과 POST의 차이점</h3>
<blockquote>
<p><code>GET</code>은 클라이언트가 서버에 리소스를 요청하는 메소드입니다. <strong>query string</strong> 혹은 <strong>path variable</strong>을 통해 데이터를 전송하며 주로 리소스 조회에 사용합니다. 데이터가 url에 노출되어 보안에 취약해집니다.</p>
</blockquote>
<blockquote>
<p><code>POST</code>는 리소스를 생성하는 메소드입니다. 데이터를 <strong>HTTP message body</strong>에 담아 리소스 등록에 사용합니다. POST는 request Header Content-type에 resource data type을 명시해야 합니다.</p>
</blockquote>
<p>이 두 메소드에선 <code>Query string</code>과 <code>Path variable</code> 그리고 <code>HTTP message body</code>의 개념이 등장합니다. 두 개념은 아래와 같이 설명할 수 있습니다.</p>
<ul>
<li><strong>Query String</strong> : url 뒤에 <strong>? 로 전달하는 파라미터</strong>를 의미합니다. 특정 값으로 데이터를 조건부 조회하는 데 쓰입니다.</li>
<li><strong>Path variable</strong> : url 뒤에 <strong>number 타입 인덱스</strong>로 리소스를 조회하는 파라미터입니다. 데이터가 url에 노출되지 않습니다. 특정 인덱스의 데이터를 조회하는 데 쓰입니다.</li>
<li><strong>HTTP message body</strong>  :  request로 전송하는 리소스를 담고있는 부분으로 GET일 경우 Body가 존재하지 않고 POST일 경우 body가 존재합니다.</li>
</ul>
<p>또한 GET과 POST는 <strong>요청에 Body 유무</strong>와 <strong>멱등성</strong>이라는 특성에서 차이를 보입니다. </p>
<h3 id="put과-patch의-차이">PUT과 PATCH의 차이</h3>
<h2 id="http-상태코드란">HTTP 상태코드란?</h2>
<p>HTTP의 상태코드는 request가 성공적으로 완료되었는지를 알려줍니다. console의 network탭에서 request를 클릭해 확인할 수 있으며 총 5가지의 그룹으로 나뉩니다.</p>
<blockquote>
<ol>
<li>정보 응답</li>
<li>성공 응답</li>
<li>리다이렉션 메세지</li>
<li>클라이언트 에러</li>
<li>서버 에러</li>
</ol>
</blockquote>
<p>또한 필요한 경우 서버에서 커스텀 에러메세지를 보낼수도 있습니다.</p>
<h3 id="1xx--정보-응답">1xx : 정보 응답</h3>
<p>101번대의 에러 코드는 정보 응답 코드에 대한 코드입니다. </p>
<ul>
<li><code>100 Continue</code> : reqeust가 이미 요청 되었거나 중첩되는 경우 무시함을 나타내는 코드</li>
<li><code>101 Switching Protocol</code> : request 헤더에 대한 응답을 처리하며 protocol이 변경됨을 알려주는 코드</li>
<li><code>102 Processing</code> : 서버 요청 수신 후 처리중</li>
<li><code>103 Early Hints</code> : 서버 응답 준비 동안 preloading을 지원합니다. 단, 현재는 Firefox와 Safari에서 지원하지 않습니다.</li>
</ul>
<h3 id="2xx--성공-응답">2xx : 성공 응답</h3>
<ul>
<li><code>200 OK</code> :  200번 코드는 request가 성공했다는 의미입니다. 각 메소드에 따라 성공의 의미가 달라집니다.
GET의 경우 &#39;리소스를 불러와 메세지 바디로 전송되었습니다&#39;, HEAD의 경우 &#39;헤더가 메세지 바디에 있습니다&#39;, PUT 또는 POST의 경우 &#39;수행 결과에 대한 리소스가 메세지 바디로 전송되었습니다&#39; 등을 의미합니다.</li>
<li><code>201 Created</code> :  요청이 성공하여 리소스를 생성했다는 의미.</li>
<li><code>202 Accepted</code> </li>
<li><code>203 Non-Authoritative Information</code></li>
<li><code>204 No Content</code> : request는 성공했으나 응답할 콘텐츠가 없을 수 있습니다.</li>
</ul>
<h3 id="3xx--리다이렉션-메세지">3xx : 리다이렉션 메세지</h3>
<p>300번대의 상태 코드는 URI 변경에 대한 메세지를 나타냅니다. 클라이언트가 request를 마치기 위해서는 추가적인동작을 취해야 합니다.</p>
<p>웹 브라우저의 경우, response로 3xx의 상태 코드가 오고 Location header가 존재하면 그 위치로 자동 이동하게 됩니다. </p>
<p><a href="https://m.blog.naver.com/fbfbf1/222682991444">3xx 상태코드 예시</a></p>
<h3 id="4xx--클라이언트-에러">4xx : 클라이언트 에러</h3>
<p><strong>클라이언트의 오류</strong>에 의해 발생하는 상태 코드입니다.</p>
<ul>
<li><code>400 Bad Request</code> : 잘못된 문법으로 인하여 서버가 클라이언트 요청을 이해할 수 없음</li>
<li><code>401 Unauthorized</code> : 인증되지 않은 사용자의 요청</li>
<li><code>403 Forbidden</code> : 클라이언트 사용자 식별은 가능하지만, 접근 권한이 없는 사용자임을 나타내는 코드</li>
<li><code>404 Not Found</code>: requested resource를 서버에서 찾을 수 없다는 의미로 URL이 존재하지 않거나 리소스가 존재하지 않는 경우</li>
</ul>
<h3 id="5xx--서버-에러">5xx : 서버 에러</h3>
<p><strong>서버 오류</strong>에 의해 발생하는 상태 코드입니다.</p>
<ul>
<li><p><code>500 Internal Server Error</code> : 서버가 처리 방법을 알 수 없는 경우</p>
</li>
<li><p><code>501 Not Implemented</code> : 서버가 request method를 이해하지 못하거나 리소스를 지원하지 않는 경우</p>
</li>
<li><p><code>502 Bad Gateway</code> : 서버가 request에 필요한 response를 위해 게이트웨이에서 작업하는 동안 잘못된 경우</p>
</li>
<li><p><code>503 Service Unavailable</code> : 서버가 request를 받을 준비가 되지 않은 경우로, 작동이 중단되거나 과부화된 경우.</p>
</li>
<li><p><code>504 Gateway Timeout</code> : 서버가 게이트웨이 역할을 하고 있지만 response를 받을 수 없을 때 주어집니다.</p>
</li>
</ul>
<h2 id="http의-특징">HTTP의 특징</h2>
<h3 id="무상태성">무상태성</h3>
<blockquote>
<p><code>무상태(stateless)</code>
HTTP에서의 무상태(Stateless)란 서버에서 클라이언트의 상태를 저장하지 않는 것을 의미합니다. 따라서 클라이언트는 요청에 필요한 데이터를 모두 가지고 있어야 합니다. 또는 서버가 클라이언트로 받은 요청 사항을 모두 저장해야 합니다. 이 방법들을 쿠키(Cookie)와 세션(Session)이라고 부릅니다.</p>
</blockquote>
<p>무상태의 장점은 <strong>서버 확장성이 높다</strong>는 점입니다. <strong>클라이언트 상태를 저장하지 않기</strong>에, 요청에 응답하는 서버가 바뀌어도 됩니다. 따라서 특정 서버에 문제가 생겨 응답하지 않더라도 신규 서버 확장으로 요청에 따른 응답을 보내 문제를 해결할 수 있습니다.</p>
<h3 id="비연결성">비연결성</h3>
<blockquote>
<p><code>비연결성(connectionless)</code>
HTTP의 비연결성(Connectionless)은 클라이언트에서 요청을 보낸 후 서버로부터 응답을 받으면 연결을 끊는 것을 의미합니다. 비연결성은 불특정 다수를 대상으로 하는 서비스에 유리합니다. 서버에서 응답을 받고 서도 연결을 유지하려면 그만큼 자원을 사용하게 됩니다. 따라서 연결을 필요시에만 유지하면 자원을 효율적으로 사용할 수 있습니다.</p>
</blockquote>
<p>하지만, 이런 특징 때문에 <strong>서버가 클라이언트를 기억할 수 없다</strong>는 단점이 있습니다. 또한, 동일한 클라이언트에서 연속적으로 요청이 오면 연결과 해제 과정이 반복되어 자원이 낭비됩니다.</p>
<p>이러한 단점을 보완하기 위해 HTTP 헤더의 일종인 <strong>HTTP Keep Alive</strong>를 사용하여 마지막 응답 이후 일정 시간 동안 연결을 유지합니다. 이를 통해 동일한 클라이언트로부터 온 요청이 오면 연결을 생략할 수 있습니다.</p>
<h3 id="쿠키와-세션">쿠키와 세션?</h3>
<blockquote>
<p><code>쿠키 (Cookie)</code> : 클라이언트 로컬 웹 브라우저에 저장하는 데이터파일로, 키와 값을 저장한다. 대표적인 예로는 로그인 정보와 장바구니가 있으며 약 4kb이다.</p>
</blockquote>
<blockquote>
<p><code>세션 (Session)</code> : 서버에서 클라이언트와 연결 정보를 저장 및 관리하는 것을 말한다. 서버에 데이터가 저장되므로 보안 면에서는 쿠키보다 좋지만, 접속자가 많을 경우 서버에 과부하가 올 수 있다. 용량에 제한이 없다.</p>
</blockquote>
<h1 id="http-커넥션">HTTP 커넥션</h1>
<p>HTTP는 OSI 응용 계층에서 클라이언트와 서버 사이 커넥션을 위해 TCP 프로토콜을 전송 프로토콜로 주로 이용합니다. 초기 HTTP는 request가 보내져야 할 때마다 매번 새롭게 커넥션을 생성하고, response가 도착한 이후 커넥션을 끊는 형태였습니다. </p>
<p>이렇게 각각의 <strong>TCP 연결을 계속해서 반복적으로 열고 닫는 것은 성능 상의 제약</strong>을 일으킵니다. 때문에 클라이언트와 서버 사이 커넥션 관리는 HTTP의 중요한 주제입니다. 대규모로 커넥션을 열고 유지하는 것은 웹 애플리케이션 성능에 많은 영향을 주기 때문입니다. 이를 위해서, HTTP/1.x 버전에선 몇 가지 방법이 도입되었습니다. 그 중 <code>Keep-Alive</code>와 <code>파이프라이닝</code>에 대해 알아보겠습니다.</p>
<h2 id="http11">HTTP/1.1</h2>
<p>HTTP의 첫 번째 공식 표준 버전인 HTTP/1.1은 GET, POST, PUT, DELETE method를 지원합니다. 또한, TCP keep-alive로 커넥션 재사용을 지원해 이전보다 많은 컨텐츠를 전달할 수 있습니다. 또 다른 특징으로, 파이프라이닝 기술을 지원합니다. 아래에서 더 자세히 알아보도록 하겠습니다.</p>
<h2 id="http-keep-alive">HTTP Keep-Alive</h2>
<p>Keep-Alive는 General header로, 송신자가 연결에 대한 타임아웃과 요청 최대 개수를 어떻게 정의했는지 알려줍니다. 즉 파라미터로 <code>timeout</code>과 <code>max</code>를 갖습니다.</p>
<blockquote>
<p><code>timeout</code> : 커넥션이 열려 있어야 하는 최소한의 시간(초 단위). keep-alive TCP 메세지가 전송 계층에 설정되지 않는 다면 TCP timeout 값 이상은 무시된다.</p>
</blockquote>
<p><code>max</code> : 커넥션이 닫히기 전 전송될 수 있는 최대 요청 수. </p>
<p>Keep-Alive 헤더를 포함하는 request 예제는 아래와 같습니다.</p>
<pre><code>HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 11 Aug 2016 15:23:13 GMT
Keep-Alive: timeout=5, max=1000
Last-Modified: Mon, 25 Jul 2016 04:32:39 GMT
Server: Apache

(body)</code></pre><h2 id="http-파이프라이닝">HTTP 파이프라이닝</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/c7a26a9d-d6f8-4908-a744-38672ac18400/image.png" alt=""></p>
<p>파이프라이닝이란 같은 <strong>영속적인 커넥션을 통해 response를 기다리지 않고 request을 연속적으로 보내는 기능</strong>입니다. 기본적으로 HTTP request는 왼쪽과 같이 순차적으로 작동합니다. 하지만 오른쪽 그림처럼 파이프라이닝을 통해 네트워크 지연을 회피할 수 있습니다. </p>
<ul>
<li>다만, 모던 브라우저에서 HTTP 파이프라이닝이 기본 활성화되어 있진 않고 그 구현이 매우 어렵다고 합니다. </li>
<li>또 다른 문제점으로는 버그가 있는 Proxy가 많아 개발자들이 분석하기 힘든 오류 동작을 야기합니다. </li>
<li>마지막으로 HTTP/1.1의 가장 큰 문제점으로 <strong>Head of Line Blocking</strong>이 지적되어 HTTP2가 개발되었습니다. </li>
</ul>
<h2 id="http2">HTTP/2</h2>
<p>2015년에 개발된 새로운 버전의 HTTP로, HTTP/1.1보다 훨씬 바르고 효율적입니다. HTTP의 전송 시간을 개선하기 위해 등장했으며 이를 위한 방법은 <strong>멀티 플렉싱(다중화)</strong>과 <strong>로드 프로세스의 컨텐츠 우선순위 지정</strong>이 있습니다. </p>
<h3 id="multiplexing">Multiplexing</h3>
<p>HTTP/2는 하나의 TCP 커넥션에서 여러 요청을 병렬적으로 보낼 수 있습니다.</p>
<h3 id="stream-prioritization">Stream Prioritization</h3>
<p>resource 간 전송 우선순위를 지정합니다.</p>
<h2 id="http3">HTTP/3</h2>
<p>HTTP/3는 <strong>UDP</strong>를 경유합니다. HOL 문제 해결을 목적으로 하여 구글이 개발한 전송 계층 프로토콜 중 하나인 <strong>QUIC</strong>을 사용합니다. </p>
<h1 id="https-란">HTTPS 란?</h1>
<p>HTTPS(HyperText Transfer Protocol Secure)는 보안 계층인 <strong>SSL/TLS를 이용해 HTTP의 보안을 강화한 웹 통신 프로토콜</strong>입니다. HTTP는 데이터 암호화를 거치지 않고 전송하기 때문에 보안에 취약합니다. 때문에 HTTPS가 등장하게 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/1a316000-d7ac-4cc6-8a84-3f3b226ccc2b/image.png" alt=""></p>
<h2 id="ssltls-secure-socket-layertransport-layer-security">SSL/TLS (Secure Socket Layer/Transport Layer Security)</h2>
<p>SSL은 넷스케이프에서 개발한 <strong>암호화 프로토콜</strong>입니다. 이 당시 SSL이 가진 취약점을 보완해 새로 개발한 암호화 프로토콜이 바로 <strong>TLS(Transport Layer Security)</strong>입니다. </p>
<p>현재 HTTPS에서 통용되는 암호화 프로토콜은 TLS이지만 SSL과 함께 SSL/TLS라고 부르고 있습니다. </p>
<p>HTTPS 프로토콜을 사용하기 위해선 <strong>SSL/TLS 인증서</strong>를 발급받아야 합니다. 인증서가 중요한 이유는 아래와 같습니다.</p>
<blockquote>
</blockquote>
<ul>
<li>브라우저 웹사이트 주소에 https 접두사가 포함됨</li>
<li>개인 데이터 보호 : 브라우저와 웹사이트 간 모든 통신의 암호화</li>
<li>고객 신뢰 강화 : 신뢰되는 웹사이트라는 표시로 사용자에게 안전을 보장</li>
<li>SEO 개선 : 검색 엔진 최적화 순위의 요소로 더 높은 검색 순위를 차지하게 됨</li>
</ul>
<h1 id="dns-domain-name-system">DNS (Domain Name System)</h1>
<p><code>DNS</code>이란 <strong>Domain Name System</strong>으로, 인터넷에 연결된 리소스에 대한 계층적이고 분산된 명명 시스템을 일컫습니다. DNS는 도메인 네임 리스트와 연결된 IP 주소 등의 리소스를 유지, 관리합니다.</p>
<p>인터넷 상에서는 Host를 식별하기 위해 IP 주소를 사용하지만 Number 형식으로 되어 있어 의미를 파악하기 힘듭니다. 이를 해결하기 위해 DNS는 <strong>IP주소에 사람이 이해할 수 있는 도메임 이름을 매핑</strong>하여 통신을 쉽게 합니다.</p>
<h2 id="dns의-작동-방식">DNS의 작동 방식</h2>
<p>DNS의 주요 기능은 도메인 네임을 숫자로 이루어진 IP주소(ex/ 123.0.0.123)로 변환하는 것입니다. 이를 DNS조회라 하며 사용자 요청에 따른 동작은 순서대로 아래와 같이 작동합니다.</p>
<blockquote>
<ol>
<li>사용자가 URL을 웹 브라우저 주소창에 입력한다.</li>
<li>브라우저가 URL의 유효성을 판단한다. </li>
<li>유효한 URL이라면 웹 브라우저는 DNS 서버에 연결할 IP 주소를 요청한다. 아니라면 Input을 검색한다.</li>
<li>DNS 서버로부터 매핑된 IP 주소를 받으면 3-way handshake로 TCP 통신을 위한 가상 회선을 연결한다.</li>
<li>HTTP connection request를 서버에 보내면 response로 리소스를 받고 브라우저가 화면을 출력한다.</li>
</ol>
</blockquote>
<p>이를 DNS 단위로 쪼개어 살펴보면 아래와 같이 이해할 수 있습니다.</p>
<blockquote>
<ol>
<li>사용자가 웹 브라우저를 열어 주소 표시줄에 <a href="http://www.example.com%EC%9D%84">www.example.com을</a> 입력하고 Enter 키를 누릅니다.</li>
</ol>
<ol start="2">
<li><a href="http://www.example.com%EC%97%90">www.example.com에</a> 대한 요청은 일반적으로 케이블 인터넷 공급업체, DSL 광대역 공급업체 또는 기업 네트워크 같은 인터넷 서비스 제공업체(ISP)가 관리하는 DNS 해석기로 라우팅됩니다.</li>
</ol>
</blockquote>
<ol start="3">
<li>ISP의 DNS 해석기는 <a href="http://www.example.com%EC%97%90">www.example.com에</a> 대한 요청을 DNS 루트 이름 서버에 전달합니다.<blockquote>
</blockquote>
</li>
<li>ISP의 DNS 해석기는 <a href="http://www.example.com%EC%97%90">www.example.com에</a> 대한 요청을 이번에는 .com 도메인의 TLD 이름 서버 중 하나에 다시 전달합니다. .com 도메인의 이름 서버는 example.com 도메인과 연관된 4개의 Amazon Route 53 이름 서버의 이름을 사용하여 요청에 응답합니다<blockquote>
</blockquote>
</li>
<li>ISP의 DNS 해석기는 Amazon Route 53 이름 서버 하나를 선택해 <a href="http://www.example.com%EC%97%90">www.example.com에</a> 대한 요청을 해당 이름 서버에 전달합니다.<blockquote>
</blockquote>
</li>
<li>Amazon Route 53 이름 서버는 example.com 호스팅 영역에서 <a href="http://www.example.com">www.example.com</a> 레코드를 찾아 웹 서버의 IP 주소 192.0.2.44 등 연관된 값을 받고 이 IP 주소를 DNS 해석기로 반환합니다.<blockquote>
</blockquote>
</li>
<li>ISP의 DNS 해석기가 마침내 사용자에게 필요한 IP 주소를 확보하게 됩니다. 해석기는 이 값을 웹 브라우저로 반환합니다. 또한, DNS 해석기는 다음에 누군가가 example.com을 탐색할 때 좀 더 빠르게 응답할 수 있도록 사용자가 지정하는 일정 기간 example.com의 IP 주소를 캐싱(저장)합니다. <blockquote>
</blockquote>
</li>
<li>웹 브라우저는 DNS 해석기로부터 얻은 IP 주소로 <a href="http://www.example.com%EC%97%90">www.example.com에</a> 대한 요청을 전송합니다. 여기가 콘텐츠가 있는 곳으로, 예를 들어 웹 사이트 엔드포인트로 구성된 Amazon S3 버킷 또는 Amazon EC2 인스턴스에서 실행되는 웹 서버입니다.<blockquote>
</blockquote>
</li>
<li>192.0.2.44에 있는 웹 서버 또는 그 밖의 리소스는 <a href="http://www.example.com%EC%9D%98">www.example.com의</a> 웹 페이지를 웹 브라우저로 반환하고, 웹 브라우저는 이 페이지를 표시합니다.</li>
</ol>
<p><a href="https://aws.amazon.com/ko/route53/what-is-dns/">출처</a></p>
<h2 id="dns-질의-종류">DNS 질의 종류</h2>
<p>DNS 질이에는 재귀적 질의와 반복적 질의가 존재합니다.</p>
<blockquote>
<p><strong>재귀적 질의</strong> : 도메인 네임에 해당하는 IP주소를 통해 DNS가 다른 DNS에게 재귀적으로 IP주소를 물어보는 것을 뜻합니다.
<strong>반복적 질의</strong> : IP주소를 찾기 위해 반복적으로 질의하는 것입니다. 
루컬 DNS가 루트 DNS에게 IP주소를 물어보고 찾아내지 못했다면, TLD DNS에게 물어보고 없다면 authoriativeDNS와 같이 반복적으로 상위 DNS에게 질문하는 구조를 말합니다. </p>
</blockquote>
<h2 id="dns에서-udp를-사용하는-이유">DNS에서 UDP를 사용하는 이유</h2>
<p>HTTP는 TCP를 기반으로 만들어져 있습니다. 이와 달리 <a href="https://learn.microsoft.com/ko-kr/troubleshoot/windows-server/networking/dns-works-on-tcp-and-udp">DNS는 UDP와 TCP 모두를 사용합니다.</a> 그럼 먼저, UDP를 사용하는 이유는 무엇일까요? <strong>DNS는 신뢰성보다 속도가 중요한 서비스입니다.</strong> 때문에, 연결 설정에 드는 시간 비용이 없는 UDP를 사용합니다.
DNS는 연결 상태를 유지할 필요가 없고, TCP보다 많은 클라이언트를 수용할 수 있는 UDP를 사용합니다.</p>
<h2 id="dns-레코드란">DNS 레코드란?</h2>
<p>DNS 레코드는 DNS resource와 Domain name 사이의 매핑입니다. 각 개별 DNS 레코드에는 유형(이름 및 번호), 만료 시간(수명), 유형별 데이터가 있습니다. 예시는 <a href="https://cloud.google.com/dns/docs/records-overview?hl=ko">DNS 레코드</a>에서 자세히 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터 네트워크의 물리적 네트워크란?]]></title>
            <link>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EB%AC%BC%EB%A6%AC%EC%A0%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EB%9E%80</link>
            <guid>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EB%AC%BC%EB%A6%AC%EC%A0%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EB%9E%80</guid>
            <pubDate>Thu, 11 Jan 2024 14:17:05 GMT</pubDate>
            <description><![CDATA[<h1 id="컴퓨터-네트워크의-물리적-네트워크">컴퓨터 네트워크의 물리적 네트워크</h1>
<p>컴퓨터 네트워크는 네트워크에 상주하는 서로 다른 하드웨어들을 연결하는 <code>케이블</code>과 연결된 컴퓨터에서 사용되는 <code>어댑터(호스트)</code>, <code>라우터</code> 또는 <code>브릿지</code> 그리고 집선기와 증폭기로 구성됩니다. </p>
<p>또한 물리적 컴퓨터 네트워크는 사용되는 하드웨어 유형 및 크기 면에서 다양합니다. 크게는 아래와 같이 4가지로 나뉘어 있고, 일반적인 네트워크의 종류는 <code>LAN</code>과 <code>WAN</code>입니다.</p>
<blockquote>
<ol>
<li>LAN(Local Area Network)</li>
<li>PAN(Personal Area Network)</li>
<li>MAN(Metropolitan Area Network)</li>
<li>WAN(Wide Area Network)</li>
</ol>
</blockquote>
<p>아래에서 물리적 네트워크를 연결하는 장치들을 알아보고 마지막으로 LAN과 WAN의 차이점을 비교하겠습니다.</p>
<h1 id="물리적-네트워크-장치">물리적 네트워크 장치</h1>
<h2 id="nic-network-interface-controller">NIC (network interface controller)</h2>
<p style="width:480px;">
<img src="https://velog.velcdn.com/images/naro-kim/post/1a51f375-042d-48b1-9467-0b4da86040ef/image.png"></img>
</p> 

<blockquote>
<p>컴퓨터를 네트워크에 연결하여 통신하기 위해 사용하는 하드웨어 장치. 이더넷 케이블을 꽂을 수 있는 포트를 지닌 하드웨어 장치. </p>
</blockquote>
<h2 id="리피터">리피터</h2>
<p style="width:480px;">
<img src="https://velog.velcdn.com/images/naro-kim/post/782256f2-1086-47c5-b540-aeff6cf0f209/image.png"></img>
</p>  

<blockquote>
<p>OSI 7계층 모델의 1계층, 물리계층에서 동작하는 장치. <strong>신호를 받아 증폭 및 재전송</strong>함으로써 더 먼거리까지 데이터를 전송할 수 있도록 도와주는 장치. <strong>근거리 통신망을 확장하거나 서로 연결</strong>하는데 주로 사용한다. 전자기장 확산이나 케이블 손실로 인한 신호 감쇠를 보상해주어 여러대의 리피터로 신호를 먼거리까지 전달하는 것이 가능해진다.</p>
</blockquote>
<h2 id="허브">허브</h2>
<p style="width:480px;">
<img src="https://velog.velcdn.com/images/naro-kim/post/1e82fa00-b50a-4833-ac97-52519ab87aec/image.png"></img>
</p> 

<blockquote>
<p>OSI 7계층 모델의 1계층, 물리계층에서 동작하는 장치. <strong>다수의 PC와 장치들을 묶어 LAN을 구성</strong>할 때 각 PC에 <strong>연결된 노드들을 한 곳으로 모으는 역할</strong>을 한다. 노드의 중심축에 놓여, 여러 컴퓨터들을 연결해 하나의 <code>네트워크</code>를 만들어준다. UTP 랜 케이블을 이용해 가까운 거리의 컴퓨터들을 연결하는 네트워크 장치.</p>
</blockquote>
<h2 id="브릿지">브릿지</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/f42f605f-7498-4e3b-8b72-3afc112736e8/image.png" alt=""></p>
<blockquote>
<p>OSI 7계층 모델에서 2단계, 데이터 링크 계층에 있는 여러 <strong>네트워크 세그먼트를 연결</strong>하는 장치. 물리계층의 리피터, 네트워크 허브와 비슷하지만 <strong>특정 네트워크로 부터 오는 트래픽을 관리</strong>하는 역할을 한다. 공식적으로 IEEE 802.1D 표준을 따르는 장치를 뜻한다.</p>
</blockquote>
<h2 id="스위치">스위치</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/9b796956-9a62-4984-ae23-f371bc0aec5a/image.png" alt=""></p>
<blockquote>
<p><code>OSI L2 스위치</code> : 데이터 링크 계층에 있는 장치. 자신과 연결된 장비들의 MAC주소와 그 장비가 연결된 포트를 기억해두었다가, 기억한 <strong>MAC 주소로 데이터가 오면 매칭되는 포트로 데이터를 전달</strong>하는 네트워크 장치</p>
</blockquote>
<blockquote>
<p><code>OSI L3 스위치</code> : 대상의 <strong>IP 주소를 기반으로 데이터를 전달</strong>한다. 
대부분의 스위치는 계층 2 스위치로 이더넷 케이블을 사용하여 네트워크 장치에 연결된다. </p>
</blockquote>
<blockquote>
<p><code>OSI L4 스위치</code> : 계층 4의 TCP를 읽고 처리한다. 주로 <strong>로드밸런싱(서버 부하 분산)</strong>을 처리한다. 네트워크 외부에서 들어오는 모든 요청은 서버가 아닌 L4 스위치를 거쳐야 한다. 이 요청들은 L4 스위치가 받아 서버에 적절히 분산시켜 준다. L4 스위치는 부하 분산 뿐 아니라 <code>TCP, UDP, HTTP</code>와 같은 <strong>프로토콜 헤더를 분석</strong>하며 거기에 IP를 Network address로 변환하고 더해 보낼 수 있다. </p>
</blockquote>
<blockquote>
<p><code>OSI L7 스위치</code> : 계층 7, Application Layer에서 <strong>고급 로드 밸런싱과 트래픽 관리</strong>에 사용된다. TCP/UDP뿐 아니라, <strong><code>payload</code>까지 분석해 로드밸런싱</strong>을 한다. 즉, 7 계층까지 올라온 패킷의 내용들(URL, 캐시, 쿠키)을 분석해 요청을 분산한다. 또한 L7 스위치는 App 수준 보안 기능을 제공해 <strong>방화벽으로 악성 요청을 필터링하거나 인증 및 접근 제어를 수행</strong>해 보안을 강화할 수 있다. </p>
</blockquote>
<h2 id="라우터">라우터</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/009a9dcf-37d1-44f5-b820-e1fe5f0ca3f4/image.png" alt=""></p>
<blockquote>
<p>컴퓨터 혹은 네트워크 장치들간의 경로(Route)를 알고 있는 장치. 라우터는 데이터 패킷이 네트워크를 통과한 뒤 <strong>대상에 도달하는 경로를 선택</strong>한다. 서로 다른 네트워크에 연결하고 있는 인터넷을 구성하는 대규모 네트워크인 LAN, WAN 등 네트워크에서 다른 네트워크로 데이터를 전달하여 수행한다.</p>
</blockquote>
<p>라우터는 인터넷 연결에 필요합니다. 둘 이상의 패킷 전환 네트워크 또는 서브넷을 연결하는 장치입니다. 주로 가정이나 소규모 사무실에서 인터넷 액세스를 위해 라우터를 필요로 하며 주요한 기능은 아래와 같습니다.</p>
<ol>
<li>라우터는 <strong>데이터 패킷을 의도한 IP주소로 전달</strong>하여 네트워크 트래픽을 관리합니다.</li>
<li>여러 장치가 동일한 인터넷 연결을 사용할 수 있도록 합니다.</li>
</ol>
<p>라우터에는 여러 유형이 있지만 대부분 근거리 통신망(LAN)과 광역 네트워크(WAN) 간에 데이터를 전달합니다. 라우터는 패킷을 효율적으로 전달하기 위해 네트워크 대상에 대한 경로 목록인 <code>라우팅 테이블</code>을 사용합니다. 라우터는 패킷의 헤더를 읽어들여 테이블을 참조해 효율적인 경로를 파악하고 경로상의 다음 네트워크로 패킷을 전달합니다.</p>
<h1 id="네트워크의-종류">네트워크의 종류</h1>
<h2 id="lanlocal-area-network">LAN(Local Area Network)</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/453e4ef9-1e53-4c3b-80ee-fc74a3b28d42/image.png" alt=""></p>
<blockquote>
<p>LAN(로컬 영역 네트워크): LAN은 비교적 짧은 거리에서 컴퓨터를 연결하여 데이터, 파일 및 리소스를 공유할 수 있도록 합니다. 예를 들어 LAN은 사무실 건물, 학교 또는 병원 내의 모든 컴퓨터를 연결합니다. 일반적으로 LAN은 개인이 소유하고 관리합니다.</p>
</blockquote>
<h3 id="lan의-작동">LAN의 작동</h3>
<p>대부분의 LAN은 <code>라우터</code>라는 중앙축에서 인터넷에 연결됩니다. 가정의 LAN은 단일 라우터를 주로 사용하는 한 편, 더 큰 공간이나 대형 사무실의 LAN은 보다 효율적인 패킷 전달을 위해 <code>네트워크 스위치</code>를 추가로 사용할 수 있습니다.</p>
<h2 id="panpersonal-area-network">PAN(Personal Area Network)</h2>
<blockquote>
<p>PAN(개인 네트워크): PAN은 한 사람에게 서비스를 제공합니다. 예를 들어, iPhone과 Mac을 사용하는 경우 두 기기에서 문자 메시지, 이메일, 사진 등의 콘텐츠를 공유하고 동기화하는 PAN을 설정했을 가능성이 높습니다.</p>
</blockquote>
<h2 id="manmetropolitan-area-network">MAN(Metropolitan Area Network)</h2>
<blockquote>
<p>MAN(대도시 네트워크): MAN은 일반적으로 LAN보다 크지만 WAN보다 작습니다. 보통 도시와 정부 기관에서 MAN을 소유하고 관리합니다.</p>
</blockquote>
<h2 id="wanwide-area-network">WAN(Wide Area Network)</h2>
<blockquote>
<p>WAN(광역 네트워크): 이름에서 알 수 있듯, WAN은 지역 간 또는 대륙 간 등 넓은 지역의 컴퓨터를 연결합니다. 인터넷은 전 세계 수십억대의 컴퓨터를 연결하는 가장 큰 WAN입니다. WAN은 일반적으로 집합적 또는 분산적 소유권 모델로 관리됩니다.</p>
</blockquote>
<h2 id="이더넷">이더넷?</h2>
<blockquote>
<p>이더넷은 네트워크에 연결된 각 기기들이 48비트 길이의 고유의 MAC 주소를 가지고 이 주소를 이용해 상호간에 데이터를 주고 받을 수 있도록 만들어졌다. 전송 매체로는 BNC 케이블 또는 UTP, STP 케이블을 사용하며, 각 기기를 상호 연결시키는 데에는 허브, 네트워크 스위치, 리피터 등의 장치를 이용한다</p>
</blockquote>
<h1 id="lan과-wan의-차이점-difference-between-lan-and-wan">LAN과 WAN의 차이점 (Difference between LAN and WAN)</h1>
<ul>
<li>지역 네트워크와 광역 네트워크의 차이 </li>
</ul>
<hr>
<p>참고 자료
<a href="https://www.cloudflare.com/ko-kr/learning/network-layer/what-is-a-network-switch/">네트워크 스위치란? | 스위치와 라우터의 비교
</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터 네트워크 모델과 계층]]></title>
            <link>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Thu, 11 Jan 2024 14:10:10 GMT</pubDate>
            <description><![CDATA[<h1 id="컴퓨터-네트워크-모델이란">컴퓨터 네트워크 모델이란?</h1>
<p>컴퓨터 네트워크에는 다양한 기기 간 통신을 위해 <strong>약속된 구조</strong>가 존재합니다. </p>
<blockquote>
</blockquote>
<p>OSI Model
TCP/IP Model</p>
<p>이러한 모델들의 목표는 한 컴퓨터 시스템으로부터 다른 컴퓨터 시스템으로의 데이터 전송입니다. 모델은 여러 계층으로 이루어져있고, 각 계층에선 신뢰성 있는 전송 서비스가 요구됩니다. </p>
<p>또한, 이러한 목적으로 만들어졌기 때문에 <strong>통신이 일어나는 과정을 단계별로 파악</strong>할 수 있습니다. 계층화를 통해 <strong>데이터 흐름을 한눈에 알아보기 쉽고</strong>, 사람들이 이해하기 쉬워 집니다. OSI 모델의 경우 7단계 중 <strong>특정한 곳에 이상</strong>이 생기면 다른 단계의 장비 및 소프트웨어를 건들이지 않고도 <strong>이상이 생긴 단계만 고칠 수 있습니다.</strong> </p>
<h2 id="osi-7-layer-model">OSI 7 Layer Model</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/0a10fc2b-73ed-4aeb-834a-6338cd841d90/image.png" alt=""></p>
<p>OSI 7 계층(Open Systems Interconnection Reference Model)은 표준 <strong><a href="https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EA%B3%84%EC%B8%B5#%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%B4%EB%9E%80">프로토콜</a></strong>을 사용하여 다양한 통신 시스템이 통신할 수 있도록 국제표준화기구에서 만든 개념 모델입니다. 이 모델은 7개의 추상적 계층으로 통신 시스템을 나누며 각 계층은 다음 계층 위에 쌓인 형태입니다. </p>
<blockquote>
<ul>
<li>7.응용 프로그램 계층 (Application Layer)</li>
</ul>
</blockquote>
<ul>
<li>6.표현 계층 (Presentation Layer)</li>
<li>5.세션 계층 (Session Layer)</li>
<li>4.전송 계층 (Transport Layer)</li>
<li>3.네트워크 계층 (Network Layer)</li>
<li>2.데이터 링크 계층 (Data Link Layer)</li>
<li>1.물리 계층 (Physical Layer)</li>
</ul>
<p>OSI 7계층 모델에서의 데이터의 송수신은 간략하게 아래와 같이 말할 수 있습니다.</p>
<blockquote>
<p>송신 : 높은 계층에서 낮은 계층으로 데이터 전달
수신 : 낮은 계층에서 높은 계층으로 데이터 전달</p>
</blockquote>
<p>각 계층은 서로 독립적이며, 송신 과정에서 각 계층에 필요한 정보를 점진적으로 추가해 데이터를 가공합니다. 이때, 각 데이터를 제어하는 <strong>헤더(header)</strong>나 <strong>트레일러(trailer)</strong>가 붙게 되는데 이 과정을 <strong>데이터 캡슐화</strong>라고 부릅니다.</p>
<h3 id="데이터-캡슐화encapsulation과-역캡슐화decapsulation">데이터 캡슐화(Encapsulation)과 역캡슐화(Decapsulation)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/074e4588-ffb8-450c-9b56-d721acd69896/image.png" alt=""></p>
<blockquote>
<p>은닉화 : 캡슐화는 한 계층에서 추가된 헤더를 다른 계층이 볼 수 없게하는 <strong>은닉</strong>의 효과를 갖는다. </p>
</blockquote>
<p>다음으로, OSI 7계층에서 가장 상위에 위치한 응용 프로그램 계층부터 탑다운으로 살펴보겠습니다.</p>
<h3 id="7응용-프로그램-계층-application-layer">7.응용 프로그램 계층 (Application Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/d78a733c-f2c5-43b2-b6dc-9af4a33f300a/image.png" alt=""></p>
<blockquote>
<p>요약 : HTTP, SMTP, FTP 등의 프로토콜을 사용자에게 UI를 통해 제공한다.</p>
</blockquote>
<p>응용 프로그램 계층은 <strong>사용자의 데이터와 직접 인터랙션</strong>하는 유일한 계층입니다. 웹 브라우저 및 이메일 클라이언트와 같은 SW 애플리케이션들은 통신을 위해 이 계층을 의지합니다. 즉, <strong>HTTP, FTP, SMTP</strong>와 같은 프로토콜을 응용 프로그램의 UI를 통해 제공하는 계층입니다. 더 정확히 말하자면 응용 프로그램 계층은 <strong>소프트웨어가 사용자에게 의미있는 데이터를 제공</strong>하기 위한 <strong>프로토콜과 데이터를 조작</strong>하는 역할을 합니다.</p>
<h3 id="6표현-계층-presentation-layer">6.표현 계층 (Presentation Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/510780c3-97c1-432d-b935-3edf7c0f4ee6/image.png" alt=""></p>
<blockquote>
<p>요약 : 데이터를 <strong>표준화</strong>된 형식으로 변경한다.</p>
</blockquote>
<p>표현 계층은 주로 데이터의 번역을 담당하며, 응용 프로그램 계층 대신 사용자 시스템에서 데이터 형식 처리를 진행합니다. 즉, <strong>데이터 변환, 암호화, 압축</strong>을 담당합니다. 예시로는, SMTP 프로토콜에 사용되는 전자우편 포맷인 &quot;MIME&quot;의 인코딩과 암호화가 이 계층에서 이루어집니다.</p>
<p>데이터 변환 : 서로 다른 두 통신 장치는 서로 다른 인코딩 방법을 사용하고 있을 수도 있으므로, 표현 계층은 수신 장치의 애플리케이션이 이해할 수 있는 구문으로 수신 데이터를 변환합니다. </p>
<p>암호화 : 장치가 암호화된 연결로 통신할 경우, 최종 송신자에게 암호화를 추가하거나 수신자에게 디코딩해 읽기 쉬운 데이터로 애플레킹션 계층 사용을 돕는 역할을 합니다.</p>
<p>압축 : 애플리케이션 계층에서 수신한 데이터를 제 5계층인 세션 계층으로 전송하기 전에 압축합니다. 전송 데이터 양을 최소화하여 통신 속도와 효율을 높입니다.</p>
<h3 id="5세션-계층-session-layer">5.세션 계층 (Session Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/43e3a4f2-e5d3-4c9b-813e-a9555c46c6a2/image.png" alt=""></p>
<blockquote>
<p>요약 : <strong>세션의 유지 및 해제</strong> 등 응용 프로그램 간 <strong>통신 제어와 동기화</strong>를 한다.</p>
</blockquote>
<p>세션 계층에서는 세션 복구도 가능한데 이때, <code>체크포인트</code>라는 것을 통해 동기화를 진행한다. 예를 들어 클라이언트에서 서버로 데이터를 전송할 때, 5MB를 체크포인트로 설정하고 48MB 데이터 전송 중 연결이 끊어졌다고 해봅시다. 5MB 단위의 체크포인트 설정으로 인해 45MB부터 데이터 전송 세션을 재개할 수 있습니다.</p>
<h3 id="4전송-계층-transport-layer">4.전송 계층 (Transport Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/4467561a-7f7a-4c8d-b306-c5acdc792c35/image.png" alt=""></p>
<blockquote>
<p>요약 : 신뢰성 있는 데이터 전달을 위한 계층으로, <code>TCP</code>나 <code>UDP</code>같은 전송 방식과 포트번호를 결정한다. 세션 계층에서 가져온 데이터를 하위로 보내기 전에 데이터를 <code>세그먼트</code>로 분할하는 역할을 한다. </p>
</blockquote>
<p>서로 다른 두 네트워크 간의 전송을 담당하는 계층으로, 상위 계층에서 데이트를 받아<code>세그멘테이션</code>하여 하위 계층으로 데이터를 전송합니다. 작은 단위로 나뉜 데이터로 인해 유튜브 영상을 보는 것과 같이, preload된 데이터를 미리 볼 수 있고 손실률이 적어집니다. 
<code>흐름제어</code>는 서로 전송 속도가 다른 기기 사이에서 전송률을 알고리즘을 통해 제어하는 방식입니다.
<code>오류제어</code>는 전송 과정에서 보낸 데이터에 오류가 발생했는지 확인하고 제어하는 방식입니다.</p>
<h3 id="3네트워크-계층-network-layer">3.네트워크 계층 (Network Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/68d63398-2d43-44ce-a7e8-6648bab66fa8/image.png" alt=""></p>
<blockquote>
<p>요약 : 데이터를 송신부에서 수신부가지 보내기 위한 <strong>최적 경로를 선택</strong>하는 라우팅을 수행한다. 이때 선택한 최적 경로를 <code>라우트(route)</code>라고 한다. 네트워크 계층의 장비로는 <code>라우터(router)</code>가 있다.</p>
</blockquote>
<p>데이터의 전송을 담당하며 호스트에게 IP번호를 부여하고, 도착지까지 최적 경로를 찾습니다. 데이터 링크 계층은 동일한 네트워크 내의 전송을 담당하는 한편, 네트워크 계층은 서로 다른 두 네트워크의 전송을 담당합니다.</p>
<h3 id="2데이터-링크-계층-data-link-layer">2.데이터 링크 계층 (Data Link Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/386700fa-81f5-4efe-a864-91bcc94f8e59/image.png" alt=""></p>
<blockquote>
<p>요약 : 데이터 흐름을 관리하며 <strong>동일한</strong> 네트워크에 있는 두 장치 간 데이터 전송을 용이하게 한다. 데이터의 <strong>오류 검출 및 복구</strong>를 수행하며, <code>브릿지(bridge)</code>, <code>스위치(Switch)</code>, <code>이더넷(Ethernet)</code>의 장치가 이 계층에 속한다.</p>
</blockquote>
<p>데이터 링크 계층의 데이터 단위를 <strong>프레임</strong>이라고 부릅니다. 프레임은 데이터그램에 헤더와 트레일러를 붙여 캡슐화한 단위입니다. 이때, <strong>MAC 주소</strong>가 프레임 헤더에 들어있어 데이터의 출처와 목적지를 알 수 있습니다. 물리적 주소인 <code>MAC address</code>는 xx:xx:xx:xx:xx:xx 형식의 6Byte로 전 세계에서 유일한 주소입니다.  </p>
<p>데이터 링크 계층도, 전송 계층과 유사한 <code>오류제어</code>와 <code>흐름제어</code>를 지원합니다. 단, 차이점은 데이터 링크 계층은 <strong>전진 오류 수정</strong>방식을 사용하고, 전송 계층은 <strong>검출 후 재전송</strong> 방식을 사용한다는 점입니다. </p>
<ul>
<li><code>오류제어</code> : 송신 측에서 데이터를 프레이밍하고 이를 0과 1 bit로 변환해 수신자에게 보내, 물리적 손실과 전기적 신호 변형 등에 매우 취약한 형태입니다. 따라서, 수신 장치의 링크 계층이 데이터를 받은 후 프레이밍 하는 과정에서 에러를 검출합니다. </li>
<li><code>흐름제어</code> : 송신자와 수신자의 처리 속도 차이를 해결합니다. </li>
</ul>
<h3 id="1물리-계층-physical-layer">1.물리 계층 (Physical Layer)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/26a6a7eb-170f-4195-8d1d-85f0538c0d7d/image.png" alt=""></p>
<blockquote>
<p>요약 : 데이터를 <strong>bit 단위의 0과 1의 신호로 변환</strong>한 후 송신자가 장비를 사용해 전송하거나, 수신자가 <strong>전기 신호로 데이터를 복원</strong>한다. <code>스위치(Switch)</code>와 <code>리피터(repeater)</code>나 <code>허브(hub)</code>, 케이블 등이 물리 계층에 해당하는 장치이다.  </p>
</blockquote>
<h3 id="osi-모델과-tcpip-모델의-차이점">OSI 모델과 TCP/IP 모델의 차이점</h3>
<ul>
<li>OSI는 7개의 계층이 있다. 반면, TCP/IP는 4개의 계층이 있다.</li>
<li>OSI는 수직적 접근 방식을 따른다. TCP/IP는 수평적 접근 방식을 따른다.</li>
<li>OSI는 <strong>다른 상호 연결 및 통신이 가능한 시스템에서 사용되는 네트워크 통신(개방형 시스템 상호 연결)을 정의</strong>하는 논리 모델이다. TCP/IP는 <strong>특정한 컴퓨터를 인터넷에 연결하는 방법과 컴퓨터 간 전송(Transmission 제어 프로토콜)</strong>을 정의한다.</li>
<li>OSI 헤더는 <strong>5Byte</strong>이다. TCP/IP 헤더는 <strong>20Byte</strong>이다.</li>
<li>OSI는 라우터, 스위치, 마더보드 및 기타 하드웨어를 표준화한다. TCP/IP는 서로 다른 유형의 컴퓨터 간 연결 설정을 돕는다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/d9ddd0a7-3c4f-4f94-9fe3-3927c6713474/image.png" alt=""></p>
<h2 id="tcpip-model">TCP/IP Model</h2>
<blockquote>
</blockquote>
<ul>
<li><code>TCP(Transmission Control Protocol)</code>는 데이터를 나눈 단위인 패킷의 전달 여부와 전송 순서를 보장하는 통신 방식입니다.</li>
<li><code>IP(Internet Protocol)</code>은 인터넷 주소 체계로, 소스 장치에서 대상 장치로 <code>패킷</code>을 전달하는 것이 핵심 기능입니다.</li>
</ul>
<p><code>TCP/IP</code>란 OSI 7계층을 단순화해 만든 인터넷에서 데이터를 주고받기 위한 네트워크 프로토콜입니다. 이 4계층의 데이터 흐름과 각 계층의 역할은 아래와 같습니다.</p>
<blockquote>
<p>4.응용 계층 (Application Layer)
3.전송 계층 (Transport Layer)
2.인터넷 계층 (Internet Layer)
1.네트워크 인터페이스 계층 (Network Interface Layer)
    - 1.1 물리 계층(Physical Layer)
    - 1.2 데이터 링크 계층(Data Link Layer)</p>
</blockquote>
<h3 id="1-네트워크-인터페이스-계층network-interface-layer">1. 네트워크 인터페이스 계층(Network Interface Layer)</h3>
<blockquote>
<p>요약 : 네트워크 접근 계층이라고도 하며, 데이터를 전기 신호로 변환하고 <strong>MAC주소를 사용해 기기에 데이터를 전달</strong>한다. 이더넷, Wi-fi등이 대표적인 프로토콜이다.</p>
</blockquote>
<h3 id="2-인터넷-계층-internet-layer">2. 인터넷 계층 (Internet Layer)</h3>
<blockquote>
<p>요약 : 데이터를 최종 목적지까지 도달할 수 있게 하는 계층으로, IP가 대표적인 프로토콜이다. 이 계층에서는 전송 계층으로부터 받은 데이터에 헤더를 붙여 캡슐화하는데 이를 <strong>패킷(Packet) ** 혹은 **데이터그램(Datagram)</strong>이라 한다. </p>
</blockquote>
<h3 id="3전송-계층-transport-layer">3.전송 계층 (Transport Layer)</h3>
<blockquote>
<p>요약 : 데이터의 신뢰성을 보장하며, <strong>포트 번호</strong>로 데이터를 적절한 응용 프로그램에 전달하는 역할을 한다. <code>TCP</code>나 <code>UDP</code> 프로토콜이 이 계층에 속하며 데이터 단위는 <code>Segment</code>라고 부른다.</p>
</blockquote>
<h3 id="4응용-계층-application-layer">4.응용 계층 (Application Layer)</h3>
<blockquote>
<p>요약 : 사용자와 소프트웨어를 연결하는 계층. <code>HTTP</code>, <code>HTTPS</code>, <code>DNS</code> 등의 프로토콜이 작동한다.</p>
</blockquote>
<h1 id="프로토콜의-개념과-application-protocols">프로토콜의 개념과 Application Protocols</h1>
<h2 id="프로토콜이란">프로토콜이란?</h2>
<p>네트워크에서 <code>프로토콜</code>은 둘 이상 장치에서 서로 통신하고 이해할 수 있도록 <strong>특정 작업을 수행</strong>하고 <strong>데이터 형식을 지정</strong>하는 표준화된 방법입니다. 다양한 프로토콜이 있지만 이번 포스트에서는 아래 프로토콜들에 대해서 알아보겠습니다.</p>
<blockquote>
<ol>
<li>TCP (Transmission Control Protocol, 전송 제어 프로토콜)</li>
<li>UDP (User Datagram Protocol, 사용자 데이터그램 프로토콜)</li>
<li>DNS (Domain Name System)</li>
<li>FTP (File Transfer Protocol)</li>
<li>SMTP (Simple Mail Transfer Protocol)</li>
<li>SNMP (Simple Network Management Protocol)</li>
<li>HTTP (HyperText Transfer Protocol)</li>
</ol>
</blockquote>
<h2 id="1-tcp-transmission-control-protocol-전송-제어-프로토콜">1. TCP (Transmission Control Protocol, 전송 제어 프로토콜)</h2>
<blockquote>
<p><code>TCP (Transmission Control Protocol, 전송 제어 프로토콜)</code>는 OSI와 TCP/IP의 <strong>전송 계층</strong>에서 사용되는 연결 지향적 프로토콜입니다. 여기서, 연결 지향적이란 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜을 의미합니다. 대표적인 예로는 P2P Socket 통신이 있습니다. TCP에서 가장 중요한 점은, <strong>신뢰성 있는 데이터 통신</strong>을 목적으로 한다는 점입니다.</p>
</blockquote>
<p><code>TCP</code>와 <code>UDP</code> 모두 포트 번호를 이용하여 주소를 지정하는 것과 데이터 오류 검사를 위한 Checksun이 존재하는 공통점을 갖고 있습니다. 하지만, 정확성을 추구할지(TCP)와 신속성을 추구할지(UDP)에 따라 차이점을 갖습니다. </p>
<blockquote>
<p>TCP 프로토콜의 기능은 아래와 같습니다.</p>
</blockquote>
<ol>
<li>신뢰성 있는 데이터 전송</li>
<li>흐름 제어 (Flow Control)</li>
<li>혼잡 제어 (Congestion Control)</li>
<li>오류 감지 (Error Detection)</li>
</ol>
<h3 id="tcp의-데이터-전송-data-delivery">TCP의 데이터 전송 (Data delivery)</h3>
<p>TCP 프로토콜은 데이터 정확히, 유실 없이, 올바른 순서로 데이터가 전송됨을 보장합니다. 만일 TCP가 사용되지 않았다면 부정확한 데이터가 전송될 수도 있습니다. 이러한 <code>신뢰성 있는 데이터 전송</code>을 위해, TCP는 연결을 시작할 때엔 <strong>3-way handshake</strong>를, 연결을 종료할 때는 <strong>4-way handshake</strong>를 통해 통신을 수행합니다. handshake의 과정에서는 송신부와 수신부 간의 연결을 제어 및 관리할 수 있도록 <code>flag</code>값을 주고받습니다.</p>
<blockquote>
<p>주요한 flag의 종류</p>
</blockquote>
<ul>
<li><code>SYN</code> : Synchronization(동기화)의 약자로, 연결을 생성할 때 사용</li>
<li><code>FIN</code> : Finish(종료)의 약자로, 연결을 끊을 때 사용</li>
<li><code>ACK</code> : Acknowledgement(승인)의 약자로, 데이터를 전송하면 수신자가 받았음을 알려줄 때 사용</li>
</ul>
<h4 id="3-way-handshake">3-way handshake</h4>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/16ebe602-952f-4527-9bba-77c429b133d3/image.png" alt=""></p>
<p>TCP 통신을 위한 네트워크 연결은 3-way handshake 방식으로 연결됩니다. 3 way handshake는 서로 포트를 확인하고 3번의 요청/응답 이후에 연결되어 UDP 방식보다 속도가 느려지는 원인이 된다. </p>
<p><strong>연결 과정</strong></p>
<blockquote>
<ol>
<li><strong>SYN</strong>, Client -&gt; Server : 첫번째 단계로, 클라이언트는 서버와 연결을 요청하려 합니다. <code>SYN</code> 과 함께 Segment를 보냅니다.</li>
<li><strong>SYN-ACK</strong>, Server : LISTEN 상태에서 클라이언트로부터 <code>SYN</code> 데이터를 받고 <code>SYN_RCV</code>(SYN Received)로 상태가 변경된다. 서버 호스트는 클라이언트에게 <code>SYN-ACK</code> bit set을 응답으로 보냅니다. 이는 정상적인 세그먼트 수신의 응답인 <code>ACK</code> 신호와 함께 클라이언트 포트를 연결해달라는 요청인 <code>SYN</code>을 전송합니다.</li>
<li><strong>ACK</strong>, Client : <code>SYN-ACK</code>를 받은 클라이언트는 ESTABLISHED로 상태를 변경하고 서버에게 정상적인 수신의 응답인 <code>ACK</code>를 전송합니다.<code>ACK</code>를 받은 서버 또한 상태가 ESTABLISHED가 됩니다.</li>
</ol>
</blockquote>
<p>위와 같은 연결 과정을 거쳐, 1~3번의 통신이 정상적으로 이루어지면 서로의 포트가 ESTABLISHED 되며 연결됩니다. </p>
<h4 id="4-way-handshake">4-way handshake</h4>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ae45aa74-b645-4a29-9659-4a97759d3f4b/image.png" alt=""></p>
<p>4-way handshake는 TCP 연결을 해제할 때 이뤄지는 과정입니다. 연결을 해제할 때에는 이름과 마찬가지로 요청과 응답을 총 4번 주고받게 됩니다. </p>
<p><strong>해제 과정</strong></p>
<blockquote>
<ol>
<li><strong>FIN</strong>, Client -&gt; Server : 클라이언트가 서버와의 연결을 종료하고자 <code>FIN</code> 메세지를 보냅니다. 송신에 성공하면 서버는 FIN_WAIT1 상태가 됩니다.</li>
<li><strong>ACK</strong>, Server -&gt; Client : 서버가 클라이언트로부터 <code>FIN</code> 메세지를 성공적으로 받으면 응답으로 <code>ACK</code> 메세지를 보냅니다. 이때 클라이언트는 CLOSE_WAIT 상태가 되고, 앱을 종료하는 등의 작업을 수행합니다. 동시에 서버에서는 <code>ACK</code> 메세지에 의해 FIN_WAIT2 상태가 됩니다.</li>
<li><strong>FIN</strong>, Client -&gt; Server : 클라이언트에서는 연결 종료 준비가 끝나면 서버에 <code>FIN</code> 메세지를 보내고 LAST_WAIT 상태가 됩니다.</li>
<li><strong>ACK</strong>, Server -&gt; Client : 서버는 3단계 클라이언트에서 보낸 <code>FIN</code> 메세지에 응답하기 위해 <code>ACK</code> 메세지를 보내고 TIME_WAIT 상태가 됩니다. 일정 시간 동안 TIME_WAIT 상태를 유지하여 수신 지연으로 발생한 <strong>패킷 유실</strong>이나 <code>ACK</code>가 제대로 전달되지 않아 발생한 <strong>연결 미해지</strong>에 대비합니다. LAST_WAIT 상태의 클라이언트는 서버로부터 <code>ACK</code>를 받고 CLOSED 상태가 됩니다.</li>
</ol>
</blockquote>
<h2 id="2-udp-user-datagram-protocol-사용자-데이터그램-프로토콜">2. UDP (User Datagram Protocol, 사용자 데이터그램 프로토콜)</h2>
<blockquote>
<p><code>UDP (User Datagram Protocol)</code>는 OSI와 TCP/IP의 <strong>전송 계층</strong>에서 사용되는 비연결 지향적 프로토콜입니다. <code>비연결</code>이란 통신 전에 연결 설정이 되어있지 않다는 점으로, TCP와 가장 큰 차이점입니다. 또한, UDP는 데이터 패킷 전달을 보장하지 않습니다. 수신자 측에서의 데이터 수신 여부가 중요치 않기 때문에 TCP보다 빠릅니다.</p>
</blockquote>
<p>이러한 특징에 의해 UDP는 주로 빠른 통신이 필요한 곳에 사용되며 게임, 스트리밍, 비디오 및 음악 스트리밍과 같이 안정성을 고려하지 않는 네트워크에서 사용합니다. </p>
<h3 id="tcp와-udp의-차이점">TCP와 UDP의 차이점</h3>
<h2 id="3-dns-domain-name-system">3. DNS (Domain Name System)</h2>
<blockquote>
<p><code>DNS</code>는 사용자가 숫자로된 인터넷 프로토콜 주소(IP)대신 <strong>인터넷 도메인 이름과 검색 가능한 URL</strong>을 사용하여 웹사이트에 접속하는 것을 가능하게 합니다. </p>
</blockquote>
<h2 id="4-ftp-file-transfer-protocol">4. FTP (File Transfer Protocol)</h2>
<blockquote>
<p><code>FTP</code>란 <strong>파일 전송 프로토콜</strong>의 약자로 TCP/IP 네트워크의 장치가 파일을 전송할 때 사용하는 규칙입니다. FTP는 클라이언트-서버 프로토콜로, 클라이언트 파일 요청 시 서버가 요청한 파일을 제공할 수 있습니다. 따라서 FTP 프로토콜 연결 설정을 위해선 두 가지 기본 채널이 필요합니다.</p>
</blockquote>
<h3 id="ftp의-원리">FTP의 원리</h3>
<ul>
<li>명령 채널 : 명령을 시작해 어떤 파일에 엑세스할 것인지 등과 같은 기본 정보를 전달한다. 제어 포트인 21번 포트로 사용자 인증, 명령을 위한 채널이 만들어진다.</li>
<li>데이터 채널 : 두 장치 간에 파일 데이터를 전송한다. </li>
</ul>
<p>연결을 설정하려면 사용자가 FTP 서버 로그인 정보를 제공해야 합니다. 일반적으론 21번 포트를 기본 통신 모드로 사용하며, 데이터 채널의 연결 모드에는 <code>능동 모드</code>와 <code>수동 모드</code> 2가지가 존재합니다.</p>
<h3 id="ftp의-보안">FTP의 보안</h3>
<p>FTP는 보안 프로토콜로 계획되지 않았기 때문에 FTP 전송은 암호화되지 않아 수많은 보약 취약점이 존재합니다. 대표적인 취약점은 아래와 같습니다.</p>
<ul>
<li>무차별 대입 공격</li>
<li>FTP 바운스 어택</li>
<li>패킷 캡처</li>
<li>포트 재킹</li>
<li>스푸핑</li>
<li>사용자 이름 열거</li>
</ul>
<p>이러한 보안상 단점으로 오늘 날엔 FTP를 대체할 <code>SFTP</code>, <code>HTTPS</code>등의 프로토콜 옵션이 시장에 출시되었으며 2020년부터 Google은 FTP 지원을 중단했습니다. </p>
<h2 id="5-smtp-simple-mail-transfer-protocol">5. SMTP (Simple Mail Transfer Protocol)</h2>
<p>단순 전자우편 전송 프로토콜(SMTP)은 네트워크를 통해 전자우편-이메일-을 전송하는 기술 표준입니다. 다른 네트워킹 프로토콜들과 마찬가지로, HW나 SW관계없이 데이터를 교환할 수 있습니다.</p>
<h2 id="6-snmp-simple-network-management-protocol">6. SNMP (Simple Network Management Protocol)</h2>
<p>SNMP))는 UDP/IP(사용자 데이터그램 프로토콜/인터넷 프로토콜)를 사용하여 이더넷 연결을 통해 네트워크 관리 작업을 수행하는 응용 계층 프로토콜입니다.</p>
<h2 id="⭐️7-http-hypertext-transfer-protocol">⭐️7. HTTP (HyperText Transfer Protocol)</h2>
<blockquote>
<p><code>HTTP (HyperText Transfer Profocol)</code>는 인터넷상에서 데이터를 전송하기 위한 프로토콜로, TCP/IP 4계층에서 응용 계층에 속한다. 주로 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜이다. 주로 TCP를 사용하고, HTTP/3부터는 UDP를 사용하며 80번 포트를 사용한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/c82e37d8-6719-45ca-9370-1f8a1a14d3db/image.png" alt=""></p>
<p>HTTP는 웹에서 이루어지는 모든 데이터 교환의 기초이며 클라이언트-서버 사이에서 이루어지는 <code>요청/응답(Request/Response)</code> 프로토콜입니다. 여기서 <code>클라이언트-서버 프로토콜</code>이란 수신자 측(클라이언트)에 의해 요청이 초기화되는 프로토콜입니다. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/f28094fc-9222-4064-887b-5972f99b460b/image.png" alt=""></p>
<p>위 이미지는 TCP/IP 4번째 계층인 응용 계층에서 Web application과 HTTP 프로토콜의 관계를 나타낸 그림입니다.</p>
<p>클라이언트와 서버는 데이터 스트림과는 대조적으로, 개별적인 <code>메세지 교환</code>에 의해 통신합니다. 보통 브라우저인 클라이언트에 의해 전송되는 메세지를 <code>요청(requests)</code>라 부르며, 그에 대해 서버에서 응답으로 전송되는 메세지를 <code>응답(responses)</code>라고 부릅니다.  </p>
<h3 id="http의-특징">HTTP의 특징</h3>
<blockquote>
<ol>
<li>비연결성</li>
<li>무상태</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ca64b3f8-9635-4824-b6d9-4f31cb1748cc/image.png" alt=""></p>
<h4 id="1-비연결성connectionless">1. 비연결성(connectionless)</h4>
<p>HTTP의 <code>비연결성(Connectionless)</code>은 <strong>클라이언트에서 요청을 보낸 후 서버로부터 응답을 받으면 연결을 끊는 것</strong>을 의미합니다. 비연결성은 불특정 다수를 대상으로 하는 서비스에 유리합니다. 서버에서 응답을 받고 서도 연결을 유지하려면 그만큼 자원을 사용하게 됩니다. 따라서 연결을 필요시에만 유지하면 자원을 효율적으로 사용할 수 있습니다.</p>
<p>하지만, 이런 특징 때문에 <strong>서버가 클라이언트를 기억할 수 없다</strong>는 단점이 있습니다. 또한, 동일한 클라이언트에서 연속적으로 요청이 오면 연결과 해제 과정이 반복되어 자원이 낭비됩니다. </p>
<p>이러한 단점을 보완하기 위해 HTTP 헤더의 일종인 <strong>HTTP Keep Alive</strong>를 사용하여 마지막 응답 이후 일정 시간 동안 연결을 유지합니다. 이를 통해 동일한 클라이언트로부터 온 요청이 오면 연결을 생략할 수 있습니다.</p>
<h4 id="2-무상태stateless">2. 무상태(stateless)</h4>
<p>HTTP에서의 <code>무상태(Stateless)</code>란 <strong>서버에서 클라이언트의 상태를 저장하지 않는 것</strong>을 의미합니다. 따라서 클라이언트는 요청에 필요한 데이터를 모두 가지고 있어야 합니다. 또는 서버가 클라이언트로 받은 요청 사항을 모두 저장해야 합니다. 이 방법들을 <strong>쿠키(Cookie)</strong>와 <strong>세션(Session)</strong>이라고 부릅니다. </p>
<p>무상태의 장점은 <strong>서버 확장성이 높다</strong>는 점입니다. 클라이언트 상태를 저장하지 않기에, 요청에 응답하는 서버가 바뀌어도 됩니다. 따라서 특정 서버에 문제가 생겨 응답하지 않더라도 신규 서버 확장으로 요청에 따른 응답을 보내 문제를 해결할 수 있습니다.</p>
<h4 id="쿠키와-세션">쿠키와 세션?</h4>
<blockquote>
<p><code>쿠키 (Cookie)</code> : 클라이언트 <strong>로컬 웹 브라우저에 저장하는 데이터파일</strong>로, 키와 값을 저장한다. 대표적인 예로는 로그인 정보와 장바구니가 있으며 약 4kb이다.
<code>세션 (Session)</code> : <strong>서버에서 클라이언트와 연결 정보를 저장 및 관리</strong>하는 것을 말한다. 서버에 데이터가 저장되므로 보안 면에서는 쿠키보다 좋지만, 접속자가 많을 경우 서버에 과부하가 올 수 있다. 용량에 제한이 없다.</p>
</blockquote>
<h3 id="http-메세지-구조">HTTP 메세지 구조</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/e7ed6134-8ae0-4b21-ae46-a7bb26e04f23/image.png" alt=""></p>
<p>클라이언트와 서버 사이 소통은 ASCII 메세지로 이루어진다. </p>
<h3 id="http-메서드">HTTP 메서드</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/28ea1f6d-6817-48fe-bd9d-2a288d5c7bbe/image.png" alt=""></p>
<hr>
<p>참고자료
<a href="https://www.ibm.com/kr-ko/topics/dns">IBM - DNS(Domain Name System)란 무엇인가요?</a>
<a href="https://experience.dropbox.com/ko-kr/resources/what-is-ftp">Dropbox - what is FTP</a>
<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Overview">MDN web docs - HTTP Overview</a>
<a href="https://coding-factory.tistory.com/614">[Network] TCP / UDP의 개념과 특징, 차이점</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터 네트워크 데이터 전송과 교환]]></title>
            <link>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%86%A1%EA%B3%BC-%EA%B5%90%ED%99%98</link>
            <guid>https://velog.io/@naro-kim/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%86%A1%EA%B3%BC-%EA%B5%90%ED%99%98</guid>
            <pubDate>Thu, 11 Jan 2024 12:58:18 GMT</pubDate>
            <description><![CDATA[<h1 id="컴퓨터-네트워크란---컴퓨터-네트워크의-정의">컴퓨터 네트워크란? - 컴퓨터 네트워크의 정의</h1>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/fd7aea40-df89-4fa2-9e17-639c5d2806cd/image.png" alt=""></p>
<p>일반적으로 통용되는 IT에서의 사용되는 정의는 <code>두 대 이상의 컴퓨터들을 연결하고 서로 통신할 수 있는 링크의 조합</code>을 네트워크라고 말합니다.</p>
<blockquote>
<ul>
<li>물리적 네트워크 : 네트워크를 구성하는 하드웨어 (어댑터, 케이블 및 전화선과 같은 장비)</li>
</ul>
</blockquote>
<ul>
<li>논리적 네트워크 : 소프트웨어 및 개념 모델</li>
</ul>
<p>컴퓨터 네트워크는 <code>General-purpse programmable hardware</code>에 기초하여 만들어져있고 ‘전화’나 ‘tv 신호 전달’과 같은 특정 어플리케이션에 최적화되어 있기보다 <strong>다양한 어플리케이션의 데이터를 전달할 수 있으며, 광범위하고 지속적으로 성장하는 어플리케이션을 지원</strong>합니다.</p>
<h1 id="컴퓨터-네트워크-시스템">컴퓨터 네트워크 시스템</h1>
<p>위에서 설명한 것처럼 모든 네트워크 통신에는 하드웨어 및 소프트웨어가 사용됩니다. </p>
<ul>
<li><code>하드웨어</code> :  물리적 네트워크에 연결되는 물리적 장비. </li>
<li><code>소프트웨어</code> : 특정 시스템 조작과 연관된 프로그램 및 장치 드라이버로 구성됩니다.</li>
</ul>
<p>이렇게 전세계를 연결하고 있는 하드웨어들을 노드와 링크로 표현할 수 있습니다. 가장 낮은 단계의 네트워크는 동축 케이블이나 광섬유 등을 이용하여 두 대 이상의 컴퓨터가 직접 연결되어 이루어집니다. 여기서 연결 매체가 되어주는 동축 케이블과 광섬유를 <code>링크</code>라고 부릅니다. 그리고 링크를 통해 연결된 컴퓨터들을 <code>노드</code>라고 부릅니다.</p>
<h1 id="컴퓨터-네트워크에서-데이터-전송을-구분-하는-법">컴퓨터 네트워크에서 데이터 전송을 구분 하는 법</h1>
<p>컴퓨터 네트워크 사이의 데이터는 <strong>전송</strong>과 <strong>교환</strong>을 통해 링크 사이에서 움직입니다. 이 중에서 <code>전송(Transmission)</code> 은 특정한 물리 매체에 의하여 <strong>일대일로 직접 연결된 두 시스템 간의 신뢰성있는 데이터 전송을 보장</strong>을 목표로 하며 라우팅 개념을 포함하지 않습니다.</p>
<p>컴퓨터 네트워크에서의 데이터 전송 방식은 아래처럼 구분할 수 있습니다.</p>
<blockquote>
<ol>
<li>방향에 따른 데이터 전송</li>
<li>직렬 전송과 병렬 전송</li>
<li>동기 전송과 비동기 전송</li>
</ol>
</blockquote>
<p>그럼 이제 1번부터 전송 방식에 대해 알아보겠습니다.</p>
<h2 id="방향에-따른-데이터-전송-구분">방향에 따른 데이터 전송 구분</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/929b8b1e-d1da-49d9-baea-11b4d23d51e4/image.png" alt=""></p>
<p>이미지와 같이 방향에 따른 데이터 전송의 종류는 아래와 같습니다.</p>
<blockquote>
<ol>
<li>단방향 통신 (Simplex Mode)</li>
<li>반이중 통신 (Half Duplex)</li>
<li>전이중 통신 (Full Duplex)</li>
</ol>
</blockquote>
<p><code>단방향 통신</code></p>
<ul>
<li>한쪽 방향으로만 데이터 전송이 가능한 통신
ex) 키보드 (입력), 모니터 (출력)</li>
</ul>
<p><code>반이중 통신</code></p>
<ul>
<li>양쪽 방향으로 데이터 전송이 가능</li>
<li>때때로 한 방향으로만 데이터 전송이 가능
ex ) 워키토키, 라디오</li>
</ul>
<p><code>전이중 통신</code></p>
<ul>
<li>동시에 양쪽 방향으로 데이터 전송이 가능
ex ) 전화, WebSocket</li>
</ul>
<h2 id="직렬과-병렬-데이터-전송">직렬과 병렬 데이터 전송</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/1fff4025-942f-491d-984a-1cfded519a63/image.png" alt="">
<a href="https://en.wikipedia.org/wiki/Serial_communication">이미지 출처</a></p>
<p>다음으로 직렬전송과 병렬전송은 데이터 전송 방식중의 하나이며 공통점도 갖고 있지만 몇몇 차이점도 갖고 있습니다. 둘을 구분짓는 가장 큰 차이점은 단위 시간 당 직렬전송에서는 <code>bit-by-bit</code> 전송이 이루어진다는 반면, 병렬 전송은 <code>1byte(8 bits)</code> 전송이 이루어진다는 점입니다. 또다른 차이점으로는, 병렬 전송은 이러한 특성에 의해 시간에 민감한 한편 직렬전송은 그렇지 않다는 점도 있습니다.</p>
<h3 id="직렬-전송serial-transmission">직렬 전송(Serial Transmission)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/adf95dd9-107d-40a4-a58c-03a122bfac47/image.png" alt=""></p>
<p>위 이미지에서와 같이, 직렬전송은 한 컴퓨터 시스템에서 다른 컴퓨터 시스템으로 data bit가 흐릅니다. 데이터의 각 비트는 고유한 클럭 펄스 속도를 갖습니다.</p>
<p>직렬 전송에선 <code>Parity bit</code>라고 불리는 시작과 끝을 알리는 start bit, stop bit가 전달 됩니다. 이들을 포함하여 <strong>한 번에 8비트</strong>가 전송됩니다.</p>
<p>이 직렬 전송의 유형에는 두 가지가 있습니다.</p>
<blockquote>
<ol>
<li>비동기 직렬 전송</li>
<li>동기식 직렬 전송</li>
</ol>
</blockquote>
<h4 id="비동기-직렬-전송-asynchronous-serial-transmission">비동기 직렬 전송 (Asynchronous Serial Transmission)</h4>
<p>먼저 비동기 직렬 전송은, 모든 byte에 추가 bit를 붙여 수신자에게 새로운 데이터를 전송했음을 알립니다. 보통 start bit는 0으로, stop bit는 1로 보냅니다. </p>
<h4 id="동기식-직렬-전송-synchronous-serial-transmission">동기식 직렬 전송 (Synchronous Serial transmission)</h4>
<p>동기식 직렬 전송에는 추가 비트가 없습니다. 대신, data는 수많은 바이트로 구성된 프레임의 형태로 전송됩니다. 또, 동기 직렬 전송을 세부적으로 나누면 아래와 같은 방식도 존재합니다.</p>
<ul>
<li><ol>
<li>문자 동기 방식 (Character Oriented Synchronization)</li>
</ol>
</li>
<li><ol start="2">
<li>비트 동기 방식 (Bit Oriented Synchronization)</li>
</ol>
</li>
</ul>
<h3 id="직렬전송의-장단점">직렬전송의 장단점</h3>
<h4 id="장점">장점</h4>
<ol>
<li>매우 효율적이다.</li>
<li>장거리 통신을 위해 사용할 수 있다.</li>
<li>다른 전송 방식들보다 안정적이고 신뢰할 수 있다.</li>
</ol>
<h4 id="단점">단점</h4>
<ol>
<li><a href="https://www.sick.com/il/en/glossary/data-transmission-rate/g/p544444">Transmission rate</a>가 낮습니다.</li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A3%A8%ED%92%8B">throughput</a>이 bit rate에 의존합니다.</li>
</ol>
<h3 id="병렬-전송parallel-transmission">병렬 전송(Parallel Transmission)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/76aa4b1a-11ea-4858-bb97-e34db6dd7aee/image.png" alt=""></p>
<p>병렬전송은 한 컴퓨터 시스템에서 다른 컴퓨터 시스템으로 수많은 bit들을 동시에 전송하는 것을 의미합니다. 병렬전송은 직렬전송보다 bit를 전송하는데 걸리는 시간이 빠르기에 단거리 통신에 쓰입니다. 또한 컴퓨터나 통신 시스템 같은 전자 장비는 내부에 병렬 회로를 사용하기 때문에 하드웨어 적으로도 적합하며 직렬전송에 비해 유리합니다. 하지만 장단점 역시 존재합니다.</p>
<h3 id="병렬전송의-장단점">병렬전송의 장단점</h3>
<h4 id="장점-1">장점</h4>
<ol>
<li>N개의 비트를 동시에 전송할 수 있습니다. 따라서 병렬 전송은 직렬 전송보다 N배 빠르게 수행할 수 있습니다.</li>
<li>병렬 전송을 사용하여 데이터를 컴퓨터에서 출력 장치들로 전송할 수 있습니다. 예를 들어, 프린터는 컴퓨터의 병렬 포트에 연결되어 있으며 매우 빠른 데이터 전송을 보여줍니다.</li>
<li>근거리 통신에 적합합니다.</li>
<li>bit set이 동시에 전송됩니다. </li>
</ol>
<h4 id="단점-1">단점</h4>
<ol>
<li>여러 통신 채널이 요구됩니다.</li>
<li>data stream 전송을 위해선 N개의 통신 회선이 필요합니다. 따라서 N개의 와이어가 연결되어 있어야 합니다. </li>
<li>비용이 많이 들기 때문에 근거리 통신에 사용되는 한계점이 있습니다.</li>
<li>장거리 통신에 사용할 경우, 신호 손실을 최소화하기 위해선 와이어의 두께를 늘려야 합니다.</li>
</ol>
<p>이렇게 <code>방향에 따른 데이터 전송</code>과 <code>직렬전송과  병렬전송</code>, <code>동기전송과 비동기 전송</code>을 알아보며 데이터 전송 방식의 구분에 대해서 알아보았습니다. 이제 다음으로 컴퓨터 네트워크에서 데이터를 전송하는 방식에 대해 알아보겠습니다.</p>
<h1 id="컴퓨터-네트워크의-데이터-전송-구조-network-topology">컴퓨터 네트워크의 데이터 전송 구조 (Network Topology)</h1>
<p>앞에서 설명한 것처럼, 네트워크의 연결은 노드와 링크로 이루어집니다. 이때 노드와 링크의 배열을 Network Topology라 부릅니다. 다양한 방식으로 네트워크 토폴로지를 구성할 수 있습니다. 노드와 링크의 배열인 토폴로지의 몇 가지 유형은 아래와 같습니다.</p>
<h2 id="데이터-전송-구조network-topology의-종류">데이터 전송 구조(Network Topology)의 종류</h2>
<blockquote>
<ol>
<li>점대점 (Point-to-Point)<ol>
<li>스타형 (Start)</li>
<li>트리 (Tree)</li>
<li>Ring</li>
<li>완전형</li>
<li>불규칙형</li>
</ol>
</li>
<li>브로드캐스팅 (Broadcasting)<ol>
<li>버스형</li>
<li>링형</li>
</ol>
</li>
<li>멀티 포인트 (Multipoint)<ol>
<li>유니캐스팅</li>
<li>브로드캐스팅</li>
<li>멀티캐스팅</li>
</ol>
</li>
</ol>
</blockquote>
<p>여기까지, 컴퓨터 네트워크 데이터 전송과 교환에 대해 알아보았으며 다음 포스트로 넘어가 프로토콜의 개념과 HTTP, OSI 7 Layer, TCP/IP과 같은 프로토콜 계층에  대해 알아보겠습니다.</p>
<hr>
<p><strong>참고자료</strong>
<a href="https://www.ibm.com/docs/ko/aix/7.3?topic=management-network-communication-concepts">네트워크 및 통신 개념</a>
<a href="https://velog.io/@ckstn0777/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%97%B0%EA%B2%B0%EB%B0%A9%EC%8B%9D">네트워크-네트워크-개념과-연결방식</a>
<a href="https://www.gilbut.co.kr/book/view?bookcode=BN003825">기술 면접 대비 CS 전공 핵심요약집</a>
<a href="https://www.javatpoint.com/difference-between-serial-and-parallel-transmission">Difference between Serial and Parallel Transmission</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[닌텐도 슈퍼마리오와 '탐닉의 설계자들']]></title>
            <link>https://velog.io/@naro-kim/%ED%83%90%EB%8B%89%EC%9D%98-%EC%84%A4%EA%B3%84%EC%9E%90%EB%93%A4-%EB%8B%A4%EC%9D%B4%ED%82%A4-%EC%8B%A0%EC%9D%B4%EC%B9%98%EB%A1%9C-%EC%A0%80%EB%A5%BC-%EC%9D%BD%EA%B3%A0</link>
            <guid>https://velog.io/@naro-kim/%ED%83%90%EB%8B%89%EC%9D%98-%EC%84%A4%EA%B3%84%EC%9E%90%EB%93%A4-%EB%8B%A4%EC%9D%B4%ED%82%A4-%EC%8B%A0%EC%9D%B4%EC%B9%98%EB%A1%9C-%EC%A0%80%EB%A5%BC-%EC%9D%BD%EA%B3%A0</guid>
            <pubDate>Tue, 09 Jan 2024 15:44:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/b60b543a-5cb1-4542-8416-c0720336bc93/image.png" alt=""></p>
<p> (+ 2024/01/10 추가)
최근에 젤다의 전설 게임 디자인과 관련된 아티클을 읽으면서 <a href="https://www.4gamer.net/games/341/G034168/20170901120/">(원문)</a>, 게임 설계의 명가 닌텐도 답게 유저의 시각적 흐름과 직감을 일깨우는 섬세한 레벨 디자인을 볼 수 있었습니다. 이번 글은 2021년도에 작성했던 닌텐도의 게임 UX 관련 도서의 리뷰를 벨로그로 옮겨 작성해본 글입니다. 게임의 설계의 뒷면에 사용자의 인식과 놀라움, 호기심을 교묘하게 유도하는 장치들이 있음을 공유해봅니다.</p>
<hr>
<h2 id="이-책을-읽은-이유">이 책을 읽은 이유</h2>
<p>본문에 들어가기에 앞서 이 책을 선정해 읽었던 이유를 짧게 소개하고 넘어가겠다. 2020년에 잠깐 일했던 스타트업에서 홀로렌즈를 써보기도 하고 AR 앱을 만들고 난 영향인지 2021년도 당시 산업디자인학과 졸업작품으로 가상 세계의 경험을 탐구하고 싶었다. 가상 세계의 개념을 토대로 한 메타버스의 경험은 게임 속 경험과 유사하기 때문에, 기존의 게임 경험들을 토대로 VR을 설계하는 것이 도움이 될거라 생각하였다. </p>
<p>그래서 게임 기획에 관련된 책들 중에서도 인지 심리학과 UX와 연관이 큰 서적을 골랐다. 또한 &lt;탐닉의 설계자&gt;의 경우 슈퍼마리오나 젤다나 드래곤 퀘스트 중에서도 구체적인 장면과 이벤트가 묘사된 점도 이 책을 읽은 이유 중 하나이다. 이 케이스들을 통해서 졸전 프로젝트 <code>인터랙티브 테스트</code> 구상에 적절한 아이디어를 얻을 수 있을 것이라 예상했다.</p>
<h2 id="인덱스">인덱스</h2>
<blockquote>
<p> 1장. 직감 디자인
 2장. 놀람 디자인
3장. 이야기 디자인
4장. 체험 디자인</p>
</blockquote>
<h2 id="책-소개">책 소개</h2>
<p>저자인 다마키 신이치로는, 유명 게임의 사례들을 UX의 개념을 통해 장마다 분석하며 유저들의 관점에서 마주하는 경험을 설명한다. 누구나 이해할 수 있게끔 단순 졸라맨으로 그려낸 게임 UX 사례들은 직관적으로 맥락의 이해를 돕고있으며 직감 디자인-어포던스-에서 시작해 체험 디자인으로 이어지는 책 전반에는 신이치로가 말하고자 하는 <strong>&#39;게임 기획&#39;</strong>이란 무엇인지, 그의 철학이 설계되듯 녹아있다.</p>
<p><code>직감 - 이야기 - 체험</code> 순으로 이어지는 글의 서사에서, 저자는 사례를 통해 인지 심리학을 바탕으로 사용자가 어떻게 게임 인터페이스와 오브젝트에 반응하는지 보여주며 게임의 기본 설계 원리들 -예를 들어 <code>리턴</code>과 <code>리스크</code>-을 설명한다. 또 책에서 나온 사례들은 닌텐도 슈퍼마리오, 젤다의 전설, 스퀘어에닉스의 드래곤 퀘스트 등 2030대 이상의 게이머라면 누구나 알 법한 게임들로 UX나 기획을 모르는 사람이더라도 쉽게 이해할 수 있다. 또한 각 사례별로 심리 패턴 혹은 설계 개념을 그림과 도식화된 이미지로 설명하여 독자의 이해를 돕고 있다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6b21ed46-61a7-4566-8f58-4a2bb34503d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/2cc51723-eb2c-4ae3-9294-d2542d5c300d/image.png" alt=""></p>
<h2 id="1장-직감-디자인">1장. 직감 디자인</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/e1521399-5da4-4efc-917b-d2a16ab9992b/image.png" alt=""></p>
<p><strong>직감 디자인</strong>이란 무엇일까? 이것은 사용자가 서비스, 제품, 게임 등 무언가를 경험할 때 자연스레 이루어지는 <code>가설 - 시행 - 환희</code>의 과정을 디자인하는 과정이다.</p>
<p>이 디자인 과정에 대한 이해와 효과를 알아야 내 디자인에도 적용할 수 있다. 가설이란 사용자가 마주한 인터페이스와 어포던스를 통해 떠올리는 생각을 말한다. 간단하게 예를 들어 말하자면, 가게의 문 앞에서 &#39;PUSH&#39; 글자를 보았을 때, &#39;문을 밀면 문이 열린다&#39;는 생각이 가설에 해당한다. 따라서 이 책에서 말하는 직감 디자인은 UX 디자인의 과정에서 가상의 유저 플로우를 작성하는 방법과도 제법 유사해 보인다.</p>
<p>이 UX 이론을 게임이란 분야에서 살펴본 시각이 가장 인상적인데, 직감 디자인을 위해 게임 디자이너는 컨텐츠를 관통하는 하나의 룰을 설계한다고 한다. 이 책에선 직감 디자인을 설명하는 컨텐츠로 &#39;슈퍼마리오&#39;를 든다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/715c13b8-4180-4d8d-a813-c09f154620f6/image.png" alt=""></p>
<p>위 이미지에서 생각해 보아야할 것은 다음과 같다.</p>
<blockquote>
<p><strong>사용자가 슈퍼마리오를 가장 처음 접했을 때, 어떤 가설을 세울까?</strong> </p>
</blockquote>
<p>또 그 가설을 세운 이유는 무엇일까?
디자이너는 무엇을 어떤 의도로 디자인했을까?</p>
<p>답을 하기 위해 가장 먼저 유저가 마리오를 클리어한 경험과 디자이너의 의도 순으로 이야기를 살펴보자. 유저는 게임 패드로 마리오를 오른쪽으로 횡이동할 수 있다는 것을 깨닫는다. 굼바가 나타나고 벽돌이 나타나고, &#39;?&#39;모양의 아이템 블록이 나타나고.. 그렇게 스테이지를 이동하다 보면 쿠파를 마주한다. 도끼로 다리를 끊는 순간 빌런 쿠파는 용암속으로 사라지고 마리오는 피치 공주를 구하는데 성공한다. 이 경험에서 가장 근본적인 사용성은 &#39;오른쪽으로 이동한다&#39;이다. 마리오를 오른쪽으로 이동하지 않으면, 유저는 결코 게임을 클리어할 수 없다. 그렇다면 무엇이 유저를 오른쪽으로 이동하게 만들었을까?</p>
<p>여기서 디자이너의 설계가 빛을 발한다.</p>
<p>게임이 게임으로 작동하기 위한 &#39;오른쪽으로 간다&#39;라는 규칙은, 디자이너의 설계없인 작동할 수 없다. 이 룰은 가장 먼저 유저가 마리오를 시작한 순간 1-1 스테이지의 첫 장면에서 부터 자연스레 숨겨져있다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/034e0e02-f884-4311-bbd8-0af814b784fc/image.png" alt=""></p>
<p>왼쪽에 원경으로 자리한 산과 오른쪽을 바라보는 마리오, 오른쪽으로 누워있는 풀과 좌우 이동을 반복하는 구름은 유저로 하여금 &#39;오른쪽으로 이동할 수 있을 것 같다&#39;라는 가설을 세우게 만든다. 유저는 이 가설을 시행하고, 마리오를 오른쪽으로 이동해 공간을 이동하는 기쁨을 누린다. 새로운 공간과의 첫 만남에서 새로운 가상 세계에 진입한 설렘을 느끼는 것이다. 또 다시 &#39;오른쪽으로 이동한다&#39;는 가설을 반복하면, 마리오의 적인 &#39;굼바&#39;를 만날 수 있다. 유저는 또 다시 새로운 적인 굼바를 만나며 가설을 세우고, 시행하고, 승리하는 기쁨을 얻는다.</p>
<blockquote>
<p>이렇게 가설 - 시행 - 환희와 같은 일련의 과정은 <strong>유저 스스로 가설을 세워 쟁취하는 자기성취의 경험</strong>을 제공하고 <strong>계속해서 게임에 머무르고 게임을 추구하게 한다.</strong></p>
</blockquote>
<h2 id="느낀-점">느낀 점</h2>
<p>물론 이런 직감 디자인이 반복되면, 끊임 없는 태스크가 주어지는 데에 대한 스트레스가 생긴다. 이에 따라 등장하는 것이 제 2장의 &#39;놀람 디자인&#39;이다. 이렇게 저자는 유저에게 &#39;재미와 성취감, 환희를 제공하기&#39;라는 목적을 게임 기획의 1순위로 두고 기획 과정을 분석해 책을 통해 차근차근 알려주고 있다. 실제로 이 책을 읽으면 게임 스테이지를 클리어하는 기분을 받을 수 있는데 스포일러가 될 수도 있는 부분이라 오늘의 글에선 1장의 일부만을 발췌해 정리했다.</p>
<p>또 전체 장을 읽어나가며 모바일과 웹 디자인에서는 익숙한 디자인 프로세스이지만 이 프로세스가 게임에도 적용되고 있다는 발견에 놀라움을 느꼈고 이전에 게임, 웹, 모바일을 떠난 다른 분야에서도 체득한 직감디자인, 놀람디자인, 이야기 디자인, 체험 디자인을 나만의 방식으로 사용했었던 기억을 되살려볼 수 있었다.</p>
<p>이 책에서 가장 도움이 되는 점은 각 장마다 UX 디자인 프로세스와 함께 그에 맞는 게임 케이스가 자세히 설명되어 있단 점이다. 1장에서 살펴본 것처럼 유저의 입장에서 부터 디자이너가 설계한 요소 하나하나 짚어가며 그 효과가 설명되어있는데 이전까지 내가 프로젝트 리서치를 하면서도 이런 심리학과 관련된 게임 경험을 대부분 웹 사이트의 개인의 경험에 기반해 수집해왔기 때문에 신빙성이 떨어졌었다. 그러나 이 책의 저자의 경험은 닌텐도 발인만큼 신뢰할 수 있다고 생각하며나 참고 자료들 또한 링크까지 첨부되어있어 실험 설계에 매우 큰 도움이 되었다.</p>
<h2 id="책을-읽고-나서">책을 읽고 나서</h2>
<p>다이키 신이치로님의 탐닉의 설계자를 읽고나서, 책의 이러한 설계들을 바탕으로 게임의 설계 측면과 사용자의 인터랙션 측면을 더욱 연구하고 싶어졌었다. 특히 <code>게임 설계자가 쌓아둔 이야기의 흐름과 직감을 유도한 설계를 유저들이 어떻게 비틀고, 어떻게 발전시켜 나갔는가?</code>에 집중하고, <strong>가상 세계 속 사람들의 관계</strong>를 살펴보고 싶어했던 것 같다.</p>
<p>아래는 2021년도에 1학기 졸업작품 발표 자료 중 일부인데, 가상 세계의 인터랙션과 즐거움, 이야기에 빠져 <code>인터랙션 아카이빙</code> 웹을 준비하기도 했었다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/481310f8-9cdb-4f23-b42c-8a3b508e29c4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/3fdf93dc-8623-409a-8aed-90c341912f67/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/dfc31885-9264-4ed0-9fa4-4aeedbd72966/image.png" alt=""></p>
<p>그 당시엔, 아카이빙 프로젝트로 끝나는 리서치가 되어 볼륨이 적합하지 않아 교수님으로부터 반려됐었지만 다시 돌아보아도 여전히 나한테 <code>인터랙션 아카이빙</code>은 흥미로운 주제이다. 언젠가 기회가 되면 아카이빙해서 Three.js든 AR이든 VR이든 가능한 수단으로 아카이빙 웹을 만들어보아야지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[그래프] 2024 Kakao winter internship 도넛과 막대 그래프 Javascript]]></title>
            <link>https://velog.io/@naro-kim/%EA%B7%B8%EB%9E%98%ED%94%84-2024-Kakao-winter-internship-%EB%8F%84%EB%84%9B%EA%B3%BC-%EB%A7%89%EB%8C%80-%EA%B7%B8%EB%9E%98%ED%94%84-Javascript</link>
            <guid>https://velog.io/@naro-kim/%EA%B7%B8%EB%9E%98%ED%94%84-2024-Kakao-winter-internship-%EB%8F%84%EB%84%9B%EA%B3%BC-%EB%A7%89%EB%8C%80-%EA%B7%B8%EB%9E%98%ED%94%84-Javascript</guid>
            <pubDate>Tue, 09 Jan 2024 03:31:57 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-접근하기">문제 접근하기</h1>
<p>문제에 주어진 그래프의 종류와 제한 조건을 이해하느라 시간이 걸렸던 문제이다.
그래프의 종류는 총 3가지로, <code>도넛</code>, <code>막대</code>, <code>8자</code>가 있다.</p>
<blockquote>
<ul>
<li>그래프의 종류 : 도넛, 막대, 8자</li>
<li>도넛 모양 그래프 - n 정점, n 간선</li>
<li>막대 모양 그래프 = n 정점, n-1 간선</li>
<li>8자 모양 그래프 = 2n+1 정점, 2n+2 간선</li>
</ul>
</blockquote>
<p>처음엔 무지성 그래프 순회로 풀려고 코드를 짜고 있었는데, 짜다보니 정점에 들어온 간선 개수를 확인해야 조건 분기로 그래프 종류를 체크할 수 있을 것 같았다.
그래서 map을 이용해 그래프를 처리하기로 방향을 바꾸었다.</p>
<p>정리하면 문제를 해결하기 위한 접근 순서는 아래와 같다.</p>
<blockquote>
<ol>
<li>그래프를 Map 객체로 만들어 나가고 들어오는 간선 개수 체크하기</li>
<li>그래프 종류를 파악하기 위한 노드 특징 파악하기</li>
<li>파악한 특징을 바탕으로 조건문 분기 처리하기</li>
<li>answer 리스트에 답 넣기</li>
</ol>
</blockquote>
<p>그럼 이 순서에 따라 먼저 그래프를 Map으로 만들어보자. </p>
<h2 id="그래프-map으로-만들기---기본">그래프 Map으로 만들기 - 기본</h2>
<pre><code class="language-javascript">const graph = edges.reduce((map, key) =&gt; {
    if (!map.has(key[0])) {
        map.set(key[0], [key[1]]);
    } else {
        map.get(key[0]).push(key[1]);
    }
    return map;
}, new Map());</code></pre>
<p>위 코드는 <code>edges</code>를 reduce함수를 이용해 매핑하는 코드이다. 초기화 칸에 <code>new Map()</code>을 넣어서 Map객체로 만들 것을 표기한다. 그리고 key마다 값이 있다면 get이후 push를 통해 value를 추가할 수 있다.</p>
<p>기본 코드를 이해했다면 이를 응용해서, 각 정점마다 <code>[나가는 간선 개수, 받는 간선 개수]</code>의 형태로 value를 저장하자.</p>
<h2 id="그래프-map으로-만들기---응용">그래프 Map으로 만들기 - 응용</h2>
<pre><code class="language-javascript">const getInfo = (edges) =&gt; { 
    const info = edges.reduce((map, key) =&gt; {
    if (!map.has(key[0])) {
        map.set(key[0], [1, 0]);
    } else {
        const [give, receive] = map.get(key[0]);
        map.set(key[0], [give+1, receive]);
    }
    if(!map.has(key[1])){
        map.set(key[1], [0, 1]);
    } else {
        const [give, receive] = map.get(key[1]);
        map.set(key[1], [give, receive+1]);
    }
    return map;
}, new Map());
    return info;
}</code></pre>
<p style="width:640px;">
  <div >
  <img src="https://velog.velcdn.com/images/naro-kim/post/bb20eeda-934d-40f0-b361-8fb65aad12d4/image.png" alt="도넛과 막대그래프 js"> 
    </div>
</p>

<h2 id="조건문으로-그래프-종류를-처리하기">조건문으로 그래프 종류를 처리하기</h2>
<p>문제에서 제시된 내용을 바탕으로 그래프 종류별로 edge 처리 조건을 구상해보자. 처음에 구상했던 처리해야할 그래프 종류, 정점 별 조건은 아래와 같다.</p>
<blockquote>
<p>** 💡 실패한 제한 조건 **
    - 도넛
    : in edge == out edge || ( in ==1 &amp;&amp; out == 0)
    - 막대
    : ( in edge == out edge -1 ) ||  (in edge == 0 || out edge == 0 )
    - 8자
    : ( in edge &gt; out edge )
    - 생성 정점
    : ( in edge &lt; out edge ) </p>
</blockquote>
<p>그런데 이렇게 처리할 경우, 입력 예제 2번에서 조건 처리가 터지게 된다. 그래서 다시 빠진 조건은 무엇인지 그래프를 천천히 살펴보고 가장 중요한 핵심을 찾아냈다.</p>
<blockquote>
<p>이 그래프들과 <strong>무관한 정점</strong>을 하나 생성한 뒤, 각 <strong>도넛 모양 그래프, 막대 모양 그래프, 8자 모양 그래프의 임의의 정점 하나로 향하는 간선들</strong>을 연결했습니다.</p>
</blockquote>
<p>즉, 정리하면 생성 정점은 아래와 같은 조건을 충족하면서 생성됩니다.</p>
<ol>
<li>생성 정점은 기존 그래프의 연결 관계를 변경하지 않는다</li>
<li>생성 정점은 기존 그래프 하나 당 하나의 연결 관계를 갖는다</li>
</ol>
<p>이 조건에 더해, 기존 그래프의 개수가 최소 2개 이상이므로 in &gt;= 2이고 out = 0 이면 생성 정점이 됨을 알 수 있다.</p>
<p>또한, 기존엔 모든 노드를 탐색해 조건을 충족하는지 살펴 봤지만 각 그래프를 이루는 정점 중 가장 특징이 두드러지는 노드만 찾아내도 그 그래프가 존재한다고 말할 수 있다.</p>
<blockquote>
<ul>
<li>막대 : 막대 그래프의 최상단에 위치한 노드는 간선에 대해 out = 0 이다.</li>
</ul>
</blockquote>
<ul>
<li>8자 : 8자 그래프의 중앙점은 간선에 대해 in &gt;= 2 &amp;&amp; out &gt;=2 를 만족한다.</li>
<li>생성 정점: 생성 정점은 기존 관계를 변경하지 않으므로 in == 0 &amp;&amp; out &gt;=2 를 만족한다. <blockquote>
<p>마지막으로 도넛 그래프를 살펴보자.</p>
</blockquote>
</li>
<li>도넛 : 사이클을 이룬다는 점 외에 정점 개수, 간선 개수로 판별할 수 있는 특징이 없다. 
때문에 생성 정점이 기존 그래프마다 1개 간선을 갖는다는 아이디어를 이용해서 총 그래프 개수에서 막대, 8자 그래프 개수를 빼준다.</li>
</ul>
<h1 id="성공-코드-js">성공 코드 (js)</h1>
<pre><code class="language-javascript">const getInfo = (edges) =&gt; { 
    const info = edges.reduce((map, key) =&gt; {
    if (!map.has(key[0])) {
        map.set(key[0], [1, 0]);
    } else {
        const [give, receive] = map.get(key[0]);
        map.set(key[0], [give+1, receive]);
    }
    if(!map.has(key[1])){
        map.set(key[1], [0, 1]);
    } else {
        const [give, receive] = map.get(key[1]);
        map.set(key[1], [give, receive+1]);
    }
    return map;
}, new Map());
    return info;
}

const chkInfo = (info) =&gt; {
    const res = new Array(4).fill(0); 
    for(const [key, io] of info){ 
        const [give, receive] = io;  
        if( 2 &lt;= give &amp;&amp; receive == 0) { 
            res[0] = key;
        } else if(give == 0) {
            //막대그래프 최상단은 give == 0
            res[2]++;
        } else if(give &gt;= 2 &amp;&amp; receive &gt;= 2){
            res[3]++;
        }  
    }       
    // 도넛은 사이클이 이루어진다는 것 밖에 없기 때문에 개수 계산으로 판별할 수 없다. 
    // 생성 정점은 기존에 존재하던 모든 그래프에 간선을 하나 씩 연결한다.
    // 따라서, 생성 정점의 간선 개수에서 막대, 8자 그래프 개수를 빼면 도넛 그래프 개수가 나온다.
    res[1] = info.get(res[0])[0] - res[2] - res[3];
    return res;
}

const solution = (edges) =&gt; {
    const mapInfo = getInfo(edges);
    const answer = chkInfo(mapInfo);
    return answer;
}</code></pre>
<hr>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ddef6843-169c-4a15-8342-75c8c8c2eaea/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼 대학 10기 커뮤니티 프로젝트 회고]]></title>
            <link>https://velog.io/@naro-kim/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EC%BB%A4%EB%AE%A4%EB%8B%88%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@naro-kim/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC-%EC%BB%A4%EB%AE%A4%EB%8B%88%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 07 Jan 2024 16:02:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/868140b6-2515-490e-b417-4cc08905986e/image.png" alt=""></p>
<p>2024년이 되면서 2023년의 시작과 함께했던 프로젝트를 되돌아보고자 이번 회고록을 작성합니다. 이번 글에서는 아래 내용을 중점으로 이야기하려 해요.</p>
<blockquote>
<ol>
<li>프로젝트를 시작한 이유</li>
<li>프로젝트 관리 방법</li>
<li>도입한 기술 스택과 이유</li>
<li>기억에 남는 트러블 슈팅 경험</li>
<li>배운 점</li>
</ol>
</blockquote>
<p>프로젝트는 아래 url을 통해 직접 배포된 웹 페이지를 체험하거나 소스 코드를 확인하실 수 있습니다.</p>
<p>서비스 URL : <a href="https://likelionhongik.com">https://likelionhongik.com</a>
깃 레포지토리 URL : <a href="https://github.com/Likelion-HongikUniv/likelion-hongik-client">https://github.com/Likelion-HongikUniv/likelion-hongik-client</a></p>
<h2 id="왜-프로젝트를-시작했나요">왜 프로젝트를 시작했나요?</h2>
<p>2022년, UX 디자인을 떠나, User Experience Driven Developer가 되겠다는 포부로 디자인을 그만두고 개발로 전향하며 교내 개발 동아리인 &#39;홍익대학교 멋쟁이 사자처럼&#39;에 합류했어요. 그동안 다져온 사용자 중심 디자인 지식과 경험을 보다 직접적으로 &#39;구현&#39;해내는 개발자가 되고 싶었어요.</p>
<p>그 목표를 위해 2022의 1년 동안, 멋사의 테크잇, 노마드코더 등의 강의를 통해 동아리 친구들과 함께 리액트 기반 웹 개발을 배우고 토이 프로젝트들을 경험했어요. 이렇게 프레임워크와 기술들을 하나하나 배운 뒤에, 실제로 사용자가 사용하는 프로젝트를 만들고자 동아리 커뮤니티 사이트를 기획하게 되었어요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/f6401fbd-4064-40f4-a8b1-75b7af1345d2/image.png" alt=""></p>
<p>바로 <strong>멋쟁이사자처럼 2023년도 11기 신규 회원 모집을 위한 커뮤니티 홈페이지</strong>이었어요. 다음 기수 회장 친구의 권유로 시작해 동아리 내에서 공부한 걸 활용하기 위해 팀원들을 구성하고, 추가로 원활한 프로젝트 진행을 위해 디자이너와 프론트엔드 멘토를 영입했어요. 프로젝트에 프론트엔드 리드로 인턴 경험이 있는 친구가 멘토이자 리드로 합류하며 코드를 더욱 개발자 친화적으로, 가독성 높게 작성하는 방향으로 프로젝트를 진행할 수 있었어요. </p>
<h2 id="무슨-역할을-했나요">무슨 역할을 했나요?</h2>
<h3 id="디자인-리드이자-프론트엔드-서브리드">디자인 리드이자 프론트엔드 서브리드</h3>
<p>여기서 저는 디자인 리드와 함께 프론트엔드 서브리드 포지션을 담당했어요. 프로덕트에 있어 가장 중요한 것은 &quot;왜 이 프로덕트를 만드는가?&quot;라고 생각해요. 프로덕트가 가진 목적과 방향성이 뚜렷해야 합류하는 동료들도 주체적인 의식을 갖고 더 나은 프로덕트에 기여한다고 믿고있어요.</p>
<p>그렇기 때문에 UX 디자인 프로세스인 <a href="https://www.nngroup.com/articles/discovery-phase/">&#39;더블 다이아몬드 프로세스&#39;</a>를 통해 동아리원들의 사용자 경험을 바탕으로 커뮤니티를 기획하고자 했어요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/99841684-4ad2-45ad-a2fb-710ba0025413/image.png" alt=""></p>
<p>그래서 진행한 1단계 &#39;Discover&#39;를 통해 수집된 문제에는 &#39;이전 기수가 다음 기수에게 인수인계 하기 편리해야 한다&#39;, &#39;동아리원 전체의 정보가 DB에 남아있으면 좋겠다&#39;, &#39;프로젝트 내용도 보존되면 좋겠다&#39; 등이 있었어요.</p>
<p>10기로 활동했던 작년 한해를 돌아보았을 때, 이 문제점은 프로젝트 인원들의 큰 공감을 얻었어요. 저 스스로 또한 네이버 카페와 카카오톡 공지방, 일반방 등등으로 분리되어 있는 플랫폼에서 과제 보고와 활동 사진등을 따라잡아 추려내는데 어려움을 겪었었거든요.</p>
<p>모두의 공감을 얻은 문제점을 바탕으로, 노션과 피그마를 통해 더욱 세부적으로 개선된 웹 페이지 기획을 다듬는 &#39;Define&#39;단계로 접어들었어요.</p>
<h3 id="노션-피그마로-프로젝트-관리하기">노션, 피그마로 프로젝트 관리하기</h3>
<p>가장 먼저, &#39;11기 회원을 위한 커뮤니티&#39;라는 큰 틀에서, 어떤 기능과 컨셉과 기술 스택을 활용해 개발할 것인가를 두고 약 한달 간 모든 개발 구성원들간의 1단계 &#39;Discover&#39; 기획 회의를 가졌어요. </p>
<p>그러면서 찾아낸 Pain point들을 바탕으로 로우파이 디자인과 화면 기획서등을 기획할 수 있었어요. 또, 문제들을 구체화하고 기능을 기획하며 DB나 서버 스펙등을 가늠하고 이를 문서들로 정리했어요.</p>
<p>그리고 정리한 문서를 토대로 각 구성원이 어떤 페이지와 기능을 개발할 것인지 순차적으로 역할을 분배했어요. 디자인 시스템은 피그마로 관리하고, 프로젝트 전체 관리는 노션과 디스코드를 활용해 각 주차별 공지와 자료 공유, 회의록 정리를 진행했어요.</p>
<p align="center" style="color:gray"> 
  <img style="margin:50px 0 10px 0" src="https://velog.velcdn.com/images/naro-kim/post/508f1edd-9499-43e3-9387-f4b2e2799470/image.png" alt="figmasample" width=1040 />
  피그마를 통해 정리한 페이지 사이트맵
</p>



<p align="center" style="color:gray"> 
  <img style="margin:50px 0 10px 0" src="https://velog.velcdn.com/images/naro-kim/post/b8d383b9-f7ed-4d93-a096-5f7c1954a23e/image.png" alt="notionsample2" width=440 />
  2학기부터 웹 기획, 스터디를 병행하며 다음 년도 2월까지 개발과 테스팅을 진행했어요.
</p> 


<p align="center" style="color:gray"> 
  <img style="margin:50px 0 10px 0" src="https://velog.velcdn.com/images/naro-kim/post/3284f3f9-89f0-41e8-ae46-6bd310b37a22/image.png" alt="notionsample" width=440 />
  기능 명세서를 통해 구현할 API와 역할 분담을 체계적으로 진행했어요.
</p> 




<h2 id="어떻게-기술-스택을-선택했나요">어떻게 기술 스택을 선택했나요?</h2>
<p>프로젝트에서 도입한 기술 스택은 아래와 같아요.</p>
<blockquote>
<p>프레임워크 : React
스타일링 : Styled-Components
상태관리 : Recoil
데이터 캐시 : React-Query
코드 정적분석 : TypeScript, ESLint, Prettier</p>
</blockquote>
<p>왜 이 기술들을 채택했냐 하면 그 이유는 두가지로 설명할 수 있어요.</p>
<p>첫째, 동아리에서 교육이 이루어진 기술들이에요. 프로젝트 구성원이 동아리원들로 이루어져 있고 동아리내 강의와 스터디로 학습한 내용을 실무로 적용해보자는 의미에서 채택하게 되었어요.</p>
<p>둘째, 생태계가 활발하고 진입 장벽이 낮아요. 선택한 기술들은 온라인 상에 학습 자료가 많이 배포되어 있어요. 따라서 동아리원들이 주도적으로 학습하고 어려움이 있을 때에 같이 해결 방법을 고민하고 페어 프로그래밍으로 해결할 수 있을거라 예상했어요. </p>
<p>특히 이런 고민이 빛을 발한 건 <code>Recoil</code>을 도입하며 간편해진 컴포넌트 상태 관리 부분이에요.</p>
<h2 id="trouble-shooting---상태-관리에-recoil을-도입한-이유">Trouble Shooting - 상태 관리에 Recoil을 도입한 이유</h2>
<p>상태관리 매니지먼트인 <code>Recoil</code>을 도입하게 된 계기는 크게 3가지가 있어요.</p>
<blockquote>
<ol>
<li>컴포넌트 사이 <code>Props Drilling</code> 발생</li>
<li>Tree에서 레벨이 다른 컴포넌트 사이 State 공유</li>
<li>즉각적인 UI 동기화 필요</li>
</ol>
</blockquote>
<p>위와 같은 이유로 프로젝트에 Recoil을 쓰게 되었는데요, Mypage와 그리고 Header 컴포넌트의 state 관리, 마지막으로 로그인에 따른 게시글 페이지 관리 예시를 보여드릴게요.</p>
<h3 id="mypage에서의-recoil-사용기">Mypage에서의 Recoil 사용기</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/1435aa78-3061-433f-8feb-a57d86ae97df/image.png" alt="mypage ui"></p>
<p>위 이미지는 마이페이지의 UI 디자인이에요. 사이드바에서 유저의 닉네임과 팀을 노출하고 있고, 동시에 마이페이지에서 유저가 자신의 개인 정보를 변경하면 사이드바의 내용도 즉각 변경되도록 해야 했어요. </p>
<p>여기서 중요했던 점은 <strong>컴포넌트 상태에 따라 또 다른 컴포넌트의 상태도 변경될 때</strong>를 파악해야 했다는 점이에요. Props를 기반으로 컴포넌트 사이의 정보를 동기화하려 했더니 말로만 듣던 <code>Props Drilling</code>이 무엇인지 체감할 수 있었어요. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/39f6190d-d628-447b-b27a-97a4d1ea16c9/image.png" alt=""></p>
<p>멋사 커뮤니티 프로젝트의 디렉토리 구조는, src/component 하위에 페이지별로 필요한 component를 담고 있었어요. 그런데, Mypage같은 경우 페이지에 필요한 EditWrapper 내부에 각 세부 항목 별 Edit 컴포넌트들이 child로 들어가 있는 구조였어요.</p>
<p>그리고 각 <code>NickEdit</code>과 <code>MajorEdit</code>, <code>PartEdit</code> 안에서 수정한 state들을 다시 끌어올려서 부모인 <code>EditPart</code>에 전달해 api로 보내야 했어요. 이를 위해서는 useState 단독으론 기능 구현하기에 어려움을 느껴, <code>useRecoilState</code>를 통해 자식들과 부모 사이 상태 관리를 도입해 해결했습니다. </p>
<pre><code class="language-javascript">//EditPart.tsx
/* 초반 생략 */
// profile에 필요한 모든 정보는 editState에 저장되어 있어요.
const [info, setInfo] = useRecoilState(editState);
...
//state를 업데이트하기 위한 customHook에 recoil state의 값을 넣어주었어요.
const changeNickname = useNickInput(info.nickname);
const changeMajor = useInput(info.major);
const changePart = useSelect(info.part);
...
  return (
    &lt;EditWrapper&gt;
      &lt;BasicEdit /&gt;
      &lt;Flex&gt;
        &lt;div&gt;
          &lt;NickEdit {...changeNickname} /&gt;
          &lt;MajorEdit {...changeMajor} /&gt; 
          &lt;EditTitle&gt;멋사 정보 변경&lt;/EditTitle&gt;
          &lt;PartEdit {...changePart} /&gt;
          &lt;SaveBtn
            disabled={/* check logic*/}
            onClick={onClickSave}
          &gt;
            저장
          &lt;/SaveBtn&gt;
        &lt;/div&gt;
      &lt;/Flex&gt;
    &lt;/EditWrapper&gt;
  );
}</code></pre>
<p>이렇게 서로 다른 UI 컴포넌트 사이의 상태 변경을 즉각적으로 관리해야 하는 &#39;프로필&#39;, &#39;사이드바&#39;, &#39;헤더&#39;, &#39;게시글&#39; 등의 컴포넌트를 관리하기 위해 Recoil을 이용한 상태 관리를 도입했어요. 다음으로 Header component를 살펴볼게요.</p>
<h3 id="header-state의-recoil-사용기">Header State의 Recoil 사용기</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/87a86b4d-b5a1-4b4c-bd9e-ce67b43fac03/image.png" alt=""></p>
<p>Header는 사용자의 로그인 여부에 따라 다른 UI를 보여주어야 했어요. 그리고 로그인 전후로 onClick event에 이어지는 로직도 달랐기 때문에 로그인 상태는 서비스의 모든 페이지에서 계속 관리되어야 했어요. 그래서 전체 html 바디 최상단에 Header컴포넌트를 렌더링하고, RecoilState를 통해 Login state를 트래킹했어요.</p>
<pre><code class="language-typescript">// useAutoLogin.ts
const setIsLoggedIn = useSetRecoilState(isLoggedInState);
const [userInfo, setUserInfo] = useRecoilState(userState);</code></pre>
<pre><code class="language-typescript">// src/components/elements/Header.tsx
export function Header() {
    useAutoLogin();
    const setNavTag = useSetRecoilState&lt;Tag&gt;(tagState);
      const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);
      /* 생략 */
}</code></pre>
<h3 id="게시글-페이지에서의-recoil-사용기">게시글 페이지에서의 Recoil 사용기</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/56a2275c-4b0b-4b0b-a078-e5970aebfe76/image.png" alt=""></p>
<p>위 이미지는 커뮤니티 게시글의 상세 화면이에요. 위에서 적었던 중요한 점과 동일하게 이 페이지에서도 사용자가 만약 <code>댓글 작성</code>한다면 페이지 댓글 리스트 하단에 새로운 댓글 컴포넌트를 추가해야 했어요. 따라서 새로 등록한 댓글과 대댓글, 좋아요 수를 관리하기 위해 recoil state를 사용했어요.</p>
<pre><code class="language-typescript">export function PostPage() {
  const [board, setBoardData] = useRecoilState&lt;IBoard&gt;(boardState);
  const [comments, setCommentsData] = useRecoilState&lt;IComment[]&gt;(commentsListState);
... 
return (
    &lt;Section&gt;
      &lt;Column&gt;
        &lt;Board {...board} /&gt;
        &lt;CommentsList {...comments} /&gt;
      &lt;/Column&gt;
    &lt;/Section&gt;
    );
}</code></pre>
<p>처음에는 위에 작성한 코드처럼 <code>boardState</code>와 <code>commentsListState</code> 라는 Recoil state 두 개를 두고, 각각 게시글의 전체 상태와 댓글들의 상태를 관리했어요.</p>
<p>여기서 <code>boardState</code>는 게시글의 좋아요를 컨트롤하기 위한 state였어요. 사용자가 좋아요 버튼을 클릭하면 Like fetch API를 통해 Likes 수가 변경되는데요, 이때 서버에서 데이터를 받아와 전체 페이지를 다시 렌더링하는게 아니라 일단 먼저 클라이언트에서 Like를 업데이트하도록 만들기 위해 boardState에 Recoil을 써보았어요. 그래서, 서버 상의 업데이트와 클라이언트 상 렌더링을 분리할 수 있었죠.</p>
<p>다음으로 댓글은 <code>commentList</code>라는 이름의 Recoil state 배열로 관리해보았어요. 이 작업도 <code>boardState</code>와 유사하게 서버와 클라이언트 렌더링을 분리하는 시도였어요. 신규 댓글이 작성되면 서버에서 데이터를 refetch하는 대신, 클라이언트에서 유저 데이터와 input value등 댓글에 들어갈 컨텐츠들을 묶어 state 배열에 추가했고 그에 따라 페이지 최하단에 새 댓글이 렌더링되도록 했어요.</p>
<p>하지만, Get API로 받아오는 Board 데이터를 뜯어보면 그 구조가 아래와 같아요.</p>
<pre><code class="language-json">{
    &quot;postId&quot;: 17,
    &quot;author&quot;: {
        &quot;authorId&quot;: 1,
        &quot;nickname&quot;: &quot;김스르으응ㄹ기&quot;,
        &quot;profileImage&quot;: null,
        &quot;isAuthor&quot;: true
    },
    &quot;title&quot;: &quot;게시문 5번쨰&quot;,
    &quot;body&quot;: &quot;게시물 5번쨰&quot;,
    &quot;createdTime&quot;: null,
    &quot;likeCount&quot;: 0,
    &quot;comments&quot;: [
        {
            &quot;id&quot;: 24,
            &quot;author&quot;: {
                &quot;authorId&quot;: 1,
                &quot;nickname&quot;: &quot;김스르으응ㄹ기&quot;,
                &quot;profileImage&quot;: null,
                &quot;isAuthor&quot;: true
            },
            &quot;body&quot;: &quot;댓글 2번쨰&quot;,
            &quot;isDeleted&quot;: false,
            &quot;createdTime&quot;: &quot;2023-01-17T17:40:41.041311&quot;,
            &quot;likeCount&quot;: 0,
            &quot;replies&quot;: [
                {
                    &quot;id&quot;: 25,
                    &quot;author&quot;: {
                        &quot;authorId&quot;: 1,
                        &quot;nickname&quot;: &quot;김스르으응ㄹ기&quot;,
                        &quot;profileImage&quot;: null,
                        &quot;isAuthor&quot;: true
                    },
                    &quot;body&quot;: &quot;대댓글 1번쨰&quot;,
                    &quot;createdTime&quot;: &quot;2023-01-17T17:41:04.001386&quot;,
                    &quot;likeCount&quot;: 0,
                    &quot;deleted&quot;: false
                }
            ]
        }
    ],
    &quot;imageUrls&quot;: []
}
...</code></pre>
<p>보다시피 게시글 데이터의 구조가 이미 <code>BoardData</code>내에 <code>Comments</code>의 배열 데이터가 포함되어있는 형태이기 때문에 이 두 데이터를 Recoil State로 관리하면 중복이 생겼어요. 그래서 이 중복을 없애고자 API 통신이 성공하면 refetch가 일어나도록 React-Query을 사용해 로직을 개선했어요.</p>
<h2 id="recoil과-react-query를-같이-사용해보자">Recoil과 React Query를 같이 사용해보자</h2>
<p>React-query의 <code>Query</code>객체는 자기 자신을 가진 <code>QueryCache</code>객체와 함께 상태가 변경되었을 때 호출할 수 있는 <code>Observer</code> 옵션을 갖고 있어요. 당시 작업 기록에 따르면 캐시 이슈를 아래와 같이 기록해두었더라구요.</p>
<blockquote>
</blockquote>
<p>캐시가 남아있어, 쿼리 키에 따른 데이터가 바뀌면 지난 데이터를 렌더링하는 문제가 발생했다. 그래서 refetch 실행과 함께 RecoilTransactionObservation이란 snapshot tracker를 등록해 새로 렌더링했다. </p>
<p>지금은 <code>Mutate</code>를 사용하지만 당시엔 React-Query 사용에 익숙치 않아 Observer에 <code>transactionObservation_UNSTABLE</code>라는 옵션을 주고 data 변경 시 RecoilState에 대해 setBoardData가 다시 일어나게 만들었어요. 따라서, QueryProvider로 App을 감싸뒀다면 변경된 상태값이 전부 적용되도록 했어요.</p>
<pre><code class="language-javascript">// useGetPostDetail.js
...
type QueryResult = {
  board: IBoard;
  status: string;
};

export function useGetPostDetail(postId: number): QueryResult {
  const [board, setBoardData] = useRecoilState&lt;IBoard&gt;(boardState);
  const { status, refetch } = useQuery([&quot;postData&quot;, postId], async () =&gt; await getPostDetail(postId), {
    onSuccess: (data) =&gt; {
      setBoardData(data);
    },
    enabled: !!postId,
  });

  useEffect(() =&gt; {
    refetch();
  }, [postId]);

  // useTransactionObservation_UNSTABLE hook :
  //    observe changes to the Recoil state. This function takes a callback that is called
  //    whenever a transaction is committed. In the callback, we check if the current value of
  //     boardState is different from the previous value, and if so, we update the board state 
  //     using the setBoard function.

  const transactionObservation_UNSTABLE = useRecoilTransactionObserver_UNSTABLE(({ snapshot }) =&gt; {
    const currentBoard = snapshot.getLoadable(boardState).valueOrThrow();
    if (currentBoard !== board) {
      setBoardData(currentBoard);
    }
  });

  return { board, status };
}
</code></pre>
<pre><code class="language-javascript">
// postPage.tsx
... 

export function PostPage() {
  const { id } = useParams&lt;{ id: string }&gt;();
  const { board, status } = useGetPostDetail(id); // QueryProvider가 state를 감지하면 board 데이터가 변경돼요.

  if (status === &quot;loading&quot; || !board) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  return (
    &lt;&gt;
      &lt;Header /&gt;
      {isPC &amp;&amp; board &amp;&amp; (
        &lt;Section&gt;
          &lt;Column&gt;
            &lt;Board {...board} /&gt;
            &lt;CommentsList {...board.comments} /&gt;
          &lt;/Column&gt;
        &lt;/Section&gt;
      )}
      ...
    &lt;/&gt;
  );
}</code></pre>
<p>그땐 이렇게 작성해서, 표면상으로 state가 적절히 관리되고 있는 것처럼 보였어요. useMutate와의 차이도 시각적으로 두드러지지 않아 transactionObserver를 그대로 두었는데 이후에 <code>useMutate</code>를 postReply api에 적용해 썼어요.</p>
<pre><code class="language-typescript">// api/postReply.ts
async function postReply(props: ReplyProps) {
  const token = localStorage.getItem(&quot;token&quot;);
  return await client
    .post(`community/comment/${props.id}`, JSON.stringify(props.body), {
      headers: {
        &quot;Content-Type&quot;: `application/json`,
        JWT: token,
      },
    })
    .then((response) =&gt; {
      if (response.status === 200) {
        return response.data;
      }
    });
}

export const usePostReply = () =&gt; {
  const queryClient = useQueryClient();
  return useMutation(postReply, {
    onSuccess: () =&gt; {
      return queryClient.invalidateQueries({ queryKey: [&quot;postData&quot;] });
    },
  });
};</code></pre>
<p>이렇게 postReply에 mutate를 적용하며, onSuccess에 바로 query validation으로 캐시 데이터들을 즉각적으로 수정해줄 수 있었어요! </p>
<h2 id="어려웠던-점">어려웠던 점</h2>
<p>저는 이 프로젝트를 통해서 처음으로 React-Query를 다루어 보았어요. useQuery의 콜백 동작에 async/await 키워드를 사용하며 비동기 동작을 더 자세히 살펴보아야 했고 query의 개념을 이해하느라 바쁜 나머지 <code>mutation</code>의 역할을 이해하지 못했었어요. 이후 프로젝트들에선 점차 개선해, 먹팟에선 mutation으로 깔끔한 캐시 동기화를 할 수 있었어요. 짧게 남겨놓은 평가는 아래와 같이 남아있어요.</p>
<blockquote>
<p>useQuery 동작의 option이 매우 다양해서 라이브러리를 계속 들여다봐야했음.
시간을 써도써도 잘 모르겠다 ㅠ- ㅜ
쿼리키와 스테이트간 연관 관계가 어떻게 되는걸까? </p>
</blockquote>
<h2 id="배운-점">배운 점</h2>
<p>그럼에도, 이 프로젝트를 통해 새로운 기술 도입에 겁을 내지 않게 되었어요. 이전까진, 지금 배우는 것도 벅차 새로운 기술을 쓰게 되면 오히려 나쁜 결과를 초래하지 않을까 겁이 났었거든요. 그렇지만 오히려 새로운 걸 쓰며 계속해서 <strong>왜 생태계에 해당 기술이 나타났는지</strong> 알아보기도 하고, 변하는 와중에도 <strong>변하지 않는 핵심</strong>을 찾아 나갈 수 있었어요.</p>
<p>또한 기존의 860줄 코드에서 React-Query를 도입하며 524줄의 코드로 약 60% 가량으로 줄였으며, client 로직과 server 로직 분리를 고민한 경험이었어요.</p>
<p>그리고 무엇보다 밤새워 만든 커뮤니티로 다음 학기에 <strong>300명이 넘는 학생들이 동아리에 지원해주었다</strong>는 소식과 <strong>실제로 공지, 과제에 사용하고 있다</strong>는 이야기에 실제 사용자들을 만나 너무 기뻤어요.</p>
<h2 id="아쉬웠던-점">아쉬웠던 점</h2>
<p>마지막으로 프로젝트에서 아쉬웠던 점을 이야기하면 어려웠던 점에서 이야기한 것과 비슷한데요,</p>
<ul>
<li>mutation 리팩토링</li>
<li>테스트 환경 구축</li>
<li>상대 경로의 절대 경로 변경</li>
<li>스토리북 도입</li>
</ul>
<p>프로젝트 볼륨에 비해 Test code 작성이나 StoryBook을 도입하지 않아서 아쉬웠던 점과 상대경로 사용 등을 개선할 수 있을 것으로 보여요.</p>
<p>이 프로젝트는 후에 회장이었던 친구가 <strong>전국 멋사 대학 커뮤니티 프로젝트</strong>로 발전시켰어요!
훨씬 더 개선된 코드와 디자인과 기능들로 무장한 업그레이드 버전의 프로젝트에요.</p>
<p>아래 링크로 접속하시면 대학 별 멋사 모집 알림 신청과 커뮤니티를 열람하실 수 있어요. 이번에 멋사 대학의 혜택이 더욱 확대된다 하니 관심있는 분들은 알림 신청을 해두길 추천해드려요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8724d870-8f8d-4a13-bf54-8e4dd3873e87/image.png" alt=""></p>
<p><a href="https://likelion.university/">-&gt; 멋쟁이사자처럼 대학 바로가기</a></p>
<p>감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 아키텍처의 역사와 Zustand & React-Query 사용기]]></title>
            <link>https://velog.io/@naro-kim/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%9D%98-%EC%97%AD%EC%82%AC-TIL</link>
            <guid>https://velog.io/@naro-kim/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%9D%98-%EC%97%AD%EC%82%AC-TIL</guid>
            <pubDate>Fri, 05 Jan 2024 06:39:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/2a306275-cd0b-4770-9a81-329b054d1baa/image.png" alt=""></p>
<p>이번 <a href="https://www.youtube.com/watch?v=nkXIpGjVxWU">우아콘 영상</a>을 계기로 리액트 패턴의 역사를 정리해본다. 위 이미지에 대략적으로는 나와있지만, 찾아보면 아래와 같은 흐름으로 발전해왔다고 한다. </p>
<blockquote>
<p>MVC, MVVM -&gt; Component -&gt; Container-Presenter -&gt; Flux -&gt; Redux -&gt; Mobx -&gt; ContextAPI -&gt; React Hooks -&gt; React-Query -&gt; Zustand -&gt; Recoil, Jotai, Valtio..</p>
</blockquote>
<h1 id="react-pattern-history">React Pattern History</h1>
<h2 id="mvc">MVC</h2>
<p>Old Fashioned, Classic pattern.. (작성중)</p>
<h2 id="container-presenter-architecture">Container-Presenter Architecture</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6b549269-6dcf-441f-8681-5453ca178a59/image.png" alt=""></p>
<p>리액트의 베이스는 관심사를 재사용가능한 컴포넌트로 쪼개 분리하고, 필요에 따라 조립해 웹 서비스를 조립하는 패턴이다.</p>
<ul>
<li>비즈니스 로직을 관장하는 컴포넌트는 Container Component</li>
<li>데이터만 뿌려주는 UI와 같은 컴포넌트는 Presenter Component</li>
</ul>
<p>뼈대가 되는 아키텍쳐에서 컴포넌트 구조가 복잡해지면, 부모가 자식에게 데이터를 전달하기 위해 props를 계속 전달하며 수많은 props가 쌓이는 <code>props drilling</code>이 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5f8d0ec2-32c8-4efa-bbc8-770b355ca155/image.png" alt=""></p>
<p>이에 따라 발생한 단점</p>
<blockquote>
<ol start="0">
<li>컴포넌트의 재사용과 독립성의 지나친 강조</li>
<li>Component간 데이터 전달이 어렵다</li>
<li>Model의 관리가 파편화 된다</li>
<li>Props drilling이 발생한다</li>
</ol>
</blockquote>
<p>이를 해결하기 위해 <strong>&quot;Flux Architecture&quot;</strong>가 등장하게 된다.</p>
<h2 id="flux-architecture">Flux Architecture</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/9b4ef98c-d4a8-450b-97d9-2d4e5cf47555/image.png" alt=""></p>
<p><code>Flux</code>는 meta(구 facebook)에서 클라이언트사이드 웹 어플리케이션을 만들기 위해 사용했던 어플리케이션 아키텍쳐이다. 이미지에서 보이듯 Action -&gt; View로 이어지는 단방향 데이터 흐름을 활용해 뷰 컴포넌트를 구성하는 React를 보완하는 역할을 한다.</p>
<p>Flux application은 3가지 메인 파트를 갖고 있다 : <strong>dispatcher, stores, views (React components)</strong>. 이는 MVC pattern과 유사하지만, Flux엔 Contoller가 존재하지 않는 다는 점에서 다르다.</p>
<p>Flux의 구조는 단방향이다. View에서 Action을 호출하면 Dispatcher를 통해 Store에 Data가 보관되고 이 Data는 다시 View로 전달된다.</p>
<p>이 구조는 함수형 반응 프로그래밍을 다시 재현하는 것을 쉽게 만들거나 데이터-흐름 프로그래밍, 흐름 기반 프로그래밍을 쉽게 만들어준다. 어플리케이션 데이터 흐름이 양방향 바인딩이 아닌 단방향이기 때문이다. 어플리케이션의 상태는 오직 store에 의해서만 관리되고 어플리케이션의 다른 부분들과는 완전히 분리된다. store 사이에 의존성이 발생하더라도 엄격한 위계하에 dispatcher에 의해 동기적으로 관리된다.</p>
<p><a href="https://facebookarchive.github.io/flux/">자료 출처</a></p>
<h2 id="state-management">State Management</h2>
<p>이렇게 기존 아키텍쳐에서 완전히 분리하면서 이 개념을 <code>State Management</code>(상태 관리)라고 부르게 되었다. 상태관리의 개념을 종합해서 기존 패턴과의 차이점을 정리하면 아래와 같다.</p>
<blockquote>
<p>공통적으로 사용되는 비지니스 로직의 Layer와 View의 Layer를 완전히 분리되어 상태관리라는 방식으로 관리한다.
각각의 독립된 컴포넌트가 아니라 하나의 거대한 View 영역으로 간주한다.
둘 사이의 관계는 Action과 Reduce라는 인터페이스로 분리한며 Controller는 이제 양방향이 아니라 단반향으로 Cycle을 이루도록 설계한다.</p>
</blockquote>
<p>대표적인 Flux 패턴의 상태관리 라이브러리는 <code>Redux</code>와 <code>MobX</code>가 존재한다. 
하지만 Flux 패턴의 상태관리 역시 사용자가 늘고 시간이 지나며 단점들이 드러났다.
아래는 Redux의 대표적인 단점들이다.</p>
<blockquote>
<ol>
<li>store 뿐 아니라 비동기 처리 역할도 확장되면서 어플리케이션의 복잡성이 높아진다.</li>
<li>스토어 자체를 관리하기 위한 모든 action을 dispatcher에 등록해야 한다.</li>
<li>redux-thunk, redux-saga, redux toolkit과 같은 추가 미들웨어 library를 필요로 한다.</li>
</ol>
</blockquote>
<p>이런 단점을 보완하기 위해 recoil, contextAPI 등이 도입되었다.</p>
<hr>
<p>리덕스 스터디 기록</p>
<ul>
<li><a href="https://velog.io/@naro-kim/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%8D%95%EC%8A%A4-React-Redux">리액트 리덕스 React Redux</a></li>
<li><a href="https://velog.io/@naro-kim/Redux-Toolkit-%EA%B8%B0%EC%B4%88">React Toolkit 기초</a></li>
<li><a href="(https://velog.io/@naro-kim/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%A6%AC%EB%8D%95%EC%8A%A4-Pure-Redux)">바닐라 JS Pure Redux</a></li>
</ul>
<h1 id="client-state-management">Client State Management</h1>
<h2 id="react-hooks-contextapi">React Hooks, ContextAPI</h2>
<p>앞서 Redux와 Flux 패턴에서 지적한 문제점들을 극복하기 위해 React18부터 <a href="https://react.dev/reference/react/useContext">ContextAPI</a>가 도입되었다. </p>
<p>그럼 Context는 왜 도입되었을까? <a href="https://react.dev/learn/passing-data-deeply-with-context">공식문서 참조</a>에 따르면 가장 큰 목적은 <code>props drilling</code>을 해결하기 위해서이다. Context는 부모 컴포넌트가 하위 트리 전체에 데이터를 전달할 수 있도록 도와준다.</p>
<p> 
      <image src="https://velog.velcdn.com/images/naro-kim/post/2e059702-0e80-40c0-8e5f-4c196dc6af30/image.png"/>
</p>


<h3 id="contextapi-core-concept">ContextAPI core concept</h3>
<p>다음으로 공식 문서에 따르면 ContextAPI는 아래와 같은 기능을 한다.</p>
<blockquote>
<ol>
<li>tree의 깊은 곳까지 데이터를 전달한다</li>
<li>context를 통해 전달된 데이터를 업데이트한다</li>
<li>provider가 없더라도 <code>default value</code>를 사용할 수 있다</li>
<li>tree의 일부분에서 사용되는 context를 overriding할 수 있다</li>
<li>context와 useMemo의 사용을 통해 리렌더링을 최적화할 수 있다</li>
</ol>
</blockquote>
<p>위의 기능 목록을 살펴보면 React 자체 Hook으로서 큰 역할을 해주는 것처럼 보인다. 사용 시에도 React의 <code>useState</code>, <code>useEffect</code>등과 함께 상태 관리를 편리하게 해준다. 하지만 이에 따른 단점 역시 존재한다.</p>
<ul>
<li>작은 규모의 앱은 괜찮지만, 큰 프로덕트에서 context값이 변경되면 useContext가 리렌더링을 유도한다. 이는 React.memo를 사용하거나 context 객체를 분리하여 해결할 수도 있다.</li>
</ul>
<h2 id="recoil">Recoil</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6af171a7-fac9-4043-81d4-1d270a2b37ca/image.png" alt=""></p>
<p><code>Recoil</code>은 <code>Redux</code>와 <code>Context</code>가 가지는 단점들을 해결하고 React다운 개발을 위해 등장한 상태관리 라이브러리이다. <a href="https://recoiljs.org/ko/docs/introduction/motivation/">공식문서 motivation 참조</a>를 보면 제작 동기를 알 수 있다.</p>
<blockquote>
<ul>
<li>컴포넌트의 상태는 <strong>공통된 상위요소까지 끌어올려야</strong>만 공유될 수 있으며, 이 과정에서 거대한 <strong>트리가 다시 렌더링</strong> 되는 효과를 야기하기도 한다.</li>
<li>Context는 <strong>단일 값만 저장</strong>할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값의 집합을 담을 수는 없다.</li>
<li>이 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 <strong>코드 분할을 어렵게</strong> 한다.</li>
</ul>
</blockquote>
<p>Recoil은 이런 단점을 개선하고자 <code>atoms</code>와 <code>selectors</code>로 구성된 data-flow graph 기반 상태관리를 제안했다. 상태 변화는 그래프의 뿌리인 atom에서 시작해 selector를 거쳐 컴포넌트로 흐른다.</p>
<h3 id="recoil-core-concept">Recoil core concept</h3>
<blockquote>
<ul>
<li>우리는 공유상태(shared state)도 React의 내부상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다. (필요한 경우 reducers 등으로 캡슐화할 수도 있다)</li>
<li>우리는 동시성 모드(Concurrent Mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.</li>
<li>상태 정의는 점진적이고(incremental) 분산되어 있기 때문에, 코드 분할이 가능하다.
상태를 사용하는 컴포넌트를 수정하지 않고도 상태를 파생된 데이터로 대체할 수 있다.
파생된 데이터를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터는 동기식과 비동기식 간에 이동할 수 있다.</li>
<li>우리는 탐색(Navigation)을 일급 객체 개념으로 취급할 수 있고 심지어 링크에서 상태 전환을 인코딩할 수도 있다.</li>
<li>전체 애플리케이션 상태를 하위 호환되는 방식으로 유지하기가 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.</li>
</ul>
</blockquote>
<p>이러한 Recoil의 등장 배경에 따라, Recoil의 Core concept인 Atom과 Selector를 살펴보자. <strong>Atoms(공유상태)</strong>는 상태 단위로 업데이트와 구독이 가능하다. atom이 업데이트되면 구독된 컴포넌트는 새로운 값을 반영해 렌더링한다. </p>
<p><strong>Selector</strong>는 atoms나 다른 selectors를 입력으로 받는 순수 함수(Pure function)이다. 상위 aotms 또는 selectors가 업데이트되면 하위 selectors도 재실행된다. 컴포넌트는 이런 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 재렌더링된다.</p>
<h1 id="server-state-management">Server State Management</h1>
<p>client state를 다루면서 -특히 Redux와 관련하여- 서버 데이터와의 동기화나 비동기 로직을 처리하는 과정에서 상태관리 매니저가 비대해졌다. thunk와 toolkit과 같은 개념들은 개발자의 허들을 높이고 코드의 분할과 가독성을 비효율적으로 만들었다. 이런 문제점들을 해결하기 위해 <code>Server State Management</code>도 등장했다. 그중 하나인 React-Query를 살펴보자.</p>
<h2 id="react-query">React-Query</h2>
<p><code>React-Query</code>의 <a href="https://tanstack.com/query/v3/docs/react/overview">공식문서</a>에 따르면 자기 자신을 &quot;React Query is hands down one of the best libraries for managing server state. It works amazingly well out-of-the-box, with zero-config, and can be customized to your liking as your application grows.&quot; 라고 소개하고 있다. <code>Server State Management</code> 답게, 기능은 아래와 같이 정리할 수 있다.</p>
<blockquote>
<ol>
<li>API Fetching</li>
<li>Synchronizing and update server state</li>
<li>caching</li>
</ol>
</blockquote>
<p>그럼 다음으로 React-Query의 <a href="https://tanstack.com/query/latest/docs/react/quick-start">core concept</a>를 살펴보자.</p>
<h3 id="react-query-core-concept">React-Query core concept</h3>
<p>React query의 core concept은 3가지 이다.</p>
<ul>
<li>Queries <a href="https://tanstack.com/query/latest/docs/react/guides/queries">docs</a></li>
<li>Mutations <a href="https://tanstack.com/query/latest/docs/react/guides/mutations">docs</a></li>
<li>Query Invalidations <a href="https://tanstack.com/query/latest/docs/react/guides/query-invalidation">docs</a></li>
</ul>
<p> <strong>Queries</strong></p>
<blockquote>
<p>query는 <code>unique key</code>에 묶여있는 비동기 선언적 의존성입니다. query는 server로부터 data를 fetch하기 위한 method에 기반한 Promise와 사용할 수 있습니다. 만일 Delete나 Put, Patch등의 method로 server data를 수정해야하는 경우, <code>Mutations</code>를 대신 사용합니다. </p>
</blockquote>
<p>query를 component나 custom hook에서 subscribe하고 싶은 경우, 유니크한 queryKey와 promise를 반환하는 queryFn을 인자로 넘깁니다.</p>
<pre><code class="language-typescript">import { useQuery } from &#39;@tanstack/react-query&#39;

function App() {
  const result = useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchTodoList })
}</code></pre>
<p> <strong>Mutations</strong></p>
<blockquote>
<p><code>query</code>와 다르게, <code>mutation</code>은 보통 데이터의 <strong>create, update, delete</strong> 메소드 혹은 서버의 <strong>side-effect</strong>를 위해 사용됩니다. 이러한 목적하에 TanStack Query는 <code>useMutation</code> hook을 지원합니다. </p>
</blockquote>
<p> 기본 사용법은 아래와 같다. 아래는 server에 새로운 todo를 작성해 추가하는 예시이다.</p>
<pre><code class="language-typescript">function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) =&gt; {
      return axios.post(&#39;/todos&#39;, newTodo)
    },
  })

  return (
    &lt;div&gt;
      {mutation.isPending ? (
        &#39;Adding todo...&#39;
      ) : (
        &lt;&gt;
          {mutation.isError ? (
            &lt;div&gt;An error occurred: {mutation.error.message}&lt;/div&gt;
          ) : null}

          {mutation.isSuccess ? &lt;div&gt;Todo added!&lt;/div&gt; : null}

          &lt;button
            onClick={() =&gt; {
              mutation.mutate({ id: new Date(), title: &#39;Do Laundry&#39; })
            }}
          &gt;
            Create Todo
          &lt;/button&gt;
        &lt;/&gt;
      )}
    &lt;/div&gt;
  )
}
</code></pre>
<p>여기서 mutations가 말하는 <a href="https://tanstack.com/query/latest/docs/react/guides/mutations#mutation-side-effects">sever side-effect</a>란 무엇일까? </p>
<h4 id="mutation-side-effect">mutation side-effect</h4>
<p>아래는 sandbox와 함께 React-Query의 개념을 익혀볼 수 있는 챌린지 사이트의 링크이다.
프로젝트나 실무에 적용하기 전에 학습해보자~!
<a href="https://query.gg/?s=tanstack">React-Query challenges</a></p>
<h1 id="usage-example">Usage Example</h1>
<h2 id="zustand--react-query">Zustand &amp; React-Query</h2>
<p>Redux의 단점들을 극복하고, Server State 관리의 목적을 위해 <a href="https://github.com/YAPP-Github/mukpat-client">먹팟 프로젝트</a>에선 Zustand와 React-Query를 조합해 사용했어요. </p>
<h3 id="server-side-state-management">Server side state management</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5f7f0923-2e9c-4b55-9237-fecaec9b1f72/image.png" alt=""></p>
<h3 id="client-state-management-1">Client state management</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/12d042c8-ccdb-4a3d-9b18-8f811a0de282/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인터랙티브 3D web으로 만든 눈내리는 풍경 : 항해+ 코육대 2회 회고]]></title>
            <link>https://velog.io/@naro-kim/3D-web%EC%9C%BC%EB%A1%9C-%EB%88%88%EC%9D%84-%EC%B9%98%EC%9A%B0%EC%9E%90-%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80-2%ED%9A%8C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@naro-kim/3D-web%EC%9C%BC%EB%A1%9C-%EB%88%88%EC%9D%84-%EC%B9%98%EC%9A%B0%EC%9E%90-%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80-2%ED%9A%8C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 01 Jan 2024 08:30:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/naro-kim/post/984c413b-7214-45f1-ade0-e3f5c8d93881/image.png" alt=""> </p>
<h1 id="interactable-3d-web-project-winter-wonderland-프로젝트-개요">Interactable 3D web project: Winter wonderland 프로젝트 개요</h1>
<blockquote>
<p>서비스 링크 : <a href="https://snowy-winter-wonderland.vercel.app/">https://snowy-winter-wonderland.vercel.app/</a>
개발 레포지토리 링크 : <a href="https://github.com/naro-Kim/snowy-window">https://github.com/naro-Kim/snowy-window</a></p>
<p>위 url을 통해 프로젝트를 체험하고 살펴보실 수 있어요.</p>
</blockquote>
<p>Winter wonderland는 <code>three.js</code>기반의 <code>react-three-fiber</code>와 <code>Next.js</code> 프레임워크로 제작한 프로젝트입니다. 평소 좋아하던 3d로 개인 작업을 하고 싶었고 올 한해 배웠던 프론트엔드 지식들과 three.js 지식들을 최종 복습하고자 이번 항해 코육대를 기회로 시도했어요. </p>
<p>이런 목적에 맞게, 프로젝트 구현에 있어 가장 신경썼던 부분들은 <code>성능 최적화</code>였어요. 이전에 여러 3d web 프로젝트들을 체험했을 때 사용자 경험 상 성능의 문제가 발생하면 페이지 컨텐츠를 다 보기도 전에 이탈하게 되더라구요. 이런 문제를 해결하기 위해 도입했던 아이디어는 3d asset preload 부분이었어요.</p>
<blockquote>
</blockquote>
<ul>
<li>three.js 프로젝트에서 항상 문제가 되는 것은 성능 최적화 부분! 따라서, 이번 프로젝트에서 Lighthouse 성능지표를 최대로 끌어올려보자</li>
<li><strong>gltf to tsx convert로 로딩 시간을 최소화</strong>하자</li>
<li><strong>불필요한 texture, polygon, mesh수</strong>를 줄여 용량을 최소화하자</li>
</ul>
<p>최적화를 목표로 하면서, 다음으로 &#39;눈닦기&#39;라는 이번 주제에 맞춰 프로젝트 기능을 기획했어요.</p>
<h2 id="프로젝트-기능-기획">프로젝트 기능 기획</h2>
  <p style="display:flex; flexDirection:column; alignItems:center;">
      <img src="https://velog.velcdn.com/images/naro-kim/post/665fc932-a3fc-4487-8f2c-969e884e8917/image.png">
      <span style="marginTop:-24px; color:gray; fontSize:0.8rem;">프로젝트의 모티브가 되어준 겨울 풍경</span> 
</p>

<p>눈이 오는 겨울, 집에 있을 때 창밖을 멍하니 보던 경험을 기억하시나요? 저는 가끔 눈이 많이 오는 날이면 유튜브에서 <code>winter jazz 24/7</code>과 같은 검색어로 음악을 켜두고 작업을 하는데요, 이런 경험에서 떠오른 기능들로 프로젝트를 기획했어요.</p>
<blockquote>
<ol>
<li>눈이 계속 쌓여 하얀 화면을 구현합니다. 
: 프로젝트에 접속한 <strong>사용자의 뷰를 하나의 &#39;창&#39;</strong>으로 생각했어요. 창밖을 바라보는 경험을 떠올리고, 창틀 너머로 눈송이가 내리는 풍경을 기획했어요.</li>
</ol>
</blockquote>
<ol start="2">
<li>마우스 커서/터치에 따라 하얗게 쌓인 눈이 닦이는 웹/앱을 구현하세요. 
: 창가에 쌓여있는 <strong>눈뭉치들을 밀어내는 인터랙션</strong>을 구현했어요. 눈을 터치하면 눈뭉치가 밀려나고, 빠작빠작거리는 소리가 나요. 다만, 현실의 경험에서 비롯해 창 한가득 내린 눈 대신 어느정도 쌓여있는 눈을 구현했어요.<blockquote>
<ol start="3">
<li>닦인 화면에도 눈이 쌓이고, 다시 하얗게 변합니다.
: 시간이 지나면 눈이 쌓여요. 풍경을 넉넉히 감상하기 위해 <strong>다시 눈이 쌓이는 시간은 1분 20초</strong> 정도로 기획했어요. 눈을 치우고 풍경을 보다가 하루 일을 하고 다시 돌아보면 어느새 눈이 다시 쌓여있곤 했거든요.</li>
</ol>
</blockquote>
</li>
</ol>
<h2 id="어떻게-구현했나요">어떻게 구현했나요?</h2>
<p>위에서 기획한 부분을 토대로, 크게 아래처럼 구현 요구사항 리스트를 계획해서 프로젝트를 진행했어요.</p>
<blockquote>
</blockquote>
<ol start="0">
<li>기술 스택 선택</li>
<li>3d 에셋 제작</li>
<li>눈송이 시뮬레이션 구현</li>
<li>눈 쌓기 &amp; 치우기 시뮬레이션 구현 </li>
<li>기타 인터랙션 구현</li>
</ol>
<h3 id="0-기술-스택-선정하기">0. 기술 스택 선정하기</h3>
<p>프로젝트 기간이 일주일 남짓인 만큼, 기술 스택은 가장 익숙하고 활용하기 편한 기술들을 선택했어요. 그러면서도 최적의 성능을 만드려고 노력했어요.</p>
<blockquote>
</blockquote>
<ol>
<li>💡 <code>Nextjs</code> : canvas 요소를 제외한 컴포넌트들의 SSG를 위해 (추후 방명록 등 인터랙션과 관련한 추가 기능들이 있을 수 있으므로..)</li>
<li><code>React</code> : canvas element는 Client side rendering이 필요하기 때문에 &#39;use client&#39;로 CSR 방식의 리액트 컴포넌트들을 사용했어요.</li>
<li><code>React-three-fiber</code> → useFrame, useThree와 같은 간편한 훅으로 scene control의 편리성과 가독성을 높이기 위해 선택했어요. 덕분에 눈을 치우거나 눈사람을 클릭하는 object interaction을 구현할 수 있었어요.</li>
<li><code>Vercel</code> : 레포지토리 기반의 편리한 프론트엔드 배포로 빠른 시간안에 구현된 페이지를 보여주는데 적합하다 생각했어요</li>
</ol>
<h3 id="1-블렌더로-3d-에셋-제작하기">1. 블렌더로 3d 에셋 제작하기</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/d236cc8d-48de-4691-ae54-0f94930a5386/image.png" alt=""> </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/01b271f8-535e-419b-802c-57670e51a8c9/image.png" alt="blender3d screenshot">
귀엽게 잘 만들었나요?</p>
<p>기술을 선정한 이후엔, 빠르게 3d씬을 만들었어요. 평소 사용하던 가볍고 빠른(c4d, rhino등에 비해) 3d툴인 블렌더를 사용했습니다. 창밖의 겨울 풍경을 그리는 만큼 크리스마스는 지나가지만 겨울스러운 눈사람과 트리를 준비했어요. 가능한한 키 비주얼이 될 에셋들(특히 눈사람)은 직접 만들고, 이외의 환경을 구성할 눈 쌓인 나무 에셋들은 <a href="https://itch.io/game-assets/top-rated/tag-3d">itch.io</a>에서 무료 에셋들을 가져와서 썼어요.</p>
<p>web에서 사용할 3d 에셋 제작할 때 신경써야할 점들은, blender의 렌더링 환경과 three.js의 렌더링 환경이 완전히 다르다는 점이에요. 자세한 건 <a href="https://discourse.threejs.org/t/a-big-difference-between-the-render-in-the-blender-and-the-threejs/28106/2">관련 포럼 글</a>을 참조해주세요! 이런 차이에 의해 초반에는 모든 텍스쳐들을 cycle 렌더러에서 bake하고 three.js로 넘어갔었어요.</p>
<p>만들어진 모델을 export 할 땐, glb+bin으로 나누어 추출하는 옵션을 선택했어요. 이 방식으로 추출한 모델은 gltf embeded 방식보다 텍스쳐 해상도 최적화 작업 후 용량이 훨씬 줄어들거든요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/61c20419-a3ef-4dab-b27b-3b3790dac94b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/3df711e5-cc13-40d6-855a-e5d98816f9bc/image.png" alt=""></p>
<p>위 이미지는 glb+bin으로 추출한 snow scene asset을 텍스쳐 해상도 최적화 후 합친 gltf 용량과 비교한 사진이에요.** 약 81.75%가 감소한 걸 확인할 수 있죠.** 이렇게 에셋 최적화가 하나씩 이루어지면, 인터랙티브 웹을 이루는 경험이 개선될 수 있어요!</p>
<p>이 과정에서 유용하게 이용했던 사이트는 2가지가 있어요. 하나는 gltf 압축 사이트인 <a href="https://gltf.report/">gltf.report</a>이고 다른 한 가지는 <a href="https://gltf.pmnd.rs/">gltf -&gt; react three fiber converter</a>에요. 이 두 가지 사이트 모두 react-three-fiber, react-drei 등을 만든 poimndres 소속의 <a href="https://github.com/donmccurdy">Don McCurdy</a>가 만들었다는 점에 안정성을 믿고 선택했어요.</p>
<p>요약하면 <code>에셋 디자인 &gt; 텍스쳐링 &gt; gltf 압축 &gt; gltf to tsx convert</code> 의 과정으로 모델을 준비했어요.</p>
<p>그리고, 에셋을 화면에 띄우고 window 사이즈에 맞춰 canvas resizing을 진행해요. 화면비에 알맞게 asset의 중심점이 위치할 지점을 <code>window.innerWidth/window.innerHeight + 1.5</code>의 거리로 구했어요.</p>
<h3 id="2-눈송이-시뮬레이션-구현">2. 눈송이 시뮬레이션 구현</h3>
<p>scene에 올릴 assets들이 어느정도 준비된 다음, 구현한 기능은 눈송이 시뮬레이션이었어요. 눈이 내리는 겨울 풍경을 구현할 방법을 가장 먼저 생각해봤어요.</p>
<ul>
<li>첫째, object 눈송이를 내린다 -&gt; polygon이 너무 많아지므로 기각</li>
<li>둘째, point instances를 이용해 webp texture를 씌워 가장 효율적으로 렌더링해본다</li>
</ul>
<p>두번째 방법을 채택하고, 기존에 unity나 three.js로 제작된 참고 자료들을 바탕으로 react-three-fiber의 particle system을 작성했어요!</p>
<p>처음엔 vector를 어떻게 이동시켜야하는지 감이 잡히지 않아 어려움을 겪었어요. 아래는 초기의 vector 이동 코드에요.</p>
<pre><code class="language-typescript">useFrame((_, dt) =&gt; {
      const posArr = positionRef.current.array;
      const velArr = velocityRef.current.array;

      for (let i = 0; i &lt; posArr.length; i += 3) {
         const x = i;
         const y = i + 1;
         const z = i + 2;

        // x축은 양옆으로 움직여야 한다.
         const velX = Math.sin(dt * 0.001 * velArr[x]) * 0.1;
        // z축은 앞뒤로 움직여야 한다.
         const velZ = Math.cos(dt * 0.0015 * velArr[z]) * 0.1;

         posArr[x] += velX;
         posArr[y] += velArr[y];
         posArr[z] += velZ;

         if (posArr[y] &lt; -minRange) {
            posArr[y] = minRange;
         }
      }

      positionRef.current.needsUpdate = true;
      velocityRef.current.needsUpdate = true;
   });
</code></pre>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5bf97d86-6302-49d9-83e5-22d6b73aeef0/image.gif" alt="앞으로 날아드는 눈송이">
눈송이가 예상한 것과 다르게 y축 이동 값보다 z축 이동값이 너무나도 컸는지 앞으로 마구 달려들어, 참고 자료와 다르게 프로젝트의 카메라 방향이나, 세팅에 맞춰 이동 방향을 바꾸어야했어요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/36f82e83-efba-4eb7-9022-b775ed9ea5e6/image.gif" alt="일자로 내리기 시작한 눈송이"></p>
<p>따라서 z축 이동을 일단 없앴어요. 완벽하다 할 순 없지만, 이번 3d scene을 기준으로 꽤 적절한 시뮬레이션을 보여줬어요. 이 과정에서 시뮬레이션의 값을 각자의 프로젝트에서 구현되길 원하는 비쥬얼과 방향성에 맞춰 조절해야 함을 느꼈어요. 약간의 주석과 함께 코드는 아래와 같이 썼어요.</p>
<pre><code class="language-typescript">import * as THREE from &#39;three&#39;;
import { useMemo, useRef } from &#39;react&#39;;
import { useFrame, useLoader, useThree } from &#39;@react-three/fiber&#39;;

const SnowInstances = ({ count = 200, velocity = 0.01 }) =&gt; {
  const particles = useRef&lt;THREE.Points&gt;(null!);
  const positionRef = useRef&lt;THREE.BufferAttribute&gt;(null!);
  const velocityRef = useRef&lt;THREE.BufferAttribute&gt;(null!);
  const [minRange, maxRange] = useMemo(() =&gt; [-8, 8], []);

  // count개수만큼의 눈송이가 가질 위치 벡터 정보를 담은 array
  const points = useMemo(() =&gt; {
    const p = new Array(count)
      .fill(0)
      .map((v) =&gt; (0.5 - Math.random()) * maxRange);
    return new THREE.BufferAttribute(new Float32Array(p), 3);
  }, [count]);

  // count개수만큼의 눈송이가 가질 속력 벡터 정보 array
  const velocities = useMemo(() =&gt; {
    const v = new Array(count * 3)
      .fill(0)
      .map(() =&gt; (Math.random() - 0.5) * 0.1);
    return new THREE.BufferAttribute(new Float32Array(v), 3);
  }, [count]);

  // textureLoader로 webp 이미지를 받아 눈송이 텍스쳐에 적용했어요.
  const flakeMaterial = useMemo(() =&gt; {
    const snowflakeMap = useLoader(THREE.TextureLoader, &#39;/assets/snowflake.webp&#39;);
    const mat = {
      size: 0.2,
      color: 0xffffff,
      vertexColors: false,
      map: snowflakeMap,
      transparent: true,
      fog: true,
      depthWrite: false,
    };
    return mat;
  }, []);

//useFrame 훅을 통해, 매 프레임마다 이동하는 눈송이를 구현합니다.
  useFrame((_, dt) =&gt; {
    const posArr = positionRef.current.array;
    const velArr = velocityRef.current.array;

    // 창밖의 표현 구현과 동시에 최적화를 위해 z축 이동은 멈추어두었어요. 
    // 의도에 따라, velocity 변경은 없앴어요.
    for (let i = 0; i &lt; posArr.length; i += 3) {
      const x = i;
      const y = i + 1;

      const velX = Math.sin(dt * velArr[x] * (i &lt; 4 ? i + 1 : -(i + 1))) * 0.01;
      let velY = Math.cos(dt * 0.01 * velArr[y]) * velocity;

      posArr[x] += velX;
      posArr[y] -= velY; //눈이 내리는 위치 조정

      // recycle snow
      // 지정된 범위를 지나친 눈송이는 다시 시야에 보이는 각도에 렌더링해요.
      if (posArr[y] &lt; minRange) {
        posArr[y] = (1 - Math.random()) * maxRange * 0.01;
        velY = 0;
      }
      if (posArr[x] &gt; maxRange || posArr[x] &lt; minRange) {
        posArr[x] = (1 - Math.random()) * maxRange * 0.01;
      }
    }

    positionRef.current.needsUpdate = true;
    velocityRef.current.needsUpdate = true;
  });

  // 눈송이는 maxRange 위치에서 내려오기 시작해요
  return (
    &lt;group position={[0, maxRange, 0]}&gt;
      &lt;points ref={particles}&gt;
        &lt;bufferGeometry&gt;
          &lt;bufferAttribute
            ref={positionRef}
            attach=&quot;attributes-position&quot;
            {...points}
          /&gt;
          &lt;bufferAttribute
            ref={velocityRef}
            attach=&quot;attributes-velocity&quot;
            {...velocities}
          /&gt;
        &lt;/bufferGeometry&gt;
        &lt;pointsMaterial {...flakeMaterial} sizeAttenuation={true} /&gt;
      &lt;/points&gt;
    &lt;/group&gt;
  );
};

export default SnowInstances;</code></pre>
<p>여기까지 작성한 눈송이 시뮬레이션을 시각적으로 확인해보았어요. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/5f82b736-2964-415b-9843-9c18175ccc54/image.gif" alt=""></p>
<p><code>OrbitControl</code>을 통해 씬을 돌려보니, 전체 씬이 심심했어요. 아무래도 눈이 더 쌓여있는 모습이 좋을 것 같아 텍스쳐 수정과 함께 몇 가지 오브젝트를 추가하기로 결정했어요. 그리고 눈송이가 날리는게 더 잘보이도록 Size도 키웠습니다.</p>
<p><strong>참고자료</strong></p>
<ul>
<li><a href="https://www.youtube.com/watch?v=OXpl8durSjA">falling snow in three.js</a></li>
<li><a href="https://blog.maximeheckel.com/posts/the-magical-world-of-particles-with-react-three-fiber-and-shaders/">particle system in react-three-fiber</a></li>
<li><a href="https://stackoverflow.com/questions/66136122/push-particles-away-from-mouseposition-in-glsl-and-three-js">particle interaction</a></li>
</ul>
<h3 id="3-눈-쌓기--눈-치우기-구현">3. 눈 쌓기 &amp; 눈 치우기 구현</h3>
<p>눈 내리는 시뮬레이션을 구현한 다음엔, 내린 눈이 쌓이는 모습을 구현해야 했어요. 그리고 눈이 쌓인 다음엔 그걸 닦고 치우는 인터랙션도 가능해야 했기 때문에 이를 고려해 크게 두가지 접근 방법을 생각했어요.</p>
<ul>
<li>첫째, openGL 쉐이더로 만든 ‘쌓인 눈’ geometry vertex의 height를 깎아 내린다</li>
<li>둘째, Box Geometry로 만든 ‘쌓인 눈’에서, React-three-fiber가 지원해주는 onPointerEnter 이벤트와  eventObject를 사용해, onPointerUp(모바일 대응) 이벤트 발생 시, 해당 객체의 y좌표를 깎아내려준다.</li>
</ul>
<p>1번 방법으로 접근해서 쌓인 눈을 구현하다가, react-three-fiber 내부의 구현이 glsl 사용과 혼용하기엔 복잡하단 것을 깨달았어요. 애초에 three.js 대신 react-three-fiber를 사용한 이유도 함수형 컴포넌트로 통일된 코드를 작성하기 위함이었거든요. react-three-fiber에 glsl을 사용하는 순간, fragment와 vertex가 하드코딩으로 노출되면서 코드의 가독성이 떨어졌어요. displacement map 이미지를 씌우는데에만 무려 25줄이 쓰이고 여기에 클릭 인터랙션에 따라 더욱 복잡한 로직 추가가 필요했어요.</p>
<pre><code class="language-typescript">//생략
...
 const points = useMemo(() =&gt; {
    ...
  }, [count]);

  const flakeMaterial = useMemo(() =&gt; {
    ...
  }, []);

  const shader = {
    uniforms: {
      texture: { value: new THREE.TextureLoader().load(&quot;/assets/snowDisplacementMap.webp&quot;) },
    },
    vertexShader: `
      attribute vec3 position;
      attribute vec3 offset;
      uniform float time;
      varying vec2 vUv;

      void main() {
        vec3 newPosition = position + offset;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
        vUv = uv;
      }
    `,
    fragmentShader: `
      uniform sampler2D texture;
      varying vec2 vUv;

      void main() {
        gl_FragColor = texture2D(texture, vUv);
      }
    `,
  };

  useFrame((_, dt) =&gt; { 
    ...
  }
...
//후략</code></pre>
<p>동시에 Noise 형태의 이미지를 구해서, glsl 텍스쳐의 displacement map으로 사용하고자 했어요. 이를 planeBufferGeometry에 씌우려 하자 react-three-fiber에 planeBufferGeometry가 정의되어 있지 않다는 에러와 함께 extend해달란 경고가 떴어요. 하지만 이전 프로젝트에서 planeBufferGeometry를 별도의 extend없이 호환해 사용했었던 경험이 있었고 ts defined docs에서도 문제점을 찾지 못해, 2시간 정도 헤매이고 2번 방법으로 방향성을 바꾸었어요.</p>
<p>바꾼 코드는 아래와 같습니다.</p>
<pre><code class="language-typescript">//SnowAccumulation.tsx
/**
* @params {Three.Vector3} position : 눈더미가 렌더링될 위치입니다.
* @params {number} count : 렌더링될 눈더미의 개수입니다.
*/
const SnowAccumulation = ({ count = 20, position }: AccumulationProps) =&gt; {
    const ref = useRef&lt;any&gt;(null!);
    const vec = useMemo(() =&gt; new THREE.Vector3(), []);
    useFrame((gl, _dt) =&gt; {
        // 시간이 지나면 눈이 쌓입니다. 화면을 뒤덮을 정도로 쌓이지 않도록 elapsedTime으로 조절합니다.
        if (gl.clock.elapsedTime % 100 &lt;= 0.02 &amp;&amp; gl.clock.elapsedTime &lt; 1000) {
            const curPos = ref.current.position.clone();
            ref.current.position.lerp(vec.set(curPos.x, curPos.y + 0.2, curPos.z),
                0.05);
        }
    });

    const snowEffectSound = new Audio(snowSound);
    const points = useMemo(() =&gt; {
        const p = [];
          // 랜덤한 위치에 눈더미가 렌더링됩니다.
        for (let i = 0; i &lt; count; i++) {
            const x = (0.5 - Math.random()) * 3;
            const y = (0.5 - Math.random()) * 0.1;
            const z = (0.5 - Math.random()) * 0.01;
            p.push(new THREE.Vector3(x, y, z));
        }
        return p;
    }, [count]);

      // 터치 혹은 클릭 시 인터랙션 구현
    const handlePointEnter = useCallback((e: any) =&gt; {
        e.stopPropagation();
        // 눈을 0.1씩 깎아 내림
        const t = e.eventObject.position.clone();
        e.eventObject.position.y = MathUtils.lerp(t.y, t.y - 0.2, 0.2);
        // 눈 치우는 소리 재생
        snowEffectSound.play();
    }, []);

    return (
        &lt;Instances ref={ref} position={position} limit={count} range={count}&gt;
            &lt;boxGeometry /&gt;
            {points.map((pt, i) =&gt; (
                &lt;SnowBlock
                    scale={0.3}
                    onPointerDown={(e) =&gt; handlePointEnter(e)}
                    key={i}
                    position={pt}
                /&gt;
            ))}
        &lt;/Instances&gt;
    );
};
export default SnowAccumulation;</code></pre>
<p>여기서 더 나아가려면 onPointerDown 이벤트 발생시 e.eventTarget 내부에 event가 일어난 pointer 좌표도 함께 나타나요. 그렇다면 접점과 오브젝트 중점 사이 distance를 구할 수 있으므로 normalized vector position을 clone하면 들어온 포인터 방향에 밀려나간 오브젝트를 구현할 수 있어요. 하지만 제가 구현하던 과정 중에는 vector 뺄셈 계산시 결과에 NaN이 나타나 시간 상 스킵했습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6e77e01d-f512-44ec-972d-2525a77b49e4/image.png" alt=""></p>
<p>결과적으로, 고민했던 시간보다 훨씬 빠르게 창가 랜덤 좌표에 instance 내부 Box Geometry를 렌더링해서 쌓인 눈을 구현할 수 있었습니다. 브라우저 크기에 따라, pc 버전에선 창문틀에 쌓인 눈이 보이고 mobile 버전에선 시야에 바로  눈이 보여요. 원하는 효과를 구현해내며, 다음에 glsl에 다시 도전하기로 기약했어요!</p>
<h3 id="4-기타-인터랙션-구현">4. 기타 인터랙션 구현</h3>
<p>여기까지 구현해도 충분히 마음에 들고 멋진 Scene이었지만, 테스트해보니 심심한 느낌이 들었어요. 그리고 주변 친구들로부터도 선물 상자나 눈사람과 상호작용하고 싶다는 피드백도 들었어요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/33d8e8e6-b2f2-401f-9b72-82953f359e96/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ac60f4ef-3771-416e-9ae6-c8f072d29af9/image.png" alt=""></p>
<p>여기서 그만둘까?도 했지만 이때가 12월 26일로 마감일자까지 시간 여유가 조금 남아있었어요. 그래서 친구들의 요청에 따라 새로 방명록 기능과 .. 선물 인터랙션을 조합해보기로 합니다.
그리하여 만든 선물 방명록!</p>
<p>급하게 기획했지만, 떠오른 기능은 명확했어요. 사용자들이 들어와서 맵을 둘러보고 음악도 듣고 눈도 치우고 스노우맨도 눌러보다 선물을 누르면 확대되고, 사용자의 선물인 방명록을 작성할 수 있도록 했어요. 그리고, 개발 과정을 보여줄 수 있도록 개발 깃허브로 바로가는 버튼도 두었어요.</p>
<p><img src="https://github.com/naro-Kim/snowy-window/assets/51940808/c4abf89b-b1b3-49e9-8b0a-451a75223778" alt="present"></p>
<p>선물을 클릭해서 나오는 메세지는 이전에 활용했던 Geometry onPointerUp 이벤트와 dialog tag를 활용했어요. DB는 supabase를 활용해서, 간편하게 연동해두었고 정상적으로 comment가 submit되는 것을 확인했어요.</p>
<pre><code class="language-typescript">&#39;use client&#39;;
import { insertData, supabase } from &quot;@/api/client&quot;; // supabase를 활용한 data insert api
import { useSceneContext } from &quot;@/context/SceneContext&quot;; // Scene 전체에서 현재 선물이 선택되어 있는지 판단하는 contenxtAPI.
import Link from &quot;next/link&quot;;
import { useCallback, useRef, } from &quot;react&quot;;

export const GuideMessage = () =&gt; {
  const { zoom, setZoom } = useSceneContext() as any;
  // Scene전체에서 하나의 물체에만 zoom이 잡히도록 ContextAPI를 활용했어요.
  const handleBackButton = useCallback((e: any) =&gt; {
    e.stopPropagation();
    setZoom(false);
  }, []);

  const handleSubmit = useCallback(async (e: any) =&gt; {
    e.preventDefault();
    e.stopPropagation(); 
    try {
      // 간단하게, form에 입력한 내용을 바로 insert해요.
      insertData({ table: &#39;comments&#39;, name: e.target.name.value, content: e.target.content.value });
      setZoom(false);
    } catch (error) {
      console.log(&#39;Error occurred&#39;, { error })
    }
  }, []);

  return (
    &lt;&gt;
    //context와 연동해 dialog show를 컨트롤해요. 
      &lt;dialog open={zoom} className={`w-full sm:w-1/2 max-w-7xl rounded-xl bg-[rgba(0,0,0,0.5)] z-10 absolute bottom-1/4 left-1/2 -translate-x-1/2 translate-y-1/2 duration-500`}&gt;
        &lt;div className={&quot;text-white p-4 sm:p-8 grid grid-flow-rows gap-2&quot;}&gt;
          &lt;div className=&quot;grid grid-flow-col mb-2&quot;&gt;
            &lt;h1 className={&quot;font-semibold text-md sm:text-lg&quot;}&gt;Leave a Comment!&lt;/h1&gt;
            &lt;button className=&quot;justify-self-end text-xs max-w-[160px] bg-gray-500/25 p-2 rounded-lg&quot;&gt;&lt;Link href={`https://github.com/naro-Kim/snowy-window`}&gt;개발 깃허브 바로가기&lt;/Link&gt;&lt;/button&gt;
          &lt;/div&gt;
          &lt;span className={&quot;font-light leading-relaxed text-pretty text-xs sm:text-sm&quot;}&gt;
            &lt;p&gt;반가워요! 새해를 맞이하는 마음으로, 일주일 간 개발한 react-three-fiber 프로젝트입니다. 프로젝트를 응원하는 메세지를 남겨주시면 큰 힘이 됩니다!&lt;/p&gt;
          &lt;/span&gt;
          &lt;form onSubmit={handleSubmit} className=&quot;grid gap-4 py-4&quot;&gt;
            &lt;div className=&quot;grid grid-cols-4 items-center gap-4&quot;&gt;
              &lt;label className=&quot;text-right text-md&quot; htmlFor=&quot;name&quot;&gt;
                Name
              &lt;/label&gt;
              &lt;input autoFocus required id=&quot;name&quot; name=&quot;name&quot; className=&quot;rounded-lg p-2 bg-[rgba(0,0,0,0.2)] col-span-3&quot; placeholder=&quot;Enter your name&quot; /&gt;
            &lt;/div&gt;
            &lt;div className=&quot;grid grid-cols-4 items-center gap-4&quot;&gt;
              &lt;label className=&quot;text-right text-md&quot; htmlFor=&quot;message&quot;&gt;
                Comment
              &lt;/label&gt;
              &lt;textarea required id=&quot;content&quot; name=&quot;content&quot; className=&quot;rounded-lg p-2 bg-[rgba(0,0,0,0.2)] col-span-3 min-h-[100px]&quot; placeholder=&quot;Type your comment here&quot; /&gt;
            &lt;/div&gt;
            &lt;div className=&quot;text-xs sm:text-sm grid grid-flow-col justify-self-end w-1/2 gap-2&quot;&gt;
              &lt;button type=&quot;submit&quot; className={&#39;rounded-lg bg-blue-500 py-2 px-4&#39;}&gt;Submit Comment&lt;/button&gt;
              &lt;button onPointerUp={handleBackButton} className={&#39;text-gray-400/50 rounded-lg border-2 px-4 py-2 border-gray-400/50&#39;} onClick={undefined}&gt;
                Close
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/form&gt;
        &lt;/div&gt;
      &lt;/dialog&gt;
    &lt;/&gt;
  )
};</code></pre>
<p>이렇게 작성한 dialog는 zoom context와 함께, 선물 외부를 클릭하거나 다른 물체와 상호작용시 자동으로 닫히게 돼요. onPointerUp에 showModal()을 부여하는 것도 작성해 보았지만, 무슨 이유에서인지 pointer event가 일어나지 않아 open value에 zoom을 주는 방법으로 대체했어요.</p>
<p>이전까지 방명록 기능은 백엔드에서 api를 받아와 사용해본 적만 있는데요, 이번에는 개인 프로젝트를 진행하며 supabase client와 관련한 튜토리얼을 빠르게 배워 적용했어요. 공식 문서의 사용 튜토리얼을 따르면 api, db table 설계까지 빠르게 진행 가능해서 아래와 같이 빠른 방명록 기능 테스트가 가능했어요.
<img src="https://github.com/naro-Kim/snowy-window/assets/51940808/b68c473f-7cda-4bdc-82d9-e75b13c557b7" alt="Interactive Winter Wonderland_ Three js Snowscape Project - Chrome 2024-01-04 10-43-28"></p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/f629c20a-6d4c-4781-86ff-16103a0e1534/image.png" alt=""></p>
<h2 id="프로젝트-소감">프로젝트 소감</h2>
<p>이렇게 해서 완료한 프로젝트 배포 링크는 <a href="https://snowy-winter-wonderland.vercel.app/">https://snowy-winter-wonderland.vercel.app/</a> 여기에요! 언제든지 피드백은 대환영입니다. 이번 프로젝트로, 올 한해 배웠던 three.js와 react-three-fiber 활용법을 총복습할 수 있었어요. 특히 협업 스터디에서 아쉬웠던 메시 최적화 부분에서, 직접 에셋을 제작해 원하는 만큼 polygon을 최적화할 수 있어 만족스러웠습니다. 그리고 이런 노력의 결과 덕분인지 3d임에도 chrome Lighthouse에서  꽤 괜찮은 성능 점수를 받을 수 있었어요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/c3a6aed4-69ac-402e-90e5-7b44ae156d5e/image.png" alt="">
위는 pc버전에서의 chrome lighthouse 점수에요. 그리고, opengraph를 활용해 웹페이지의 한 장면을 성공적으로 미리보기에 넣을 수 있었습니다.
<img src="https://velog.velcdn.com/images/naro-kim/post/886a8f1f-b7a1-4295-9b35-c7a55577ec1a/image.png" alt=""></p>
<p>다만 아쉬운건 여전히 Next.js의 <code>Metadata</code> 부분의 활용이 아직 이해하기 어렵다는 점이었어요. 분명히 opengraph를 작성하며 <code>import type { Metadata } from &#39;next&#39;;</code>를 통해 layout에서 metadata의 description을 넣어주고 있는데, SEO 점수를 보면 알 수 있듯 description을 인식하지 못하고 있어요. 이 점은 차후에 더더욱 개선해나갈 예정이에요. </p>
<hr>
<p>2024/01/11 업데이트) 도움을 받아 <a href="https://github.com/naro-Kim/snowy-window/issues/8">description issue를 해결</a>해 SEO점수 100점을 달성했어요! 이번 이슈를 통해 og의 description은 소셜미디어를 위한 역할이고, meta 태그의 description은 search engine을 위한 역할을 수행한다는 점을 확실히 구분하게 되었어요.</p>
<hr>
<p>총 정리하면 이번에 얻은 경험은 아래와 같아요.</p>
<blockquote>
<ol>
<li>React-three-fiber 기반의 3d 인터랙티브 웹 개발</li>
<li>audio, dialog등의 HTML 태그 기반 컴포넌트 활용</li>
<li>Canvas interaction과 UI rendering의 융합</li>
<li>Three.js에서 vector position movement 구현</li>
<li>프로젝트 시작에서 시작해 끝까지 기록하며 회고록 작성</li>
<li>og와 meta 태그의 description은 각기 다른 역할임을 깨달음 (+ 2024/01/11)</li>
</ol>
</blockquote>
<p>개인적으로 아쉬웠던 점이나, 추후 개선할 점은 다음과 같아요.</p>
<blockquote>
<ol>
<li>any대신 3d event type generic을 사용하기</li>
<li>유저가 보낸 comment를 3d instance에 map으로 렌더링하기</li>
</ol>
</blockquote>
<p>이전까지 프로젝트 시작에서 끝까지 세세히 기록해나갔던 경험은 좀처럼 없는데 이번 항해 코육대 &#39;눈닦기&#39; 주제에 프로젝트를 출품하며 새로운 도전을 할 수 있었어요. 앞으로도 3D 웹 프로젝트들과 관련해 해외 글들을 번역해가며 블로그에 계속해서 공유해가려 해요. </p>
<p>여기까지 글을 읽어주셔서 감사하고, 코드나 프로젝트와 관련된 피드백은 항상 환영하고 있습니다.
다들 2024년에도 행복하고 즐거운 한 해 되세요! :)</p>
<hr>
<p><a href="https://snowy-winter-wonderland.vercel.app/">https://snowy-winter-wonderland.vercel.app/</a>
<a href="https://github.com/naro-Kim/snowy-window">https://github.com/naro-Kim/snowy-window</a></p>
<hr>
<p>감사하게도, 좋은 평가를 받아 항해+ 제2회 코육대 눈닦기 부문 최우수상을 수상하게 되었습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IT 연합 동아리 YAPP 22기 프론트엔드 서류 + 면접 회고]]></title>
            <link>https://velog.io/@naro-kim/YAPP-22%EA%B8%B0-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%9C%EB%A5%98-%EB%A9%B4%EC%A0%91-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@naro-kim/YAPP-22%EA%B8%B0-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%9C%EB%A5%98-%EB%A9%B4%EC%A0%91-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 30 Sep 2023 08:19:23 GMT</pubDate>
            <description><![CDATA[<p>학교 후배가 이번에 YAPP을 지원한다기에, 이전에 준비한 자료를 다른 사람들에게 전하고싶어졌다. (2023년이 끝나가지만) 그래서 22기가 끝나고서야 올리는 프론트엔드 면접 회고!</p>
<h2 id="yapp-22기-지원-준비하기">YAPP 22기 지원 준비하기</h2>
<h3 id="지원서-준비">지원서 준비</h3>
<p>얍에서 활동하기 위한 첫번째 과정은 단연 지원서 제출이다. 
나는 개발 관련 경험이 부족하다 느꼈고, YAPP을 통해 성장하고자 지원했기 때문에 학교에서 개발 동아리 프로젝트를 1년 활동한 경험을 위주로 답변했다. 그래서 프로젝트에 온 시간을 쏟아 넣겠다는 다짐과 열정을 강조하면서 자소서를 쓰려했다</p>
<p>자소서 항목은 총 3개였다.</p>
<blockquote>
<ol>
<li>YAPP에 지원하게 된 동기를 포함하여 자유롭게 자기소개를 해 주세요.</li>
<li>최근 관심을 가지고 공부하고 있는 Web 관련 기술을 소개해 주세요.</li>
<li>프로젝트를 하면서 팀원과 겪었던 갈등 상황과 이를 어떻게 해결했는지 설명해 주세요.</li>
</ol>
</blockquote>
<p>지원 동기는 솔직하게 느낀 그대로 작성했다. 
YAPP의 프로젝트 결과물들을 보면서 개발 과정에서 마주한 문제나 극복한 경험을 스스럼없이 공개하는 문화나 커뮤니티의 강점을 말했고 iOS, Android, Web, 디자이너, PM 등 다양한 포지션의 사람들과 짧은 시간동안 몰입해서 교류하고 프로젝트를 진행할 수 있다는 게 매력적이라고 썼다.  </p>
<p>두번째 항목은 기술 관련 항목이었는데 평소에 관심있는 분야인 메타버스와 webGPU 이슈, web 3D 라이브러리에 대해 작성했다. 왜 web3D를 공부하게 되었는지 배경 상황과 그에 따라 공부하고 있는 내용을 썼다. three.js, websocket, react-three-fiber 등을 언급하고 webGPU가 뜨고있다 까지 언급했는데 이 항목은 나중에 면접때도 질문이 들어왔다. YAPP도 그렇지만 어떤 자소서를 쓰더라도 본인이 잘 알고 있거나 흥미가 큰 부분을 적는 걸 추천한다. 그래야 답변할 때 떨지않고 말도 술술 나오고, 상대방이 알기 쉽게 설명할 수 있다.</p>
<p>근데 막상 자소서에 쓴 내용 학습은 아직도 끝나지 않는다.. 코테 공부도 하고 자격증도 따고 자소서 정리도 하고 웹소켓 도입해서 3D 하고파.. 몸이 하나론 부족해 ㅜ 흑흑</p>
<p>세번째 항목은 교내 개발 동아리 프로젝트 사례를 썼다. 프로젝트에 참여한 인원이 좀 많았는데 이 상황에서 어떻게 개발을 분배하고 스케쥴링했는지를 위주로 썼다. 그리고 결과도 적고. 면접에서 이 항목에 대해서도 추가 질문이 들어왔다. 특히 면접관 입장에서 자소서는 글자수가 제한되어 있기 때문에 지원자가 개발 상황에서 어떻게 문제에 대응했는가를 구체적으로 알기 어렵다. 그러므로, 면접에 들어가기 전에 자기가 작성한 프로젝트의 진행 과정과 이슈 대응 방법을 잘 정리해 두도록 하자.</p>
<p>교내를 벗어나, 처음으로 학교 인원이 아닌 사람들과 &#39;개발&#39; 프로젝트를 하고자 지원한 YAPP.
다행히 서류 합격으로 이어져 면접을 준비했다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/d988bc3d-52b8-461d-8119-020e9ad43a3b/image.png" alt="YAPP 지원 서류 합격"></p>
<hr>
<h3 id="면접-준비하기">면접 준비하기</h3>
<p>서류 합격 소식을 받은 다음, 면접에 대비해 <code>자소서 분석</code>과 <code>기술 면접 대비</code>를 시작했다. 
자소서야, 내가 썼던 내용이니까 쓸 때 떠올렸던 나의 경험 속 상황과 문제와 대응방법과 해결 결과를 위주로 한번씩 다시 적었다. 이 내용은 노션에 적어두고, 두고두고 다른 자소서 쓸 때 사용하는 중</p>
<p>그리고 특히 기술 관련 지식을 공부하는데에 힘썼는데.. 왜냐면 내가 독학으로 프로젝트를 진행하고 현업 경험이 없기 때문에 엄청난 실무자들이 가득할 YAPP에서 살아남기 위해 벼락치기라도 하고자 했다! 
그래서, 일단은 내가 <code>진행했던 프로젝트에서 쓴 기술 스택 -&gt; 내가 자소서에 쓴 기술 스택 -&gt; 프론트엔드에 두루 쓰이는 개발 지식</code> 순으로 접근하면서 공부하고자 했다.</p>
<p>깊게는 공부하진 못했지만, 넓게 개념을 잡으면서 공부하고 면접 때 모르는 걸 질문하시면 모른다고 답하자고 생각했다. Github과 Velog에서 프론트엔드 면접 후기들을 보며 예상 질문도 뽑았다. YAPP에 한정짓지 않고, 넓게 찾아보려 했다.</p>
<p>특히, 진로를 디자인에서 프론트엔드로 바꾼 이유도 예상 질문으로 뽑았는데 감사하게도 면접때 물어봐주셔서 자신있게 답변했다! 컴공을 복전하면서까지 진로를 전향했던 이유도 지난 멋사 동아리 면접때도 궁금해했었고, 스스로도 가치관에 왜 개발을 하고싶은가가 뚜렷하게 박혀있어야 한다 생각해서 답변을 준비했었다. 사실 이게 프로젝트를 끝까지 열심히 몰두하게 하는 원동력이니까 @_@ </p>
<p>아무튼, 공부를 위해 살펴봤던 문서들은 아래와 같다. </p>
<p><a href="https://nextjs.org/">nextjs 공식문서</a>
<a href="https://www.rldnd.net/nextjs-app-directory-">Next.js의 App Directory 관련 기능을 뜯어보자</a></p>
<p><a href="https://github.com/baeharam/Must-Know-About-Frontend">취준생이 반드시 알아야 할 프론트엔드 지식들</a>
<a href="https://velog.io/@honeysuckle/%EC%8B%A0%EC%9E%85-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C">honeysuckle/신입-프론트엔드-면접-질문-모음</a>
<a href="https://github.com/d-virusss/interview_frontend">d-virusss / 네이버 면접 준비하며 정리했던 내용들입니다</a></p>
<hr>
<h3 id="면접-질문-복기하기">면접 질문 복기하기</h3>
<p>대망의 면접날, YAPP의 프론트엔드 운영진 두분이 들어오셨고, 실무자인 다른 면접자분과 함께 비대면 2:2 면접을 진행했다. 이 날, 나는 면접하면서 질문받는 내용들을 적어내리고 같이 들어오셨던 면접자분의 인상깊은 답변도 적었었다. 미리 스크립트를 짜거나 그런건 아닌데 면접관이 질문했던 내용을 헷갈릴까봐 키워드를 조금씩 적어가며 머리가 하얘질 때 쉼호흡하면서 다시 답변하려는 의도였다. 딴 얘기지만 면접 중 필기는 면접에 따라 허용되지 않을 수 있기에 주의하자 </p>
<blockquote>
<p>웹 Socket이 어떻게 작동하는지 알고 있나요? 설명해주세요
→ 꼬리질문 1.  ) Socket이 어떤 프로토콜 위에서 작동하는 지 알고 계신가요?
→ 꼬리질문 2. ) TCP Handshaking에 대해서 알고 계신가요?</p>
</blockquote>
<ul>
<li><p>공부했던 내용 중 생각나는 내용만 말했다. 확신하지 못하는 부분도 있다 말씀드렸다.</p>
</li>
<li><p>기술 면접이 처음이라 진짜 새하얘졌다! 면스 면스  또 면스가 필요함을 느꼈다.</p>
</li>
<li><p>node.js 기반의 예제로 Web socket 통신을 공부했는데 서버 파일과 클라이언트 파일에서 소켓을 임포트하고 유저 객체가 통신을 요청하면 서버에서 접속해있는 다른 유저의 아이디와 정보를 다시 전달해준다 그 데이터를 토대로 다른 유저의 캐릭터나 정보를 렌더링해주는 거 같다고 답변했다.. 자신있진 않고 추측성 답변을 해부럿다</p>
</li>
<li><p>어떤 프로토콜이라는게 TCP 말씀하시는 건가? 싶어서 혹시 TCP 말씀이신가요..? 하고 소심하게 대답함</p>
</li>
<li><p>핸드셰이킹 컴네 수업 때 패킷 전송 과정을 배웠던 희미한 기억만 남아있었는데, web socket에서의 통신 원리도 TCP handshake였다. 수업으로만 듣던 cs가 실제 라이브러리에 활용되고 있단게 새롭고 흥미로웠다. 역시 이게 개발인가싶기도하고! 모르는 부분을 오히려 알려주셔서 감동 포인트.. 갓갓. 여유가 되면 다시 찾아보고 또 공부해야지..</p>
</li>
</ul>
<p>이외에도 질문 받은 항목은 대충 아래와 같다. 기억이 완전하진 않지만 이렇게 물어봤던 것 같다
면접관분들이 분위기를 부드럽게 풀어주셔서 모르는 내용도 (속으로만 울고) 모른다고 자신있게 답변할 수 있었다.</p>
<blockquote>
<p>SSR과 CSR의 차이에 대해서 말해주실 수 있나요?
→ 꼬리질문 1) SSG 도 알고 계시나요? 설명해주실 수 있나요?</p>
</blockquote>
<blockquote>
<p>이 기술만큼은 자신 있다 하는 내용이 있으시다면, 설명해주실 수 있으실까요?</p>
</blockquote>
<blockquote>
<p>프로젝트 시 같이 활동하고 싶은 팀원과 활동하고 싶지 않은 팀원에 대한 본인만의 기준이 있을까요?</p>
</blockquote>
<blockquote>
<p>프로젝트를 제외하고도, 요즘 들어 가장 인상 깊었던 일이 있다면 알려주세요</p>
</blockquote>
<blockquote>
<p>프로젝트 과정에서 작성해주신 전역 상태 관리 매니지먼트를 도입했던 이유는 어떻게 되나요?</p>
</blockquote>
<blockquote>
<p>WebGPU에 대해 알고 계시나요</p>
</blockquote>
<blockquote>
<p>css hack에 대해서 알고 계시나요</p>
</blockquote>
<blockquote>
<p>자소서 답변 중에, git conflict 문제가 발생했던 경험을 적어주셨는데 어떤 문제였고 어떻게 해결했는지 더 자세하게 설명해주실 수 있나요?</p>
</blockquote>
<blockquote>
<p>타입스크립트를 공부하고 계신거 같은데, 장점과 단점을 알려주실 수 있나요?
→ 꼬리 질문 ) 단점을 외부 라이브러리에서 끌어다 쓸 때 타입 적용에 문제가 있으셨다 했는데 어떻게 해결했는지 발생했던 문제에 대해 설명해주세요</p>
</blockquote>
<hr>
<h2 id="면접-후기">면접 후기</h2>
<p>전반적으로 지원할 때 작성했던 내용을 기반으로 질문을 해주셔서 <strong>면접관 분들이 지원자들의 자소서를 꼼꼼히 읽어주셨다</strong>는 걸 느꼈다. so 스윗 그저 대우성과 갓갓 아기상어~ </p>
<p>면접 후기로는 한 마디로 &quot;너무 긴장했다&quot;였다. 물론 끝나고 나면 아쉬운 점이 많이 생각나겠지만, 아는 한에서는 충분히 전달하고자 최선은 다했다! 여러모로 아쉬움이 많았지만 면접 자체에서도 배운 점이 참 많았다. </p>
<p>침착하게 말을 했어야 했지만 당황한 티를 내지 않겠다고 오히려 빠르게 말했다. 그러면서 스스로 무엇을 말하는지 흐름을 놓치고, 키워드도 반복해서 말하며 핵심 주제를 뒤죽박죽으로 말했다. 또 예상했던 것보다도 디테일한 기술 질문도 많이 받았었다. 그래서인지 모르는 기술에 대한 질문을 받을 때마다 긴장이 배가 되고 면접관께서 내가 말한 걸 다시 되묻는 때가 많았다.</p>
<p>이런 점에서, 오히려 <strong>모를 땐 한번 질문을 곱씹으면서 내가 어딜 모르고 어디까진 아는지 파악한 다음 차분하게 대답했으면 어땠을까</strong> 싶다. 같이 면접에 들어가신 분이 이런 점에서 너무 인상 깊었다. 평온하고 안정된 태도로 차분하게 프로젝트에서 마주했던 문제와 접근 방식을 논리적으로 단계별로 말씀해주시는게 이게 사회인인가! 싶었다.</p>
<p>그리고, <strong>내가 작성했던 블로그와 깃헙도 철저히 복습</strong>하자. 내가 왜 이 기술을 도입했고 그걸로 무얼 했는지 진짜 확실하게 설명할 수 있게 준비하자. 이번 면접에선 기술적인 역량을 강조하기 보다 열정과 시간이 넘쳐남을 강조하는 전략으로 부딪혀버렸다. </p>
<p>나는 <strong>기술적으로 모르는 것은 모른다</strong> 말하고, 커뮤니케이션과 관련된 소프트스킬 역량을 강조하는 쪽을 택했다. 당장 문제에 부딪히면, 최선을 다해 해결해보려 시도하고 안되면 그 때 도움을 요청하겠다.. 했던가? 당장은 부족하더라도 최대한 시간을 쏟아부어 성장한다는 마인드로 답했다. 아무튼, 이런 열정과 협업에 대한 태도를 좋게 봐주신 것 같다. </p>
<p>하지만 내 답변들이 최선일 수도 아닐수도 있다. <strong>모두 저마다 강점을 갖고 있을텐데 그 강점들을 스스로 자신있게 답변</strong>하면 면접에서 좋은 결과를 얻을 수 있다고 생각한다. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/cd7b18b2-6d05-4af0-8147-18307d0415c9/image.png" alt=""></p>
<p>결과적으론 YAPP 22기에 최종 합격하고 진짜 다신 없을 좋은 경험을 했다. 너무나도 좋은 사람들과 만나 YAPP 기간동안 즐겁게 지내고 개발에서도 많은 걸 배웠다. 멋쟁이 프엔 사람들.. </p>
<p>누구든, 개발에 진심이라면 YAPP에 합류하길 추천한다! 같이 프로젝트를 진행하는 모두에게서 많은 걸 배웠다. 때마침 추석 연휴라 시간이 조금 나서 면접 후기를 작성했는데 다음 코테와 서류 작성을 마치고 또 틈이나면 활동 회고도 올려야겠다. </p>
<p>그리고 마무리로, YAPP 22기 회장직으로 힘내주셨던 채원님의 브런치 스토리를 공유하며 마친다.
이 글을 읽는 사람이 YAPP 지원자일 수도 아닐 수도 있지만 모두 바라는 대로 일이 잘 흘러가길~</p>
<p><a href="https://brunch.co.kr/@chxxnkim/18">너(님) 내 동료가 돼라</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(번역) Solid Vs. Outline Icons: Which Are Faster to Recognize? ]]></title>
            <link>https://velog.io/@naro-kim/%EB%B2%88%EC%97%AD-Solid-Vs.-Outline-Icons-Which-Are-Faster-to-Recognize</link>
            <guid>https://velog.io/@naro-kim/%EB%B2%88%EC%97%AD-Solid-Vs.-Outline-Icons-Which-Are-Faster-to-Recognize</guid>
            <pubDate>Mon, 11 Sep 2023 15:38:37 GMT</pubDate>
            <description><![CDATA[<h1 id="솔리드-vs-아웃라인-아이콘--어느-것을-더-빠르게-인지할-수-있을까"><a href="https://uxmovement.medium.com/solid-vs-outline-icons-which-are-faster-to-recognize-9bb0fc24821f">솔리드 vs 아웃라인 아이콘 : 어느 것을 더 빠르게 인지할 수 있을까?</a></h1>
<hr>
<p>우리가 모바일 앱을 설계할 때, 솔리드 아이콘을 사용할지 아니면 외곽선 아이콘을 사용할지 결정해야 하는 시기가 옵니다. 어떤 스타일이 사용자 경험에 더 나을까요? </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/4ce75c43-e7e9-4940-8ae9-9bb3b54439cf/image.png" alt=""></p>
<p>몇몇 사람들은 둘의 차이가 단지 선호나 취향의 문제라고 생각합니다. 하지만 리서치를 보면, 그 보다 더 중요한 결과를 볼 수 있습니다. 바로 한 스타일이 다른 스타일보다 더 빠른 인식 비율을 보여준 다는 것이지요.</p>
<p>어느 때에 솔리드 혹은 외곽선 스타일을 쓸지 아는 것은 사용자들이 당신의 앱을 항해하는 것을 더 쉽게 만들어 줄 것입니다. 사용자들은 당신의 아이콘을 빠르게 알아차리고 올바른 옵션을 선택할 수 있을 것입니다.</p>
<p><a href="https://cdr.lib.unc.edu/concern/masters_papers/6w924g35w">“Filled-in vs. Outline Icons: The Impact of Icon Style on Usability,”</a>라는 논문에서는 아이콘 스타일이 업무 수행능력에 영향을 끼친 다는 사실을 발견하였습니다. 여기서 업무 수행능력은 아이콘을 인지하고 선택할 때에 있어서 속도와 정확성으로 측정하였습니다.</p>
<p>솔리드 아이콘은 보통은 외곽선 아이콘들 보다 빠르게 알아차려졌지만 몇 가지 예외가 있었습니다. 그리고 몇몇 아이콘들은 업무 수행 시간에 있어 별다른 차이를 보여주지 않았습니다. 이 것은 특징 신호와 관련이 있습니다.</p>
<h3 id="특징--신호-characteristic-cues">특징  신호 (Characteristic Cues)</h3>
<p>특징 신호라는 것은 사용자들이 아이콘을 판별할 때 사용하는 것을 말합니다. 만약 특징 신호가 없거나 알아차리기 어렵다면, 그 아이콘은 인지할 수 없게 되죠.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/3e9a751c-c5cb-4fa9-9750-3ab012c0a9c9/image.png" alt=""></p>
<p>예를 들어, 말풍선의 꼬리 부분같은 경우 바로 특징 신호이죠. 이게 없다면 그 아이콘은 단순히 동그라미일 뿐입니다. 자물쇠 아이콘의 열쇠 구멍도 그 아이콘의 특징 신호입니다. 톱니바퀴 아이콘의 톱니또한 그것의 신호이지요. 만약 그게 없다면, 그 아이콘은 단지 도넛일 뿐입니다.</p>
<p>이 논문 연구 속에서 쓰인 자물쇠 아이콘은 열쇠 구멍이 없었고, 모든 인지 실패 사례들 중의 4분의 1 이상을 차지한 가장 인지하기 어려운 아이콘이었습니다. 열쇠구멍이 없다면 단순히 가방이나 지갑 혹은 심지어 주전자로도 보이기 때문에 그것은 필수불가결한 특징 신호입니다.</p>
<p>이런 특징 신호들은 사용자들이 아이콘 인지에 전적으로 의지하는 것들입니다. 아이콘을 사용할 때, 사용자들이 분별하기위해 필요한 모든 특징 신호들을 확실하게 사용하세요. 만약. 한 아이콘이 다른 사물과 같이 보이게 되면, 추가적인 특징 신호를 더하는 방법을 고려해보세요.  </p>
<blockquote>
<h4 id="외곽선-아이콘의-인지가-더-빠른-경우">외곽선 아이콘의 인지가 더 빠른 경우</h4>
</blockquote>
<p>특징 신호를 포함하는 것에 더해, 그 신호는 분명해야하고 인지하기 쉬워야합니다. 때때로 특정 아이콘에서 쓰인 특징 신호들이 솔리드 스타일에서 쓰인 것 보다 외곽선 스타일에서 쓰인 경우가 더 분명해 보이는 때가 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/350948ec-60bb-41f0-a0fe-de8805e91c62/image.png" alt=""></p>
<p>연구에서 외곽선 스타일인 경우에 빠르게 인지할 수 있는 세가지 아이콘들을 찾았습니다. 바로 말풍선, 휴지통, 열쇠 아이콘이었습니다. 이들 아이콘들은 형태 외곽에 나타나는 아주 세밀한 특징 신호들을 가지고 있었습니다. 이런 특징 때문에, 외곽선 스타일이 이 신호들을 더 쉽게 알아차리도록 만들어줍니다.</p>
<p>말풍선의 꼬리의 경우 솔리스 스타일에서 쉽게 놓치기 쉽지만 외곽선 스타일에서는 더욱 분명해집니다. 쓰레기통 뚜껑의 경우 솔리드 스타일에선 알아차리기 어렵지만, 곽선 스타일에선 더 알아보기 쉽지요. 열쇠의 이빨들은 세밀하지만 톱니 엣지들은 외곽선 스타일에서 더 분명히 드러납니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/7b115063-c02f-4ee8-bad6-644138e8a646/image.png" alt=""></p>
<p>아이콘의 특징 신호들이 세밀하고 형태의 엣지에서 나타날 때엔, 외곽선 스타일을 사용하세요. 이 방법은 더 빠른 인지라는 결과로 이어지도록 신호들을 더 분명하게 만들어줍니다. </p>
<p>아이콘을 고를 때, 일관성있는 스타일을 유지하는 게 좋습니다. 솔리드와 외곽선 스타일을 사용하는 대신에, 특징적으로 드러나는 날카로운 각과 분명한 메인 특징 신호들을 가진 아이콘 셋을 고르도록 하세요. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/dcc7e7e8-5414-4bd3-b6ae-b091ab33f436/image.png" alt=""></p>
<blockquote>
<h4 id="솔리드-아이콘의-인지가-더-빠른-경우">솔리드 아이콘의 인지가 더 빠른 경우</h4>
</blockquote>
<p>대부분의 아이콘들은 현실에서의 물리적인 사물들을 표현합니다. 이런 사물들은 솔리드 형태로 이루어져있고 실루엣으로 나타납니다. 아이콘을 외곽선으로 보는 것은 대부분의 사물이 보여지는 것처럼 현실성있는 표현은 아닙니다. 이것은 솔리드 아이콘을 더 빠르게 인지할 수 있는 이유이지요.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/090f7e3a-6b6f-462d-8044-7233753072af/image.png" alt=""></p>
<p>이런 것들에도 불구하고, 사용자들은 여전히 외곽선 아이콘을 인지할 수 있습니다. 하지만, 만일 아이콘의 형태 외곽선이 너무 가까이에 있다면 인지하는데에 더 많은 시간을 들게 할 것입니다.</p>
<p>이 논문에서는 엄지, 가위, 전화기 그리고 도구 아이콘들이 솔리드 스타일에서 훨씬 빠르게 인지된다는 사실을 발견했습니다. 이것은 이 아이콘들의 특징 신호 부분의 외곽선이 너무 좁은 내부 공간(inner space)를 갖고있기 때문이었고 이는 시각적인 장애를 초래했습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/3605b2c2-b813-423b-912d-d3b74eb06fd9/image.png" alt=""></p>
<p>만일 좁은 내부공간을 가진 사물들이라면 솔리드 아이콘을 사용하는 것이 적합합니다. 솔리드 스타일의 실루엣 형태는 아이콘을 판별하기 쉽게 만들며 단순해진 형태를 재공합니다.</p>
<blockquote>
<h4 id="스타일이-인지에-있어-차이를-만들지-않는-경우">스타일이 인지에 있어 차이를 만들지 않는 경우</h4>
</blockquote>
<p>논문은 또한 두 스타일 모두에서 쉽게 인지할 수 있는 아이콘들을 찾아내었습니다. 예를 들어 별, 쇼핑카트 그리고 깃발 아이콘은 모두 비슷한 인지 횟수를 가졌죠. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/4167178b-3b05-4dc3-9979-18a9a700dc2d/image.png" alt=""></p>
<p>이 사례는 외곽선 스타일이 사용자들의 인지 속도를 늦추지 않는다는 것을 뜻합니다. 이는 그 아이콘들이 가지고 있는 충분히 넓은 내부 공간(inner space)이 시각정 장애를 줄였기 때문입니다. 내부 공간이 줄어들 수록, 더 많은 시각적 장애물들이 만들어지고 이는 사용자들의 인지를 방해합니다.</p>
<h3 id="버튼-선택을-위한-아이콘-스타일">버튼 선택을 위한 아이콘 스타일</h3>
<p>탭바에서 활성화된 버튼을 강조하기 위해, 나머지 버튼들은 외곽선 형태로 남겨둔 채로 솔리드 아이콘을 사용하는 것은 흔한 작업입니다. 하지만 이 디자인 작업은 규칙을 거스르고 있어 다른 방법이 필요합니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/941c3618-71a9-4e67-9434-418f383c98d9/image.png" alt=""></p>
<p>사용자들은 그들이 이미 선택한 옵션들이 아니라 미처 발견하지 못한 옵션들을 더욱 빠르게 인지할 필요가 있습니다. 따라서, 활성화된 버튼에 솔리드 아이콘을 사용하는 것은 불필요합니다. 비활성화된 아이콘들을 솔리드 아이콘 스타일로 사용하는게 더 중요하지요.</p>
<p>활성화 버튼을 강조하고싶다면, 솔리드 아이콘이 아닌 외곽선 아이콘을 쓰세요. 이 방법은 스타일과 컬러에 있어서 더 분명한 변화를 보여주고 선택된 버튼을 강조합니다.</p>
<h3 id="아이콘의-관습을-타파하는-지침">아이콘의 관습을 타파하는 지침</h3>
<p>만약 인지 속도가 당신의 사용자들에게 중요하다면, 반드시 아이콘 인지 성공 비율을 고려해야 합니다. 그리고 만일 당신이 더 빠른 인지를 원한다면, 솔리드 스타일의 아이콘을 사용하는 편이 더 좋습니다. 하지만 이 규칙에는 당신이 꼭 기억해야할 몇가지 예외가 있습니다. 이런 예외사항들을 기억해둔다면 적절한 상황에서 외곽선 스타일을 사용할 수 있을 것입니다.</p>
<p>요약하자면, 여기 당신이 기억해야할 아이콘 스타일 사용 지침이 있습니다.</p>
<ul>
<li>분별가능하고 분명히 드러나는 특징 신호로 아이콘을 구성해야 합니다.</li>
<li>특징 신호가 세밀하거나 충분히 분명하지 않는 한, 솔리드 아이콘의 인지 속도가 빠릅니다.</li>
<li>외곽선 아이콘 안에  충분히 넓은 내부공간이 있다면 더욱 인지하기 쉽습니다.</li>
<li>만일 솔리드 스타일 버전의 아이콘이 형태의 엣지에 세밀한 특징 신호를 가지고 있다면, 외곽선 아이콘을 사용하세요.</li>
<li>외곽선 스타일의 아이콘이 너무 좁은 내부 공간을 갖고 있다면 솔리드 아이콘을 사용하세요.</li>
</ul>
<hr>
<h2 id="번역-후기">번역 후기</h2>
<p>서비스를 사용자들에게 잘 전달하기 위해 몇 가지 중요한 것들이 있겠지만, 디자이너와 개발자가 공감할 수 있는 하나는 &#39;사용성&#39;일 것이다. 디자이너는 사용성을 위해 시각적 지표들(affordance)를 포함해 UX 시나리오를 기반으로 인터페이스를 설계하고, 개발자는 사용성을 위해 더 나은 렌더링을 위한 웹 최적화 방법과 다양한 기술 스택들로 빠르고 오류없이 안정적인 웹을 사용자들에게 전달한다.</p>
<p>이런 점에서, 프론트엔드 개발자는 왜 디자이너가 이런 디자인 시안을 제안했는지 빠르게 이해하고 개발에 더 많은 시간을 투자하고 디자이너는 개발자가 왜 최적화와 사용성을 위해 어떤 기술을 채택했는지 서로 이해하면 좋을 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(번역) Facebook 프로덕트 개발 팀에서 사람을 지키는법 : Designing with compassion]]></title>
            <link>https://velog.io/@naro-kim/Designing-with-compassion-%ED%94%84%EB%A1%9C%EB%8D%95%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EC%8B%AC%EC%9E%A5%EB%B6%80%EC%97%90%EC%84%9C-%EC%82%AC%EB%9E%8C%EC%9D%84-%EC%A7%80%ED%82%A4%EB%8A%94%EB%B2%95-UX-%EB%B0%A9%EB%B2%95%EB%A1%A0-%EC%95%84%ED%8B%B0%ED%81%B4%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@naro-kim/Designing-with-compassion-%ED%94%84%EB%A1%9C%EB%8D%95%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EC%8B%AC%EC%9E%A5%EB%B6%80%EC%97%90%EC%84%9C-%EC%82%AC%EB%9E%8C%EC%9D%84-%EC%A7%80%ED%82%A4%EB%8A%94%EB%B2%95-UX-%EB%B0%A9%EB%B2%95%EB%A1%A0-%EC%95%84%ED%8B%B0%ED%81%B4%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sat, 09 Sep 2023 15:36:52 GMT</pubDate>
            <description><![CDATA[<h2 id="사용자에-대한-이해로-디자인하기--프로덕트-개발의-심장부에서-사람을-지키는-법"><a href="https://medium.com/designatmeta/designing-with-compassion-59a5ca077031">사용자에 대한 이해로 디자인하기 : 프로덕트 개발의 심장부에서 사람을 지키는 법</a></h2>
<hr>
<p>Facebook의 컨텐츠 전략가이자, 연구자이자, 프로덕트 디자이너로서 우리는 Facebook 제품을 개발할 때 언어에서나 디자인에서나 분명하고 일관성있게 사용자를 이해하는 경험을 만드는 것을 목표로 합니다. </p>
<h6 id="--여기서-원문의-compassionate는-facebook의-콘텐츠-전략가-연구자-및-제품-디자이너들이-facebook-제품을-개발할-때-사람들에게-보다-편안하고-사람다운-경험을-제공하기-위해-사용자들을-이해하고-그들의-감정과-상황을-공감하며-고려하여-제품을-설계하고-개선하려는-의미를-나타냅니다">( * 여기서, 원문의 &quot;compassionate&quot;는 Facebook의 콘텐츠 전략가, 연구자 및 제품 디자이너들이 Facebook 제품을 개발할 때 사람들에게 보다 편안하고 사람다운 경험을 제공하기 위해 사용자들을 이해하고 그들의 감정과 상황을 공감하며 고려하여 제품을 설계하고 개선하려는 의미를 나타냅니다.)</h6>
<p>우리가 사용하는 톤에서부터 컨트롤들까지 우리가 제공하는 모든 것들은 페이스북 전반에 걸쳐 더욱 사려깊은, 인간적인, 그리고 사람들에게 더 나은 편의성을 제공합니다.</p>
<h2 id="사람을-위해-계획하기-building-for-people-first">사람을 위해 계획하기 Building for People First</h2>
<p>우리는 프로덕트 디자인과 컨텐츠 전략 둘 모두에 아주 깊은 주의를 가하면서 사용자 경험 전체를 고려하여 일합니다. 앞서 말한 두 가지는 반드시 우리가 만들어낸, 사람들이 마주한 문제들을 해결할 방법 안에 공존해야 합니다. 그리고 사용자 경험은 직관적이고 사용하기 쉬워야 하며, 시각적인 디자인은 유저에게 친숙하고 연관성있어야 합니다.</p>
<p>우리 팀은 페이스북 산하의 앱들을 아우러 웰빙을 위한 디자인에 전념하고 있습니다. 우리는 위험군의 사람들이나 걱정스러운 가족, 친구들을 위한 도구나 자원을 포함하면서 자살 예방과 같은 주제들을 받아들입니다. 돌아가셨던 사람의 소원을 보존하거나 지인들 지원하기 위해 말입니다.  </p>
<h3 id="사례-연구">사례 연구</h3>
<p>어떻게 사람들이 우리의 프로덕트와 사용하는지를 연구하고 얻은 키 인사이트들은 새로운 프로덕트와 기능들을 실행하기 위해 우리가 제시한 아이디어들을 정리하도록 돕습니다. 이것들은 이미 시장에 출시된 제품을 발전시키고 그를 발전시킬 특정 기능들을 나타나게 하며, 사람들을 위해 더 나은 경험을 만들어낼 새로운 기회들을 중심으로 팀을 구성할 수 있게 합니다.</p>
<p>2017년으로 돌아가보면, 우리 팀은 사람들이 페이스북에서 겪은 불편함을 탐구하고 있었습니다. 특히 원하지 않는 메세지를 받는 경우에 관해서요. 이런 케이스 연구들을 통해, 우리는 메신저에서 대화를 무시할 수 있도록 설계했습니다. </p>
<p>이 프로덕트 케이스를 들여다보며 프로덕트 디자인 프로세스와 내용을 확인해보죠! 우리 팀은 프로젝트의 프로세스를 4단계로 생각하고 있습니다. 바로 - Understand (이해하기), Design (디자인), Gather feedback (피드백 수집), Build (설계하기) - 입니다. 물론 여기서 진행하는 단계마다 약간의 유연성을 갖지요.  </p>
<h3 id="프로세스-1단계--understand-이해하기">프로세스 1단계 : Understand (이해하기)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/08c80a32-ce1e-43a2-a64e-28dbf4408c82/image.png" alt=""></p>
<p>개별 프로젝트에서 프로세스는 <code>문제를 이해하는 작업</code>에서 부터 시작합니다. 이 작업은 우리가 사람들의 니즈를 알아차리고 설계할 프로덕트를 발전시킬 기회를 찾아내도록 합니다.여기서 핵심 프로덕트 팀은 프로덕트 매니저, 데이터 사이언티스트, 컨텐트 전략가, 프로덕트 디자이너, 리서쳐, 엔지니어로 구성됩니다.</p>
<p>불편함에 대한 개선 작업의 사례로, 먼저, 팀은 &quot;불편을 줄이기 위해 도입한 도구&quot;들이 사람들의 니즈를 적절히 전달하고 있는지를 탐구하기 위한 목표를 정했습니다. 다음으로, 사람들이 실제로 경험하고 있는 것들을 이해하기 위해 <code>기초 리서치 인사이트</code>들과 <code>서베이 응답</code>을 살펴보았습니다. 또한, 사용자 패턴을 찾고 규모에 따른 행동을 이해하기 위해 데이터들로 뛰어들었습니다.</p>
<p>마지막으로 목적에 맞게 포커스를 좁히고 집중하기 위해, 우리는 위의 모든 정보들을 사람들이 대면하는 문제의 구체적 설명이라는 <code>People Problem</code>으로 정리했습니다. 가장 큰 기회들(&quot;Opportunities&quot;)을 정의내리기 위한 설계적인 제한도 덧붙여서 말입니다. 앞서 리서치 자료와 존재하는 데이터에서 발견한 것들을 토대로 어떤 기회를 결합할지, 강조할지를 토론하고 논의했습니다. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/835b7bc7-2753-4af0-bee4-3c6a6202c6fa/image.png" alt=""></p>
<h4 id="팀-스프린트-과정-중에-마주한-people-problem과-opportunity들">팀 스프린트 과정 중에 마주한 People problem과 opportunity들</h4>
<p>많은 UX 디자인 사례들에서, 특히 우리팀의 경우에 <code>People problems</code>는 심각하고 현실적인 문제들이었습니다. 예를 들어 &quot;온라인으로 방금 만난 사람이 메신저에서 부적절한 이미지를 보내는걸 차단하고 싶어요&quot;와 같은 것이 &quot;People problems&quot;에 해당할 수 있습니다. </p>
<p><code>People problem</code>이란 키워드로 재정의된 이런 시나리오들은 문제들을 추상화하고 보편화하여 우리가 해결 가능한 단계로 만들어줍니다. 하지만 개개인은 추상화되거나 보편화될 수 없습니다. 그래서 우리는 절대로 안전과 평판이 위협받을 수 있는 현실에서, 이 문제들을 마주한 사용자들의 관점을 잃지 않으려고 합니다.</p>
<p>문제들을 이해하기 위한 작업의 결과로, 우리는 메신저에 대한 불편함을 신고하기 위한 부가적인 기능들을 설계하는 것이 가장 큰 기회 (*해결 방법) 라고 생각했습니다. 구체적으로 예시를 들자면, 페이스북이나 어떤 메신저에서 &quot;누군가를 차단&quot;한다는 개념이, 반복적인 괴롭힘을 제공하는 사람들이나 불편한 사람들을 &quot;직접적으로 알고있는 사람들&quot;에게 극단적으로 느껴진다는 것을 들었습니다. </p>
<p>때문에 차단 기능은 차단 당한 사람에게 차단당했다는 사실을 명확하게 선언하지 않는 한 편, 차단을 실행한 유저들이 이를 알아차릴 수 있는 몇 가지 신호들을 제공합니다. 차단 기능을 필요로 하는 사람일지라도, 감정적으로 차단은 무례하게 느껴질 수 있습니다. 그리고 이 불편함을 겪은 사례들 중에서, 누군가를 차단하는 것이 그 무례자들을 당신으로부터 멀어지도록 하기 때문에 오히려 그들을 신고하지 못하게 한다고 합니다.</p>
<h3 id="프로세스-2단계--design-디자인">프로세스 2단계 : Design (디자인)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/ab838f02-71fb-48b6-834b-4cc4ade85426/image.png" alt=""></p>
<p>우리는 스펙트럼을 따라 디자인 스텝에 접어들며 메신저의 불쾌함을 덜어줄 기능에 대해 생각해보기 시작했습니다. 단순하게 알람을 끄는 음소거에서 부터 메세지 차단, 페이스북 차단 기능까지 말입니다. 하지만 음소거와 메세지 차단 사이에는 아래 이미지처럼 채워야할 간극이 있었습니다. 우리는 어떻게 대화를 &quot;숨기는&quot; 것이 가능할지를 궁금해했습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/173eb211-f57c-48f7-a220-d6cfa6fb1e49/image.png" alt=""></p>
<h4 id="불쾌함을-줄이기-위한-기능의-범주를-정의한-이미지">불쾌함을 줄이기 위한 기능의 범주를 정의한 이미지</h4>
<p>넓은 선상에서, 우리는 먼저 &#39;숨기기&#39;기능이 적용된 프로덕트에서의 사용자 경험을 그렸습니다. 즉, &#39;숨겨진 사람&#39;들은 그들이 메세지가 보이지않을 것이란 인지 없이 계속해서 메세지를 보낼것이고, 대화를 가린 사람은 불쾌한 메세지들을 알아차릴 일 없이 지낼 것이란 뜻입니다. 만일 수신자가 대화를 찾아낼 필요가 있다면, 손쉽게 그 대화를 찾아내고 드러낼 수 있고, 또한, 숨겨두었던 사람들을 차단하는 것도 가능할 것 입니다. </p>
<p>우리는 구체적인 디테일부터 시작해 매우 넓은 범주까지 프로덕트 기능성에 대한 광범위한 대화들을 가졌습니다. 아래와 같이 말입니다.</p>
<ul>
<li>사람들은 어디에서 대화 숨기기 옵션을 찾으려고 할까?</li>
<li>누군가를 압박하는 일 없이,  무슨일이 생길지에 대해 소통할 수 있을까?</li>
<li>가벼운 마음을 갖는것과 충만함과 만족감을 느끼는 것 사이에서 어디에 균형을 맞추어야할까?</li>
<li>무시하기 기능으로 가려진 사람은 정확히 무엇을 보아야할까?</li>
<li>특정한 기능의 온-오프를 표시할 Visual affordance(시각적 행태)는 무엇인가?</li>
<li>어디에서 차단으로 강화될 기능을 마주하는걸까?</li>
<li>안드로이드, iOS, 모바일웹, 데스크톱웹 등 우리가 지원하는 플랫폼들 전부에서 작동할 수 있을까?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/688f9f1b-537b-470a-8a19-ae3b0e48e31e/image.png" alt=""></p>
<h4 id="화이트보드를-이용한-유저-플로우-매핑-와이어프레임">화이트보드를 이용한 유저 플로우 매핑 와이어프레임</h4>
<p>우리는 위와 같은 질문들에 대답하며, 가장 강력한 효과를 가질 수 있도록 넓은 것에서 시작하여 좁혀가며 디자인 솔루션들을 그려나갔습니다. 그 다음으로 디자인 디테일들을 시각화하고 맥락 상의 컨텐츠 옵션들을 조사하며 지금의 프로덕트 상태에 솔루션이 어떻게 맞추어질지 확인하고 더 나은 감각을 갖기 위해 중간 단계 목업을 만들었습니다. </p>
<p>컨텐츠에 대해서도 스스로 많은 대화를 나누었고 또, 문제를 경험하는 사람들의 관점에서 생각하는 것을 보장하고 피드백을 받기위해 디자인 직원들 사이에서도 많은 이야기를 나누었습니다. 인터랙션 디자인에 살을 붙이고 우리의 아이디어를 더 현실적인 프로덕트로 느껴지게 하기 위해, 하이피델리티 프로토타입을 만들어냈습니다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/071c380f-2dc9-4aea-bd10-d7fd99bd595e/image.png" alt=""></p>
<h4 id="그렇게-만들어낸-high-fidelity-고충실도-인터페이스-연구작업의-이미지">그렇게 만들어낸 High-fidelity (고충실도) 인터페이스 연구작업의 이미지</h4>
<hr>
<h3 id="프로세스-3단계--gather-feedback-피드백-수집">프로세스 3단계 : Gather Feedback (피드백 수집)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/35f678ef-ac43-4a18-b4ce-0c2481591d5f/image.png" alt=""></p>
<p>어떻게 문제들을 깊게 이해할 수 있을까요? 먼저, 면대면 리서치를 할 필요가 있습니다. 불편함에 대한 정보들과 현존하는 리서치는 문제를 이해하는 방식에 도달하게 합니다. 실질적으로 솔루션이 사용자들의 니즈에 도달하도록 하기 위해, 컨텐츠 전략팀과 디자인 팀은 리서치에 강하게 의존했습니다. </p>
<p>그리고 이 단계는 종종 사무실을 떠나 진짜 세상으로 향합니다. 예를 들어, 우리는 캘리포니아에 위치해있었기 때문에, 우리 문화권의 사람들이 페이스북과 메신저에서 불편을 겪는 것과 비교했을 때 다른 문화권 사람들의 경험은 어떻게 다른지 직접 이해하기 위해 인도로의 여행을 계획했습니다.</p>
<p>그 결과로 2명의 리서쳐, 1명의 프로덕트 매니저, 2명의 디자이너, 1명의 컨텐츠 전략가와 1명의 엔지니어를 포함한 우리 팀은, 델리와 인근 소도시 가지아바드로 여행을 떠났습니다. 우리는 넓은 범위의 사람들과 이야기하며 &#39;포커스 그룹&#39;을 설정하고, 가정 내 인터뷰를 진행했습니다.</p>
<p>그들이 지금 &#39;차단 옵션&#39;을 어떻게 이해하고 사용하는지 감을 잡기 위해, 그들에게 메세지 요청을 받거나 태그된 포스트들의 미리보기 기능을 켤 수 있는 &#39;메세지 차단 도구&#39;를 보여주었습니다. 그러고 나서 메세지 차단 인지를 키우기 위한 프로모션 알림과 대화 가리기 기능을 포함한 새로운 아이디어의 프로토타입을 토대로 그들의 피드백을 받았지요. </p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/644e16da-4e66-49a6-953e-8ff70494f743/image.png" alt=""></p>
<h4 id="리서치에서-사용자-피드백을-위해-사용된-초기-프로토타입의-이미지">리서치에서 사용자 피드백을 위해 사용된 초기 프로토타입의 이미지</h4>
<p>이 리서치를 하면서, 메신저에서 불편을 겪는 사람과 누군가에게 계속해서 연락을 취하려는 사람, 양쪽 모두의 관점을 이해하고 싶었습니다. 포커스 그룹내의 사람들은, 페이스북을 세상의 모든 이들에게 연락하기 위한 방법 중 하나로 보았는데 이는 새로운 사람을 온라인으로 만난다는 점에서 무척이나 흥미로웠습니다. </p>
<p>아무리 먼 곳에 있더라도 여성과 남성 모두 어떤 친구 요청이든 받아들인다고 하였죠. 또한 몇몇 여성들로 부터 어떻게 친절한 인사가 쉽게 다가오고, 반면에 자주 원치않는 연락이나 불편이 선을 넘었는지에 대해 들을 수 있었습니다. 모든 종류의 상호 작용 속에서, 우리는 현실에서 불쾌함이 발생하는 광범위한 방법들에 대해 이해할 수 있었습니다. </p>
<p>우리는 페이스북에서 많은 불쾌함을 경험했다고 말하는 참여자들에게 어려웠던 경험의 이야기들을 들었습니다. 하지만, 손에 쥐어진 도구 (*모바일 디바이스)에 대한 깊은 친근감을 포함한 용기와 강한 면모또한 들었습니다.</p>
<p>여성과 남성들은 페이스북에서 사람뿐 아니라 메세지도 차단할 수 있는 기능을 이해하고 있었고, 그들을 괴롭히는 누군가를 차단하는 데에 문제를 느끼지 않았습니다. </p>
<p>&quot;난 그냥 차단했어.&quot; 이 말은 우리가 정말 듣고 또 들었던 말입니다. 하지만 차단이 현실에서 심각한 갈등을 초래할 수 있기 때문에, 특정 상황 속에서는 명백하게 <code>차단</code>하는 것 없이 상대가 불쾌한 메세지를 보내는 것을 차단할 수 있어야 한다는 것 또한 확인할 수 있었습니다. </p>
<p><strong>&quot;원치않고 불쾌한 메세지들은 우리 플랫폼에서 필요 없다&quot;</strong></p>
<p>이 것은 문제가 정말로, 정말로 어려워지는 길이었습니다. 메신저를 넘어서, 불쾌함은 수백만의 사람들을 매일같이 괴롭힙니다. 우리는 메신저안의 버튼 하나가 불쾌한 경험을 지워버리지 못할 거란걸 알고 있습니다. 그리고 그게 짜증나는 일이 다시 일어나지 않을 거란 것을 의미하지 않는다는 것도요. 사용자가 원치않고 불쾌한 메세지들은 우리 플랫폼에서 필요없습니다. 이 불쾌함에 대한 사용자들의 전투를 돕기위해, 우리는 누군가 사용할 수 도 있는 도구들- <code>차단 도구</code>들을 늘리고 조정하기 위해 일합니다. </p>
<p>이 프로젝트의 초반 단계에서, 우리는 이 기능을 <code>숨김</code>으로 나타냈습니다. 우리는 리서치 여행에서 사람들이 이미 페이스북에서 코멘트를 숨기는 기능에 익숙하며 이 것이 메신저에서 누군가와의 대화 전체를 숨기는 아이디어와 맥락이 다르단 것을 깨달았습니다. </p>
<p>따라서, 가능한 옵션들에 대한 더 많은 토의와 탐구 이후에, 우리는 해당 기능에 대한 단어를 <code>무시하기</code>로 변화 시켰습니다.</p>
<h3 id="프로세스-4단계--build-설계하기">프로세스 4단계 : Build (설계하기)</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/aeb29863-845a-456b-84f3-7b48b9b01e1b/image.png" alt=""></p>
<p>어떠한 프로덕트라도, 프로덕트 디자이너들은 백엔드 기능과 프론트엔드 인터페이스를 설계하는 엔지니어 동료들과 협업합니다. 따라서 설계 단계에서는 디자인한 프로덕트가 직관적 사용성을 유지하면서도, &#39;기술적으로 실현 가능할 것&#39;을 보장해야합니다. </p>
<p>일단 우리는 <code>무시하기</code> 기능을 런칭한 이후에, 그 기능이 사용자들에게 어떻게 느껴졌고, 어떻게 작동하고 있는지 이해하기 위해 서베이를 시행하고 데이터를 분석합니다. 이를 통해 우린 무시하기 기능이 사용자들에게 빠르게 받아들여지고 있으며 다른 불쾌함을 줄이는 기능들과 조화를 이루고 있다는 것을 발견했습니다. </p>
<p>하지만, 여기에 그룹 메세지를 위한 설계 또한 필요했기에 디자인을 반복하고, 설계된 프로덕트가 직관적이고 가치있으며 쉽게 이해가능함을 확인하기 위한 사용자 테스트를 진행했습니다.</p>
<h4 id="메세지-무시하기-기능을-위한-최종-프로토타입의-이미지">메세지 무시하기 기능을 위한 최종 프로토타입의 이미지</h4>
<p> <img src="https://velog.velcdn.com/images/naro-kim/post/ab08b0cc-b6dd-4df9-9a75-bb780441c34d/image.png" alt=""></p>
<h3 id="마무리---학습자의-자세">마무리 - 학습자의 자세</h3>
<p>마무리하며, 프로덕트 디자이너로서, 컨텐츠 전략가로서, 또 리서쳐로서, 팀 안에서 여러분들은 사용자들이 마주한 문제들을 깊게 이해하기 위해 다른 사람들이 가진 스킬을 활용할 수 있습니다. </p>
<p>그 문제들의 해결을 위해 솔루션을 디자인 하는 팀의 노력을 이끌 수도 있으며 현실에서는 피드백을 위해 프로토타입을 보여줄 수도 있고, 사용자의 일상 생활 속으로 최종 프로덕트 경험을 실현시키기 위해 엔지니어들과 협업할 수도 있습니다.</p>
<p>하지만 여기에서 디자인 작업이 끝나는 것은 아닙니다. 시험, 학습, 반복 그리고 때때로 기능이나 프로덕트를 철회하기 위한 의사 결정들은 전부 우리 프로덕트를 사용하는 사람들을 위해 올바른 길로 나아가는 여정의 일부입니다. </p>
<p>이 &#39;무시하기 기능&#39; 프로젝트 케이스는 메신저에서 불쾌함을 피하려는 사람들을 돕기위한 최선의 방법을 결정하려는 여정의 한 단계였습니다. 이 단계에서 &#39;사람&#39;을 우리 업무의 가운데에 두고, 매일같이 계속해서 웰빙을 위해 디자인하고, 학습하고 배울 기회를 가져 영광이었습니다.</p>
<hr>
<p>번역 후기 )</p>
<p>옛날에 번역해둔 페이스북 프로덕트 디자인 팀의 아티클을, 자료 공유 차원으로 벨로그에 업로드합니다. 
방법론에 대한 구체적인 제시안들은 그대로 살리되, 문체가 번역체스러운 부분들에 구어식 표현과 의역을 더해 자연스럽게 번역하고자 했습니다. 
번역 오류나 방법론에 대한 질문을 댓글로 남겨주시면 환영합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nextjs13 App dir에서 페이지 이탈 방지 모달을 만들고 route.events 대체하기]]></title>
            <link>https://velog.io/@naro-kim/Nextjs13-App-dir%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9D%B4%ED%83%88-%EB%B0%A9%EC%A7%80-%EB%AA%A8%EB%8B%AC%EC%9D%84-%EB%A7%8C%EB%93%A4%EA%B3%A0-route.events-%EB%8C%80%EC%B2%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@naro-kim/Nextjs13-App-dir%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9D%B4%ED%83%88-%EB%B0%A9%EC%A7%80-%EB%AA%A8%EB%8B%AC%EC%9D%84-%EB%A7%8C%EB%93%A4%EA%B3%A0-route.events-%EB%8C%80%EC%B2%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 10 Aug 2023 15:57:43 GMT</pubDate>
            <description><![CDATA[<h2 id="개발-상황">개발 상황</h2>
<p>현재 동아리에서 내가 맡은 파트는 먹팟의 생성과 수정 페이지이다. 프론트는 Nextjs13 app directory를 사용하고 있고 생성/수정 페이지는 react-hook-form과 zod, zustand를 사용하고 있다. 그래서 만드는 UI는 아래와 같은 페이지다. 총 2step으로 나뉘어있고, 첫 스텝에서 화면상 &#39;&lt;-&#39;모양의 뒤로가기 버튼을 누르면 <code>router.back()</code>을 일으키도록 했다. 이 과정에서 작성한 내용이 있다면 페이지 이탈을 막는 모달을 띄우도록 구현해야 했다.</p>
<h3 id="디자인과-요구사항">디자인과 요구사항</h3>
<blockquote>
<h4 id="요청-사항">요청 사항</h4>
<p>먹팟 글 작성/수정 페이지</p>
<ul>
<li>모달 떠야하는 상황<ul>
<li>내용 입력 후 1단계에서 ← 버튼 누른 경우</li>
<li>내용 입력 후 새로 고침 누른 경우 (내용 입력하지 않고 뒤로가기 누른 경우 별도의 모달 없이 목록페이지로 이동됨)</li>
</ul>
</li>
</ul>
</blockquote>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8c87c62b-be71-4331-ba29-f7b41d10e239/image.png" alt="이탈방지 모달 디자인"></p>
<ul>
<li>디자인 요구사항으로 전달받은 내용은 위와 같았다. 브라우저에서 유저가 새로고침과 뒤로가기를 누른 경우 페이지 이탈 방지 모달을 띄워야 하는데, <code>Nextjs13 App directory</code>에선 이게 엄청나게 까다로워졌다. </li>
</ul>
<h2 id="nextjs-13-app-directory-때문에-발생한-문제">Next.js 13 App directory 때문에 발생한 문제?</h2>
<p>먼저, 지금 프로젝트에서는 Nextjs 13 App directory를 사용하고 있다!</p>
<ul>
<li><p><code>Nextjs12</code>에선 <code>router.events</code>에서 페이지 이탈을 쉽게 컨트롤 할 수 있었지만, <code>Nextjs13의 App directory</code>에선 <code>router.events</code>가 사라졌기 때문에 브라우저 상에서 발생하는 router 이벤트에  개발자가 유연하게 대응할 수 없게 되었다. (이와중에 Page directory에서는 router를 next/Router에서 import해 사용할 수 있다고 공식문서에 적혀있다.)</p>
</li>
<li><p>next/navigation의 {useRouter}에서는 <code>router.events</code>가 삭제되었고 이에 따라 <code>router.events</code>에 존재하는 <code>beforePopState</code> 를 사용할 수 없게 되었다. 이런 연유로 새로고침 시 모달 이벤트를 freeze하는 것도, default message custom에도 어려움을 겪어 UI 상의 페이지 이탈 액션에 대해서만 모달 오픈을 적용해야 했다. 
<a href="https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#step-5-migrating-routing-hooks">app-router-migration 관련 공식문서 내용</a></p>
</li>
<li><p><del>1 ) useRouter app dir suspense로 감싸기 (실패)</del></p>
</li>
<li><p><del>2 ) 모든 버튼에 방지 모달 띄우는 액션 걸기 (일부 성공) ….</del></p>
<ul>
<li>첨부한 레딧 글로 볼 수 있듯이, Nextjs 13 app directory 버전에선 navigation events를 abort  할 수 없다..
  <a href="https://www.reddit.com/r/nextjs/comments/13kwcax/the_app_router_is_not_productionready_yet/">The app router is not production-ready yet</a>  <br/></li>
<li>완전히 이 문제를 해결하려면 nextjs12로 내려서 <code>router.events</code> 혹은 <code>router.beforePopState</code>를 쓰거나 직접 js로 window.onbefroeunload로 에러 케이스별로 관리하거나 addListner로 웹에서 일어날 수 있는 모든 네비게이션 이벤트를 감지해야 할 것 같다. 아래 링크는 낮은 버전에서 route change event를 다루는 방법으로 <code>router.events</code>로 편안하게 해결한다.
  <a href="https://blog.logrocket.com/next-js-routechangestart-router-events/#before-history-change-event">Nextjs 12로 해결하기 / Understanding Next.js routeChangeStart and router events - LogRocket Blog</a> <br/>

</li>
</ul>
</li>
</ul>
<h2 id="그래도-해결해보자">그래도 해결해보자!</h2>
<ul>
<li>최대한 페이지 이탈 방지 모달을 Nextjs버전 다운그레이드 없이 제작하기 위해 아래 Gist 문서를 참조했다. 자세한 구현 방법은 아래에서 작성할 예정이다. 
<a href="https://gist.github.com/run4w4y/329f14be5e0574bef0be81e79074b398">Attempt at getting an alternative to router events in Next.js 13 with app router</a></li>
</ul>
<ul>
<li>UI내 버튼을 이용한 페이지 이동이나, 직접 사용자가 링크를 입력해 떠나는 페이지 이동에는 모달을 띄우는 것은 가능했다. 주어진 Nextjs13 App dir 환경에서,<code>RouteChangeProvider</code>을 이용해서 UI상에 존재하는 모든 버튼 동작에 발생하는 event들을 ContextAPI를 통해 state를 관리했다.</li>
</ul>
<ul>
<li>크롬이나 사파리 등등,, 브라우저 탭의 새로고침 버튼 뒤로가기 버튼 등에선 기본 브라우저 모달을 사용할 수 밖에 없었지만 우리 프로젝트 UI내 버튼과 링크에선 모달 컴포넌트를 사용할 수 있었다.</li>
</ul>
<h2 id="try-페이지-이탈-방지하기">Try! 페이지 이탈 방지하기</h2>
<p><a href="https://gist.github.com/run4w4y/329f14be5e0574bef0be81e79074b398">run4w4y/useLeaveConfirmation.tsx</a>
위 링크의 퍼블릭 Gist 작성자에게 무한한 감사를! 진짜 스택오버플로, 레딧, Nextjs 이슈를 다 뒤져도 관련 자료가 별로 없었는데, 이탈 방지와 관련한 단어를 검색어로 때려넣으면서 찾았다..</p>
<p>그러던 도중에 발견한 귀하디 귀한 자료.. app directory에서 <code>router.events</code>가 사라지자 모든 <code>a</code> 태그 이벤트를 체크하는 코드를 짠 사람이 나타났다!</p>
<p>사실 전체를 이해하지 못하긴 했지만 이 분의 코드를 짧게 설명하면 ContextAPI를 활용해서, 브라우저에서 Route Change context를 관리하는 <code>RouteChangeProvider</code>를 만들었다. </p>
<p>freeze request 스택안에 popstate가 발생할 경우 state를 잠시 보관한다.</p>
<h3 id="routechangeprovider">RouteChangeProvider</h3>
<pre><code class="language-tsx">// FreezeRequestContext를 선언
const FreezeRequestsContext = React.createContext&lt;FreezeRequestsContextValue&gt;({
  freezeRequests: [], 
  setFreezeRequests: () =&gt; {},
});

export const useFreezeRequestsContext = () =&gt; {
  const { freezeRequests, setFreezeRequests } = useContext(FreezeRequestsContext);

  return {
    freezeRequests,
    // url 이동 요청을 context에 추가
    request: (sourceId: string) =&gt; {
      setFreezeRequests([...freezeRequests, sourceId]);
    },
    // url 이동 요청을 삭제
    revoke: (sourceId: string) =&gt; {
      setFreezeRequests(freezeRequests.filter((x) =&gt; x !== sourceId));
    },
  };
};</code></pre>
<p>그리고 <code>CustomEvent</code>를 통해 dispatch detail에 targetUrl을 담고 다른 함수들에서 상황에 맞게 url을 사용할 수 있다. 
<a href="https://ko.javascript.info/dispatch-events">참고) Modern Javascript Tutorial - 커스텀 이벤트 디스패치 </a></p>
<pre><code class="language-tsx">...
export const triggerRouteChangeStartEvent = (targetUrl: string): void =&gt; {
  const ev = new CustomEvent(&#39;routeChangeStartEvent&#39;, { detail: { targetUrl } });
  if (!isServer) window.dispatchEvent(ev);
};
...
//(중략)
...
    window.addEventListener(
      &#39;routeChangeStartEvent&#39;,
      (ev) =&gt; {
        callbacks.onRouteChangeStart &amp;&amp; callbacks.onRouteChangeStart(ev.detail.targetUrl);
      },
      { signal: abortController.signal },
    );
...</code></pre>
<p>또, <code>AbortController</code>를 이용해서 페이지 fetch를 제어한다. 동시에 이전에 추가한 이벤트 핸들러와 콜백 함수들을 시그널을 이용해서 제거할 수 있다. 이 코드에선 아래 예시처럼 작성해 a태그 클릭 시 이벤트 핸들러 시그널을 관리할 수 있다.
<a href="https://velog.io/@sehyunny/abort-controller-is-your-friend">참고) AbortController는 당신의 친구입니다</a></p>
<pre><code class="language-tsx">// AbortController를 사용한 일부 코드
...
  useEffect(() =&gt; {
    const abortController = new AbortController();

    const handleAnchorClick = (event: MouseEvent | ForceAnchorClickEvent) =&gt; {
      const target = event.currentTarget as HTMLAnchorElement;
      const isFrozen = freezeRequests.length !== 0;
      if (isFrozen &amp;&amp; !(event as ForceAnchorClickEvent).isForceAnchorClickEvent) {
        event.preventDefault();
        event.stopPropagation();
        window.addEventListener(
          &#39;routeChangeConfirmationEvent&#39;,
          (ev) =&gt; {
            if (ev.detail.targetUrl === target.href) {
              const forceClickEvent = createForceClickEvent(event);
              target.dispatchEvent(forceClickEvent);
            }
          },
          { signal: abortController.signal },
        );

        triggerBeforeRouteChangeEvent(target.href);
        return;
      }
...</code></pre>
<p>그 다음으로, <code>MutationObserver</code>로 DOM tree의 변경까지 감지한다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/MutationObserver">참고) Mdn web docs &gt; MutataionObserver</a></p>
<p>그리고 <code>pushState</code>의 <code>apply()</code>와 <code>getPrototypeOf()</code>를 Proxy 객체인 <code>pushStateProxy</code>를 써서 커스텀했다. </p>
<p>아무튼 대략 이해한건 여기까지고.. 
Provider의 전체 코드는 아래와 같다.</p>
<pre><code class="language-tsx">&#39;use client&#39;; //클라이언트 렌더링이어야 함

import React, { useContext, useEffect, useRef, useState } from &#39;react&#39;;
import { nanoid } from &#39;nanoid&#39;; 
// 브라우저 변경을 확인하기 위해 useRouteChangeEvent 훅에서 nanoid를 레퍼로 사용한다.

type HistoryURL = string | URL | null | undefined;

// targetUrl은 Freeze 이후 이동할 url
type RouteChangeStartEvent = CustomEvent&lt;{ targetUrl: string }&gt;;
type RouteChangeEndEvent = CustomEvent&lt;{ targetUrl: HistoryURL }&gt;;
type ForceAnchorClickEvent = MouseEvent &amp; { isForceAnchorClickEvent: true };

declare global {
  interface WindowEventMap {
    beforeRouteChangeEvent: RouteChangeStartEvent;
    routeChangeConfirmationEvent: RouteChangeStartEvent;
    routeChangeStartEvent: RouteChangeStartEvent;
    routeChangeEndEvent: RouteChangeEndEvent;
  }
}

// url 이동 요청을 freeze
interface FreezeRequestsContextValue {
  freezeRequests: string[];
  setFreezeRequests: React.Dispatch&lt;React.SetStateAction&lt;string[]&gt;&gt;;
}

// server 컴포넌트인지 확인?
const isServer = typeof window === &#39;undefined&#39;;

// FreezeRequestContext를 선언
const FreezeRequestsContext = React.createContext&lt;FreezeRequestsContextValue&gt;({
  freezeRequests: [], 
  setFreezeRequests: () =&gt; {},
});

export const useFreezeRequestsContext = () =&gt; {
  const { freezeRequests, setFreezeRequests } = useContext(FreezeRequestsContext);

  return {
    freezeRequests,
    // url 이동 요청을 context에 추가
    request: (sourceId: string) =&gt; {
      setFreezeRequests([...freezeRequests, sourceId]);
    },
    // url 이동 요청을 삭제
    revoke: (sourceId: string) =&gt; {
      setFreezeRequests(freezeRequests.filter((x) =&gt; x !== sourceId));
    },
  };
};

type PushStateInput = [data: unknown, unused: string, url: HistoryURL];

//customEvent 설정
export const triggerRouteChangeStartEvent = (targetUrl: string): void =&gt; {
  const ev = new CustomEvent(&#39;routeChangeStartEvent&#39;, { detail: { targetUrl } });
  if (!isServer) window.dispatchEvent(ev);
};

export const triggerRouteChangeEndEvent = (targetUrl: HistoryURL): void =&gt; {
  const ev = new CustomEvent(&#39;routeChangeEndEvent&#39;, { detail: { targetUrl } });
  if (!isServer) window.dispatchEvent(ev);
};

export const triggerBeforeRouteChangeEvent = (targetUrl: string): void =&gt; {
  const ev = new CustomEvent(&#39;beforeRouteChangeEvent&#39;, { detail: { targetUrl } });
  if (!isServer) window.dispatchEvent(ev);
};

export const triggerRouteChangeConfirmationEvent = (targetUrl: string): void =&gt; {
  const ev = new CustomEvent(&#39;routeChangeConfirmationEvent&#39;, { detail: { targetUrl } });
  if (!isServer) window.dispatchEvent(ev);
};

const createForceClickEvent = (event: MouseEvent): ForceAnchorClickEvent =&gt; {
  const res = new MouseEvent(&#39;click&#39;, event) as ForceAnchorClickEvent;
  res.isForceAnchorClickEvent = true;
  return res;
};

export const RouteChangesProvider: React.FC&lt;{ children: React.ReactNode }&gt; = ({ children }) =&gt; {
  const [freezeRequests, setFreezeRequests] = useState&lt;string[]&gt;([]);

  useEffect(() =&gt; {
    const abortController = new AbortController(); //요청 취소 컨트롤러

    const handleAnchorClick = (event: MouseEvent | ForceAnchorClickEvent) =&gt; {
      const target = event.currentTarget as HTMLAnchorElement;
      const isFrozen = freezeRequests.length !== 0;
      if (isFrozen &amp;&amp; !(event as ForceAnchorClickEvent).isForceAnchorClickEvent) {
        event.preventDefault();
        event.stopPropagation();
        window.addEventListener(
          &#39;routeChangeConfirmationEvent&#39;,
          (ev) =&gt; {
            if (ev.detail.targetUrl === target.href) {
              const forceClickEvent = createForceClickEvent(event);
              target.dispatchEvent(forceClickEvent);
            }
          },
          { signal: abortController.signal },
        );

        triggerBeforeRouteChangeEvent(target.href);
        return;
      }

      triggerRouteChangeStartEvent(target.href);
    };

    const handleAnchors = (anchors: NodeListOf&lt;HTMLAnchorElement&gt;) =&gt; {
      anchors.forEach((a) =&gt; {
        a.addEventListener(&#39;click&#39;, handleAnchorClick, { signal: abortController.signal, capture: true });
      });
    };

    const handleMutation: MutationCallback = (mutationList) =&gt; {
      mutationList.forEach((record) =&gt; {
        if (record.type === &#39;childList&#39; &amp;&amp; record.target instanceof HTMLElement) {
          const anchors: NodeListOf&lt;HTMLAnchorElement&gt; = record.target.querySelectorAll(&#39;a[href]&#39;);
          handleAnchors(anchors);
        }
      });
    };

    const anchors: NodeListOf&lt;HTMLAnchorElement&gt; = document.querySelectorAll(&#39;a[href]&#39;);
    handleAnchors(anchors);

    const mutationObserver = new MutationObserver(handleMutation);

    mutationObserver.observe(document, { childList: true, subtree: true });

    const pushStateProxy = new Proxy(window.history.pushState, {
      apply: (target, thisArg, argArray: PushStateInput) =&gt; {
        triggerRouteChangeEndEvent(argArray[2]);
        return target.apply(thisArg, argArray);
      },
      getPrototypeOf: (target) =&gt; {
        return target;
      },
    });

    window.history.pushState = pushStateProxy;

    return () =&gt; {
      mutationObserver.disconnect();
      abortController.abort();
      window.history.pushState = Object.getPrototypeOf(pushStateProxy);
    };
  }, [freezeRequests]);

  return (
    &lt;FreezeRequestsContext.Provider value={{ freezeRequests, setFreezeRequests }}&gt;
      {children}
    &lt;/FreezeRequestsContext.Provider&gt;
  );
};

interface RouteChangeCallbacks {
  onBeforeRouteChange?: (target: string) =&gt; boolean; // if `false` prevents a route change until `allowRouteChange` is called
  onRouteChangeStart?: (target: string) =&gt; void;
  onRouteChangeComplete?: (target: HistoryURL) =&gt; void;
}

const useRouteChangeEvents = (callbacks: RouteChangeCallbacks) =&gt; {
  const id = useRef(nanoid());
  const { request, revoke } = useFreezeRequestsContext();
  const [confrimationTarget, setConfirmationTarget] = useState&lt;string | null&gt;(null);

  useEffect(() =&gt; {
    request(id.current);
    return () =&gt; revoke(id.current);
  }, []);

  useEffect(() =&gt; {
    const abortController = new AbortController();

    window.addEventListener(
      &#39;beforeRouteChangeEvent&#39;,
      (ev) =&gt; {
        const { targetUrl } = ev.detail;
        const shouldProceed = callbacks.onBeforeRouteChange &amp;&amp; callbacks.onBeforeRouteChange(targetUrl);
        if (shouldProceed) {
          triggerRouteChangeConfirmationEvent(targetUrl);
        } else {
          setConfirmationTarget(targetUrl);
        }
      },
      { signal: abortController.signal },
    );

    window.addEventListener(
      &#39;routeChangeEndEvent&#39;,
      (ev) =&gt; {
        callbacks.onRouteChangeComplete &amp;&amp; callbacks.onRouteChangeComplete(ev.detail.targetUrl);
      },
      { signal: abortController.signal },
    );

    window.addEventListener(
      &#39;routeChangeStartEvent&#39;,
      (ev) =&gt; {
        callbacks.onRouteChangeStart &amp;&amp; callbacks.onRouteChangeStart(ev.detail.targetUrl);
      },
      { signal: abortController.signal },
    );

    return () =&gt; {
      abortController.abort();
    };
  }, [callbacks]);

  return {
    allowRouteChange: () =&gt; {
      if (!confrimationTarget) {
        console.warn(&#39;allowRouteChange called for no specified confirmation target&#39;);
        return;
      }
      triggerRouteChangeConfirmationEvent(confrimationTarget);
    },
  };
};

export default useRouteChangeEvents;</code></pre>
<p>진짜진짜 짱길다.. 이 Provider에 더해서, 커스텀한 Router 훅과 Modal을 띄우는 훅, Funnel에서의 사용법까지 차례대로 더 적어 보겠다.</p>
<h3 id="usecustomrouter">useCustomRouter</h3>
<pre><code class="language-tsx">// useCustomRouter.ts
import { useEffect, useRef, useState } from &#39;react&#39;;
import { useRouter as usePrimitiveRouter } from &#39;next/navigation&#39;;
import {
  triggerBeforeRouteChangeEvent,
  triggerRouteChangeStartEvent,
  useFreezeRequestsContext,
} from &#39;@/app/write/contexts/RouteChangeProvider&#39;;

interface NavigateOptions {
  scroll?: boolean;
}

type AppRouterInstance = ReturnType&lt;typeof usePrimitiveRouter&gt;;

const createRouterProxy = (router: AppRouterInstance, isFrozen: boolean, signal?: AbortSignal) =&gt;
  new Proxy(router, {
    get: (target, prop, receiver) =&gt; {
      if (prop === &#39;push&#39;) {
        return (href: string, options?: NavigateOptions) =&gt; {
          const resolvePush = () =&gt; {
            triggerRouteChangeStartEvent(href);
            Reflect.apply(target.push, this, [href, options]);
          };

          if (isFrozen) {
            window.addEventListener(
              &#39;routeChangeConfirmationEvent&#39;,
              (ev) =&gt; {
                if (ev.detail.targetUrl === href) resolvePush();
              },
              { signal },
            );

            triggerBeforeRouteChangeEvent(href);
            return;
          }
          resolvePush();
        };
      }

      return Reflect.get(target, prop, receiver);
    },
  });

const useCustomRouter = (): AppRouterInstance =&gt; {
  const router = usePrimitiveRouter();
  const { freezeRequests } = useFreezeRequestsContext();
  const abortControllerRef = useRef(new AbortController());
  const [routerProxy, setRouterProxy] = useState&lt;AppRouterInstance&gt;(
    createRouterProxy(router, freezeRequests.length !== 0, abortControllerRef.current.signal),
  );

  useEffect(() =&gt; {
    return () =&gt; abortControllerRef.current.abort();
  }, []);

  useEffect(() =&gt; {
    abortControllerRef.current.abort();
    const abortController = new AbortController();

    setRouterProxy(createRouterProxy(router, freezeRequests.length !== 0, abortController.signal));

    return () =&gt; abortController.abort();
  }, [router, freezeRequests]);

  return routerProxy;
};

export default useCustomRouter;</code></pre>
<h3 id="useleavemodaltsx">useLeaveModal.tsx</h3>
<p>그리고, 위의 Router Change Event들이 발생할 때, 기존에는 useOverlay 훅을 사용해 Modal을 띄웠는데, FreezeModal 컴포넌트와 함께 Form 페이지 내에서 간편하게 사용하기 위해 useLeaveModal을 작성했다.
수정 페이지와 생성 페이지 모두 공통으로 페이지 이탈 방지를 설정해야 했고 FormStore를 사용하고 있어 이 방식을 채택했다.</p>
<pre><code class="language-tsx">import { useCallback } from &#39;react&#39;;
import { useRouter } from &#39;next/navigation&#39;;
import { useOverlay } from &#39;@/hooks&#39;;
import useRouteChangeEvents from &#39;@/app/write/contexts/RouteChangeProvider&#39;;
import { FreezeModal } from &#39;@/app/write/components&#39;;
import useFormStore from &#39;@/app/write/store/useFormStore&#39;;

const useLeaveModal = (shouldPreventRouteChange: boolean) =&gt; {
  const [openModal, closeModal] = useOverlay();
  const { reset } = useFormStore();
  const router = useRouter();

  useRouteChangeEvents({
    onBeforeRouteChange: useCallback(
      (targetUrl: string) =&gt; {
        if (shouldPreventRouteChange) {
          openModal(
            &lt;FreezeModal
              onClose={closeModal} // 페이지 이탈 취소
              onClick={() =&gt; { // 페이지 이탈
                if (reset) reset();
                router.push(targetUrl);
                closeModal();
              }}
            /&gt;,
          );
          return false;
        }
        return true;
      },
      [closeModal, openModal, reset, router, shouldPreventRouteChange],
    ),
  });
};

export default useLeaveModal;</code></pre>
<h3 id="freezemodaltsx">FreezeModal.tsx</h3>
<p>다음으로 페이지 이탈이 일어날때마다 마운트될 overlay modal 컴포넌트인 &#39;FreezeModal&#39;은 아래처럼 작성했다. </p>
<pre><code class="language-tsx">import { Button, Modal, Typography } from &#39;@/components&#39;;

interface Props {
  onClose: () =&gt; void;
  onClick?: () =&gt; void;
  content?: string;
  footer?: string | string[];
}

const FreezeModal = ({
  content,
  footer,
  onClose,
  onClick,
}: Props) =&gt; {
  return (
    &lt;Modal onClose={onClose} size=&quot;small&quot;&gt;
      &lt;Modal.Header type=&quot;info&quot; title=&quot;안내&quot; /&gt;
      &lt;Modal.Content size=&quot;small&quot;&gt; 
        {content}
      &lt;/Modal.Content&gt;
      &lt;Modal.Footer type=&quot;horizontal&quot;&gt; 
        {footer}
      &lt;/Modal.Footer&gt;
    &lt;/Modal&gt;
  );
};

export default FreezeModal;</code></pre>
<h3 id="layouttsx">layout.tsx</h3>
<p>그리고, root layout파일에 Provider를 추가한다. 우리는 Provider를 여러가지 쓰고 있어 Nested 형태가 되었다. react의 portal이란게 모달 overlay에 쓰인다고도 하는데, 아직 공부해보진 않았지만 이 Nest의 향연을 개선할 방법이 있을지도?</p>
<pre><code class="language-tsx">...
 export default function RootLayout({ children }: { children: React.ReactNode }){
  return(
    ...
    &lt;QueryProvider&gt;
          &lt;ProfileProvider&gt;
            &lt;RouteChangesProvider&gt;
              &lt;OverlayProvider&gt;{children}&lt;/OverlayProvider&gt;
            &lt;/RouteChangesProvider&gt;
          &lt;/ProfileProvider&gt;
        &lt;/QueryProvider&gt;
    ...
    )
...</code></pre>
<h3 id="구현된-구조를-간략화한-다이어그램">구현된 구조를 간략화한 다이어그램</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/8fe41472-d6fe-498d-9b5a-23acfe46594a/image.png" alt=""></p>
<h3 id="한계점">한계점</h3>
<p>페이지 마다 useLeaveModal 훅을 걸어준 뒤, 새로고침과 뒤로가기 이벤트(onbeforeunload)에 대해서도 동일하게 적용하려 하였으나 DOM tree내의 a태그가 아닌 유저 브라우저의 이벤트는 제어할 수 없었다..
(추가 + <code>beforeunload</code> 이벤트에 의해 띄워지는 새로고침 방지 모달은 커스텀이 불가능하다.
<a href="https://developer.chrome.com/blog/chrome-51-deprecations/#remove-custom-messages-in-onbeforeunload-dialogs">Chrome release note</a>에 따르면, 다수의 브라우저에서 custom message 제어 기능도 빠졌다.)
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event">beforeunload - mdn web docs</a></p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/62700780-5d64-4fd6-ae8f-b195b30f47c5/image.png" alt=""></p>
</blockquote>
<p>유저 사용성을 위해, 새로고침 이벤트를 freeze시키는 것과 모달 커스텀을 할 수 없게 정한 듯하다. 그래서 기본 새로고침 모달을 어떻게 커스텀할지는 아직까지 해결 방법을 찾지 못했다. 사실 우리 서비스에선 아무 페이지에서나 새로고침 이벤트를 막는게 아니라, 여러 스텝으로 나뉜 Form에 기입한 정보를 날려버리지 않기 위한 의도였지만 어찌되었건 브라우저 표준에 어긋나지 않는 웹 사용성이 우선인 거 같다.</p>
<p>지금은 페이지 디자인 상 존재하는 뒤로가기 버튼에 따로 onClick시 FreezModal만 띄워주도록 처리했다. </p>
<p>마지막으로, 뒤로가기 버튼에는 토스의 useFunnel을 참고해 app directory에서 &#39;또&#39; 없어진 router의 shallow copy에 대한 대응 방법도 있는데 다음에 정리해보려 한다. </p>
<h3 id="페이지-이탈방지의-결과물">페이지 이탈방지의 결과물!</h3>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/7151ed40-1948-413b-87e6-3c2210ffc68b/image.png" alt=""></p>
<h2 id="결론">결론</h2>
<blockquote>
<ul>
<li><code>NextJs13 App directory</code>는 상용 서비스에 적용하고자 한다면 조금 있다 사용하자!!!!!</li>
</ul>
</blockquote>
<p>페이지 이탈 방지 모달을 만들면서 가장 먼저 느낀 점은, 새로운 기술 스택을 선정하는 것은 양날의 검이라는 점이다. </p>
<p>App directory가 폴더명으로 웹 구조를 관리하기도 하고 layout이나 fetch등에서 강점을 보이지만, 각종 이슈들로 단점도 확실히 갖고 있다.</p>
<p>사이드 프로젝트라면 괜찮지만 큰 볼륨의 서비스엔  진입 장벽도 높고 데이터도 적다. 충분한 유저풀과 솔루션이 쌓이고 나서 사용해야할 거 같다.</p>
<p>다행히 중간에 router.events를 대체할 커스텀훅 구현해놓은 자료를 찾게되서 적용하겠다고 window의 갖가지 event와 contextAPI를 다시 돌아보느라 몇일이나 걸린건지... 원작자분은 정말 대단한 사람인 거 같다.... </p>
<pre><code class="language-tsx">  useEffect(() =&gt; {
    window.addEventListener(&#39;beforeunload&#39;, handleBeforeunload);
    Router.events.on(&#39;routeChangeStart&#39;, routeChangeStart);
    Router.events.on(&#39;routeChangeError&#39;, routerChangeError);

    return () =&gt; {
      window.removeEventListener(&#39;beforeunload&#39;, handleBeforeunload);
      Router.events.off(&#39;routeChangeStart&#39;, routeChangeStart);
      Router.events.off(&#39;routeChangeError&#39;, routerChangeError);
    };
  }, [confirmed, hasChanged]);</code></pre>
<p>결과적으로, Next.js13 Page directory였거나 하위 버전이었다면 짧디 짧은 위의 코드로 모르고 지나쳤을 페이지 이탈 방지의 원리를 ContextProvider에 CustomRouter, CustomEvent, dispatch, proxy.. 방대한 양의 코드를 살펴봐야 했다. 그래도 최대한 이해하려 애쓰며 성장하긴 했다!</p>
<p>또 한편으론, App directory의 layout등으로 pageProps를 빙빙 감싸주지 않아도 되는 간소함이 진짜 편했지만 이 부분도 이전 버전과 달라지면서 팀원끼리 꼭 상의해서 프로젝트를 진행해야 한다. 이번의 routers.events가 없어진게 갑작스레 큰 이슈를 불러와서, 프로젝트 막바지 완성 주간에 예상치 못한 공수 시간이 추가 소요됐다. 그 외에도 SSR에 의한 MediaQuery 이슈도 있고, 다양한 이슈들을 만났었는데 차차 정리해야지~</p>
<p>긴 글을 읽어주셔서 감사합니다. 혹시나 페이지 이탈 방지 모달을 만들기 위한 다른 아이디어가 있다면 조언해주시면 감사하겠습니다..!</p>
<p>추가 ++ 2023.08.15) Gist에 이 훅을 공유해준 @run4w4y가, npm package로 위 코드들을 만들어주었다..!
참고용으로 깃헙 링크를 남겨둘테니, 페이지 이탈 방지와 큰 고민을 갖고 있던 분들은 살펴보면 큰 도움이 될 거 같다!</p>
<p><a href="https://github.com/run4w4y/nextjs-router-events">원작자 github url</a>
<a href="https://www.npmjs.com/package/nextjs-router-events">원작자 분께서 배포한 npm 패키지 링크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-Hook-Form과 Zod를 사용한 Input 컴포넌트 만들기 (1)]]></title>
            <link>https://velog.io/@naro-kim/React-Hook-Form%EA%B3%BC-Zod%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-Input-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-1</link>
            <guid>https://velog.io/@naro-kim/React-Hook-Form%EA%B3%BC-Zod%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-Input-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-1</guid>
            <pubDate>Wed, 09 Aug 2023 07:21:44 GMT</pubDate>
            <description><![CDATA[<h2 id="👍-쉽지않았던-input-공통-컴포넌트-개발-회고">👍 쉽지않았던 Input 공통 컴포넌트 개발 회고</h2>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/c49f26f5-3366-43e0-8bdd-347ad1dcc8f4/image.png" alt=""></p>
<h3 id="input-공통-컴포넌트-개발을-맡다">Input 공통 컴포넌트 개발을 맡다</h3>
<p>가장 먼저, 이번 먹팟 프로젝트엔 프론트엔드로 3명이 참여했기 때문에 각자 어디까지 담당할 지를 나누어야 했다. </p>
<p>그래서 회의를 통해 사용할 기술 스택을 선정한 이후엔 다른 프론트엔드 파트 팀원들이 셋업을 준비해주었고 나는 글로벌 스타일 파일을 만들었다.</p>
<p>어느 정도 프로젝트 셋업이 끝난 후에 공통 인풋 개발을 위해 역할을 분배했다. 
<img src="https://velog.velcdn.com/images/naro-kim/post/8cea6008-e316-4d51-a0f7-07bcef1b42a9/image.png" alt=""></p>
<p>여기서 어려웠던 점은 어디까지가 공통 컴포넌트의 영역인지 구분하는 것이었는데, 그래서 공통 컴포넌트인 Input은 가장 먼저 Figma 디자인 시스템 파일에 주어진 status별 Input 디스플레이를 구현하는 것을 목표로 했다. </p>
<p>Input의 요구 사항은 다음과 같았다.</p>
<ul>
<li>Title이 존재할 것</li>
<li>Title의 HelperText를  입력할 수 있을 것</li>
<li>placeholder를 지정할 수 있을 것</li>
<li>submit에서 validation을 통과하지 못하면 border color를 error/red500으로 변화시킬 것</li>
<li>2가지 사이즈에 대응할 것</li>
<li>505px width의 인풋은 타이틀이 왼쪽으로 정렬된 형태. label이 아니다</li>
</ul>
<p>디자인 시안을 보고 가장 먼저 하나의 인풋 컴포넌트에서 모든 사이즈의 인풋에 대응하려 했다.</p>
<p>그 목표를 달성하기 위해 3가지 사이즈로 대응하는 방법을 적용했다. 그런데 개발 도중에 vanilla-extract에 미숙한 탓에 난관에 부딪혔다. </p>
<ul>
<li>email 입력 폼의 small 사이즈 적용 시, children flex를 정리하기 위한 추가 css 코드가 필요하다</li>
<li>medium 사이즈와 large 사이즈 인풋의 label typhograpy와 div flex 구조를 각 타입에 알맞게 변경시키려면 조건부로 css를 변경할 코드가 필요하다.</li>
</ul>
<p>이 두 문제는, vanilla-extract의 recipe variants를 사용해 해결할 수 있었다. variants를 설정하면, 컴포넌트의 Props로 size, color등을 넘겨주어 조건부 css를 사용할 수 있다.</p>
<p>하지만 이후에 Input 컴포넌트가 Calendar나 Map등 다른 컴포넌트들과 복합적으로 쓰이게 되면서 size를 결정 짓는 width는 상속으로 처리했다.</p>
<p>대신, Input이 사용되는 case별로 type variants를 나누어 아래처럼 코드를 작성했다.</p>
<pre><code class="language-css">export const inputBase = recipe({
  base: { 
    minWidth: &#39;100%&#39;,
    height: &#39;56px&#39;,
    fontSize: fontSize.md,
    padding: space[&#39;lg&#39;],
    paddingRight: space[&#39;4xl&#39;],
    backgroundColor: color.grey50,
    color: color.hint,
    borderRadius: borderRadius.md,
    border: `1px solid ${color.grey100}`,
    selectors: {
      &#39;&amp;::-webkit-search-cancel-button&#39;: {
        display: &#39;none&#39;,
      },
      &#39;&amp;:not(:focus)&#39;: {
        color: color.primary,
      },
      &#39;&amp;:not(:disabled):focus&#39;: {
        color: color.primary,
        border: `1px solid ${color.primary500}`,
      },
    },
  },
  variants: {
    type: {
      textArea: {
        alignItems: &#39;flex-start&#39;,
        padding: &#39;16px&#39;,
        gap: &#39;8px&#39;,
        height: &#39;299px&#39;,
      },
      title: {
        fontSize: fontSize.xl,
        fontWeight: fontWeight.semibold,
      },
      search: {
        paddingLeft: space[&#39;5xl&#39;],
      },
      password: {},
    },
  },
});
</code></pre>
<h3 id="✅-이외의-문제들">✅ 이외의 문제들</h3>
<ul>
<li>inner clear button을 어떻게 만들까? <pre><code>- watch API,  setValue API를 활용해 각각의 인풋의 값에 대해 clear function이 작동하도록 해야한다. </code></pre>→ ⚠️ 이 경우, onBlur에 clearButton이 보이지 않게 처리하려면 결국 useState로 Input의 state를 관리해야 하기 때문에 리렌더링이 일어나게 된다. 
→ 🆗 따라서 디자이너분과 협의하에 onBlur에 clearButton이 사라지지 않고 그대로 있도록 했다.</li>
</ul>
<ul>
<li>error status field가 존재할 경우, 버튼 하단에 error label 추가하긴 했다. 
→ ⚠️ Object.keys(errors).length 조건으로 렌더링하는게 맞나? errors에 좀 더 똑똑하게 접근하고파 ..
→ 🆗 위 조건을 없애고<code>useFormContext</code>를 사용해서 onSubmit시 필드 에러를 체크해 사용자가 변화를 인지할 수 있게끔 만들었다.</li>
</ul>
<pre><code class="language-tsx">const InputErrorMessage = ({ name, showError = true }: errorProps) =&gt; {
  const { formState } = useFormContext();
  const errorMessage = showError &amp;&amp; (formState.errors[name]?.message as string);

  return (
      &lt;Typography color=&quot;red500&quot; variant=&quot;label5&quot; as=&quot;p&quot;&gt;
        {errorMessage}
      &lt;/Typography&gt;
  );
};</code></pre>
<p>며칠 동안 컴포넌트를 작성하며 이런 저런 문제들을 발견하고, 초기 설계 접근 방향이 잘못되었다 생각해 다른 방식의 인풋 컴포넌트를 만들 방법을 구상했다.</p>
<p>그 중, 눈에 들어온 <a href="https://velog.io/@syoo970/react-hook-form%EA%B3%BC-MUI%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EC%9E%88%EB%8A%94-Input-%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0TypeScript">블로그 아티클</a>. </p>
<p>드롭 다운은 팀원이 직접 구현한 공통 컴포넌트를 쓰겠지만 캘린더는 외부 라이브러리 사용이 예정되어있으므로 controller를 이용한 type별 인풋 컴포넌트 분리가 적절할거라 생각했다.</p>
<p>그래서 위 아티클을 참고해, 한 일주일~이주일 정도는 컴포넌트를 수정했다.</p>
<p>결국 나중 가선, 공통 ControllerInput 컴포넌트가 아니라 각각의 캘린더, 드롭다운을 Controller에 말아서 사용하는 페이지 단일 컴포넌트로 쓰긴 했지만 위에서 삽질하며 많은 도움을 얻을 수 있었다.</p>
<p>다음 시리즈에 이어서, 어떻게 최종까지 Input 컴포넌트를 개선시켜나갔는지 계속 적어보려 한다.</p>
<p><img src="https://velog.velcdn.com/images/naro-kim/post/6c964ce8-bb17-420c-a65f-942863acf6f2/image.png" alt=""></p>
<hr>
<h4 id="controller를-도입한-인풋-공통-컴포넌트-개발-참고-자료">Controller를 도입한 인풋 공통 컴포넌트 개발 참고 자료</h4>
<p><a href="https://2mojurmoyang.tistory.com/221">공부 | React Hook Form 정리</a></p>
<p><a href="https://www.pumpkiinbell.com/blog/react/performant-form">재렌더링 비용이 적은 폼 컴포넌트 만들기  |  Blog by pumpkiinbell</a></p>
<p><a href="https://velog.io/@syoo970/react-hook-form%EA%B3%BC-MUI%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EC%9E%88%EB%8A%94-Input-%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0TypeScript">react-hook-form과 MUI를 사용한 재사용성 있는 Input 공통 컴포넌트 만들기(TypeScript)</a></p>
<hr>
<table>
<thead>
<tr>
<th>🛠 기술 스택</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>Framework</td>
<td><img src="https://img.shields.io/badge/Next.js-13.4.12_(App_dir)-353b48?logo=next.js" alt="Nextjs">  <img src="https://img.shields.io/badge/React-18.2.0-00a8ff?logo=react" alt="React"></td>
</tr>
<tr>
<td>Styling</td>
<td><img src="https://img.shields.io/badge/Vanilla_Extract-1.11.0-8EF5E0?logo=vanilla-extract" alt="vanilla-extract"></td>
</tr>
<tr>
<td>State Management</td>
<td><img src="https://img.shields.io/badge/tanstack/ReactQuery-4.29.7-FF4154?logo=react-query" alt="tanstack/React Query"> <img src="https://img.shields.io/badge/Zustand-4.3.8-blue" alt="Zustand"></td>
</tr>
<tr>
<td>Testing</td>
<td><img src="https://img.shields.io/badge/tanstack/vitest-0.31.1-6E9F18?logo=vitest" alt="vitest"> <img src="https://img.shields.io/badge/testing_library/react-14.0.0-E33332?logo=TestingLibrary" alt="@testing-library"></td>
</tr>
<tr>
<td>+</td>
<td><img src="https://img.shields.io/badge/React_Hook_Form-7.43.9-EC5990?logo=reacthookform" alt="react-hook-form"> <img src="https://img.shields.io/badge/Zod-3.21.4-3E67B1?logo=zod" alt="Prettier">  <img src="https://img.shields.io/badge/typescript-5.1.3-3178C6?logo=typescript" alt="typescript"> <img src="https://img.shields.io/badge/Yarn_berry-3.6.0-2C8EBB?logo=yarn" alt="yarn berry"></td>
</tr>
</tbody></table>
]]></description>
        </item>
    </channel>
</rss>