<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ony_.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 29 Jul 2025 08:34:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ony_.log</title>
            <url>https://velog.velcdn.com/images/ony_/profile/1c60f891-4bd1-4cb4-bd14-58319f080686/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ony_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ony_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[구름톤 14기 해커톤 후기(9oormthon in JEJU)]]></title>
            <link>https://velog.io/@ony_/%EA%B5%AC%EB%A6%84%ED%86%A4-14%EA%B8%B0-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9B%84%EA%B8%B09oormthon-in-JEJU</link>
            <guid>https://velog.io/@ony_/%EA%B5%AC%EB%A6%84%ED%86%A4-14%EA%B8%B0-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9B%84%EA%B8%B09oormthon-in-JEJU</guid>
            <pubDate>Tue, 29 Jul 2025 08:34:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/c8120c60-4267-48a3-93a0-a928d3080a8c/image.png" alt=""></p>
<h2 id="👋-지원-계기">👋 지원 계기</h2>
<p>나는 퇴사한 지 한 달 된 프론트엔드 개발자로
그동안 쌓인 피로를 풀고 여유를 갖는 것도 좋지만, 한편으로는 “다시 한 번 몰입해서 개발해보고 싶다”는 갈증도 있었다. 그래서 눈에 띈 게 바로 구름톤이었다.</p>
<p>해커톤이라는 형식 자체가 처음이었지만, 단기간에 집중해서 무언가를 만들어내는 환경, 그리고 개발자로서의 좋은 기억을 가지고 있는 제주도에서 다양한 사람들과 함께한다는 점이 큰 매력으로 다가왔다.
딱 지금 나에게 필요한 경험이라 생각하고 고민 없이 지원서를 썼다.</p>
<h3 id="14기의-지원서의-문항은-다음과-같았다">14기의 지원서의 문항은 다음과 같았다.</h3>
<ol>
<li><p>프론트엔드 개발자용 질문
1-1. 사용 가능한 프레임워크/라이브러리(React, Vue 등)를 나열하고, 그중 가장 자신 있는 기술을 하나 골라 이유를 설명해주세요.(300자)
1-2. 상태 관리를 어떤 방식으로 해봤고, 왜 그 방식을 선택했는지 설명해주세요.(300자)
1-3. API 호출 및 데이터 바인딩을 어떻게 처리하는지 예시와 함께 설명해주세요. (300자)
1-4. 웹팩, 바벨 등의 빌드 도구에 대한 이해도와 사용 경험이 있나요? (300자)</p>
</li>
<li><p>구름톤 참여 동기를 작성해주세요. (300자)</p>
</li>
<li><p>구름톤을 통해 어떤 부분의 &#39;성장&#39;을 기대하고 있는지, 있다면 작성해주세요 (300자)</p>
</li>
<li><p>협업 시 지키는 본인만의 규칙이나 전략은 무엇인가요? 갈등 상황이 발생했을 때의 해결 방법도 함께 알려주세요. (500자)</p>
</li>
<li><p>진행하신 프로젝트 중 하나를 선택하여 설명해 주시고, 그 경험으로 인해 얻을 수 있었던 결과를 작성해주세요. (500자)</p>
</li>
</ol>
<p>항목이 생각보다 많아 당황했지만 1번 문항은 짧게 현 상태에 대해 작성하면 됐고, 나머지 항목들도 해커톤과 같이 짧은 기간에 몰입해서 완성을 목표로 한다는 것을 이때까지의 사례와 함께 작성했다. 
다른 기수는 어떤 주제를 하고싶냐는 항목도 있었던 것 같지만 이번에는 체크박스로 여러 항목 중에 고르는 형식이었고 필자는교육과 의료 관련 항목을 선택했다.
처음 지원한 해커톤이었고, 경쟁률이 높다고 들어서 기대를 안하고 있었는데 운 좋게 합격하여 참가할 수 있었다!😄</p>
<h3 id="🗓️-일정표">🗓️ 일정표</h3>
<p><img src="https://velog.velcdn.com/images/ony_/post/2b62d546-0b2c-45e7-a5b2-8a2059b26968/image.png" alt="">
14기 구름톤의 상세일정 이전 기수까지는 카카오도 함께했던 것 같은데 이번에는 오직 구름에서만 주최를 해서 카카오 현직자 특강이 빠졌고 그덕에 비어파티 전에도 해커톤 작업을 할 시간이 더 주워졌었다.</p>
<h2 id="📣-day-1--주제-발표-및-특강">📣 DAY 1 : 주제 발표 및 특강</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/0ee3421b-7289-42fe-9c5d-9d416f6fdc8c/image.jpg" alt="">구름스퀘어에 도착해서 구름스퀘어에 도착하여 위와 같은 키트를 받고, 미리 짜주신 조별로 앉아 <strong>아이스브레이킹</strong> 시간을 가졌다. 아이스브레킹 조원들과 제공해준 도시락을 점심으로 먹고 오후에는 <strong>self PR</strong>시간을 가졌다. 떨려서 자기소개페이지의 것들을 그냥 읽었던 기억이 나는데, 가벼운 개인적인 이야기보다는 지금 생각해보면 <strong>좀 더 기술적으로나 기획자, 디자이너분들에게 어필될만한 이야기</strong>를 더 하면 좋았겠다라는 생각이 들었다.</p>
<p><img src="https://velog.velcdn.com/images/ony_/post/22f90d6d-6dfe-447e-b140-3930176b45c6/image.PNG" alt=""> 이후 기획자 특강과 이어서 <strong>주제가 발표</strong>되었고, 이번 기수의 주제는 <strong>&quot;스마트 관광을 통한 지속가능한 지역 활성화&quot;</strong>였다. 사실 나는 경쟁률이 높은데 내가 합격했던 이유가 선택했던 주제가 맞아서 그런가하며 교육이 주제일까 싶어 관련 아이디어를 생각하기도 했는데 나의 완전한 착각이었다.ㅎㅎ</p>
<p><img src="https://velog.velcdn.com/images/ony_/post/fa23dfae-70f9-438f-8408-44810939ea6b/image.jpg" alt="">잠시 쉬는 시간을 가지고 FE개발자와 디자이너는 <strong>구름의 디자인시스템인 vapor</strong>에 대한 강의를 들었다. 프로덕트 디자이너인 쥬디님과 FE 개발자인 노아님이 발표해주셨고 웹접근성 부분과 UX라이팅 가이드가 기억에 남는다. 
필자도 전 직장에서 웹 접근성이 중요한 프로젝트를 진행하면서 관심히 가서 질문했는데 구름에서 접근성을 고려하게 된 계기도 같은 프로젝트라서 내적 친밀감을 느낄 수 있었다. UX라이팅은 따로 UXUI디자이너가 없었던 전 직장에서 같은 문제에 대해서 기획팀에 말씀드려 통일시키는 경험이 있었는데 디자이너가 주도적으로 만든 가이드라인이 있다면 정말 좋겠다는 생각을 했다..</p>
<p>특강 이후 단체사진을 찍고 저녁은 흑돼지와 이재모피자로 나눠져서 먹으러 갔는데, 부산사람으로서 흑돼지를 먹으러갔다. 20명이 넘는 인원이었지만 장소특성상 같은 테이블에 앉은 분들이랑만 이야기를 할 수 있어서 다른 테이블분들이랑 이야기를 하지못한게 살짝 아쉬웠다.</p>
<p>숙소로 돌아가서 주제에 대한 1page 아이데이션을 해서 제출하고 3,4시쯤 잠에 들었다.
<br/></p>
<h2 id="🍺-day-2--팀빌딩-및-비어파티">🍺 DAY 2 : 팀빌딩 및 비어파티</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/8b3f84ed-328f-4b86-9833-47dd3ac59c04/image.png" alt="">둘째날의 첫 일정은 <strong>원페이지 프레젠테이션</strong>! 다들 생각하는 아이디어가 비슷할 것 같아, 근래 이슈가 된 산불과 반려견순찰대에서 아이디어를 얻어서 발표했다. 발표 이후 팀 빌딩 시간을 가졌고, 생각보다 팀빌딩 전에 정해진 팀이 많아보였고, 어쩌다 팀을 꾸리게 되었다. 내 아이디어에 관심을 가지신 디자이너 한 분이 같이 하고싶다고 말씀해주셨는데 함께하지 못해 너무 아쉬웠다.(다른 해커톤이나 사이드프젝에서 만나게 된다면 꼬옥 함께하고 싶어요😭😭)</p>
<p><img src="https://velog.velcdn.com/images/ony_/post/193bc205-7edf-4c87-9fbc-fc52ebfba70f/image.jpg" alt="">프로젝트 팀원들과 제공해준 도시락 점심을 먹고 <strong>플레이스 캠프로 이동</strong>했다. 1인 1숙소를 배정받고 들어갔는데, 화장실이 통유리에 신발을 따로 벗는 곳이 없어 살짝 당황했지만, 어짜피 해커톤하느라 잠깐 씻고 눈붙이러 오는 수준이라고 들어서 괜찮았다. 이틀동안 잠깐 눈붙일 때마다 침대와 침구가 너무 좋다고 생각했는데 다른 참가자분이 시몬스 침대라고 말씀해주셔서 아 그래서 잘잤구나 생각했다.</p>
<br/>
짐을 풀고 나와 비어파티 전까지 아이데이션을 진행했고, 아이디어는 기획자분과 디자이너분의 의견을 위주로 가는 것이 맞는거라고 여겨져서 해당 방향으로 진행했다.

<p><img src="https://velog.velcdn.com/images/ony_/post/ce24a13f-9fa4-448c-bfc3-051784e77890/image.jpg" alt="">비어파티는 처음에는 랜덤으로 짜진 조로 이야기 나누고, 이후에는 직군별로 나눠 이야기했다. 다른 FE개발자분들과 서로 회사의 이야기나 프로젝트, 테스트코드 등에 대해 이야기하면서 앞으로 사이드 프로젝트를 열심히 해야겠다고 생각했고 많은 것을 느끼고 배울 수 있었다. 이야기 나누며 이전에 멀티레포에서 모노레포로 변환하려다가 너무 무거워서 다시 멀티로 돌아왔던 경험이 있는데, 상대적으로 레거시가 꽤 많은 무거운 프로젝트라서 그랬음을 깨달을 수 있었다. 
어느정도 이야기를 나누다가 돌아가서 vapor 및 기본적인 프로젝트 셋팅을 진행했고 새벽 4시 정도에 자러들어갔다. 
<br/></p>
<h2 id="🧑💻-day-3-4--1시간-자면서-개발하기">🧑‍💻 DAY 3, 4 : 1시간 자면서 개발하기</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/012a7e5f-f2a4-419b-830c-7d42eb9dcbb3/image.jpg" alt="">셋째날 아침이 밝았고 전날 디자이너분이 작업해주신 것을 바탕으로 프론트 작업에 들어갔다. 점심은 플레이스캠프 인근의 식당에서 고등어구이와 제육볶음을 먹었다. 어쩌다보니 다른 팀원분들과 한 테이블에서 먹었는데, 전날 랜덤조에서 함께한 분도 있고 처음 이야기하는 분도 있어서 다양하게 이야기할 수 있어서 좋았고, 또 고등어가 맛있었다!!ㅋㅋ</p>
<p><img src="https://velog.velcdn.com/images/ony_/post/71203957-89df-459e-a4c8-f50a6f1d479e/image.jpg" alt=""> 열심히 작업하다가 숙소가 성산일출봉 앞인데 한번도 못보고가면 아쉬울 것 같아 팀원들과 함께 산책도 하고 성산일출봉도 보고왔다! 날이 좋진 않아 사진은 잘 안나왔지만, 제주도 여름이 얼마나 뜨거운지 알다보니 아쉽진 않았다! 시원하게 잘걷다가 돌아와서 작업을 이어갔다.
<img src="https://velog.velcdn.com/images/ony_/post/b6979849-05fb-4248-a34a-911b84101792/image.JPG" alt="">보통 구름에서 저녁으로 치킨을 제공해주신다고 후기들에서 봤는데 이번에는 살짝 변주를 줘서 피자로 시켜주신 것 같다. 감사합니다 🤍
<img src="https://velog.velcdn.com/images/ony_/post/e02475ae-0d55-4253-a443-e50580f60d04/image.jpg" alt="">완성은 꼭 해야한다는 생각과 우리팀 디자이너분이 챙겨주신 글루콤 덕분에 1시간만 자고도 개발할 수 있었다. 오전 11시 제출 마감이었는데 와이파이가 좋지 않아 업로드하는데 너무 오래걸려 결국 감점 1점을 받아서 아쉬웠다. 제출 이후 잠시 팀원들과 옆 카페에서 이야기도 나누고 발표 흐름을 짰고, 최종 발표시간이 되었다. </p>
<h2 id="📝-배운-점">📝 배운 점</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/22c80a7c-de76-4c0a-bf2f-baa5ef0732fd/image.jpg" alt="">아쉬운 점이 없다고 하면 거짓말이지만 후회는 없다.
가장 크게 느낀 점은 <strong>아이디어의 방향성과 문제 정의가 그 무엇보다 중요하다</strong>는 걸 배웠다.
또한 짧은 시간 안에 기획, 디자인, 개발, 발표까지 진행하면서 각 직무의 중요성을 피부로 느낄 수 있었다. 어느 역할 하나라도 빠지면 프로젝트가 제대로 굴러가지 않기에, 모든 팀원이 협력해야 한다는 걸 깨달았다.</p>
<p>근래 이렇게 <strong>몰입</strong>해본 적이 없었고 이렇게 밤을 새본 적도 없었다. 구름톤이 아니었다면 이렇게 단기간에 mvp라도 프로젝트 하나를 완성할 수 없었을 것이다.</p>
<p><strong>또 함께 했던 사람들과 인연</strong>을 이어갈 수 있어 좋다.
구름톤에서 만난 사람들은 정말 다들 열정이 넘쳤고, 그 분위기에 나도 자연스럽게 푹 빠질 수 있었다. 
특히 나는 제주도에 온 김에 하루 더 머무르기로 했는데, 돌아가는 버스에서 옆자리에 앉은 한솔님과 이런저런 대화를 나누다가 다음날 만나기로 약속을 잡았다. 다음 날 만나 밥도 먹고 카페에서 한참 이야기를 나누었고, 덕분에 해커톤 이후의 하루까지도 따뜻하고 즐겁게 마무리할 수 있었다.</p>
<p>이 경험을 통해 앞으로는 사이드 프로젝트도 더 적극적으로 작업하고, 기회가 된다면 다른 해커톤이나 연합 동아리에도 도전해보고 싶다는 다짐도 하게 되었다. <br/><br/></p>
<h2 id="💭-마무리하며">💭 마무리하며</h2>
<p>개발 외에도 정말 많은 걸 느낄 수 있었던 시간이었다.
몰입의 즐거움, 협업의 에너지, 그리고 좋은 사람들과의 인연까지.</p>
<p>앞으로가 더 기대된다.
언젠가 또 해커톤에 참여하게 된다면, 이번보다 더 잘할 수 있겠지.
그리고 그때도 다시 좋은 사람들과, 재미있고 진지한 시간을 보낼 수 있길.</p>
<br/>

<blockquote>
<h4 id="번외로-다음-기수분들께-꿀팁을-드리자면">번외로 다음 기수분들께 꿀팁을 드리자면</h4>
</blockquote>
<ol>
<li>밤샘 작업에 <strong>글루콤(비타민)</strong>은 큰 도움이 됩니다.</li>
<li>숙소 체크아웃 시간과 제출마감 시간이 같으니 <strong>미리 체크아웃</strong>을 하고 제출 작업에 들어가면 좋을 것 같습니다. (작업하다 짐을 싸러가면 정신이 없어요😱)</li>
<li>제출마감 <strong>20분 전에는 업로드</strong> 시작하기. (pdf와 영상파일을 올리기에 와이파이가 좋지 못해요🥹)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[매일 npm을 사용하지만 자세히는 알지 못하는 개발자라면 <npm Deep Dive>]]></title>
            <link>https://velog.io/@ony_/%EB%A7%A4%EC%9D%BC-npm%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%A7%8C-%EC%9E%90%EC%84%B8%ED%9E%88%EB%8A%94-%EC%95%8C%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-npm-Deep-Dive</link>
            <guid>https://velog.io/@ony_/%EB%A7%A4%EC%9D%BC-npm%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%A7%8C-%EC%9E%90%EC%84%B8%ED%9E%88%EB%8A%94-%EC%95%8C%EC%A7%80-%EB%AA%BB%ED%95%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-npm-Deep-Dive</guid>
            <pubDate>Wed, 23 Jul 2025 08:28:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/5983ea2d-6891-4ddf-b914-b42f37ec4777/image.png" alt=""></p>
<p>매일 npm을 사용하는 개발자라면 읽을 가치는 충분한 책,  <a href="https://product.kyobobook.co.kr/detail/S000216669881">npm Deep Dive</a>을 완독하고 이에 대해 이야기해보려고 한다.</p>
<h3 id="읽게-된-배경">읽게 된 배경</h3>
<p><img src="https://velog.velcdn.com/images/ony_/post/51e7a100-eb48-40f3-89f4-6a1a9064fd7d/image.png" alt="">어쩌다 해당 책의 완독 챌린지를 모집하는 공지를 보았는데 아주 찔리는 문구가 있었다. &#39;언젠가는 제대로 공부해야지..&#39; 너무 공감가는 문구에 챌린지에 참여했고, 혼자 읽었다면 완독에 어려움이 있을 수도 있었지만 한달 동안 함께한 챌린저들이 있어서 읽어낼 수 있었다.</p>
<p><br/><br/></p>
<h3 id="간단한-책-소개">간단한 책 소개</h3>
<p><strong>npm의 역사</strong>로 시작해서 
<strong>npm, yarn, pnpm의 비교 분석, CommonJS와 ESModule의 역사와 차이점, 트랜스파일러와 폴리필</strong> 등을 다룬다.
또한 끝에는 <strong>npm 패키지와 CLI 도구 및 모노레포를 이용한 프로젝트</strong> 예제가 있어서 패키지를 직접 다뤄보며 이해할 수 있었다. </p>
<p><br/><br/></p>
<h3 id="책을-읽으며-가장-인상-깊었던-내용이나-새롭게-알게-된-개념">책을 읽으며 가장 인상 깊었던 내용이나 새롭게 알게 된 개념</h3>
<p><strong>package.json )</strong>
‘들어가며’ 장에서 언급하는 peer update --force 를 사용하는 개발자 중 1명으로 살짝 찔렸었다. package.json는 dependencies와 script를 파악할 때 보는 것이 대부분이었는데, 앞으로는 package.json의 다른 영역도 자주 보게될 것 같다. </p>
<p><strong>유령의존성 )</strong>
&#39;유령의존성(phantom dependency)&#39;이란 평탄화 작업으로 인해 실제 의존성으로 선언하지 않은 패키지가 실행 가능해지는 문제를 말한다. 사실 이 개념을 알기 전에 이전 직장에서 멀티레포에서 모노레포로 전환하는 과정에서 해당 경험을 한 적이 있는데, &quot;아, 이래서 그랬구나!&quot;하고 깨달았다. 덕분에 다음 프로젝트에서는 npm보다는 pnpm을 사용하려고 한다.</p>
<p><strong>CommonJS와 ESModule )</strong>
부끄럽지만 단순히 서버단은 CommonJs 브라우저단은 ESModule이라고 어렴풋이 생각하고 있었는데, 이는 단순히 문법 차이만이 아니라 모듈 시스템 전체의 차이였다.
<strong>CommonJS</strong> - require()가 런타임에서야 사용될 모듈이 <strong>동적으로</strong> 결정되어, 빌드 시점에는 실제로 어떤 모듈을 사용하는지 몰라 트리쉐이킹 최적화가 어렵다.
<strong>ESModule</strong> - mport, export문을 통해 모듈을 <strong>정적으로</strong> 로드하고,  모듈 파싱 -&gt; 모듈 인스턴스화 -&gt; 모듈 평가 순으로 진행된다.
<br/><br/></p>
<p><img src="https://velog.velcdn.com/images/ony_/post/f679d53b-165c-412c-ae41-9f8a4e966df3/image.png" alt="">_약 1000쪽에 달하는 저서다보니 5권으로 분철해서 들고다니며 읽었다 _</p>
<br/>

<h3 id="챌린지-기간-동안-느낀-점이나-나에게-의미-있었던-순간">챌린지 기간 동안 느낀 점이나, 나에게 의미 있었던 순간</h3>
<p>챌린지를 진행하면서 책에서도 물론 아주 많은 정보를 얻어갔지만 다른 챌린저분들의 인사이트를 읽으면서 때마다 알아가는 꿀팁이나 견해도 좋았다. 특히 기억나는 건 pnpm에서 npm을 막는 꿀팁과 _&#39;정답은 없다. CJS는 현실이고, ESM은 방향이다. 지금은 둘 다 함께 가는 과도기다.&#39;_라고 하신 견해가 기억에 남는다.</p>
<p>또한 중간에 구름톤에 참가할 일정이 생겨 디자인 시스템을 다룬 모노레포 프로젝트를 다룬 8장을 해커톤 이후에 읽게 되었는데, 구름톤에서 구름의 디자인시스템인 vapor를 사용하면서 흥미가 생긴 부분을 어느정도 해소할 수 있었다. 앞으로 디자인 시스템을 작업할 일이 생기면 모노레포 방식으로 제작하며 해당 파트를 사전처럼 옆에 두고 함께 할 것 같다.
<br/></p>
<h3 id="추천하는-이유">추천하는 이유</h3>
<p>단순히 이론뿐아니라 <strong>실제 npm 생태계에서 있었던 사건이나 사례</strong>를 바탕으로 이야기가 전개된다는 점이 재밌고, <strong>의문을 가질말한 부분도 짚어서 작가분의 의견을 제시</strong>해줘서 이해할 수 있었다.<br/> 방대한 양으로 모든 내용을 체득하기는 어려워도, 해당 책을 읽음으로써 npm에 대한 이해도를 올라가는 건 확실하다. 프런트엔드 개발자라면 한번 쯤은 읽어보는 것을 추천한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 Chart.js로 반응형 그래프에 2개 데이터 그리기 (with TypeScript)]]></title>
            <link>https://velog.io/@ony_/React%EC%97%90%EC%84%9C-Chart.js%EB%A1%9C-%EB%B0%98%EC%9D%91%ED%98%95-%EA%B7%B8%EB%9E%98%ED%94%84%EC%97%90-2%EA%B0%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B7%B8%EB%A6%AC%EA%B8%B0-with-TypeScript</link>
            <guid>https://velog.io/@ony_/React%EC%97%90%EC%84%9C-Chart.js%EB%A1%9C-%EB%B0%98%EC%9D%91%ED%98%95-%EA%B7%B8%EB%9E%98%ED%94%84%EC%97%90-2%EA%B0%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B7%B8%EB%A6%AC%EA%B8%B0-with-TypeScript</guid>
            <pubDate>Sun, 27 Apr 2025 07:19:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/8daa603a-4298-41d8-bd9f-cba2a55006fc/image.png" alt="결과 이미지"></p>
<p>Chart.js를 사용해서 반응형으로 한 차트에 막대 그래프 2개를 렌더링 시켜보겠습니다.</p>
<h1 id="설치하기">설치하기</h1>
<p>react에서 사용하려면 react-chartjs-2도 설치가 필요합니다.</p>
<pre><code>npm install react-chartjs-2 chart.js</code></pre><h1 id="기본적인-요소">기본적인 요소</h1>
<ol>
<li>차트 <strong>컴포넌트</strong>
1-1. 차트 컴포넌트에 props로 넘길 <strong>data</strong>
1-2. 차트 컴포넌트에 props로 넘길 <strong>options</strong></li>
<li><strong>register</strong> </li>
</ol>
<br/>


<h2 id="1-차트-컴포넌트">1. 차트 컴포넌트</h2>
<p>기본적으로 차트가 그려질 컴포넌트.
컴포넌트로 선언할 수 있는 것들은 <a href="https://react-chartjs-2.js.org/components/">react-chartjs-2 공식사이트</a>에서 확인할 수 있습니다. </p>
<pre><code class="language-tsx">&lt;Bar data={data} options={options} /&gt;
&lt;Doughnut data={data} options={options} /&gt; 
&lt;Chart type={...} options={...} data={...} /&gt; //type을 넘겨주면 Chart 컴포넌트로 모든 타입의 차트사용 가능(multiple 타입 시 주로 사용)</code></pre>
<br/>


<h3 id="1-1-data객체">1-1. data객체</h3>
<pre><code class="language-tsx">import { ChartData } from &#39;chart.js&#39;;

const data: ChartData&lt;&#39;bar&#39;&gt; = {
      labels: [&#39;1번&#39;, &#39;2번&#39;, &#39;3번&#39;], // 해당 바에 해당하는 라벨
      datasets: [
        {
          label: &#39;정답률&#39;,
          type: &#39;bar&#39;,
          data: [80, 20, 40],
          backgroundColor: param.correctRate?.map((data) =&gt; 
             if (data === min &amp;&amp; data === max) return REPORT_COLORS.FIVE_PRIMARY;
          return REPORT_COLORS.THREE_PRIMARY), //조건부로 다른 색상으로 랜더링 가능
          // barThickness: 20, //두께 픽셀로 지정
          barPercentage: 0.5, //두께를 비율로 지정 최대 1
          borderRadius: 20, // 모퉁이 둥글리기
          borderSkipped: &#39;bottom&#39;, // 하단 양끝은 둥글리기 생략
        },
        {
          label: &#39;평균시간&#39;,
          type: &#39;bar&#39;, //type이 두가지가 다를 경우 다르게 선언할 수 있음 (생략가능)
          data: [100, 210, 350],
          backgroundColor: REPORT_COLORS.FIVE_PRIMARY,
          yAxisID: &#39;yRight&#39;, //두가지 바가 있을 경우 y축ID 선언 option 설정 시 사용됨.
          barPercentage: 0.5,
          borderRadius: 20,
          borderSkipped: &#39;bottom&#39;,
        },
      ],
    });</code></pre>
<p><strong>labels</strong> - 각 막대(bar)의 이름입니다.
<strong>datasets</strong> - 이 차트에 그릴 데이터 집합으로, &#39;정답률&#39;, &#39;평균시간&#39; 데이터를 선언했습니다. </p>
<ul>
<li>props로 넘길 data객체의 type을 <strong><code>ChartData&lt;&#39;bar&#39;&gt;</code></strong>로 선언하여 타입 안전성을 높여줍니다. 또한 데이터 수정 시 많은 옵션 값들을 파악하기 용이합니다.</li>
<li>두 가지의 데이터가 같은 x축을 가지고, 다른 y축을 가졌으므로 option 선언을 위해 *<em><code>yAxisID: &#39;yRight&#39;</code> *</em>를 선언하였습니다. </li>
<li>barThickness로 px로 막대의 두께를 고정하고 싶었지만 그럴 경우 두 막대의 간격이 중간으로 딱붙어버리는 상황으로 간격을 조정하는 옵션이 없어서 어쩔 수 없이 barPercentage를 사용해서 스타일링 했습니다. (사실 그냥 그래프 오픈소스를 하나 만들어볼까 고민 중입니다)</li>
</ul>
<h3 id="1-2-option">1-2. option</h3>
<pre><code class="language-tsx">import { ChartOptions } from &#39;chart.js&#39;;

 const chartOptions: ChartOptions&lt;&#39;bar&#39;&gt; = {
    responsive: true, //반응형 처리
    maintainAspectRatio: false, //기본 비율(가로:세로 비율)을 유지 X 
    plugins: {
      legend: {
        display: false,
      },
    },
    scales: {
      x: {
        grid: {
          drawTicks: false,
        },
        ticks: {
          padding: 10,
          font: {
            size: 16,
            weight: &#39;bold&#39;,
            family: fontFamily[0],
          },
          color: REPORT_COLORS.BW_767676,
        },
      },
      y: {
        grid: {
          display: false,
        },
        min: 0,
        max: 100,
        ticks: {
          callback: function (value) {
            return value + &#39;%&#39;;
          },
          stepSize: 20,
          font: {
            size: 16,
            weight: 600,
            family: fontFamily[0],
          },
          color: REPORT_COLORS.BW_A3A3A3,
          align: &#39;start&#39;,
        },
      },
      yRight: {
        min: 0,
        max: 360, //최대 5분, 그외는 6분이상으로 처리
        position: &#39;right&#39;, // 오른쪽에 배치
        grid: { drawTicks: false },
        ticks: {
          padding: 10,
          callback: function (value) {
            return Math.round(Number(value) / 60) &lt; 6
              ? `${Math.round(Number(value) / 60)}m`
              : &#39;6m ↑&#39;; //초단위 데이터를 분으로 변경
          },
          stepSize: 60, //스텝당 60초씩 계산되도록
          font: {
            size: 16,
            weight: &#39;bold&#39;,
            family: fontFamily[0],
          },
          color: REPORT_COLORS.BW_A3A3A3,
          align: &#39;start&#39;,
        },
      },
    },
  };</code></pre>
<h4 id="반응형-처리">반응형 처리</h4>
<p>  <code>{
    responsive: true, 
    maintainAspectRatio: false, 
}</code>
 위의 두 옵션을 줘야 상단 부모에서 <strong>높이를 고정하고 width를 100%로 주면</strong> 좌우로 아무리 브라우저를 늘였다 줄였다 해도 width를 다채우는 그래프를 그릴 수 있습니다.
 maintainAspectRatio를 true로 줄 경우 브라우저를 줄였을때는 비율에 맞게 줄어들지만 <strong>다시 브라우저를 늘려도 크기가 같이 커지지않는 문제가 발생</strong>하므로 높이를 고정하고 좌우 반응형으로 구현했습니다. </p>
<ul>
<li><strong>scales (y, yRight)</strong>
상단에서 선언한 &#39;yRight&#39;축(평균시간)을  <code>position: &#39;right&#39;</code>옵션을 줘서 오른쪽에 배치시켰습니다. <code>align: &#39;start&#39;</code> 로  y축 눈금(tick) 레이블의 정렬 방향을 왼쪽으로 붙도록 정의했습니다. </li>
</ul>
<br/>

<h2 id="2register">2.register</h2>
<p>chart.js에서 필요한 것들을 import 해온 후 register를 해야 차트를 렌더링 할 수 있습니다.
(ChartJS.register(...)를 빼먹으면 차트가 아예 안 나와요.)</p>
<pre><code class="language-tsx">import {
  ChartData,
  Chart as ChartJS,
  ChartOptions,
  LineElement,
  Tooltip,
} from &#39;chart.js&#39;;
ChartJS.register(LineElement, Tooltip);</code></pre>
<p>필요한 것들을 register에 등록하지 않은 경우 차트가 렌더링되지 않으면 콘솔에러가 나오므로 확인 후 필요한 요소들을 추가해주시면 됩니다!</p>
<p>이러한 과정을 거치면 위 이미지에 해당하는 그래프를 만들 수 있습니다 🥰</p>
<hr>
<p>참고링크
<a href="https://www.chartjs.org/docs/latest/charts/bar.html">https://www.chartjs.org/docs/latest/charts/bar.html</a>
<a href="https://react-chartjs-2.js.org/components/">https://react-chartjs-2.js.org/components/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1년차 프론트엔드 개발자의 테스트코드 필요성 체감기]]></title>
            <link>https://velog.io/@ony_/%EC%8B%A0%EC%9E%85-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%ED%95%84%EC%9A%94%EC%84%B1-%EC%B2%B4%EA%B0%90%EA%B8%B0</link>
            <guid>https://velog.io/@ony_/%EC%8B%A0%EC%9E%85-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%ED%95%84%EC%9A%94%EC%84%B1-%EC%B2%B4%EA%B0%90%EA%B8%B0</guid>
            <pubDate>Tue, 31 Dec 2024 09:28:48 GMT</pubDate>
            <description><![CDATA[<p>필자는 1년차 프론트엔드 개발자로서 스타트업에 근무 중으로 극악의 스케줄이었던 정부과제가 마무리되고, 해당 프로젝트를 회고하고 리팩토링하면서 관심이 갔던 테스트코드에 대해서 이야기 해보려 한다.</p>
<br/>

<p>이때까지 자사서비스를 유지보수할 때는 사내 백엔드 개발자와 소통하며 작업했기때문에 DTO를 함께 정하고 그대로 작업하고, 개발서버를 붙이면서 작업을 해가는 과정이었다.</p>
<p>하지만 정부 과제를 하면서 두 곳의 타사의 API를 받아서 해당 정보를 사용할 일이 있었는데, 
마감 전 A사의 API의 가이드라인만 나왔을 때는 <code>msw</code>로 해당 API를 목킹하여 작업을 진행했고 추후 API를 연결하고 <code>msw</code>를 제거하면서 작업을 이어갔다.   </p>
<p>마감 이후에는 바쁜 일정이 끝나고, 그나마 여유로운 시기에 MUI 제거 후 직접 구현, 레거시 코드 수정 등의 리팩토링 작업을 하고 있었는데 이때 내 눈에 들어온 영상이 <a href="https://toss.tech/article/firesidechat_frontend_3">프론트엔드 개발에서의 테스트 코드</a>였다.
<img src="https://velog.velcdn.com/images/ony_/post/24bf9034-cfed-4df1-ba26-db983d61152b/image.png" alt=""></p>
<p>이 영상이 말하는 장점을 짧게 요약하자면</p>
<blockquote>
<ol>
<li>비지니스 로직을 쭉 짜고 Hot Module Reloading을 기다리고 넘어가는 테스크 사이클이 있을 경우 바로 확인 가능 →** 피드백 사이클이 짧아져 생산성이 높아진다.**</li>
<li><strong>비지니스 로직에만 집중할 수 있다. (격리한다)</strong><ul>
<li>외부 의존성이 복잡한 경우(외부 개발사의 서버가 안열려 있을 경우) 잘 목킹해두면 정말 검증하고 싶은 부분만 좁혀서 테스트 가능하다.</li>
</ul>
</li>
<li><strong>코드를 다 짜고 방어용으로 짜는 안정성(신뢰감)</strong><ul>
<li>라이브러리 버전을 올리거나, 리팩토링할 경우 (이전에는 되었던 로직이 되는지 안되는지 손으로 클릭해서 확인할 필요가 없다.)  </li>
<li>일종의 문서가 될 수 있음 (초기 의사 결정에 따라서 작성된 테스트코드이므로) </li>
</ul>
</li>
</ol>
</blockquote>
<p>필자도 이 영상을 보기전까지는 직접적으로 테스트코드의 필요성을 느낄 일도 없었고 현재 회사에서 작성하고 있지않기때문에 궁금해하고만 있었는데, 이 영상을 보고나서는 우리 회사에서의 문제점을 해결해주는데에 테스트코드가 필요하겠다는 생각을 가지게 되었다. 이후 인프런에서 <a href="https://www.inflearn.com/course/%EC%8B%A4%EB%AC%B4%EC%A0%81%EC%9A%A9-%ED%94%84%EB%9F%B0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-1%EB%B6%80/dashboard">테스트 코드 강의</a>를 보고 자사서비스에 브랜치를 파서 적용해보기도 했다.</p>
<br/>

<hr>
<p>그러던 중 클라이언트의 요청으로 이전에 했던 정부과제를 안정화하여 재배포해야할 상황이 생겼다.</p>
<p>우리 백엔드 서버와 통신하는 부분은 사내 백엔드 개발자와 함께 개발서버에서 작업을 하고 로컬에서 확인해 볼 수 있었지만, B사의 SSO 정보를 가져와서 사용하는 부분은 <strong>실서버에 올리지않고는 파악하기 어려운 부분</strong>이 있었다.  </p>
<p>말아서 올리고 오류가 발생하면 하나 고치고 다시 배포하고를 반복했다고 하셨다. 프론트의 경우 한번 말아서 올리는데만 2,30분이 걸리고, 새벽에만 실서버에 배포할 수 있었기때문에 다들 피로한 상태에서 작업하다보니 집중력이 떨어져서 더 이런 문제가 발생했던거 같다. </p>
<p>다음날 나를 포함한 다른 프론트 팀원도 해당 부분을 해결하려고 코드를 확인하였는데, 여기에 <strong>JEST를 사용해서 B사의 API를 목킹해서 데이터가 잘 들어오는지 파악할 수 있겠다</strong>는 생각이 들었고, 테스트 코드를 아래와 같이 작성했다.</p>
<pre><code class="language-tsx">  test(&#39;유효한 토큰이 있을 때 SSO 정보를 가져와 상태를 업데이트한다&#39;, async () =&gt; {
    (getCookie as jest.Mock).mockReturnValue(false); // 토큰이 없는 상태로 설정

    // window.location.href에 code가 포함된 URL을 설정하여 목킹
    delete window.location;
    window.location = {
      href: &#39;http://localhost?code=mockCode&#39;,
    } as any;

    //B사 API의 가짜 응답을 설정
    const mockTokenResponse = {
      data: {
        data: { access_token: &#39;mockAccessToken&#39; },
      },
    };
    const mockUserInfoResponse = {
      data: {
        data: {
          email: &#39;test@example.com&#39;,
          fullname: &#39;Test User&#39;,
          id: 1,
          locale: &#39;en&#39;,
          phone: &#39;123456789&#39;,
          profile_url: &#39;https://example.com/profile.jpg&#39;,
        },
      },
    };
    (axios.post as jest.Mock).mockResolvedValue(mockTokenResponse); // SSO의 mock 응답 설정
    (axios.get as jest.Mock).mockResolvedValue(mockUserInfoResponse); // uerInfo의 mock 응답 설정

    // Provider를 렌더링하고, Context.Consumer를 사용해 context 데이터 검증
    render(
      &lt;Provider&gt;
        &lt;Context.Consumer&gt;
          {(value) =&gt; (
            &lt;div&gt;
              {value?.email}, {value?.fullname}
            &lt;/div&gt;
          )}
        &lt;/Context.Consumer&gt;
      &lt;/Provider&gt;
    );

    // context가 업데이트된 후 올바른 사용자 정보가 화면에 표시되는지 확인
    await waitFor(() =&gt; {
      expect(screen.getByText(&#39;test@example.com, Test User&#39;)).toBeInTheDocument();
    });
  });</code></pre>
<p><br/><br/>
위 코드를 통해서 전날 벽 우리 팀원분들을 괴롭혔던 부분을 만날 수 있었다. <del>좀 더 빨리 작성했었더라면 좋았겠지만</del> 이 때의 짜릿함은 잊을 수 없다!!😛 
<img src="https://velog.velcdn.com/images/ony_/post/ac3c94fd-98ed-42a6-9ca1-b4618f715e7c/image.png" alt=""></p>
<p>이렇게 <strong>외부 개발사의 api를 사용하지만 <code>msw</code>로 별도로 목킹하기에는 규모 대비 비용이 더 들 때,</strong> 
잘 목킹하여 테스트코드를 써두면 비지니스 로직은 분리되므로 각각에 더 집중할 수 있다는 <strong>장점2</strong>를 확실히 체감할 수 있었다!!  </p>
<p>솔직히 필요성을 느끼지 못하면 공부할 마음도 잘 생기지않게 되는데 직접 회사에서 적용하면서 체감하다보니 큰 동기가 되었고, 프론트팀 코드리뷰시간에 해당 부분을 공유했을 때 다들 좋게 봐주셨고, 앞으로 외부 개발사와 일하는 경우에는 해당 부분만이라도 테스트코드를 작성하자는 분위기를 이끌어낼 수 있었다!🤗🤗</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[zustand 렌더링 최적화 - shallow 대신 useShallow 사용하기! ]]></title>
            <link>https://velog.io/@ony_/zustand-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94-shallow-%EB%8C%80%EC%8B%A0-useShallow-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ony_/zustand-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94-shallow-%EB%8C%80%EC%8B%A0-useShallow-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 17 Jan 2024 06:44:09 GMT</pubDate>
            <description><![CDATA[<p>이전 글에서 언급했던 것 처럼 전체 스토어를 구독하게 되면, 스토어에 담고 있는 모든 state가 변경될 때마다 구독중인 컴포넌트가 불필요하게 렌더링된다. </p>
<h3 id="필요한-state만-구독하는-방식">필요한 state만 구독하는 방식</h3>
<p>따라서 스토어 전체구독이 아닌, 필요한 state만 구독해서 사용해야한다.</p>
<pre><code class="language-tsx"> // 전체 스토어를 구독 --&gt; 불필요한 리렌더링 발생
const {bears} = useBearStore()
// 필요한 state만 구독 --&gt; 해당 state변경시에만 리렌더링 발생
const bears = useBearStore((state) =&gt; state.bears)</code></pre>
<br/>

<p>추가적으로 이때 zustand에서 제공되는 <strong><code>shallow</code></strong> 함수를 사용하면 해당 스테이트의 값이 이전값과 다른지 비교후 달라졌을 때만 렌더링을 시켜준다.</p>
<h3 id="기존의-shallow-사용방식">기존의 shallow 사용방식</h3>
<pre><code class="language-tsx">import shallow from &#39;zustand/shallow&#39;

// ⬇️ much better, because optimized
const { bears, fish } = useBearStore(
  (state) =&gt; ({ bears: state.bears, fish: state.fish }),
  shallow
)</code></pre>
<p>기존에는 위와 같이 store에서 값을 가져올 때, 2번째 인자로 비교함수로 shallow를 전달하는 방식을 사용했고, 구글링했을 때에도 가장 많이 나왔다. </p>
<p><strong>하지만 이는 아래 이미지에서 확인할 수 있듯이 2023년 10월부터 useShallow를 사용하도록 바뀌었다.
<a href="https://docs.pmnd.rs/zustand/guides/updating-state#flat-updates">공식사이트</a>에도 이에 대한 내용이 없어서 조금 헤멨는데 더 찾아봤더니 
공식사이트에는 업데이트되어있지 않고, <a href="https://github.com/pmndrs/zustand?tab=readme-ov-file#selecting-multiple-state-slices">Readme.md</a> 와 직접 코드를 확인해서 찾을 수 있었다!</strong>
<img src="https://velog.velcdn.com/images/ony_/post/6546f583-bacc-4dac-83a3-cf07ad42573c/image.png" alt=""></p>
<p>shallow 사용에 대해서 가장 도움된 블로그 글 <a href="https://hackids.tistory.com/151">Zustand 현명하게 사용하기 (불필요한 리렌더링 막기)</a>은 2023년 6월에 작성되어 기존의 <code>shallow</code>를 비교함수를 전달하는 방식을 직접 <code>useShallow</code>를 커스텀훅으로 만드셔서 한 줄로 짧게 사용하고 계시기도했는데, 개인적으로 프로젝트에서는 가독성이 좋은 업데이트된 공식의 사용방식을 사용했다. </p>
<h3 id="업데이트된-useshallow-사용방식">업데이트된 useShallow 사용방식</h3>
<pre><code class="language-tsx">import shallow from &#39;zustand/shallow&#39;

// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts, honey } = useBearStore(
  useShallow((state) =&gt; ({ nuts: state.nuts, honey: state.honey })),
)

// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(
  useShallow((state) =&gt; [state.nuts, state.honey]),
)

// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore(useShallow((state) =&gt; Object.keys(state.treats)))</code></pre>
<p>위 방식이 공식에서 제안하는 세가지 방식으로 state.nuts나 state.honey의 값이 바뀔 때만 해당 명령어가 있는 컴포넌트의 리렌더링이 발생한다.
나는 개인적으로 Array방식을 프로젝트에 사용했다.</p>
<p>다음 글에서는 zustand에서 다른 컴포넌트에서 state값이 바뀌었을 때 리렌더링을 최소화하면서 바로바로 값을 읽고 바꾸는 방법에 대해 다뤄보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PixelBeat version1.0.0 회고]]></title>
            <link>https://velog.io/@ony_/PixelBeat-version1.0.0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@ony_/PixelBeat-version1.0.0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 04 Jan 2024 11:43:42 GMT</pubDate>
            <description><![CDATA[<p>이번 프로젝트를 진행하면서 정말 많은 걸 배우고 성장한 기회였다. </p>
<h3 id="공식문서readme에-답이-있다-영어공부하자📝"><strong>공식문서,README에 답이 있다.. 영어공부하자!!📝</strong></h3>
<p>zustand, supabase, tailwind를 처음 사용하면서 물론 공식 문서를 확인하긴하지만 시간이 없기도하고 자세히 읽지 못하고 작업에 들어가기도 했다. 리팩토링을 위해 추가적인 공부를 하면서 zustand의 경우 공식문서에는 안내되어있지않지만 github의 Readme에만 존재하는 내용도 많다는 것을 확인할 수 있었다.
또한 이전에 v3로 사용 경험이 있었던 TanstackQuery도 v5로 작업하면서 useQuery에서 없어진 OnSuccess, 바뀐 변수명 등을 만났다. 처음에는 대체 왜 안되는지 모르고 찾느라 고생했는데 별내용아니겠지하고 넘긴 공식문서의 한 문단에서 OnSuccess가 사라진 것을 찾을 수 있었다.. 영어를 잘했다면 바로 알 수 있었겠지라는 생각이 들었던 경험이었다.🥲 (겸사겸사 오픽 응시한 작성자..)</p>
<h3 id="이제는-중복-코드가-눈에-걸리기-시작했다🤔"><strong>이제는 중복 코드가 눈에 걸리기 시작했다!🤔</strong></h3>
<p>이전 프로젝트에서는 내 작업 위주로 보기바빴는데, 다른 팀원이 작업한 것은 물론 내가 쓴 코드에서도 중복된 코드가 눈에 보여, 커스텀훅으로 만들어 사용한다던지 공통컴포넌트로 빼는 작업을 하게 되었다. 바쁘지만 공통으로 빼지않으면 나중에 더 힘들어지게 될거라고 여겨 고쳤고 실제로 그때 안뺐으면 고생했겠다라는 생각이 들었다.</p>
<h3 id="초기설계의-중요성-체감하기🛠️"><strong>초기설계의 중요성 체감하기🛠️</strong></h3>
<p>어디서 시니어와 주니어의 차이는 앞을 얼마나 바라보고 프로젝트를 설계하는지라는 글을 본 기억이 있다. 이번 프로젝트를 진행하면서 정말 깊게, 체감할 수 있었다. 시간도 짧고 일단 기능이 되도록하자!하는 마음으로 프로젝트를 진행했는데 하면할 수록 오류도 만나고, 기능은 하지만 db에 대한 지식이 부족해서 일을 두번하고 있는 건가하는 생각이 들 때도 있었다. 문서화와 함께 더 넓은 범위의 지식이 필요함을 느꼈다.</p>
<h3 id="사연없는-코드는-없다-라는-말에-공감할-수-있게-되었다😂"><strong>사연없는 코드는 없다! 라는 말에 공감할 수 있게 되었다😂</strong></h3>
<p>좋은 이야기는 아니지만ㅎ.. 3주라는 짧은 시간에 기획, 디자인, 개발, 발표자료 제작하느라 좋은 코드를 쓰려고해도 시간이 없어서 아쉬워도 넘어가는 경우가 발생할 수 밖에 없었다. (리팩토링을 처음부터 열심히 하는 이유)  주요기능개발, 디자인, 발표자료제작, 발표까지 다하느라 정말 고생하면서 책임감에 대해 생각할 수 있었다. 다양한 분들과 고민에 대해 이야기하고 조언도 들으면서 팀 리딩 능력과 팀원을 어떻게 대해야할지 생각해보는 기회가 되었다.</p>
<p>정말정말 고생했던 덕일까, 현업자 3분과 강사 2분과 함께한 발표에서 현업자분 다들 칭찬해주시고 예상치 못했던 최우수상도 받았다!!😄 </p>
<p>열심히 리팩토링하고 취준하라는 의미라고 여기고, ver2.0.0로 리팩토링하며 배운 것들을 블로깅하려고 한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zustand 불필요한 리렌더링 막기]]></title>
            <link>https://velog.io/@ony_/Zustand-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@ony_/Zustand-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Sun, 10 Dec 2023 06:45:20 GMT</pubDate>
            <description><![CDATA[<p>음악플레이어를 구현하면서 zustand를 사용하면서 만난 리렌더링 문제</p>
<pre><code class="language-tsx">import { create } from &#39;zustand&#39;

const initialStore: NowPlayList = {
  owner: undefined,
  tracks: [],
  currentTrack: null,
  isPlaying: false,
  playingPosition: 0
}

export const useNowPlayStore = create&lt;NowPlayStore&gt;(set =&gt; ({
  ...initialStore,
  setCurrentTrack: (track: Track) =&gt;
    set(state =&gt; ({
      ...state,
      currentTrack: track
    })),
    setPlayingPosition: playingPosition =&gt;
    set(state =&gt; {
      return {
        ...state,
        playingPosition: playingPosition
      }
    }),
        ...
  }))
</code></pre>
<p>사용한 부분만 보다면 현재 재생목록과 관련된 store에는 현재 트랙을 의미하는 currentTrack과 이를 지정하는   setCurrentTrack이 있다. 또한 현재 재생된 분량을 뜻하는 playingPosition과 setPlayingPosition을 가지고 있다. </p>
<pre><code class="language-tsx">//BillPage.tsx
export const BillPage = () =&gt; {

  const [analysisList, setAnalysis] = useState&lt;any&gt;([])
  const numberOfObject = analysisList.length
  ...
  const averageAnalysis = useMemo(
    () =&gt;
      Object.fromEntries(
        Object.entries(reduceAnalysisList).map(([key, value]) =&gt; [
          key,
          value / numberOfObject
        ])
      ),
    [reduceAnalysisList]
  )

   const {currentTrack,setCurrentTrack } = useNowPlayStore()

    return (
      &lt;&gt; 
        {/* 음악 분석 그래프 */}
        &lt;BillGraph averageAnalysis={averageAnalysis} /&gt;
           ...
        {/* 플레이 바 */}
        {currentTrack &amp;&amp; &lt;PlayBar /&gt;}
      &lt;/&gt;

  )
}
</code></pre>
<p>BillPage에서는 추천받은 음악의 분석결과를 보여주는 chart.js 그래프와 음악을 재생하고 현재 재생된 프로그래스바를 보여주는 playbar가 있다.
빌지의 음악목록 하나의 재생버튼을 눌러 현재 재생목록을 지정하고, 플레이바는 currentTrack이 있을때 보여져야하기 때문에 useStore를 사용하여 </p>
<pre><code class="language-tsx">const {currentTrack,setCurrentTrack } = useNowPlayStore()</code></pre>
<p> 이런한 방식으로 들고왔으나, 이렇게 사용할 경우 useNowPlayStore 내부의 모든 상태를 반환한다.</p>
<pre><code class="language-tsx">  const playAndPauseNowPlay = useCallback(() =&gt; {
    toggleIsPlaying()

    if (isPlaying) {
      audioRef.current!.pause()
      clearInterval(intervalIdRef.current!)
    } else {
      audioRef.current!.play()

      intervalIdRef.current = setInterval(() =&gt; {
        const { currentTrack } = useNowPlayStore.getState()
        setPlayingPosition(audioRef.current?.currentTime)

        //노래가 끝나면 다음곡으로 넘기기
        if (audioRef.current!.ended) {
          setCurrentTrack(tracks[tracks.indexOf(currentTrack!) + 1])
          audioRef.current!.src = currentTrack?.preview_url!
          audioRef.current!.currentTime = 0
          setPlayingPosition(0)
        }
      }, 100)
    }
  }, [isPlaying, tracks, toggleIsPlaying, setPlayingPosition, setCurrentTrack])</code></pre>
<p>  그런데 여기서 playBar 컴포넌트내에서 음악이 재생된는 중에는 playingPosition 값이 setInterval의 콜백함수로 계속해서 바뀌고 있다. </p>
<p>  <img src="https://velog.velcdn.com/images/ony_/post/6de78e74-9d2f-4fe3-a119-f691c74decde/image.gif" alt=""></p>
<p> 따라서 앞의 방식으로 store의 저장된 값을 들고오면 위 gif처럼 음악 재생중에는 BillPage에 있는 모든 컴포넌트가 리렌더링 되어 <code>&lt;BillGraph /&gt;</code>는 currentTrack과 관련이 없는데에도 계속 리렌더링되어 애니메이션이 일어난다. </p>
<pre><code class="language-tsx"> const currentTrack = useNowPlayStore(state =&gt; state.currentTrack)
  const setCurrentTrack = useNowPlayStore(state =&gt; state.setCurrentTrack)</code></pre>
<p> 이런한 방식으로 들고올 경우에는 useNowPlayStore의 전체 state가 아닌 <code>state.currentTrack</code> ,<code>state.setCurrentTrack</code>만 들고온다. 
 Object가 아닌, primitive type의 변수와 절대 바뀌지 않는 action 함수만을 들고 오기에,  useNowPlayStore에 영향을 받지않는 <code>&lt;BillGraph /&gt;</code>는 리렌더링이 일어나지 않습니다. 
 <img src="https://velog.velcdn.com/images/ony_/post/c04bc9a5-6556-47d6-99b3-e54900f88eac/image.gif" alt=""></p>
<p>위의 gif를 보면 그래프는 리렌더링되지않지만 <code>&lt;playbar/&gt;</code> 내부의 앨범 이미지와 버튼 svg가 계속 리렌더링되는데 프로그레스 바외에는 리렌더링되지않도록 하고, shallow를 사용하는 추가적 로직 수정이 필요하다.</p>
<hr>
<p> <a href="https://hackids.tistory.com/151">https://hackids.tistory.com/151</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[spotify  api access_token 얻기 로그인 기능 구현하기 (프론트엔드에서 구현, supabase) ]]></title>
            <link>https://velog.io/@ony_/spotify-api-accesstoken-%EC%96%BB%EA%B8%B0-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ony_/spotify-api-accesstoken-%EC%96%BB%EA%B8%B0-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 03 Dec 2023 13:35:33 GMT</pubDate>
            <description><![CDATA[<p>음악을 소재로 프로젝트를 진행하기로 마음먹고 여러 음악 api를 찾아봤을 때 처음에는 soundcloud의 api를 사용하려고 했는데, api 신청을 위한 공식사이트의 구글폼을 확인했더니 더이상 제공하지않는다는 글을 보고 Spotify API를 사용하기로 했다.</p>
<p><a href="https://developer.spotify.com/documentation/web-api/tutorials/getting-started">https://developer.spotify.com/documentation/web-api/tutorials/getting-started</a>
위의 공식문서 자료를 보고 순서대로 하면 access_token을 얻을 수 있다.</p>
<h3 id="1-crear-an-app">1. CREAR AN APP</h3>
<p><img src="https://velog.velcdn.com/images/ony_/post/00af13a0-dbb3-4a90-87c5-ab6865e0f600/image.png" alt=""></p>
<p>Spotify API를 내 프로젝트에서 사용하기 위해서는 <code>Client ID</code>및 <code>Client Secret</code>를 발급받아야 한다. 
<a href="https://developer.spotify.com/dashboard">공식 사이트의 Dashboard</a>에서 &#39;CREATE AN APP&#39; 버튼을 눌러 앱을 생성하면 키 값을 얻을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ony_/post/011df413-0060-4c92-916f-c8c7ba757262/image.png" alt="">
Redirect URI는 일단 <code>http://localhost:8080/callback</code> 로 설정했고, Dashboard에서 나중에 수정 및 추가가 가능하다. </p>
<br/>

<h3 id="2-access_token-발급">2. access_token 발급</h3>
<p><img src="https://velog.velcdn.com/images/ony_/post/a8f534be-4fc3-409e-a2e3-80038440cc65/image.png" alt="">
Spotify Web API는 OAuth 2.0 방식으로 되어있다. 리소스에 접근하기 위해선 access_token이 필요했기 때문에 위 이미지와 같은 Client Credentials Grant 방식으로 받아왔다. 아래 로직은 <a href="https://developer.spotify.com/documentation/web-api/tutorials/getting-started">Getting started with Web API</a>를 보고 작성했다. </p>
<pre><code class="language-ts">import axios from &#39;axios&#39;;

const BASE_URL = &#39;https://accounts.spotify.com/api/token&#39;;

export const getAccessToken = async () =&gt; {
  const authParam = {
    grant_type: &#39;client_credentials&#39;,
    client_id: import.meta.env.VITE_SPOTIFY_CLIENT_ID,
    client_secret: import.meta.env.VITE_SPOTIFY_CLIENT_SECRET,
  };

  try {
    const res = await axios.post(BASE_URL, new URLSearchParams(authParam).toString(), {
      headers: {
        &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;,
      },
    });
    window.localStorage.setItem(&#39;token&#39;, res.data.access_token);
    return res.data;
  } catch (error) {
    console.error(error);
    throw error;
  }
};</code></pre>
<p>*<em>이 로직으로는 access_token만 가져올 수 있다!! *</em></p>
<p>음악 추천 서비스를 만들기 위해 사용할 api는 해당 access_token만 있어도 구현할 수 있지만, 음악 재생 기능 및 커뮤니티를 만들기 위해서는 해당 토큰만으로는 기능을 구현할 수 없다!
사용자가 로그인해야 사용할 수 있는 기능(유저 프로필, 음악 재생 등)들은 아래와 같은 Authorization Code Grant 방식으로 가져온 access_token과 refresh_token이 필요하다. </p>
<br/>

<h3 id="3-로그인-기능-구현-access_token-refresh_token-얻기">3. 로그인 기능 구현 (access_token, refresh_token 얻기)</h3>
<p>공식문서의 <a href="https://developer.spotify.com/documentation/web-api/tutorials/code-flow">Authorization Code Tutorial</a>과  <a href="https://github.com/spotify/web-api-examples/tree/master/authorization/authorization_code">spotify의 github</a>의 express 프레임워크를 사용한 예제 코드를 뜯어보며 이해할 수 있었다.
<img src="https://velog.velcdn.com/images/ony_/post/bfecc233-9aef-4f2f-acbe-775c7f6878ac/image.png" alt="">
APPLICATION = 우리가 지금 만들고 있는 앱을 의미한다
USER = 우리 앱에 로그인하려는 유저를 의미한다.</p>
<p><strong>예제 로직의 흐름을 보자면  (사용자 🩷 / 개발자 🩵 / 스포티파이 💚)</strong></p>
<ol>
<li>🩷: 로그인 버튼을 눌러 로그인을 요청한다. </li>
<li>🩵: ❶의 값들과 함께 사용자를 Spotify 로그인 URL로 보낸다.</li>
<li>🩷: 사용자는 Spotify 페이지에서 로그인하고 권한을 부여한다.</li>
<li>💚: redirect URL로 설정된 우리 앱의 <code>/callback</code> URL로 이동시킨다.</li>
<li>🩵: 이때 우리는 <code>code</code>,<code>state</code>를 전달받는다. </li>
<li>🩵: <code>code</code>,<code>state</code>를 포함한 ❷의 값들로 다시 spotify api에 요청해서 <code>access_token</code>, <code>refresh_token</code>를 받을 수 있다.</li>
</ol>
<br/>

<p>코드와 함께보자면</p>
<ol>
<li>사용자는 로그인 버튼을 눌러 로그인을 요청한다. <pre><code class="language-html">&lt;a href=&quot;/login&quot; class=&quot;btn btn-primary&quot;&gt;Log in with Spotify&lt;/a&gt;</code></pre>
</li>
</ol>
<br/>

<ol start="2">
<li><p>개발자는 ❶의 값들과 함께 사용자를 Spotify 로그인 URL로 보낸다.</p>
<pre><code class="language-js">// login 엔드포인트로의 GET 요청을 처리
app.get(&#39;/login&#39;, function (req, res) {
// 생성된 &#39;state&#39;를 &#39;spotify_auth_state&#39;라는 이름의 쿠키에 저장 (CSRF공격을 방지하기 위해 사용)
var state = generateRandomString(16);
res.cookie(stateKey, state);

// 사용자에게 요청할 권한(scope)을 정의합니다.
var scope = &#39;user-read-private user-read-email&#39;;

// 사용자를 Spotify 인증 URL로 보냄
res.redirect(
 &#39;https://accounts.spotify.com/authorize?&#39; +
   querystring.stringify({
     response_type: &#39;code&#39;, // 인증 코드 플로우를 지정합니다.
     client_id: client_id,
     scope: scope, //권한
     redirect_uri: redirect_uri,
     state: state, //보안을 위해 생성된 랜덤 state
   })
);
});</code></pre>
<br/>
</li>
<li><p>사용자는 위 로직에서 리턴된 Spotify authorize 페이지에서 로그인하고 권한을 부여한다.</p>
</li>
<li><p>권한이 부여되면 spotify가 redirect URL로 설정된 우리 앱의 <code>/callback</code> URL로 이동시킨다.</p>
</li>
</ol>
<br/>


<ol start="5">
<li><p>이때 개발자는 <code>code</code>,<code>state</code>를 전달받는다. </p>
<pre><code class="language-js">app.get(&#39;/callback&#39;, function (req, res) {

var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
...</code></pre>
</li>
</ol>
<br/>

<ol start="6">
<li><p><code>code</code>,<code>state</code>과 함께 ❷의 값들로 다시 spotify api에 요청해서 <code>access_token</code>, <code>refresh_token</code>를 받을 수 있다.</p>
<pre><code class="language-js">app.get(&#39;/callback&#39;, function (req, res) {

var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;

if (state === null || state !== storedState) {
 //state_mismatch 에러처리 로직...
} else {
 //쿠키 클리어
 res.clearCookie(stateKey);
 //post 옵션 지정
 var authOptions = {
   url: &#39;https://accounts.spotify.com/api/token&#39;,
   form: {
     code: code,
     redirect_uri: redirect_uri,
     grant_type: &#39;authorization_code&#39;,
   },
   headers: {
     &#39;content-type&#39;: &#39;application/x-www-form-urlencoded&#39;,
     Authorization:
       &#39;Basic &#39; + new Buffer.from(client_id + &#39;:&#39; + client_secret).toString(&#39;base64&#39;),
   },
   json: true,
 };

  //post 옵션과 함께 포스트 요청
 request.post(authOptions, function (error, response, body) {

   //요청 성공시 access_token, refresh_token 얻기 성공!
   if (!error &amp;&amp; response.statusCode === 200) {
     var access_token = body.access_token,
       refresh_token = body.refresh_token;
   }
});</code></pre>
</li>
</ol>
<br/>

<ol start="7">
<li><p>이제 <code>access_token</code>, <code>refresh_token</code>를 사용해서 사용자 정보 얻기 등의 Spotify Web API에 접근할 수 있다!</p>
<pre><code class="language-js">     // access token을 사용해 Spotify Web API의 사용자 정보 얻기
     var options = {
       url: &#39;https://api.spotify.com/v1/me&#39;,
       headers: { Authorization: &#39;Bearer &#39; + access_token },
       json: true,
     };

     request.get(options, function (error, response, body) {
       console.log(body);
     });
</code></pre>
</li>
</ol>
<p>```</p>
<br/>

<h3 id="4-프론트에서-spotify-로그인-로직-구현하기">4. 프론트에서 spotify 로그인 로직 구현하기</h3>
<p>위의 로직은 OAuth 2.0에 해당하는, 로그인을 다른 웹사이트로 위임하는 사용자 인증 방식 방식으로, 위 예제에서 express를 사용했듯이 Back의 로직이 필요하다. 
따라서 Next.js 공부 겸 사용할 수도 있지만, 팀원 모두 Next.js가 초면인 상황에서 프론트 3명이서 3주 프로젝트에 이를 도입하기 망설여졌다.
이때 공식문서의 <a href="https://developer.spotify.com/documentation/web-api/tutorials/code-pkce-flow">PKCE방식의 tutorial</a>을 보고, Back의 로직이 사용되지 않고 Front단에서 구현이 가능하다는 이야기를 듣고 해당 로직으로 구현하기로 했다. </p>
<p><strong>이 역시 <a href="https://github.com/spotify/web-api-examples/tree/master/authorization/authorization_code_pkce">github의 예제</a>와 함께 이해할 수 있었다!</strong></p>
<blockquote>
<p>여기서 <strong>OAuth 2.0 PKCE</strong>는 간단히 말해서 Authorization Code Grant 방식에서 Client Secret을 이용하던 것을 <strong>일회용 암호(code_verifier, code_challenge )</strong>를 이용하는 것으로 변경한 것이다. 
<a href="https://velog.io/@entry_dsm/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%82%9C-%EB%B6%80%EB%9F%BD%EC%A7%80%EA%B0%80-%EC%95%8A%EC%96%B4">이 블로그 글</a>을 보면 OAuth 2.0와 OAuth 2.0 PKCE의 차이에 대해 이해할 수 있다.</p>
</blockquote>
<br/>


<h3 id="어쩌다보니-supabase">어쩌다보니 supabase</h3>
<p>PKCE 로직을 다 이해한 뒤 이를 사용하려 했지만, 서비스를 추가적으로 기획하면서 커뮤니티를 위한 DB가 필요해졌고 백없이 DB를 다룰 수 있는 supabase를 사용하기로 결정되었다. 
처음에는 spotify token을 얻는 로직과 supabase에서 spotify로 로그인했하는 로직을 따로 구현해야하나 했는데, supabase로 로그인했더니 <code>provider_token</code>과 <code>provider_refresh_token</code>값으로 제공해주고 있었다.
살짝 허무해졌긴하지만 그래도 일련의 과정을 통해 공부한게 많아서 재밌고 좋았다!!😊</p>
<br/>

<h3 id="supabase에서-refresh_token-자동화">supabase에서 refresh_token 자동화</h3>
<p>현재로서 궁금한 건 
supabase로 로그인한 상태에서는 spotify 공식문서의 <a href="https://developer.spotify.com/documentation/web-api/tutorials/refreshing-tokens">refresh_token을 얻는 방식</a>으로 요청해봤는데 오류는 없으나 Response가 refresh_token을 제외한 새로운 access_token값만 전달해준다. 🤔 왜일까.
(참고로 PKCE 방식으로 요청 시 오류가 발생하는 걸 보니 supabase에서는 OAuth2.0 방식으로 token을 얻는 듯하다.)</p>
<p>supabase에서 토큰을 리프래시하는 로직이 있나 확인하기 위해 Docs를 읽어도 <a href="https://supabase.com/docs/guides/auth/sessions">Session</a>에 대한 글을 읽어보면 세션을 종료하는 상황과 해당방식이 보안에 좋은 장점에 대해 이야기하고 있다.</p>
<p>실제로 spotify로 supabase에 로그인 했을 때 supabase에 대한 access_token과 refresh_token을 반환하면서 이에 대한 만료기한 역시 스포티파이와 마찬가지로 1시간이었기에 supabase와 spotify 둘다 refresh Token이 필요한 상황이다.</p>
<p>결론적으로 따로 토큰을 리프래시하는 로직이 따로 존재하지않는 듯하다. 하지만 로그인한 상태에서 로그인 로직을 다시 실행시키면 새 access_token과 refresh_token을 반환한다! 따라서 현재로서는 사용자가 1시간 이상 사용했을 때 toast를 띄워 안내 후 재로그인을 시키거나, tokenVaild 로직을 구현해 자동으로 재로그인하도록 할 예정이다. </p>
<hr>
<p><a href="https://developer.spotify.com/documentation/web-api/tutorials/getting-started">https://developer.spotify.com/documentation/web-api/tutorials/getting-started</a>
<a href="https://supabase.com/docs/guides/auth/sessions#frequently-asked-questions">https://supabase.com/docs/guides/auth/sessions#frequently-asked-questions</a>
<a href="https://supabase.com/docs/guides/auth/social-login/auth-spotify">https://supabase.com/docs/guides/auth/social-login/auth-spotify</a></p>
<p><a href="https://kang-ju.tistory.com/entry/Spotify-API-%EC%82%AC%EC%9A%A9%EA%B8%B01-%EC%95%B1-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84#%EC%95%B1%20%EC%83%9D%EC%84%B1-1">https://kang-ju.tistory.com/entry/Spotify-API-%EC%82%AC%EC%9A%A9%EA%B8%B01-%EC%95%B1-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84#%EC%95%B1%20%EC%83%9D%EC%84%B1-1</a>
<a href="https://velog.io/@eunddodi/Spotify-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%98%EA%B8%B0-1%ED%8E%B8">https://velog.io/@eunddodi/Spotify-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%98%EA%B8%B0-1%ED%8E%B8</a>
<a href="https://velog.io/@entry_dsm/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%82%9C-%EB%B6%80%EB%9F%BD%EC%A7%80%EA%B0%80-%EC%95%8A%EC%96%B4">https://velog.io/@entry_dsm/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%82%9C-%EB%B6%80%EB%9F%BD%EC%A7%80%EA%B0%80-%EC%95%8A%EC%96%B4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux-toolkit 비동기 함수 type typeScript (todoList RTK 상태관리)]]></title>
            <link>https://velog.io/@ony_/Redux-toolkit-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%A8%EC%88%98-type-typeScript-todoList-RTK-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@ony_/Redux-toolkit-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%A8%EC%88%98-type-typeScript-todoList-RTK-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sat, 25 Nov 2023 17:16:23 GMT</pubDate>
            <description><![CDATA[<p>Redux 맛보기 공부를 위해 TS 환경에서 만든 todoList에 Redux를 적용하며 배우고자했다.
공식문서와 구글링하며 공부하는데 어떤 것엔 Action을 지정하고 있는데 또 createSlice할 때는 없어 처음엔 헷갈렸는데, 결론은</p>
<blockquote>
<p>Redux -&gt; RTK 
createAction, createReducer -&gt; <strong>createSlice</strong></p>
</blockquote>
<p>Redux를 쉽게 사용하기 위해 RTK에서 사용하는 createAction과 createReducer를 더욱 더 함축시킨 것이 createSlice이다.</p>
<p>공식 문서에서도 RTK의 사용을 권장하는 문구가 보이고, api를 사용하여 async 함수를 사용하기에 RTK의 createSlice를 사용했다. </p>
<h2 id="순서">순서</h2>
<ol>
<li>Redux store 생성</li>
<li>React에 store 제공</li>
<li>slice에 넣을 상태, 상태를 변경할 수 있는 함수 생성</li>
<li>Redux state slice 생성</li>
<li>store에 4번에서 생성한 Slice Reducers 추가</li>
<li>useDispatch, useSelector를 통한 Redux state와 actions 사용</li>
</ol>
<h3 id="1-store-생성---storets">1. store 생성 - store.ts</h3>
<pre><code class="language-ts">import { configureStore } from &#39;@reduxjs/toolkit&#39;

export const store = configureStore({
  reducer: {},
})</code></pre>
<h3 id="2-react에-store-제공---apptsx">2. React에 store 제공 - App.tsx</h3>
<pre><code class="language-tsx">import &#39;./App.css&#39;;
import Router from &#39;./pages/Router&#39;;
import { Provider as MyProvider } from &#39;react-redux&#39;;
import { store } from &#39;./store/store&#39;;

function App() {
  return (
    &lt;&gt;
      &lt;MyProvider store={store}&gt;
        &lt;Router /&gt;
      &lt;/MyProvider&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<h3 id="3-slice에-넣을-상태-상태변경-함수-생성---todoapireduxts">3. slice에 넣을 상태, 상태변경 함수 생성 - TodoAPIRedux.ts</h3>
<pre><code class="language-ts">import axios from &#39;axios&#39;;
import { TodoItem } from &#39;@/types/TodoTypes&#39;;
import { createAsyncThunk } from &#39;@reduxjs/toolkit&#39;;

//반환하는 상태 타입 정의
export type MyState = {
  todoListData: TodoItem[];
  selectedTodo: TodoItem;
};

// 비동기 액션에서 반환하는 타입 정의
type AsyncThunkConfig = {
  state: MyState;
  rejectValue: string;
};

export const getTodoList = createAsyncThunk&lt;TodoItem[], void, AsyncThunkConfig&gt;(
  &#39;todoData/getTodoList&#39;,
  async (_, thunkAPI) =&gt; {
    try {
      const res = await axios.get(``);

      const sortedItems = res.data.items.sort((a: TodoItem, b: TodoItem) =&gt; {
        return new Date(b.updatedAt!).getTime() - new Date(a.updatedAt!).getTime();
      });

      return sortedItems;
    } catch (error) {
      console.error(&#39;Error fetching todo list:&#39;, error);
      return thunkAPI.rejectWithValue(&#39;Error fetching todo list&#39;);
    }
  },
);

...</code></pre>
<p>상태관리를 하지않는 기존의 api 통신코드를 <code>createAsyncThunk</code> 함수를 사용하여 수정했다. typescript를 사용하므로 전역으로 사용할 데이터와 비동기 액션에서 반환하는 타입을 지정하여 사용했다. 
<a href="https://velog.io/@kandy1002/Typescript%EB%A1%9C-Redux-toolkit-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">Typescript로 Redux-toolkit 사용하기</a>를 보고 READ와 UPDATE에 해당하는 함수만<code>createAsyncThunk</code>를 사용하여 상태를 변경하고 CREATE와 DELETE의 경우 그대로 유지하고, 함수 사용 시에 api 패칭에 성공한 뒤(fulfilled) READ에 해당하는 함수들을 불러와 상태를 변경했다.</p>
<h3 id="4-redux-state-slice-생성---todoreducerts">4. Redux state slice 생성 - todoReducer.ts</h3>
<pre><code class="language-ts">import { createSlice } from &#39;@reduxjs/toolkit&#39;;
import { getTodoList, getTodoItem, patchTodoItem, updateChecked } from &#39;@/store/asyncThunks/TodoAPIRedux&#39;;
import { MyState } from &#39;./MyState&#39;;
import { defaultTodoItem } from &#39;@/types/TodoTypes&#39;;

const initialState: MyState = {
  todoListData: [],
  selectedTodo: defaultTodoItem,
};

const toDoListSlice = createSlice({
  name: &#39;toDo&#39;,
  initialState,
  reducers: {},
  extraReducers: builder =&gt; {
    // todo GET
    builder.addCase(getTodoList.fulfilled, (state, action) =&gt; {
      state.todoListData = action.payload;
    });
    builder.addCase(getTodoItem.fulfilled, (state, action) =&gt; {
      state.selectedTodo = action.payload;
    });
    //todo UPDATE
    builder.addCase(patchTodoItem.fulfilled, (state, action) =&gt; {
      state.selectedTodo = action.payload;
    });
    builder.addCase(updateChecked.fulfilled, (state, action) =&gt; {
      state.selectedTodo = action.payload;
    });
  },
});

// action을 export해야 dispatch를 사용가능
export const toDoActions = toDoListSlice.actions;

export type ReducerType = ReturnType&lt;typeof toDoListSlice.reducer&gt;;
export default toDoListSlice.reducer;
</code></pre>
<p>rtk에서 slice는 상태의 고유 string값과 초기 상태, 상태를 변경할 수 있는 함수들을 지정해주는 곳이다. reducer를 추가하여 일반적인 이벤트에 대한 상태 변경을, <strong>extraReducer를 추가하여 비동기 함수에 대한 상태 변경</strong>을 할 수 있다. 
action을 export해야 dispatch 함수를 사용할 수 있다.
extraReducer를 사용 시에 dispatch함수의 타입 지정을 위해 <code>reducerType</code>도 export한다.</p>
<p><code>createSlice</code>를 사용하여 slice를 만들었고, slice가 2개 이상이라면  <code>combineReducers</code> 병합해줘야하지만 단순한 todolist 기능으로 하나로 충분하다고 생각해서 toDoListSlice.reducer를 반환했다.</p>
<h3 id="5-store에-4번에서-생성한-slice-reducers-추가---storets">5. store에 4번에서 생성한 Slice Reducers 추가 - store.ts</h3>
<pre><code class="language-ts">import { Action, ThunkDispatch, configureStore } from &#39;@reduxjs/toolkit&#39;;
import todoReducer, { ReducerType } from &#39;./todoReducer&#39;;

// 스토어 생성
export const store = configureStore({
  reducer: {
    todoReducer,
  },
});

// useSelector의 state 타입 지정
export type RootState = ReturnType&lt;typeof store.getState&gt;;

// useDispatch 타입 지정
export type AppThunkDispatch = ThunkDispatch&lt;ReducerType, unknown, Action&lt;string&gt;&gt;;
export type AppDispatch = typeof store.dispatch;
</code></pre>
<p>store에서는 기본적으로 reducer를 설정해 생성했고, 
useSelector의 state와 useDispatch의 타입을 지정하여 export했다.</p>
<blockquote>
<h3 id="🚨-비동기-이벤트를-다루는-extrareducer의-경우">🚨 비동기 이벤트를 다루는 extraReducer의 경우</h3>
<p><strong>export type AppThunkDispatch = ThunkDispatch&lt;ReducerType, unknown, Action<string>&gt;;</strong>
<code>createAsyncThunk</code> 함수를 사용하여 만든 비동기 상태변경 함수를 사용하기 위해서는 useDispatch의 타입을 위와 같이 지정해야 사용할 수 있다.
extraReducer의 경우는 <code>useDispatch&lt;AppThunkDisPatch&gt;()</code>와 같이 타입을 명시해주지 않으면 에러를 발생시킨다. 즉 reducer와 extraReducer의 타입을 다르게 지정해주어야한다.</p>
</blockquote>
<h3 id="6-usedispatch-useselector를-통한-redux-state와-actions-사용---todoinfohooksts">6. useDispatch, useSelector를 통한 Redux state와 actions 사용 - TodoInfo.hooks.ts</h3>
<pre><code class="language-ts">import { useState, useEffect, useCallback } from &#39;react&#39;;
import { useNavigate, useParams } from &#39;react-router&#39;;
import { getTodoList, getTodoItem, patchTodoItem, deleteTodo, updateChecked } from &#39;@/store/asyncThunks/TodoAPIRedux&#39;;
import { TodoItem } from &#39;@/types/TodoTypes&#39;;
import { useDispatch, useSelector } from &#39;react-redux&#39;;
import { AppThunkDispatch, RootState } from &#39;@/store/store&#39;;

export const useTodoInfo = () =&gt; {
  const dispatch: AppThunkDispatch = useDispatch();
  const todo = useSelector((state: RootState) =&gt; state.todoReducer.selectedTodo);

  ...

  const navigate = useNavigate();
  const { _id } = useParams();

  const fetchData = useCallback(async () =&gt; {
    if (_id) {
      try {
        dispatch(getTodoItem({ _id }));
      } catch (err) {
        console.error(&#39;Error fetching todo:&#39;, err);
      }
    }
  }, [_id, dispatch]);

  const updateTodo = async (updatedTodo: TodoItem) =&gt; {
    try {
      if (updatedTodo.title === &#39;&#39; || updatedTodo.content === &#39;&#39;) {
        alert(&#39;제목과 내용을 입력해주세요&#39;);
        return;
      }
      dispatch(patchTodoItem(updatedTodo));
    } catch (err) {
      console.error(&#39;Error updating todo:&#39;, err);
    }
  };

  const handleEditClick = () =&gt; {
    if (!isEditing) {
      setIsEditing(true);
      setUpdatedTitle(todo.title);
      setUpdatedContent(todo.content);
    } else {
      if (todo.title === updatedTitle &amp;&amp; todo.content === updatedContent) {
        alert(&#39;수정된 내용이 없습니다.&#39;);
        return;
      }
      setIsEditing(false);
      updateTodo({ ...todo, title: updatedTitle, content: updatedContent });
    }
  };

  const handleDeleteClick = async (e: React.MouseEvent&lt;HTMLElement&gt;) =&gt; {
    e.preventDefault();

    const res = confirm(&#39;정말 삭제하시겠습니까?&#39;);
    if (res) {
      await deleteTodo(_id!);
      await dispatch(getTodoList());
      navigate(&#39;/&#39;);
    }
  };

  const handleCheckboxChange = async () =&gt; {
    if (todo._id !== undefined) {
      try {
        dispatch(updateChecked({ _id: todo._id, title: todo.title, content: todo.content, done: !isChecked }));
        setIsChecked(prevChecked =&gt; !prevChecked);
      } catch (error) {
        console.error(&#39;Error updating checked state:&#39;, error);
      }
    }
  };

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

  return {
    todo,
    isEditing,
    updatedTitle,
    updatedContent,
    isChecked,
    setIsEditing,
    setUpdatedTitle,
    setUpdatedContent,
    setIsChecked,
    handleEditClick,
    handleDeleteClick,
    handleCheckboxChange,
  };
};
</code></pre>
<p>위처럼 TodoInfo에서 useSelector와 useDispatch로 Redux state와 actions를 사용했다.  </p>
<hr>
<p>  <a href="https://ko.redux.js.org/introduction/why-rtk-is-redux-today/">https://ko.redux.js.org/introduction/why-rtk-is-redux-today/</a>
  <a href="https://redux-toolkit.js.org/usage/usage-with-typescript">https://redux-toolkit.js.org/usage/usage-with-typescript</a>
  <a href="https://velog.io/@kandy1002/Typescript%EB%A1%9C-Redux-toolkit-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">https://velog.io/@kandy1002/Typescript%EB%A1%9C-Redux-toolkit-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</a>
  <a href="https://velog.io/@niboo/Redux-Toolkit-ToDoList-Redux-Toolkit%EC%9C%BC%EB%A1%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%ED%95%B4%EB%B3%B4%EA%B8%B0">https://velog.io/@niboo/Redux-Toolkit-ToDoList-Redux-Toolkit%EC%9C%BC%EB%A1%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%ED%95%B4%EB%B3%B4%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React의 생애주기(lifecycle), 클래스형, 함수형, React Hook]]></title>
            <link>https://velog.io/@ony_/React%EC%9D%98-%EC%83%9D%EC%95%A0%EC%A3%BC%EA%B8%B0lifecycle-%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%ED%95%A8%EC%88%98%ED%98%95-React-Hook</link>
            <guid>https://velog.io/@ony_/React%EC%9D%98-%EC%83%9D%EC%95%A0%EC%A3%BC%EA%B8%B0lifecycle-%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%ED%95%A8%EC%88%98%ED%98%95-React-Hook</guid>
            <pubDate>Tue, 07 Nov 2023 07:26:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/ab456dd0-59e2-47dd-82f9-4662db20e44c/image.png" alt=""></p>
<p>생성(mounting) -&gt; 업데이트(updating) -&gt; 제거(unmounting)의 생명주기를 갖는다
<br/><br/></p>
<h3 id="클래스형-컴포넌트에서의-생애주기별-메서드">클래스형 컴포넌트에서의 생애주기별 메서드</h3>
<h4 id="mounting-생성-단계">Mounting (생성 단계)</h4>
<p><code>constructor</code>: 컴포넌트가 생성될 때 호출되는 생성자 메소드. 초기화 작업을 수행한다.
<code>render</code>: UI를 렌더링하는 메서드
<code>componentDidMount</code>: 컴포넌트가 DOM에 삽입된 직후에 호출된다. 초기 데이터 로딩 및 외부 리소스 초기화에 유용하다.</p>
<h4 id="updating-업데이트-단계">Updating (업데이트 단계)</h4>
<p><code>shouldComponentUpdate</code>: 컴포넌트가 업데이트되어야 하는지 여부를 결정하는 메서드. 성능 최적화에 활용된다.
<code>render</code>: UI를 다시 렌더링
<code>componentDidUpdate</code>: 컴포넌트가 업데이트된 직후에 호출된다. 업데이트 이후의 작업을 수행한다.</p>
<h4 id="unmounting-제거-단계">Unmounting (제거 단계)</h4>
<p><code>componentWillUnmount</code>: 컴포넌트가 제거되기 전에 호출됩니다. 리소스 정리와 타이머 해제와 같은 정리 작업을 수행합니다.</p>
<h4 id="error-handling-에러-처리-단계">Error Handling (에러 처리 단계)</h4>
<p><code>componentDidCatch</code>: 자식 컴포넌트에서 발생한 에러를 처리하기 위해 사용됩니다. 에러 경계(error boundary)를 정의하고 에러 처리 로직을 구현할 때 활용됩니다.
<br/><br/></p>
<h3 id="함수형-컴포넌트의-생명주기">함수형 컴포넌트의 생명주기</h3>
<p>리액트에서 Hook은 함수형 컴포넌트에서 React state와 생명주기 기능을 연동 할 수 있게 해주는 함수이다.
즉 <strong>Hook</strong>은 class 안에서는 동작하지 않고, class없이 React를 사용할 수 있게 한다.</p>
<p>대표적으로 <strong>useEffect 훅</strong>을 class형 컴포넌트의 메서드와 비교해보자면 </p>
<ul>
<li><p><strong>componentDidMount</strong> (클래스형 컴포넌트): 컴포넌트가 마운트된 직후에 호출된다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
   // componentDidMount 로직을 여기에 작성
}, []);
</code></pre>
</li>
<li><p>** componentDidUpdate** (클래스형 컴포넌트): 컴포넌트의 상태나 속성이 변경될 때 호출된다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
    // componentDidUpdate 로직을 여기에 작성
}, [dependency1, dependency2]);
</code></pre>
</li>
<li><p><strong>componentWillUnmount</strong> (클래스형 컴포넌트): 컴포넌트가 언마운트되기 전에 호출된다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  return () =&gt; {
    // componentWillUnmount 로직을 여기에 작성
  };
}, []);</code></pre>
<p>이러한 방식으로 React Hook을 사용하면 함수형 컴포넌트에서 클래스형 컴포넌트와 유사한 라이프사이클 동작을 구현할 수 있습니다. </p>
<p><br/><br/></p>
</li>
</ul>
<hr>
<p><a href="https://react.dev/learn/lifecycle-of-reactive-effects">lifecycle-of-reactive-effects</a>
<a href="https://velog.io/@minbr0ther/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4life-cycle-%EC%88%9C%EC%84%9C-%EC%97%AD%ED%95%A0">[React.js] 리액트 라이프사이클(life cycle) 순서, 역할, Hook</a>
<a href="https://velog.io/@fltxld3/React-Lifecycle-React-Hooks%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95%ED%95%A8%EC%88%98%ED%98%95">[React] Lifecycle, React Hooks(클래스형,함수형)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 TS interface와 type의 특징과 차이점]]></title>
            <link>https://velog.io/@ony_/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-TS-interface%EC%99%80-type%EC%9D%98-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@ony_/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-TS-interface%EC%99%80-type%EC%9D%98-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Sat, 04 Nov 2023 09:03:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="interface">interface</h3>
</blockquote>
<h4 id="1-정의가능-타입">1. 정의가능 타입</h4>
<p>객체, 클래스의 타입 </p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
}</code></pre>
<h4 id="2-상속-확장-방식">2. 상속, 확장 방식</h4>
<p>extends 키워드로 확장, 선언 병합</p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
}
interface Person {
  address: string;
}

// Person 인터페이스를 확장한 Employee 인터페이스
interface Employee extends Person {
  jobTitle: string;
  salary: number;
}

const employee: Employee = {
  name: &quot;Alice&quot;,
  age: 30,
   address: &#39;seoul&#39;,
  jobTitle: &quot;Software Engineer&quot;,
  salary: 60000,
};</code></pre>
<blockquote>
<h3 id="type-타입-별칭">type (타입 별칭)</h3>
</blockquote>
<p><strong>정의가능 타입</strong> 
객체, 클래스 외에도 기본 타입, 유니언 타입, 인터섹션 타입, 유틸리티 타입, 맵드 타입 등의 정의에 사용</p>
<pre><code class="language-ts">
// 1. 객체타입
type Person = {
  name: string;
  age: numbr;
};

type Employee = {
  jobTitle: string;
  salary: number;
};

// 2. 유니언타입 | 
type Color = &quot;red&quot; | &quot;green&quot; | &quot;blue&quot;;

// 3. 인터섹션 타입 &amp;
type PersonEmployee = Person &amp; Employee;

//4. 유틸리티 타입 (ex Partial, Pick, Omit)
type JustName = Pick&lt;Person, &quot;name&quot;&gt;;

//5. 맵드 타입 [a in b] 
type ReadonlyPerson = {
  readonly [K in keyof Person]: Person[K];
};

const person: Person = { name: &quot;Bob&quot;, age: 25, address: &quot;123 Main St&quot; };</code></pre>
<br/>

<h4 id="2-상속-확장-방식-1">2. 상속, 확장 방식</h4>
<p>&amp; 연산자로 확장, 두번 선언 불가능</p>
<pre><code class="language-ts">  type Todo = {
    title: string;
    content: string;
  };

  type TodoInfo = Todo &amp; {
    _id: number;
    done: boolean;
  };

  var todo1: Todo = {
    title: &#39;할일 1&#39;,
    content: &#39;등록할 때 사용.&#39;
  };</code></pre>
<hr>
<blockquote>
<h3 id="interface와-type-타입-별칭의-차이점">interface와 type (타입 별칭)의 차이점</h3>
</blockquote>
<h4 id="1-정의할-수-있는-타입-종류">1. 정의할 수 있는 타입 종류</h4>
<p><strong>interface</strong> : 객체, 클래스 정의에 사용
<strong>type</strong> : 객체, 클래스 외에도 <code>기본 타입, 유니언 타입, 인터섹션 타입, 유틸리티 타입, 맵드 타입</code> 등의 정의에 사용</p>
<h4 id="2-상속-확장-방식-2">2. 상속, 확장 방식</h4>
<p><strong>interface</strong> : extends 키워드로 확장, 선언 병합
<strong>type</strong> : &amp; 연산자로 확장, 두번 선언 불가능</p>
<h4 id="3-확장가능성">3. 확장가능성</h4>
<p><strong>interface</strong>가 <strong>type</strong> 보다 확장 가능성이 높음</p>
<hr>
<p>결론적으로 성능상 interface를 사용하고 객체가 아닌 타입 별칭으로만 정의할 수 있는 경우에만 type을 사용을 권장한다.</p>
<p>하지만 타입은 인터페이스의 거의 모든 기능을 커버한다 그러므로 경우에 따라서 선택하여 사용해야 한다. 가급적 프로젝트내에서 컨벤션을 맞추고 일관된 기준에 따라 선택해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST, RESTful API, HTTP API]]></title>
            <link>https://velog.io/@ony_/REST-RESTful-API-HTTP-API</link>
            <guid>https://velog.io/@ony_/REST-RESTful-API-HTTP-API</guid>
            <pubDate>Sat, 28 Oct 2023 09:47:39 GMT</pubDate>
            <description><![CDATA[<h3 id="rest">REST</h3>
<ul>
<li><p>네트워크 리소스를 정의하고 처리하는 방법을 설명하는 일련의 원칙을 기반으로 하는 <strong>아키텍처 스타일</strong></p>
</li>
<li><p>클라이언트와 서버가 데이터 주고받는 방식 → 정리한 원칙을 기반으로한 아키텍처 스타일</p>
</li>
<li><p>HTTP를 잘활용하기 위한 원칙을 기반으로한 아키텍쳐 (REST)</p>
</li>
<li><p>직역하자면 <strong>자원(리소스)의 표현에 의한 상태 전달(REpresentational State Transfer)</strong></p>
</li>
<li><p>URI와 HTTP method를 사용해서 자원과 행위를 표현한다.</p>
</li>
<li><p>API의 의미를 표현하기 쉽고 의미를 파악하기도 쉽다.</p>
</li>
<li><p>Client는 응답을 캐싱할 수 있어야 한다</p>
</li>
<li><p>무상태(Stateless): 각 요청 간 클라이언트의 context가 Server에 저장되어서는 안 된다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ony_/post/191f4d69-be8e-472f-8446-91cb5769f986/image.png" alt="">
규칙 중 일부를 보자면</p>
<ul>
<li>URI(자원)을 표현할 때 동사를 사용하지 않는다.</li>
<li>Collection에는 복수형 Document에는 단수형을 사용</li>
<li>자원에 대한 행위에 대해 post get put delete 와 같은 HTTP Method를 동사로서 사용한다. (CRUD)</li>
</ul>
<hr>
<h3 id="restful-api">RESTful API</h3>
<p>이러한 REST을 참고해서 설계한 API</p>
<h3 id="http-api-rest-스타일의-api">HTTP API (REST 스타일의 API)</h3>
<p>REST 스타일의 일부를 받아드린 API
REST의 목적은 성능이 아닌 일관적인 컨벤션을 통한 <strong>API의 이해도 및 호환성을 높이는</strong> 것으로 상황에 따라서는 HTTP API와 같은 REST를 차용한 API를 사용하는 것이 효율적이다. </p>
<p><img src="https://velog.velcdn.com/images/ony_/post/ea3d5013-5f0d-4b47-b767-8b47ff925b76/image.png" alt="">
예를 들어 로그인, 로그아웃 URI에 동사를 사용하지않으면 회원가입 및 탈퇴와 구분하기에 표현의 어려움이 있다. 아래처럼 사용할 경우 REST를 지키지 못하지만 의미는 더 통한다. </p>
<p><code>/login /logout (동사 사용)</code></p>
<hr>
<p><a href="https://youtu.be/NODVCBmyaXs?si=x8e0uDeJssZ4NNeg">https://youtu.be/NODVCBmyaXs?si=x8e0uDeJssZ4NNeg</a>
<a href="https://youtu.be/Nxi8Ur89Akw?si=UePD8hh0O2wAQn4w">https://youtu.be/Nxi8Ur89Akw?si=UePD8hh0O2wAQn4w</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[this 바인딩 call, apply, bind의 차이점]]></title>
            <link>https://velog.io/@ony_/this-%EB%B0%94%EC%9D%B8%EB%94%A9-call-apply-bind%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@ony_/this-%EB%B0%94%EC%9D%B8%EB%94%A9-call-apply-bind%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Thu, 21 Sep 2023 07:18:44 GMT</pubDate>
            <description><![CDATA[<h3 id="1-functionprototypecall">1. Function.prototype.call()</h3>
<blockquote>
<p>func.call(thisArg[, arg1[, arg2[, ...]]])</p>
</blockquote>
<p>thisArg: func 호출에 제공되는 <strong>this가 될 값</strong>
arg1, arg2, ...: func이 호출되어야 하는 <strong>파라미터</strong> </p>
<h4 id="첫번째-인자만-있는-경우">첫번째 인자만 있는 경우</h4>
<pre><code class="language-js">const duck = {
  age: 6 
}
function printDuckAge() {
  console.log(this.age)
}

//call
printDuckAge.call(duck) //6</code></pre>
<p>첫번째 인자로 들어간 <code>duck</code>을 this로 인식하기 때문에 duck의 age인 6을 콘솔에 찍는다.</p>
<h4 id="2번째-인자가-있는-경우">2번째 인자가 있는 경우</h4>
<pre><code class="language-js">const duck = {
  age: 6 
}
function printDucksAge(duck1, duck2) {
  console.log(`${duck1}와 ${duck2}는 ${this.age}살 오리입니다.`) 
}

//call
printDucksAge.call(duck,&#39;boo&#39;,&#39;bee&#39;) // boo와 bee는 6살 오리입니다.</code></pre>
<p>2,3번째 인자로 들어간 &#39;boo&#39;와 &#39;bee&#39;를 파라미터인 <code>duck1, duck2</code>으로 인식하기 때문에 &#39;boo와 bee는 6살 오리입니다.&#39;을 콘솔에 찍는다.</p>
<h3 id="2-functionprototypeapply">2. Function.prototype.apply()</h3>
<blockquote>
<p>fun.apply(thisArg, [argsArray])</p>
</blockquote>
<p>thisArg: func 호출에 제공되는 <strong>this가 될 값</strong>
argsArray: func이 호출되어야 하는 인수를 지정하는 <strong>유사 배열 객체</strong></p>
<pre><code class="language-js">const duck = {
  age: 6 
}
function printDucksAge(duck1, duck2) {
  console.log(`${duck1}와 ${duck2}는 ${this.age}살 오리입니다.`) 
}

//apply
printDucksAge.apply(duck,[&#39;boo&#39;,&#39;bee&#39;]) // boo와 bee는 6살 오리입니다.</code></pre>
<p><code>call()</code>과 같은 기능을 구현하지만 call은 2번째 인자 이후로 여러개의 인자를 순서대로 printDuckAge의 파라미터로 인식하지만, <code>apply()</code>의 경우 이들을 묶어 유사배열객체로 받는다.</p>
<h3 id="3-functionprototypebind">3. Function.prototype.bind()</h3>
<blockquote>
<p>func.bind(thisArg[, arg1[, arg2[, ...]]])</p>
</blockquote>
<p>thisArg: 바인딩 함수가 타겟 함수의 <strong>this가 될 값</strong>
arg1, arg2, ...: func이 호출되어야 하는 인수</p>
<pre><code class="language-js">const duck = {
  age: 6 
}
function printDucksAge(duck1, duck2) {
  console.log(`${duck1}와 ${duck2}는 ${this.age}살 오리입니다.`) 
}

//bind
const printDucksAgeAndName = printDucksAge.bind(duck,&#39;boo&#39;,&#39;bee&#39;) // console 찍히는 것 없음
printDucksAgeAndName() //boo와 bee는 6살 오리입니다.
</code></pre>
<p>call과 유사하지만 <strong>즉시 호출하지는 않고</strong> 넘겨받은 this 및 인수들을 바탕으로 <strong>새로운 함수를 반환</strong>하기만 한다.</p>
<h3 id="요약하자면">요약하자면</h3>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">call</th>
<th align="left">apply</th>
<th align="left">bind</th>
</tr>
</thead>
<tbody><tr>
<td align="left">첫번째 인자</td>
<td align="left">this로 바인딩될 값</td>
<td align="left">this로 바인딩될 값</td>
<td align="left">this로 바인딩될 값</td>
</tr>
<tr>
<td align="left">2번째 인자 이후</td>
<td align="left">(첫번째 인자,a,b,c,...)</td>
<td align="left">(첫번째 인자,[a,b,c,...])</td>
<td align="left">(첫번째 인자,a,b,c,...)</td>
</tr>
<tr>
<td align="left">즉시 호출</td>
<td align="left">O</td>
<td align="left">O</td>
<td align="left">X</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.lazy와  @loadbale/component의 차이점]]></title>
            <link>https://velog.io/@ony_/React.lazy%EC%99%80-loadbalecomponent%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@ony_/React.lazy%EC%99%80-loadbalecomponent%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Tue, 12 Sep 2023 05:19:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/b37250eb-f016-4746-a289-7412603ff3ae/image.png" alt=""> </p>
<p>리액트로 진행한 프로젝트을 라이트하우스로 돌려봤고 그 중 나온 권장 사항이 <a href="https://developer.chrome.com/docs/lighthouse/performance/unused-javascript/?utm_source=lighthouse&amp;utm_medium=devtools">사용하지않는 자바스크립트 줄이기</a>였다. 해당 글에서는 위의 이미지와 같이 리액트에서는 code-split을 추천했고 <code>React.lazy()</code>를 사용하기로 했다. </p>
<p>기존 코드</p>
<pre><code class="language-jsx">import HomePage from &#39;./HomePage/HomePage&#39;;
import MainPage from &#39;./MainPage/MainPage&#39;;
import LoginPage from &#39;./LoginPage/LoginPage&#39;;
...

export default function AppRouter() {
  return (
        ...
</code></pre>
<p>수정된 코드</p>
<pre><code class="language-jsx">const HomePage = lazy(() =&gt; import(&#39;./HomePage/HomePage&#39;));
const MainPage = lazy(() =&gt; import(&#39;./MainPage/MainPage&#39;));
const LoginPage = lazy(() =&gt; import(&#39;./LoginPage/LoginPage&#39;));
...

export default function AppRouter() {
  return (
    &lt;Suspense fullback={&lt;Spinner /&gt;}&gt;
      ....
      &lt;/Suspense&gt;</code></pre>
<p>이렇게 코드를 수정했다. <code>React.lazy()</code>를 사용한다면 <code>&lt;Suspense&gt;</code>도 필수적으로 사용해야한다. 이때 fullback안의 요소가, 비동기 작업이 자식 컴포넌트에서 처리되기 전에 보이게 된다.</p>
<p>이를 사용하여 번들의 크기를 줄이고, 초기 렌더링에서 사용되지 않는 컴포넌트를 불러오는 작업을 지연시킬 수 있다.</p>
<br/>



<hr>
<h3 id="위의-라이트하우스에서-권장하는-loadbalecomponent는-lazy와-어떤-차이가-있을까">위의 라이트하우스에서 권장하는 <strong>@loadbale/component</strong>는 lazy와 어떤 차이가 있을까?</h3>
<p><img src="https://velog.velcdn.com/images/ony_/post/5882db6a-c55c-4eff-aefe-a19f5892df02/image.png" alt="">
어떤 차이점이 있는지 궁금해 찾아보니 <a href="https://www.geeksforgeeks.org/what-are-the-differences-between-react-lazy-and-loadable-components/">What are the differences between React.lazy and @loadable/components?</a>라는 글을 발견했고 위와 같은 표를 확인할 수 있었다. </p>
<ol>
<li><p>SSR에서는 <code>React.lazy</code>를 사용할 수 없다.</p>
</li>
<li><p><code>&lt;Suspense/&gt;</code>의 경우 <code>React.lazy()</code>에서는 필수적이었던 반면에  <code>@loadbale/component</code>에서의 경우 선택적으로 사용할 수 있다고 한다.</p>
</li>
<li><p><code>@loadbale/component</code>에서는 <strong>Library Splitting</strong>을 render props를 사용해 지원한다고 한다.</p>
<pre><code class="language-jsx">import loadable from &#39;@loadable/component&#39;
const Moment = loadable.lib(() =&gt; import(&#39;moment&#39;))
function FromNow({ date }) {
 return (
     &lt;div&gt;
         &lt;Moment fallback={date.toLocaleDateString()}&gt;
             {({ default: moment }) =&gt;
                 moment(date).fromNow()}
         &lt;/Moment&gt;
     &lt;/div&gt;
 )
}
</code></pre>
</li>
</ol>
<pre><code>
4. 웹팩은 **full dynamic imports 혹은 aggressive** 코드 스플리팅을 지원한다. 따라서 재사용 가능한 Loadable Component를 만들고 dynamic import 함수에 dyname value를 넘겨서 사용할 수 있다.
```jsx
import loadable from &#39;@loadable/component&#39;
const AsyncPage = loadable(props =&gt;
    import(`./${props.page}`))
function MyComponent() {
return (
    &lt;div&gt;
    &lt;AsyncPage page=&quot;Home&quot; /&gt;
    &lt;AsyncPage page=&quot;Contact&quot; /&gt;
    &lt;/div&gt;
)
}</code></pre><p>이번 프로젝트는 CSR이었기에 lazy()를 사용하였으나 다음에는 SSR에서 @loadbale/component를 사용해보려고한다. 또 3번도 three.js를 쓸 때 번들이 커서 라이트하우스에 자꾸 걸리는게 마음에 걸렸는데 이를 사용하면 해결할 수 있을 것 같아 다음 프로젝트에서 사용해보려고 한다!</p>
<hr>
<p>그 외 참고글</p>
<p><a href="https://evan6-6.tistory.com/110">https://evan6-6.tistory.com/110</a>
<a href="https://lakelouise.tistory.com/327">https://lakelouise.tistory.com/327</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 캡쳐링과 버블링 stopPropagation()]]></title>
            <link>https://velog.io/@ony_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%BA%A1%EC%B3%90%EB%A7%81%EA%B3%BC-%EB%B2%84%EB%B8%94%EB%A7%81-stopPropagation</link>
            <guid>https://velog.io/@ony_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%BA%A1%EC%B3%90%EB%A7%81%EA%B3%BC-%EB%B2%84%EB%B8%94%EB%A7%81-stopPropagation</guid>
            <pubDate>Thu, 31 Aug 2023 11:33:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/d62f6a40-9096-49c2-9c28-1e6352a8a4dd/image.png" alt=""></p>
<p>브라우저 화면에서 이벤트가 발생하면 브라우저는 가장 먼저 이벤트를 찾는다.</p>
<h3 id="캡쳐링-단계">캡쳐링 단계</h3>
<p>브라우저가 이벤트 대상을 찾아갈 때 가장 상위의 window 객체부터 document, body 순으로 DOM 트리를 따라 내려가는 단계.
이때 이벤트 대상을 찾아가는 과정에서 브라우저는 중간에 만나는 모든 캡처링 이벤트 리스너를 실행시킨다!</p>
<h3 id="버블링-단계">버블링 단계</h3>
<p>그리고 이벤트 대상을 찾고 캡처링이 끝나면 이제 다시 DOM 트리를 따라 올라가는 단계.
이때도 만나는 모든 버블링 이벤트 리스너를 실행한다!</p>
<p>-&gt; 이러한 과정을 <code>이벤트 전파(event propagation)</code>라고 한다.</p>
<h3 id="eventstoppropagation">event.stopPropagation()</h3>
<p>이는 이름 그대로 이벤트 전파(Propagation)를 막는(stop) 메서드.
예를 들어 자식을 클릭했을때는 그 기능만 실행되어야하는데, 부모에 이벤트 리스너가 있을 경우 해당 코드도 실행할 때 이 메서드를 사용하면 막을 수 있다!</p>
<img src="https://velog.velcdn.com/images/ony_/post/16b279f6-fcc6-455d-ab9a-162cb84a8737/image.png" width="300px">

<p>위와 같은 프로젝트로 진행한 웹툰 플랫폼을 예를 들어보자면</p>
<ol>
<li><p>부모요소인 웹툰 화면을 클릭할 경우
헤더와 바텀네비게이션이 토글로 보였다 안보였다 해야함.</p>
</li>
<li><p>자식요소인 핀을 클릭할 경우 
댓글 말풍선이 토글로 생겼다 말았다 해야함.</p>
</li>
</ol>
<p>1번째 경우는 문제가 되지않으나 <strong>2번째 경우 이벤트 전파 현상으로 인해서 1번의 기능 헤더와 바텀네비가 토글되는 경우가 발생</strong>했다.</p>
<p>따라서, 이때 2번째 기능 구현 함수 내에 첫번째로 <code>e.stopPropagation()</code>를 선언해주면, 핀을 클릭했을때 아래의 1번째가 아닌 2번째 이미지처럼 핀댓글만 보이게되는 것을 확인할 수 있다!</p>
<table>
<thead>
<tr>
<th>e.stopPropagation()이 없을 때</th>
<th align="center">e.stopPropagation()이 있을 때</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/ony_/post/c67e9634-d198-4357-9854-8486b52b8178/image.png" width="300px"></td>
<td align="center"><img src="https://velog.velcdn.com/images/ony_/post/65d6e25e-0d4f-41b8-aa8b-235409762e72/image.png" width="300px"></td>
</tr>
</tbody></table>
<pre><code class="language-jsx">    // 각각 클릭했을 때
    const onOpen = (e) =&gt; {
        e.stopPropagation();
        setOpen(!open);
    };</code></pre>
<p>이벤트 전파 과정과 해당 메서드에 대해 몰랐을 때는 클릭 위치값을 연산해서 해당 부분 클릭 시에는 부모에 있는 함수가 실행되지 않도록하는..코드를 작성했는데,🥲 이 메서드를 사용해서 더 정확하고 깔끔한 코드를 작성할 수 있었다!</p>
<hr>
<p>캡쳐링 버블링 이미지 출처 - 제주코딩베이스 캠프</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 제주 웹 컨퍼런스 연사 회고]]></title>
            <link>https://velog.io/@ony_/%EC%A0%9C%EC%A3%BC-%EC%9B%B9-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-2023-%EC%97%B0%EC%82%AC-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@ony_/%EC%A0%9C%EC%A3%BC-%EC%9B%B9-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-2023-%EC%97%B0%EC%82%AC-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 15 Aug 2023 18:39:55 GMT</pubDate>
            <description><![CDATA[<p>제주 웹 컨퍼런스 연사 참여 후 강제(?) 제주 1주살이 후 쓰는 글.</p>
<h3 id="📗-i-canvas-svg-무료-책-집필기를-담아">📗 I CANVAS SVG 무료 책 집필기를 담아</h3>
<p>인터랙션 웹을 계기로 시작하게 된 개발이었기에 이를 구현하기 위해서 일단 기초적으로 알아야할 것에는 무엇이 있을까하는 의문을 가졌고, 여러 이유를 바탕으로 <code>&lt;canvas&gt;</code>와 <code>&lt;svg&gt;</code>를 꼽았고 팀원 분들과 함께 2, 3개월 여간  이에 대한 책을 집필했고 6월 중순 <a href="https://ridibooks.com/books/2773000070">&#39;I CANVAS SVG&#39; 책</a>이 출판됐다. 지금 생각해도 뿌듯했던 순간😊 제주 웹 컨퍼런스에 연사자로 참여하고 싶었기에 바쁜 최종 프로젝트 중에도 함께 집필한 팀원들에게 의견을 물어보고 참여의사가 있는 분들과 함께 지원하여 참여하게 되었다!</p>
<h3 id="👩💻-팀장으로서-참여한-연사">👩‍💻 팀장으로서 참여한 연사</h3>
<p>책 집필 기간에는 민지님이 팀장을 해주셨지만 연사에서는 빠지시기도하셨고, 뭔가 내가 가장(?) 의지가 커보였기에 팀장이 되었다. 주도적으로 발표 전체 흐름을 짜고, 피피티 전체를 제작했다. 어짜피 피그마 작업이다보니 혼자 작업하는게 편해서 하겠다고 했는데 총 84장의 PPT를 이사와 병행하면서 혼자 만들다보니 생각보다 힘들었다. 게다가 둘쨋날에 진행하다보니 첫쨋날 다른 분들의 연사를 듣고 고치고 싶은 부분이 생겨 전날밤에도 다시 만들고, 당일 오전 연습 중에도 스크린 크기가 작아 코드 크기를 키운다고 다시 뽑아서 최소 15번은 넘는 수정과정을 거쳤다.🥲 이런 과정에서 팀원들에게 피디님, 감독님 등의 호칭을 얻기도 했고(ㅋㅋ) 이렇게 열심히 참여했기에 발표하며 떨리기도 했지만 다른 개발자분들이 이런 우리 이야기를 들어주시는 것이 감사하고 뿌듯했다.😄</p>
<h3 id="🍊-즐거웠던-웹-컨퍼런스와-제주여행">🍊 즐거웠던 웹 컨퍼런스와 제주여행</h3>
<p>처음으로 참여해본 웹 컨퍼런스였기에 다른 시니어 개발자분들의 이야기 본인들의 경험을 듣는게 유익했고 즐거웠다. 테크니컬 라이팅과 관련된 연사를 들었었는데 문서화에 대한 중요성을 다시 한번 깨닫는 계기였다. 휴식 라운지에 있는 마들렌도 맛있었고ㅋㅋ 부트캠프에서 온라인으로만 뵈었던 다른 개발자분들 운영진분들과 소통할 수 있는 기회라 좋았다. 여행을 좋아하기도하고 사람들과 시간을 지냈다면 혼자만의 시간이 필요한 사람이기에 이틀간에 연사를 잘 즐기고 3일간의 혼자 제주여행 기간을 가졌다. 라고 하고싶지만 육지로 돌아오는 수요일에 태풍으로 인해 결항이 됐고 숙소를 연장하고 비싼 금요일 항공권을 다시 예매했다.😂 태풍이 왔던 수요일엔 숙소에 박혀서 사이드프로젝트 작업하며 보냈지만, 태풍 이후로는 덥지도 않고 너무나 좋은 날씨였기에 이번 여행 중 가장 행복한 시간을 만끽하고 육지로 돌아올 수 있었다.😊 
이제는 현실로 돌아왔으니 스터디랑 리팩토링에 계속 참여하고 공부하면서 이력서를 넣으려고 한다. 화이팅!!!
<img src="https://velog.velcdn.com/images/ony_/post/cdca1d7d-62d1-4065-935f-61cd9ef0e601/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프엔스쿨 5기 팀프로젝트가 끝나고]]></title>
            <link>https://velog.io/@ony_/%ED%94%84%EC%97%94%EC%8A%A4%EC%BF%A8-5%EA%B8%B0-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EA%B0%80-%EB%81%9D%EB%82%98%EA%B3%A0</link>
            <guid>https://velog.io/@ony_/%ED%94%84%EC%97%94%EC%8A%A4%EC%BF%A8-5%EA%B8%B0-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EA%B0%80-%EB%81%9D%EB%82%98%EA%B3%A0</guid>
            <pubDate>Sat, 08 Jul 2023 16:54:08 GMT</pubDate>
            <description><![CDATA[<p>멋사 프론트엔드스쿨 5기 최종 팀프로젝트가 끝난 시점에서 쓰는 회고글.</p>
<h3 id="👩👩👧👦-개발자들과-첫-프로젝트">👩‍👩‍👧‍👦 개발자들과 첫 프로젝트</h3>
<p>디자이너들과 혼자 개발자로서 팀플에 참여한 적은 있어도 개발자들과 협업한 적은 없었고, 처음이었기에 기대도 되고 두렵기도 했다. 혼자서 깃허브에 올리는 것만이 아닌 같이 깃허브를 사용하면서 브랜치 전략에 대해서도 알게 되고 깃허브 데스크탑에도 익숙해졌다. 혼자 개발할 때는 한번도 신경써본적 없었던 변수명 컨벤션을 맞추고, 코드 스타일에 대해 이야기하는게 처음엔 어색하고 신기했지만 이제는 필요하다는 것을 안다. 또한 외에도 프론트엔드 개발자로서 내가 가진 디자인 지식에 대한 이점도 많이 느낄 수 있었다! (이쪽 이론 공부도 더하면 좋을 것 같지만 해야할 개발 공부가 차고 넘쳐서 일단은 개발 공부 먼저) </p>
<h3 id="📄-문서화의-중요성">📄 문서화의 중요성</h3>
<p>노션에 그날 배운 것을 공유하거나 readme 작성하는 것 등 회의록 등 문서화 팀장인 미현님 덕분에 했던 것들이 최종 발표에서 강사님들의 피드백을 들으면서 꽤나 중요하다는 것을 느꼈다. 오류가 발생하면 캡쳐하거나 기록할 생각은 못하고 그냥 바로 해결하기 위해서 몰두하다보니 오류해결을 기록하기가 쉽지않았는데, 앞으로도 기록을 중요시하고 정리하는 습관을 가지도록 해야겠다고 깨달았다.</p>
<h3 id="✏️-일단-써보자-라이브러리">✏️ 일단 써보자, 라이브러리!</h3>
<p>이번 프로젝트에서 recoil, react-query, threejs 등을 처음 사용하게 되었는데, 하기 전부터 겁먹지말고 시도해보는게 중요하다는걸 깨달았다. 그리고 공부와 동시에 프로젝트에 적용해보는 것 만큼 라이브러리를 이해하고 공부에 재미를 붙이는 방법이 없는 것 같다! 앞으로는 라이브러리를 공부한다!라는 생각보다 만들고 싶은 거를 먼저 정한 다음에 이를 편하게 해주는 것들을 적용해본다는 생각으로 다가가면 접근이 쉬울 것 같다고 생각했다! 물론 자세한 이론도 필요하지만 적용으로 접근하고 이후에 확인하는 식으로!?</p>
<h3 id="🛞-리팩토링-리팩토링">🛞 리팩토링~ 리팩토링~</h3>
<p>뭔가 말을 두번 반복했더니 동글동글한 느낌을 주지만<del>~ 실제로는 번거롭고 고난이 예상되는 리팩토링! 메인기능에 드래그가 가능하도록 사용성을 높이고, 포탈을 사용한 모달만들기, 스크롤 위치 기억하기, 반응형으로 작업하는 등 (디자인하기 싫은데...) 할 게 태산이다!ㅎㅎ 그래도 이런게 다 도움이 되는 것들이니까</del> 내가맡은 부분이 아니더라도 코드리뷰를 열심히 할 것! 이제 이력서, 개인프로젝트 리팩토링, 컨퍼런스 연사 준비, 기술면접 스터디 등... (이사준비까지😭) 끝나도 끝난게 아닌 5.5기 서울로 이사가면 부캠분들과 더 가까워지니 같이 스터디하면서 취뽀하기를 ✊✊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좌표 위치 - clientX, offsetX, pageX, screenX, scrollY, pageYOffset 의 차이점]]></title>
            <link>https://velog.io/@ony_/%EC%A2%8C%ED%91%9C-%EC%9C%84%EC%B9%98-clientX-offsetX-pageX-screenX-scrollY-pageYOffset-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@ony_/%EC%A2%8C%ED%91%9C-%EC%9C%84%EC%B9%98-clientX-offsetX-pageX-screenX-scrollY-pageYOffset-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Sun, 28 May 2023 03:54:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ony_/post/a6351469-67fc-4431-8c49-f79464fe413d/image.png" alt="https://stackoverflow.com/questions/9262741/what-is-the-difference-between-pagex-y-clientx-y-screenx-y-in-javascript">
<img src="https://velog.velcdn.com/images/ony_/post/18e99541-6df8-4087-9070-74142ba78a5c/image.png" alt=""></p>
<h3 id="1-clientx-clienty">1. ClientX, ClientY</h3>
<p>현재 보여지는 <strong>브라우저 화면 뷰포트 기준</strong>으로 가로, 세로 좌표를 반환</p>
<h3 id="2-offsetx-offsety">2. OffsetX, OffsetY</h3>
<p><strong>이벤트 대상을 기준으로</strong>  상대적인 좌표를 반환 ex) 화면 중앙의 박스 요소에서 클릭한 위치를 찾으면 박스위 왼쪽 모서리 좌표가 0으로 측정됨</p>
<h3 id="3-pagex-pagey">3. pageX, pageY</h3>
<p><strong>이전 스크롤을 포함한 전체 문서</strong>를 기준으로 x,y 좌표를 반환 </p>
<h3 id="4-screenx-screeny">4. screenX, screenY</h3>
<p><strong>모니터 화면을 기준</strong>으로 x,y 좌표를 반환</p>
<blockquote>
<p> 이미지 출처</p>
</blockquote>
<p><a href="https://velog.io/@klloo/JavaScript-%EB%A7%88%EC%9A%B0%EC%8A%A4-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EC%A2%8C%ED%91%9Cscreen-page-client-offset">마우스 포인터 좌표</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[창, 요소 높이너비위치 - clientHeight, offsetHeight, innerHeight, OuterHeight 의 차이점]]></title>
            <link>https://velog.io/@ony_/%EC%B0%BD-%EC%9A%94%EC%86%8C-%EB%86%92%EC%9D%B4%EB%84%88%EB%B9%84%EC%9C%84%EC%B9%98-clientHeight-offsetHeight-innerHeight-OuterHeight-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@ony_/%EC%B0%BD-%EC%9A%94%EC%86%8C-%EB%86%92%EC%9D%B4%EB%84%88%EB%B9%84%EC%9C%84%EC%B9%98-clientHeight-offsetHeight-innerHeight-OuterHeight-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Sat, 20 May 2023 16:03:08 GMT</pubDate>
            <description><![CDATA[<h2 id="element-기준">Element 기준</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/7ab22618-ebdb-40ed-bd47-3db9930c860e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ony_/post/db55e758-0d58-41c7-935c-72bd99f3db51/image.png" alt=""></p>
<h3 id="clientheight">clientHeight</h3>
<p><code>box-sizing: content-box</code>일때 : 요소 + padding 의 크기
<code>box-sizing: border-box</code>일때 : 요소 + padding + ScrollBar + border 의 크기</p>
<h3 id="offsetheight">offsetHeight</h3>
<p>요소의 크기 + padding + ScrollBar + border 의 크기</p>
<h3 id="scrollheight">scrollHeight</h3>
<p>보이는 영역이 아니라 스크롤되는 실제 영역의 크기(overflow로 scroll이 있을 때) <strong>(border는 포함 X)</strong></p>
<h3 id="getboundingclientrect">getBoundingClientRect()</h3>
<p>랜더링된 크기 / transform:scale 적용된 엘리먼트 영역을 구할 때 사용.</p>
<p><br><br></p>
<h2 id="window-기준">Window 기준</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/6ceed7af-6b22-49dc-8792-66bedf9ada5c/image.png" alt=""></p>
<h3 id="innerheight">innerHeight</h3>
<p>안쪽 창 영역의 높이와 너비</p>
<h3 id="outerheight">outerHeight</h3>
<p>메뉴바, 툴바 모두 포함한 전체 창 영역의 높이와 너비</p>
<br>

<blockquote>
<h2 id="jquery---바닐라js에는-없다">JQuery ! ! 바닐라JS에는 없다</h2>
<p><img src="https://velog.velcdn.com/images/ony_/post/bcf73b87-055e-48d3-8d4e-2c3aee72177c/image.png" alt=""></p>
</blockquote>
<h3 id="innerheight-1">innerHeight()</h3>
<p>요소의 크기 + padding의 크기</p>
<h3 id="outerheight-1">OuterHeight()</h3>
<p>요소의 크기 + padding + border 의 크기</p>
<h3 id="outerheighttrue">OuterHeight(true)</h3>
<p>요소의 크기 + padding + border + margin의 크기</p>
<p><br><br></p>
<hr>
<h2 id="element-기준-위치값">Element 기준 위치값</h2>
<h3 id="offsettop-offsetleft">offsetTop, offsetLeft</h3>
<p>offsetParent 기준으로 요소가 각각 아래, 오른쪽으로 얼마나 떨어져 있는지를 나타내는 값</p>
<blockquote>
<p>offsetParent – 위치 계산에 사용되는 가장 가까운 조상 요소나 td, th, table, body</p>
</blockquote>
<h3 id="scrolltop-scrollleft">scrollTop, scrollLeft</h3>
<p>스크롤바가 오른쪽, 아래로 움직임에 따라 가려지게 되는 요소 콘텐츠의 너비와 높이</p>
<h3 id="번외-windowscrolly--windowpageyoffset">번외. window.scrollY / window.pageYOffset</h3>
<p>현재 스크롤된 위치! !
document가 수직으로 얼마나 스크롤됐는지 픽셀 단위로 반환
scrollY는 IE에서 동작하지 않으므로, 노후 환경을 신경써야할때는 pageYOffset 사용하기</p>
<blockquote>
<p>이미지출처</p>
</blockquote>
<p><a href="https://ko.javascript.info/size-and-scroll">모던자바스크립트 요소사이즈와 스크롤</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] for in, for of 그래서 무슨 차이?]]></title>
            <link>https://velog.io/@ony_/JavaScript-for-in-for-of-%EA%B7%B8%EB%9E%98%EC%84%9C-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@ony_/JavaScript-for-in-for-of-%EA%B7%B8%EB%9E%98%EC%84%9C-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Tue, 18 Apr 2023 16:30:59 GMT</pubDate>
            <description><![CDATA[<p>가장 자주 사용하는 for문이지만 헷갈렸던 <code>for in</code>과 <code>for of</code>의 차이점과 사용하는 경우에 대해 짚고 넘어가려고 한다.</p>
<h2 id="📗-for-in">📗 for in</h2>
<blockquote>
<ul>
<li>value가 아닌 <code>key</code>에 해당한 값이 변수로 반복된다. (객체에 적합)</li>
</ul>
</blockquote>
<ul>
<li><strong>enumerable(열거 가능한)</strong> 것들만 출력한다.</li>
<li>IE에서 사용가능하다.</li>
</ul>
<p>배열의 경우</p>
<pre><code class="language-js">let arr1 = [10, 20, 30, 40, 50]

for (const i in arr1) {
      console.log(i) //index를 가져온다 
      // 0 1 2 3 4 
}
</code></pre>
<p>객체의 경우</p>
<pre><code class="language-js">let obj1 = {
    &#39;one&#39;: 10,
    &#39;two&#39;: 20
}

for (const i in obj1) {
    console.log(i) //key를 가져온다
      // one two
     console.log(obj1[i]) //value를 출력 
      // 10 20
}</code></pre>
<p>console.log를 찍어보면 i에 <strong>배열의 경우 index값이,
객체의 경우 key 값이</strong> 들어가게 된다. 
키값과 밸류를 같이 보고 싶으시다면 key와 밸류값에 접근하는 방법인 <code>객체명[키값]</code>을 입력해서 코딩하면 된다.</p>
<p>배열의 반복을 위해서는 추천되지 않고, Array.prototype.forEach(), for...of가 이미 존재한다. 그렇다면..</p>
<p> 💡 *<em>언제 for in을 사용할까? *</em></p>
<ul>
<li>실질적으로 디버깅을 위해 사용</li>
<li>키-값 쌍이 선호되는 데이터의 경우(속성이 &quot;key&quot;의 역할을 함) 특정 값을 가진 키가 있는지 확인하려는 경우</li>
</ul>
<br>

<hr>
<h2 id="📕-for-of">📕 for of</h2>
<blockquote>
<ul>
<li>배열에서 <code>value</code>에 해당한 값이 변수로 반복된다. (배열에 적합)</li>
</ul>
</blockquote>
<ul>
<li><strong>iterable(반복가능한)</strong> 객체를 출력한다.</li>
<li>iterable 객체에는 String, Array, Map, Set 등이 있다. <strong>(Object는 X)</strong></li>
<li>IE 지원이 불가하다.</li>
</ul>
<p>배열의 경우 O</p>
<pre><code class="language-js">let arr2 = [10, 20, 30, 40, 50]

for (const item of arr2) {
    console.log(item) //item(value)을 가져온다!
}</code></pre>
<p>문자열의 경우 O</p>
<pre><code class="language-js">
for (const item of &#39;hello world&#39;) {
    console.log(item)
  // h e l l o w o r l d
}</code></pre>
<p>객체의 경우 X</p>
<pre><code class="language-js">let obj2 = {
    &#39;one&#39;: 10,
    &#39;two&#39;: 20
}

// 오류발생 !!
for (const item of obj2) { //of뒤에 iterable한 것이 나와야한다. 
    console.log(item)
}</code></pre>
<hr>
<p>참고글
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for...in">for...in MDN</a>
<a href="https://velog.io/@onea/JS-for-...of%EC%99%80-for-...in%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90">[JS] for ...of와 for ...in의 차이점</a></p>
]]></description>
        </item>
    </channel>
</rss>