<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sangheon.log</title>
        <link>https://velog.io/</link>
        <description>Front-end Developer</description>
        <lastBuildDate>Sun, 12 Feb 2023 13:37:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sangheon.log</title>
            <url>https://velog.velcdn.com/images/sangheon-k/profile/ad304171-0787-4565-9d12-224e767f04b6/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sangheon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sangheon-k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2차 프로젝트 기술 회고 - 2]]></title>
            <link>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0-2-vc06orkw</link>
            <guid>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0-2-vc06orkw</guid>
            <pubDate>Sun, 12 Feb 2023 13:37:36 GMT</pubDate>
            <description><![CDATA[<h2 id="상품-리스트">상품 리스트</h2>
<p>2차 프로젝트는 <strong>Product Manager</strong>로서 사이트의 기획의도 &amp; 브랜드 방향성을 고민함과 동시에, 이 기획을 <strong>테크적으로 어떻게 풀어내며 나아갈지</strong> 고민하는 시간을 많이 가졌다.</p>
<p>그 고민의 과정 중 하나는 
유저가 상품에 어떻게 접근하는 것이 좋은가? 라는 질문에 대한 답변을 찾는 것이었다.</p>
<p>중고상품을 찾는 과정이 쉬웠으면 좋겠고, 어디에서든 접근할 수 있도록 편했으면 좋겠다는 생각에 NavBar <strong>하단</strong>에 <strong>카테고리 메뉴</strong>를 바로 두어 클릭하면 관련 상품 리스트를 확인 할 수 있도록 기획하였고, <strong>상단</strong>에 고정된 <strong>검색창</strong>을 두어 어느 페이지에서든 유저가 원하는 상품을 검색할 수 있도록 작업을 요청하였다.</p>
<p>작업의 후반부가 되어 구현에 어려움을 겪는 팀원을 돕기 위해 관련 로직을 손으로 적어가며 다시한번 기술적으로 어떻게 접근해야 할지 고민하는 시간을 가졌다. </p>
<h2 id="페이지-및-라우터-재사용">페이지 및 라우터 재사용</h2>
<p>프론트엔드 관점으로 위의 두가지 접근 방법을 하나의 라우터(<code>/products</code>)로 
하나의 상품리스트 페이지에 <strong>카테고리별 상품리스트</strong>,
<strong>검색결과에 해당하는 상품리스트</strong>를 출력하려고하다보니 각각의 로직을 
하나의 페이지 컴포넌트 안에서 함께 처리하는 것이 필요하게 되었다. </p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/4dd46f3b-a239-4025-b43e-23faa0d0a22c/image.png" alt=""></p>
<p>다시 한번 정리해보면,
사용자가 <strong>상품 리스트 페이지</strong>에서 상품들을 확인하는 방법은 2가지로 </p>
<ol>
<li>NavBar의 <strong>카테고리 링크</strong> 클릭</li>
<li>검색창에 <strong>검색어 입력</strong> 후 Enter 이다.</li>
</ol>
<p>카테고리 링크를 누르는 경우와 검색하는 경우 모두 백엔드와의 통신에서 쿼리스트링으로 세팅이 되어있기 때문에, ProductList 페이지에서 쿼리스트링을 읽어와 통신하는 url로 사용하면 되는 상황이었다.</p>
<p><a href="https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0">2차 프로젝트 기술 회고 - 1</a> 에서 언급하였듯 
상품리스트가 필요한 경우 페이지 단이 아닌 공통 컴포넌트 안에서 <code>fetch</code> 가 일어나도록 작업되었기 때문에 url만 전달되면 되는 상황이므로 queryString 정보를 받아와 공통 컴포넌트로 전달해주는 방식으로 <strong>두가지 유입</strong>(NavBar 카테고리 클릭, 검색어 입력 후 Enter) 모두 처리하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/ed95ace8-9fc1-429c-b1df-0c71ec2b7bbd/image.png" alt=""></p>
<p>코드 작성 자체는 간단하게 처리 되었지만 내부 로직을 생각하느라 고민하는 시간이 있었다.
두가지 방법으로 유입되는 상황에서 각각의 상황마다 에러가 나지 않도록 고민한 결과 정상적으로
작동되는 모습을 확인하게 되어 다행이였다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2차 프로젝트 기술 회고 ]]></title>
            <link>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 12 Feb 2023 11:10:59 GMT</pubDate>
            <description><![CDATA[<h2 id="공통-컴포넌트-사용">공통 컴포넌트 사용</h2>
<p>2차 프로젝트를 진행하며 상품 리스트의 UI가 여러 페이지에서 사용하는 것이 확인되었다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/28e1cfb5-fa37-4c65-96f5-8c1988747749/image.png" alt=""></p>
<p>메인과 상품 리스트 페이지, 마이페이지 3개의 탭메뉴에서 사용되는 비슷한 구조의 UI를 
하나의 컴포넌트 묶음으로 재사용하기 위하여 팀원들과 논의 후 공통 컴포넌트로 만들어주었다.</p>
<br />

<p>🔸 <strong>List.jsx</strong>
<img src="https://velog.velcdn.com/images/sangheon-k/post/c824d249-9ab3-47a1-b8e0-886f536ed4a9/image.png" alt=""></p>
<p>상위 페이지에서 네트워크 통신이 필요한 url만 넘겨주면
<code>products</code> <code>state</code>에 리스트 데이터가 들어가도록 구조를 잡아주고 </p>
<p>리스트 한 라인에 보여질 상품 갯수가 경우에 따라 다르므로 <code>column</code> <code>props</code>를 받아 
grid에 <code>repeat</code> 함수를 이용하여 레이아웃을 잡아주었다.</p>
<br />

<h2 id="예외-상황">예외 상황</h2>
<p>🔸 <strong>판매상태 변경 함수</strong>
<img src="https://velog.velcdn.com/images/sangheon-k/post/5ca45bb1-9ce2-4895-88db-7d64b84dd9ce/image.png" alt=""></p>
<p>공통 컴포넌트 작업을 하며 고민했던 사항 <strong>두 가지 중 한 가지</strong>는 fetch된 데이터만 상위에서 props로 받아올지, <code>&lt;List /&gt;</code> 컴포넌트에서 자체적으로 fetch를 진행할지였다.</p>
<p><code>&lt;List /&gt;</code> 컴포넌트에서 자체적으로 fetch를 진행하는 경우 페이지마다 들어오는 데이터 타입이 다른경우 분기처리를 해주어야하는 이슈가 있었고, 상위에서 데이터가 담긴 state만 <code>props</code>로 받아오는 경우 각 페이지마다 fetch하는 함수가 작성되어야 함으로 비슷한 코드가 페이지마다 작성되어 전체적인 코드량이 많아지는 문제가 있었다. </p>
<p>따라서 <strong>전체적인 코드량을 줄이는 방향이 더 좋다고 생각되어</strong><code>&lt;List /&gt;</code> 컴포넌트에서 url만 <code>props</code> 로 받아 통신하는 방향으로 작업하여 페이지 컴포넌트의 코드량을 많이 줄일 수 있게 되었다.</p>
<br />

<p><strong>그리고 다른 한 가지 고민사항</strong>은 <strong>판매 상태 변경의 통신 횟수 문제</strong> 였다.
판매상태를 <code>selectOption</code> 으로 변경하는 경우 통신이 일어나도록 로직을 구성하였는데
통신 후 전체적으로 변경된 데이터를 다시 &quot;GET&quot;해서 DB의 데이터와 프론트단의 데이터의 싱크를 맞춰 안정성을 높이는 방향과, 
통신 후 변경 성공/실패 관련 메세지만 전달받고 프론트단에서 정보를 변경해주어 통신 횟수를 줄이는 방향이 있었다. </p>
<p>개인적으로 적용한 방법은 후자로 <strong>통신횟수를 줄이는게 더 좋다고 생각하기에 선택</strong>하였다. 
통신이 완료되면 response로 전달되는 성공/실패에 대한 메시지로 데이터베이스의 변경 여부를 이미 확인 할 수 있고, 횟수가 줄어듬으로 서버에 부담이 덜어지고 비용 또한 절약되는 일석 이조의 효과라고 생각하였기 때문이다.</p>
<p>그리하여, 프론트 단에서 처음 fetch한 후 저장된 <code>products</code> 아이템을 다시 가공함으로써 상태 변경에 따라 UI를 다시 그려주는 작업을 하게 되었다.</p>
<br />

<h2 id="그래서-결론은">그래서 결론은?</h2>
<p>여러 페이지에서 함께 쓰이는 컴포넌트의 공통화 작업은 
사용되는 페이지의 구조, fetch 과정, 횟수등 일어날 수 있는 다양한 상황 및 로직을 함께 고민하여야 한다는 사실을 깨닫게 되었다.
지금은 작은 규모의 프로젝트 상황에서 몇 개의 페이지만 고려하며 진행하였지만, 더 큰 규모의 프로젝트에서도 잘 적용할 수 있도록 코어를 단련해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2차 프로젝트 회고]]></title>
            <link>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 11 Feb 2023 06:56:01 GMT</pubDate>
            <description><![CDATA[<h2 id="span-stylecolorb8a990babeespan"><span style="color:#B8A990">BaBee</span></h2>
<p><img src="https://user-images.githubusercontent.com/75000708/218014848-b0d794f5-0c43-47e3-9867-48d2cd9bff82.gif" alt="2메인"></p>
<h2 id="span-stylecolor12b886summeryspan"><span style="color:#12B886">Summery</span></h2>
<p><a href="https://velog.io/@sangheon-k/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1">1차 프로젝트</a>를 팀원 및 Writer의 역할로 잘 마치고 2차 프로젝트는 <strong>Product Manager</strong>로 참여하게 되었다.
&#39;번개장터&#39; 사이트를 리뉴얼하는 미션을 진행했는데 우리 팀은 Planning Metting을 통하여 
육아용품 중고거래 사이트로 개편을 진행하기로 하였다. 
프로젝트 선정 후 2일 동안 피그마로 레이아웃과 데이터 구조 초안을 작성하고 
디자인을 바탕으로 2주간 레이아웃 및 기능 구현하며 느낀점 및 결과물을 정리하고자 한다.</p>
<br />

<h2 id="span-stylecolor12b886작업-기간-및-인원span"><span style="color:#12B886">작업 기간 및 인원</span></h2>
<h3 id="span-stylecolor12b886기간span"><span style="color:#12B886">기간</span></h3>
<p>2023년 1월 30일 ~ 2월 10일 (2주)</p>
<h3 id="span-stylecolor12b886인원span"><span style="color:#12B886">인원</span></h3>
<p>Front-end : <strong>김상헌(Product Manager)</strong> 김지환 이다혜 정다경(Project Manager)
Back-end : 김동규 심예윤
Github : <a href="https://github.com/wecode-bootcamp-korea/41-2nd-BossBaby-frontend">프론트엔드</a>, <a href="https://github.com/wecode-bootcamp-korea/41-2nd-BossBaby-backend">백엔드</a>
협업 : <a href="https://salt-oatmeal-c5b.notion.site/BaBee-7b4bebeb2ef544cc8bb246a7fd8f9b0b">노션</a>, <a href="https://www.figma.com/file/qlFXa4bYmD6sFXV5R9qKf8/TeamE?node-id=0%3A1&amp;t=JnAlM2fXoWgugxta-0">피그마</a>, <a href="https://trello.com/b/LoWKRDWC/team-bossbabe">트렐로</a></p>
<h3 id="span-stylecolor12b886디자인span"><span style="color:#12B886">디자인</span></h3>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/1e6b939e-e1dc-4afb-b927-9de1ff65a5c7/image.png" alt=""></p>
<p>프로젝트 선정 후 2일 동안 피그마로 레이아웃과 <a href="https://salt-oatmeal-c5b.notion.site/230129-API-06671285e7864df7995082247a940bc0">데이터 구조 초안</a>을 작성하였다.
디자인을 바탕으로 2주간 레이아웃 및 기능 구현을 하였다.</p>
<h3 id="span-stylecolor12b886주요-유저-플로우-및-구현된-기능span"><span style="color:#12B886">주요 유저 플로우 및 구현된 기능</span></h3>
<ul>
<li><p><strong>로그인 버튼 클릭</strong> → 모달 오픈 → 카카오 로그인 연결 → 마이페이지로 버튼 변경</p>
</li>
<li><p><strong>키워드 검색</strong> → 상품 리스트 페이지 이용하여 검색 결과 출력 (정렬 기능 제공)</p>
</li>
<li><p><strong>상품 등록</strong> → 메인페이지 : 오늘 입고된 상품 출력
  → 상품 리스트 : 관련 카테고리 리스트 출력
  → 상품 상세페이지 : 상품 정보 확인
  → 마이페이지 : 판매 목록 탭 리스트 출력</p>
</li>
<li><p><strong>상품 리스트</strong> -&gt; 초기 최신순 기준 12개 출력
  → 등록일 기준 : 최신순 정렬 기능
  → 좋아요 기준 : 인기순 정렬 기능
  → 상품가격 기준 : 저가순/고가순 정렬 기능
  → 페이지네이션으로 다음 12개 상품 보기 기능 </p>
</li>
<li><p><strong>마이페이지</strong> →  거래후기 리스트 출력
  → 찜 목록 : 유저가 좋아요 클릭한 상품 리스트 모아서 출력
  → 판매 목록 : 유저가 판매 등록한 상품 리스트 출력 → 판매 상품 상태 변경 기능 
  → 구매 내역 : 유저가 결제완료한 상품 리스트 출력
  → 거래 후기 : 판매자에 대한 리뷰 리스트 출력</p>
</li>
<li><p><strong>결제</strong> → 마이페이지 : 구매 내역 탭 리스트에 출력</p>
</li>
</ul>
<br />


<h2 id="span-stylecolor12b886결과-보기span"><span style="color:#12B886">결과 보기</span></h2>
<h3 id="로그인">로그인</h3>
<p>oAuth방식을 이용한 소셜 로그인 중 카카오 로그인 방법을 채택하여 적용하였다.
로그인 버튼을 클릭하면 모달 창 오픈과 함께 카카오 로그인 페이지로 이동하게 되고 
카카오 로그인이 완료되면 서버에서 인증 후 JWT 토큰을 클라이언트에 발급해 주는 방식이다.
<img src="https://user-images.githubusercontent.com/75000708/218014814-fb8d91b0-9866-4b4a-93ca-950ed729e0f0.gif" alt="1로그인"></p>
<h3 id="메인">메인</h3>
<p>사이트의 이미지를 보여주는 메인 배너 3개를 Slick 라이브러리를 통하여 구현하였으며, 
likes 기준 가장 많은 상품 3가지를 &#39;오늘의 상품 추천&#39; 에서 보여주고
등록일 기준 최대 12가지 상품을 &#39;오늘 입고 됐어요!&#39; 에서 보여준다.
<img src="https://user-images.githubusercontent.com/75000708/218014848-b0d794f5-0c43-47e3-9867-48d2cd9bff82.gif" alt="2메인"></p>
<h3 id="상품-리스트">상품 리스트</h3>
<p>카테고리 별로 상품 리스트를 최대 12개까지 출력해줄 수 있도록 작업되었으며 
쿼리스트링을 이용한 페이지네이션 기능을 이용해 다음 12가지 상품 리스트를 전달받도록 작업 되었다.</p>
<p>오른쪽 상단의 최신순 / 인기순 / 저가순 / 고가순 모두 쿼리스트링을 이용하여 작업되었다. </p>
<p><img src="https://user-images.githubusercontent.com/75000708/218014859-a55acf77-68fa-47c4-8812-1ba7956d43a4.gif" alt="3리스트페이지"></p>
<h3 id="판매하기">판매하기</h3>
<p>유저가 상품을 등록할 때 Preview로 보여주는 이미지 Url을 createObjectURL() API로 구현하였으며, 
실제 서버에 등록될 때는 FormData로 전달되도록 구현 되었다.
이미지는 AWS S3 버킷에 저장 되어있고, 백엔드에서 multer 라이브러리로 처리하였다.</p>
<p><img src="https://user-images.githubusercontent.com/75000708/218014862-a9067853-6bd2-4a71-9e87-3b755a7796ed.gif" alt="4판매하기페이지"></p>
<h3 id="상세-페이지">상세 페이지</h3>
<p>상품 이미지가 2개 이상인 경우 Slick을 이용한 캐러셀 슬라이드로 구현되도록 작업되었으며
페이지 로드시 조회수가 업데이트 되도록 작업하고, 찜(좋아요) 버튼이 추가/제거 되도록 작업 되었다.
바로 구매 버튼 클릭 시 결제 페이지로 이동되도록 구현되었다.</p>
<p><img src="https://user-images.githubusercontent.com/75000708/218014865-33d1b678-6bac-44c8-90b3-44b52b6e4c7d.gif" alt="5상세페이지"></p>
<h3 id="마이페이지">마이페이지</h3>
<p>회원만 입장이 가능하며 카카오 로그인을 통해 받아온 프로필사진, 이름을 출력해주고 
현재 판매중인 총 상품 갯수를 상단에 고정하여 보여준다.</p>
<p>UX를 고려하여 하단 각 탭(찜, 판매, 구매, 후기)에 리스트를 다르게 통신하여 보여주며
&#39;판매 목록&#39; 탭의 경우 사용자가 판매중인 상품의 &#39;판매 상태&#39;를 변경할 수 있도록 작업하였다.</p>
<p><img src="https://user-images.githubusercontent.com/75000708/218014869-c2113f28-d43e-42c8-8bac-37b08e6e1536.gif" alt="6마이페이지"></p>
<h3 id="결제페이지">결제페이지</h3>
<p>회원만 입장이 가능하며 상품 상세 페이지에서 navigate state를 통해 넘어온 상품 상세 정보를
기반으로 총 결제 금액을 다시 계산하여 구매하도록 작업되었다.
<img src="https://user-images.githubusercontent.com/117249829/218239816-88eedc5e-8152-4d24-a278-0191f4d33b06.gif" alt="7결제페이지"></p>
<br />


<h2 id="span-stylecolor12b886회고span"><span style="color:#12B886">회고</span></h2>
<p>1차 프로젝트를 진행하며 느꼈던 아쉬운 점은 미리 데이터 구조를 논의 하지 못하고 
브랜드 기획 및 레이아웃만으로 코드 작업에 들어가 구조의 수정 관련 추가 회의가 많았다는 점이었다.
2차 프로젝트 에서는 이러한 점을 보완하기 위해 레이아웃 밑그림을 그리며 미리 프론트에서 필요한 <a href="https://salt-oatmeal-c5b.notion.site/230129-API-06671285e7864df7995082247a940bc0">데이터 구조</a>를 전달하는 방식으로 보완하고자 노력하였다.</p>
<p>사이트를 기획하며 느꼈던 주요 포인트는 유저가 <em>판매자와 구매자가 동시에 될 수 있다는 점</em> 과 톡기능을 통하여 <em>개인적으로 연락이 가능하다는 점</em> 이었다.
전자의 경우 &#39;판매하기&#39; 페이지를 통하여 
상품이 등록되면 메인 페이지의 &#39;오늘 입고 됐어요!&#39;,<br>카테고리의 상품리스트 페이지, 
마이페이지의 &#39;판매 목록&#39;탭에 상품이 들어오며 플로우를 구현하였는데 
Socket을 이용한 채팅 기능은 시간의 부족으로 구현하지 못한 점이 매우 아쉬웠다.</p>
<p>1주차 스프린트 미팅을 통하여 나온 필수 작업사항들을 많이 구현하고 나서,
2주차 스프린트 미팅을 통하여 초기 기획 내용인 &#39;톡 기능 추가&#39; 와 &#39;페이지 연동의 안정성&#39; 간에
일정 관리 및 방향 선택이 필요하게 되었다. 
의견을 수렴하여 연동의 안정성에 더 초점을 맞추게 되었는데 
마지막까지 열심히 진행하여 많은 에러를 잡고, 데모데이에 <strong>라이브</strong>로 발표 할 수 있어서 너무나도 기뻤다.</p>
<p>마지막 5시까지 작업하며 고생한 <a href="https://github.com/wecode-bootcamp-korea/41-2nd-BossBaby-frontend#6">우리팀</a> 최고다!</p>
<br />

<p><a href="https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0">프로젝트 기술 관점 회고 - 1</a>
<a href="https://velog.io/@sangheon-k/2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%88%A0-%ED%9A%8C%EA%B3%A0-2-vc06orkw">프로젝트 기술 관점 회고 - 2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 객체 맵핑을 통한 탭 메뉴 구현]]></title>
            <link>https://velog.io/@sangheon-k/TIL-%EA%B0%9D%EC%B2%B4-%EB%A7%B5%ED%95%91%EC%9D%84-%ED%86%B5%ED%95%9C-%ED%83%AD-%EB%A9%94%EB%89%B4-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@sangheon-k/TIL-%EA%B0%9D%EC%B2%B4-%EB%A7%B5%ED%95%91%EC%9D%84-%ED%86%B5%ED%95%9C-%ED%83%AD-%EB%A9%94%EB%89%B4-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sun, 05 Feb 2023 00:52:36 GMT</pubDate>
            <description><![CDATA[<p>스타일드 컴포넌트를 이용하여 탭메뉴를 구현하는중
url의 변경사항을 감지하여 하위 컴포넌트를 렌더링해주는 방법을 적용해 보았다.</p>
<pre><code class="language-jsx">const MypageContents = () =&gt; {
  const location = useLocation();
  const url = location.pathname.split(&#39;/&#39;).pop();
  const currentTitle = TAB_LIST.find(item =&gt; item.url === url).title;

  return (
    &lt;Container&gt;
      &lt;Aside&gt;
        &lt;TabMenu&gt;
          {TAB_LIST.map(tab =&gt; (
            &lt;Tab key={tab.id} to={tab.url}&gt;
              {tab.title}
            &lt;/Tab&gt;
          ))}
        &lt;/TabMenu&gt;
      &lt;/Aside&gt;
      &lt;Contents&gt;
        &lt;Title title={currentTitle} /&gt;
        {TAB_MAPPING_OBJ[url]}
      &lt;/Contents&gt;
    &lt;/Container&gt;
  );
};

const Tab = styled(Link)`
  display: inline-block;
  padding: 28px 0;
  color: #333;
  border-top: 1px solid #d9d9d9;
  text-align: center;
  text-decoration: none;
`;

export const TAB_LIST = [
  { id: 0, url: &#39;/mypage/like&#39;, title: &#39;찜 목록&#39; },
  { id: 1, url: &#39;/mypage/sell&#39;, title: &#39;판매 목록&#39; },
  { id: 2, url: &#39;/mypage/buy&#39;, title: &#39;구매 내역&#39; },
  { id: 3, url: &#39;/mypage/reviews&#39;, title: &#39;거래 후기&#39; },
];

export const TAB_MAPPING_OBJ = {
  like: &lt;List column={3} /&gt;,
  sell: &lt;List column={3} selectOpt={true} /&gt;,
  buy: &lt;List column={3} /&gt;,
  reviews: &lt;ReviewList /&gt;,
};

</code></pre>
<p>탭 리스트가 map함수를 통과하며 스타일링된 Link컴포넌트의 to 속성으로 url 키값이 전달된다. 
그 결과 아래와 같은 DOM 형태로 렌더링 되는데 
<code>styled-components</code> -&gt; <code>JSX</code> -&gt; <code>DOM Element</code>로 변환되는 과정을 거치게 된다.</p>
<pre><code class="language-jsx">// JSX
&lt;Link key=0 to=&#39;/mypage/like&#39;&gt;찜 목록&lt;/Link&gt;
...

// DOMElement
&lt;a key=0 href=&#39;/mypage/like&#39;&gt;찜 목록&lt;/a&gt;
...</code></pre>
<p>여기에서 컴포넌트가 렌더링 됨에 따라 상단에 호출해준 <code>url</code> 변수가 계속해서 변경을 감지할 수 있게 되고 </p>
<p><code>&lt;Tab to=&#39;/mypage/like&#39; /&gt;</code> Click 
-&gt; <code>url</code> 변경 
-&gt; 변경된 <code>url</code> 감지하여 맵핑된 객체의 <code>value</code>에 있는 컴포넌트 렌더링 </p>
<p>의 과정을 거쳐 원하는 컴포넌트만 화면에 보여줄 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] styled-components]]></title>
            <link>https://velog.io/@sangheon-k/React-styled-components</link>
            <guid>https://velog.io/@sangheon-k/React-styled-components</guid>
            <pubDate>Sat, 04 Feb 2023 06:05:19 GMT</pubDate>
            <description><![CDATA[<h3 id="정의">정의</h3>
<p>자바스크립트 파일 안에서 css를 작성할 수 있는 방법(CSS-in-JS)중 
대표적인 방법이 <code>styled-components</code>이다.</p>
<p><code>styeld-components</code> 를 사용하면 하나의 컴포넌트 안에서 태그설정, 
스타일 변경, attribute 설정을 함께 할 수 있다.</p>
<h3 id="설치">설치</h3>
<pre><code class="language-bash">npm install styled-components</code></pre>
<h3 id="기본-사용법">기본 사용법</h3>
<pre><code class="language-jsx">// App.js
import React from &#39;react&#39;;
import styled from &#39;styled-components&#39;;

export default function App() {
  return (
    &lt;Container&gt;
      &lt;Title&gt;Hello!&lt;/Title&gt;
    &lt;/Container&gt;
  )
}

const Container = styled.div`
  width: 100%;
`

const Title = styled.strong`
  display: block;
  font-size: 32px;
  line-height: 1.5;
`</code></pre>
<h3 id="응용">응용</h3>
<p><code>styled-components</code> 의 장점은 상황에 따라 특정 속성만 변하는 컴포넌트를 
적은양의 코드로 만들어 줄 수 있다는 점이다.</p>
<h4 id="스타일된-컴포넌트의-재사용">스타일된 컴포넌트의 재사용</h4>
<p><code>&lt;Button /&gt;</code> 을 스타일드 컴포넌트로 선언해준뒤 하위의 <code>&lt;TomatoButton /&gt;</code> 을 
기존 컴포넌트의 스타일 값 + 변경된 스타일의 오버라이딩으로 만들어 줄 수 있다. </p>
<pre><code class="language-jsx">const Button = styled.button`
  background: palevioletred;
  border-radius: 3px;
  border: none;
  color: white;
`

const TomatoButton = styled(Button)`
  background: tomato;
`

return (
  &lt;&gt;
    &lt;Button&gt;I&#39;m purple.&lt;/Button&gt;
    &lt;br /&gt;
    &lt;TomatoButton&gt;I&#39;m red.&lt;/TomatoButton&gt;
  &lt;/&gt;
)</code></pre>
<h4 id="props-전달">props 전달</h4>
<p>아니면 컴포넌트에서 전달해준 <code>props</code>를 스타일의 변수처럼 받을 수 있다는 점을 활용하여 <code>props</code> 로 관련 속성정보를 넘겨받아 변경시켜 줄 수도 있다.</p>
<pre><code class="language-jsx">const Button = styled.button`
  background: ${props =&gt; props.bgColor || palevioletred};
  border-radius: 3px;
  border: none;
  color: white;
`

return (
  &lt;&gt;
    &lt;Button&gt;I&#39;m purple.&lt;/Button&gt;
    &lt;br /&gt;
    &lt;Button bgColor=&quot;tomato&quot;&gt;I&#39;m red.&lt;/Button&gt;
  &lt;/&gt;
)</code></pre>
<h4 id="themeprovier의-사용">ThemeProvier의 사용</h4>
<pre><code class="language-jsx">// index.js
import { ThemeProvider } from &#39;styled-components&#39;

const theme = {
  purple: &#39;palevioletred&#39;,
  red: &#39;tomato&#39;,
};

&lt;ThemeProvider theme={theme}&gt;
  &lt;App /&gt;
&lt;/ThemeProvider&gt;



// App.js
const Button = styled.button`
  background: ${props =&gt; props.theme.purple};
  border-radius: 3px;
  border: none;
  color: white;
`

const TomatoButton = styled(Button)`
  background: ${props =&gt; props.theme.red};
`


return (
  &lt;&gt;
    &lt;Button&gt;I&#39;m purple.&lt;/Button&gt;
    &lt;br /&gt;
    &lt;TomatoButton&gt;I&#39;m red.&lt;/TomatoButton&gt;
  &lt;/&gt;
)</code></pre>
<p>index.js에 <code>ThemeProvier</code> 를 만들어준 뒤 <code>theme</code> 을 공급해주면 
스타일 속성을 전역적으로 사용할 수 있는 상태가 된다.
이후 원하는 곳에서 <code>props.theme.[변수명]</code> 으로 접근해서 사용해주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/b8018750-628a-4274-977c-d44735f0f65e/image.png" alt=""></p>
<p>위의 세 가지 방법 모두 동일한 결과를 반영해주며 상황에 맞게 사용하면 된다.</p>
<h3 id="props의-응용처리">props의 응용처리</h3>
<p><code>props</code> 로 받아주는 경우 컴포넌트 내부에서 자바스크립트의 어떠한 기능도 이용할 수 있게 되며 응용하면 아래와 같은 레이아웃의 변경 처리도 가능해진다.</p>
<pre><code class="language-jsx">
const List = ({ column=3 }) =&gt; {
  const [products, setProducts] = useState([]);

  useEffect(() =&gt; {
    fetch(&#39;/data/productData.json&#39;)
      .then(res =&gt; res.json())
      .then(data =&gt; {
        setProducts(data);
      });
  }, []);

  return (
    &lt;ListWrapper column={column}&gt;
      {products.map(product =&gt; {
        return (
          &lt;ProductCard
            key={product.productid}
            product={product}
            selectOpt={selectOpt}
          /&gt;
        );
      })}
    &lt;/ListWrapper&gt;
  );
}

const ListWrapper = styled.div`
  display: grid;
  grid-template-columns: ${props =&gt; &#39;1fr &#39;.repeat(props.column)};
  grid-gap: 40px;
  place-items: center;
  margin: 35px auto 0;
`;

export default List;
</code></pre>
<p>데이터를 fetch하여 상품 리스트를 그려주는 컴포넌트인데 페이지마다 한 줄에 
보여줄 상품의 갯수가 달라질 수 있다.</p>
<p>그런 경우 <code>column</code> 이라는 속성을 <code>props</code> 로 받아 스타일드 컴포넌트 안에서 javascript의 
<code>repeat</code> 메서드를 실행해주면 페이지마다 다른 갯수를 가진 상품리스트 페이지를 얻을 수 있게 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] WebSocket 이해]]></title>
            <link>https://velog.io/@sangheon-k/TIL-WebSocket-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@sangheon-k/TIL-WebSocket-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Sat, 04 Feb 2023 02:40:02 GMT</pubDate>
            <description><![CDATA[<h2 id="개념">개념</h2>
<p>WebSocket은 서버와 클라이언트 간에 Socket Connection을 유지해서 언제든 양방향 통신 또는 데이터 전송이 가능하도록 하는 기술(프로토콜)입니다. </p>
<p>이전에는 서버와 클라이언트간에 HTTP요청만으로 한 방향의 데이터의 통신만 가능했지만, 웹소켓의 등장으로 Socket Connection을 통한 서버와 클라이언트간의 영구적인 연결이 가능해졌습니다.</p>
<p>이를 통해 실시간 채팅 기술을 사용한 웹 어플리케이션 개발이 가능하며 실시간으로 여러명이서 수정이 가능한 웹 문서작업 기능 구현 또한 가능해졌습니다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/804270d7-d396-4fd9-917a-7487c2a3726f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 로그인/회원가입 리팩토링]]></title>
            <link>https://velog.io/@sangheon-k/React-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@sangheon-k/React-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Thu, 26 Jan 2023 09:57:57 GMT</pubDate>
            <description><![CDATA[<h2 id="현재-상황">현재 상황</h2>
<p>현재 로그인 / 회원가입 페이지의 경우 <code>&lt;User /&gt;</code> 라는 컴포넌트 하나로 관리되고 있고
라우팅에 따라 다른 화면이 나타나도록 구현되어 있었다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/68dd018a-0caa-4cd1-90c2-4b437f926c5c/image.png" alt="login.jpg1"></th>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/2753e4b1-f549-49c8-93fb-85e9734960c8/image.png" alt="signUp.jpg2"></th>
</tr>
</thead>
</table>
<p>그리고 크게 3개의 기능으로 이루어져 있는데</p>
<ol>
<li><strong>인풋값 핸들링</strong></li>
<li>유효성 검사 </li>
<li><strong>서버와의 통신</strong>이다. </li>
</ol>
<p>위의 기능을 포함한채 <code>&lt;User /&gt;</code> 라는 로그인 회원가입 페이지의 공통 컴포넌트를 구현하다 보니 
관련 기능을 구현한 내용이 한눈에 보이지 않는 단점이 나타났다</p>
<p>따라서 이전에 수정한 이력이 있는 유효성 검사를 제외한 두 가지 기능에 대해 
<code>custom hook</code> 과 <code>fetch함수의 분리</code> 로 리팩토링을 진행하였다. </p>
<br />

<h2 id="리팩토링">리팩토링</h2>
<h3 id="인풋값-핸들">인풋값 핸들</h3>
<h4 id="as-is"><code>As-is</code></h4>
<p>🔸 <strong>User.js</strong></p>
<pre><code class="language-js">const initialUserInfo = {
  email: &#39;&#39;,
  password: &#39;&#39;,
  userName: &#39;&#39;,
  phoneNumber: &#39;&#39;,
};
const [userInfo, setUserInfo] = useState(initialUserInfo);</code></pre>
<p>🔸 <strong>Input.js</strong></p>
<pre><code class="language-js">const saveUserInfo = (e) =&gt; {
  const { name, value } = e.target;
  setUserInfo(prev =&gt; ({ ...prev, [name]: value }));
};</code></pre>
<h4 id="to-be"><code>To-be</code></h4>
<p>🔸 <strong>useUserInfo.js</strong></p>
<pre><code class="language-js">import { useState } from &#39;react&#39;;

export const initialUserInfo = {
  email: &#39;&#39;,
  password: &#39;&#39;,
  userName: &#39;&#39;,
  phoneNumber: &#39;&#39;,
};

export const useUserInfo = () =&gt; {
  const [userInfo, setUserInfo] = useState(initialUserInfo);

  const saveUserInfo = e =&gt; {
    const { name, value } = e.target;
    setUserInfo(prev =&gt; ({ ...prev, [name]: value }));
  };

  return [userInfo, setUserInfo, saveUserInfo];
};
</code></pre>
<p>🔸 <strong>User.js</strong></p>
<pre><code class="language-js">const [userInfo, setUserInfo, saveUserInfo] = useUserInfo();</code></pre>
<p><code>userInfo</code>와 <code>setUserInfo</code>, <code>saveUserInfo</code>를 리턴해주는 <code>Custom Hook</code> 을 만들어 줌으로써
사용하는 쪽의 컴포넌트에서는 각각 필요한 값만 뽑아서 사용할 수 있도록 변경하였다. 
<code>&lt;User /&gt;</code> 컴포넌트의 코드가 1차적으로 단순하게 변하여 <em>코드의 30%가량이 줄어들었다</em></p>
<br />


<h3 id="서버와의-통신">서버와의 통신</h3>
<h4 id="as-is-1"><code>As-is</code></h4>
<p>🔸 <strong>User.js</strong></p>
<pre><code class="language-js">const handleFormSubmit = e =&gt; {
    e.preventDefault();

    if (allVaild) {
      let userData =
        title === &#39;로그인&#39;
          ? {
              email: userInfo.email,
              password: userInfo.password,
            }
          : {
              email: userInfo.email,
              password: userInfo.password,
              name: userInfo.userName,
              phoneNumber: userInfo.phoneNumber,
            };

      let fetchUrl = title === &#39;로그인&#39; ? FETCH_SIGN_IN_API : FETCH_SIGN_UP_API;

      fetch(fetchUrl, {
        method: &#39;POST&#39;,
        headers: {
          &#39;Content-Type&#39;: &#39;application/json;charset=utf-8&#39;,
        },
        body: JSON.stringify(userData),
      })
        .then(res =&gt; res.json())
        .then(data =&gt; {
          console.log(data);
          if (title === &#39;회원가입&#39;) {
            alert(&#39;회원가입이 완료되었습니다!&#39;);
            setUserInfo(initialUserInfo);
            navigate(&#39;/login&#39;);
          }
          if (data.accessToken) {
            alert(&#39;로그인이 완료되었습니다!&#39;);
            localStorage.setItem(&#39;accessToken&#39;, data.accessToken);
            navigate(&#39;/&#39;);
          }
        });
    } else {
      alert(&#39;Validation Error!&#39;);
    }
  };</code></pre>
<h4 id="to-be-1"><code>To-be</code></h4>
<p>🔸 <strong>config.js</strong></p>
<pre><code class="language-js">import { FETCH_SIGN_IN_API, FETCH_SIGN_UP_API } from &#39;../../../config&#39;;

export const fetchUser = async (title, userInfo) =&gt; {
  let userData =
    title === &#39;로그인&#39;
      ? {
          email: userInfo.email,
          password: userInfo.password,
        }
      : {
          email: userInfo.email,
          password: userInfo.password,
          name: userInfo.userName,
          phoneNumber: userInfo.phoneNumber,
        };

  let fetchUrl = title === &#39;로그인&#39; ? FETCH_SIGN_IN_API : FETCH_SIGN_UP_API;

  try {
    const res = await fetch(fetchUrl, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Content-Type&#39;: &#39;application/json;charset=utf-8&#39;,
      },
      body: JSON.stringify(userData),
    });
    return res.json();
  } catch (e) {
    return e;
  }
};
</code></pre>
<p>🔸 <strong>User.js</strong></p>
<pre><code class="language-js">const handleFormSubmit = async e =&gt; {
  e.preventDefault();

  const allVaild = validCondition.email &amp;&amp; validCondition.password;

  if (!allVaild) {
    alert(&#39;Validation Error!&#39;);
    return;
  }

  const data = await fetchUser(title, userInfo);

  if (data.accessToken) {
    alert(&#39;로그인이 완료되었습니다!&#39;);
    localStorage.setItem(&#39;accessToken&#39;, data.accessToken);
    navigate(&#39;/&#39;);
  }

  if (data.message === &#39;SIGNUP_SUCCESS&#39;) {
    alert(&#39;회원가입이 완료되었습니다!&#39;);
    setUserInfo(initialUserInfo);
    navigate(&#39;/login&#39;);
  }
};</code></pre>
<p>코드의 가독성을 위해 <code>else문</code>에 해당하는 사항을 위쪽으로 끌어올려 예외상황에서 return문이 먼저 실행되도록 변경하였고, <code>fetch</code> 함수를 페이지 내에 별도의 <code>config</code> 파일로 저장하여 Promise 핸들링이 완료된 결과값만 받도록 코드를 간소화 시켜주었다.
마지막으로 <code>allValid</code> 조건은 위 함수에서만 사용하므로 안쪽으로 이동시켜 주었다.</p>
<h3 id="마무리">마무리</h3>
<p>이번 리팩토링은 코드가 쉽게 읽히는 것과 관심사의 분리 부분만 신경쓰며 진행하였지만
다음 번에는 리렌더링을 최소화하는 방향도 같이 확인해야겠다는 생각이 머리를 스친다.</p>
<p>우선 전체 코드량 감소 및 가독성이 증가하였으므로 만족하고 넘어가야겠다. </p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/d885e1a9-34e6-4988-9976-f9aa6fc81726/image.png" alt="login.jpg1"> As-Is</th>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/e4bfd005-4787-4bd7-a4ba-24c519af888c/image.png" alt="signUp.jpg2"> To-Be</th>
</tr>
</thead>
</table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redux] Redux - React Redux -  Ducks Pattern]]></title>
            <link>https://velog.io/@sangheon-k/Redux-Redux-React-Redux-Ducks-Pattern</link>
            <guid>https://velog.io/@sangheon-k/Redux-Redux-React-Redux-Ducks-Pattern</guid>
            <pubDate>Tue, 24 Jan 2023 15:16:29 GMT</pubDate>
            <description><![CDATA[<h2 id="redux">Redux</h2>
<p>자바스크립트 앱을 위한 예측 가능한 상태 컨테이너</p>
<p>상태의 변화를 특정 함수(Dispatch)에 의해서만 변할 수 있게 통제해 상태변화를 예측 할 수 있게 만듬</p>
<p>데이터가 단방향으로 흐르도록 설계한 Flux 디자인 패턴에서 영감을 받아 자바스크립트로 구현한 구현체</p>
<br />

<h2 id="redux의-구조">Redux의 구조</h2>
<h3 id="3가지-원칙">3가지 원칙</h3>
<ul>
<li>어플리케이션(Application)은 <strong>단일 저장소(Store)</strong>를 가진다</li>
<li>state를 수정할 수 있는 유일한 방법은 무슨일이 벌어지는지 묘사하는 <strong>액션 객체를 전달</strong>하는 것임.</li>
<li>변화는 <strong>순수 함수</strong>로 작성되어야한다.</li>
</ul>
<h3 id="개념">개념</h3>
<blockquote>
<h4 id="life-cycle">Life Cycle</h4>
</blockquote>
<ol>
<li>UI -&gt; 이벤트 발생 -&gt; 이벤트 핸들러의 <code>Dispatch</code> 메서드 실행</li>
<li><strong>Dispatch(ActionCreator())</strong> -&gt; Store 내부의 <strong>Reducer</strong>로 <strong>Action객체 전달</strong> </li>
<li>Reducer에 따라 <strong>State 업데이트</strong> -&gt; 새로운 State 반환 -&gt; <code>useSelector()</code> 로 UI 사용</li>
<li>사이클 종료
<code>event</code> -&gt; <code>ActionCreator(Action)</code> -&gt; <code>Dispatch</code> -&gt; 
<code>Reducer</code> -&gt; <code>Store state 변경</code> -&gt; <code>UI반영</code></li>
</ol>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/797486a0-6d29-4e2d-b504-77f44affe536/image.gif" alt=""><em><a href="https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow">출처 : 리덕스 공식 홈페이지</a></em></p>
<h4 id="주요-용어">주요 용어</h4>
<p><code>Action</code> : 상태 변화에 대한 의도를 표현하는 단순한 <strong>자바스크립트 객체</strong>
<code>Action Creator</code> : <code>Action</code> 객체를 정해진 틀에 맞게 리턴하는 <strong>함수</strong> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ㄴ컴포넌트에서 쉽게 Action 객체를 만들 수 있기 때문에 사용
<code>Dispatch</code> :  Action 객체를 Reducer에 보내는 역할을 하는 redux store의 <strong>메서드</strong>
<code>Store</code> : 모든 state의 <strong>저장소</strong>
<code>Reducer</code> : 이전의 상태와 액션을 받아서 새로운 상태를 반환하는 순수<strong>함수</strong>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ㄴReducer는 감지된 Action 타입에 따라 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;이벤트를 처리하는 이벤트 리스너로도 생각할 수 있음</p>
<p>&nbsp;</p>
<h4 id="reducer의-관리">Reducer의 관리</h4>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/7a1ee83f-e2fb-440d-8f9a-c99290de5ec4/image.png" alt=""></p>
<pre><code class="language-js">// reducer 형태
(previousState, action) =&gt; newState;</code></pre>
<p>리듀서는 기본적으로 이전 상태값과 액션을 인자로 받아 
새로운 상태값을 리턴하는 함수입니다. </p>
<p>이러한 리듀서 하나의 파일에 모든 로직을 넣게 되면 유지보수하기 어려우므로
관심사별로 slice Reducer를 만들고 combineReducer 메서드로 slice Reducer들을 root Reducer로 만들어서 관리합니다.</p>
<pre><code class="language-js">const rootReducer = combineReducer({ todoSlice, countSlice })</code></pre>
<br />

<h2 id="react-redux란">React-Redux란?</h2>
<p>자바스크립트 기반의 프레임워크(Vue, Angular, React 등)에서는 모두 사용할 수 있는 Redux가 
React의 <code>state</code>, <code>props</code>와는 연동이 되지 않아 <em>연동하기 위해 만들어진 라이브러리</em></p>
<p>React-Redux에서 제공하는 API를 통해 전역으로 상태를 관리할 수 있음.</p>
<p><code>Provider</code> : react app 전체에 제공할 _store_를 주입하는 컴포넌트 입니다.</p>
<pre><code class="language-jsx">&lt;Provider store={store}&gt;
  &lt;App /&gt;
&lt;/Provider&gt;</code></pre>
<p><code>useSelector</code> : store의 <em>state_에 접근하는 _hook</em> 입니다.</p>
<pre><code class="language-jsx">const todos = useSelector(state =&gt; state.todos) // store에 저장된 state 가져옴</code></pre>
<p><code>useDispatch</code> : action을 reducer로 보내는 역할입니다.</p>
<pre><code class="language-jsx">// 호출
const dispatch = useDispatch();
// 사용
const eventHandler = () =&gt; {
  dispatch(ActionCreator()) // Action 생성 -&gt; (dispatch) -&gt;  Reducer로 전달
}</code></pre>
<br />

<h2 id="ducks-패턴">**Ducks 패턴</h2>
<p>하나의 파일에 { Action Type, Action Creator, Reducer } 가 모두 담겨져 작성되는 패턴을 의미함
이렇게 작성된 하나의 파일은 &#39;모듈&#39;이라고 지칭함</p>
<h4 id="기본폴더-구조">기본폴더 구조</h4>
<pre><code class="language-js">src
└── components
    ├── ...
└── pages
    ├── ...
└── redux
    ├── index.js   // root reducer
    ├── counter.js // slice 1 
    └── todo.js    // slice 2</code></pre>
<h4 id="루트-리듀서">루트 리듀서</h4>
<pre><code class="language-jsx">import { combineReducers } from &#39;redux&#39;;
import counter from &#39;./counter&#39;;
import todo from &#39;./todo&#39;;

const rootReducer = combineReducers({ counter, todo })

export default rootReducer;
</code></pre>
<h4 id="모듈-파일">모듈 파일</h4>
<pre><code class="language-js">// Action Type
const INCREASE = &#39;counter/INCREASE&#39;;
const DECREASE = &#39;counter/DECREASE&#39;;

// Action Creator
export const increase = () =&gt; {
  return { type: INCREASE }
}
export const decrease = () =&gt; {
  return { type: DECREASE }
}

// InitialState
const initialState = {
  count: 0,
};

// Reducer
function counterReducer(state = initialState, action) {
  if(action.type === &#39;INCREASE&#39;){
    return { ...state, count: state.count + 1 }
  } else if(action.type === &#39;DECREASE&#39;){
    return { ...state, count: state.count - 1 }
  } else {
    return state
  }
}

export default counterReducer;</code></pre>
<p><a href="https://github.com/erikras/ducks-modular-redux">Ducks 패턴 참조</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1차 프로젝트 회고]]></title>
            <link>https://velog.io/@sangheon-k/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1</link>
            <guid>https://velog.io/@sangheon-k/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1</guid>
            <pubDate>Wed, 18 Jan 2023 08:42:53 GMT</pubDate>
            <description><![CDATA[<h3 id="팀-구성">팀 구성</h3>
<p><code>2022. 12. 30 (금)</code>
한해를 마무리하는 시기에 1차 프로젝트 팀원이 발표되었다.
Front-end : 강서윤, 김상헌, 김환성, 오현주
Back-end : 김광휘, 이민규
Github : <a href="https://github.com/sangheon-k/41-1st-A.C.P-frontend">https://github.com/sangheon-k/41-1st-A.C.P-frontend</a></p>
<p>팀원 발표가 끝난 직후 해당 팀원들과 자리를 같이 앉게 되었는데
우리 팀원들은 유연함과 열정을 동시에 가지고 계신 분들 밖에 없었다.</p>
<p>자연스럽게 바로 다음날부터 Planning Meeting을 시작했고,
Product , User, Tech 관점에서 우리가 만드려고 하는 무언가를 &#39;<strong>정의</strong>&#39;하고 찬찬히 뜯어보기 시작했다. </p>
<h3 id="planning-meeting">Planning Meeting</h3>
<p><code>2022. 12. 31 (토)</code> 
오후 4시부터 시작된 미팅은 밤 열시가 넘어서야 종료가 되었고,
아직 정리하지 못한 사항들이 많이 남아있었지만 시간이 다되어 다른날을 기약하기로 했다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/2dde97ab-4b6b-4b2d-84a0-b93d4fc7fdf7/image.jpeg" alt="image.jpg1"></th>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/3fd6785a-7d69-424e-866b-affa881a27d2/image.jpeg" alt="image.jpg2"></th>
<th><img src="https://velog.velcdn.com/images/sangheon-k/post/7fe63827-60b2-49ad-8c83-29654de0d7b3/image.jpeg" alt="image.jpg3"></th>
</tr>
</thead>
</table>
<p><code>Planning Meeting</code>을 진행하며 
우리는 Product 관점에서 선정된 사이트<a href="https://www.aesop.com/kr/">(이솝)</a>와 유사한 이미지를 가지고 있는 사이트를 만들기로 구상하였고, Plant 제품을 판매하는 <code>Plait</code> 이라는 브랜드를 기획하게 되었다. <a href="https://resisted-geese-d3f.notion.site/8ece2a5283e7411bb6ef9d25d76fd6a4">- 기획내용 상세 Notion</a>
<br /></p>
<p>여러 고민을 거쳐 구현이 꼭 필요한 페이지와,
기간에 상관없이 추가적으로 구현하기 원하는 페이지를 분리하였는데 
최종적으로 필수항목으로 결정된 페이지는 아래와 같다.</p>
<ul>
<li>로그인</li>
<li>회원가입</li>
<li>메인</li>
<li>상품 리스트 (+필터)</li>
<li>상세 페이지</li>
<li>장바구니</li>
<li>주문 </li>
<li>주문 완료</li>
</ul>
<br />

<h3 id="layout-with-figma">Layout with Figma</h3>
<p>기존 웹사이트의 기능만 따라 구현해보는 프로젝트가 아닌 
상품기획부터 시작하여 End-User까지 생각하며 준비한 만큼 
기존의 레이아웃에서 약간의 변화가 필요하게 되었다.</p>
<p>그리고 우리는 각자 페이지를 분담하여 <a href="https://www.figma.com/file/Tk0KATo7qNLvDgLVmMUkvx/%5B%EC%9C%84%EC%BD%94%EB%93%9C-41%EA%B8%B0%5D-A.C.P---1%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-(%EC%9D%B4%EC%86%9D--%3E-%ED%94%8C%EB%9E%9C%ED%85%8C%EB%A6%AC%EC%96%B4)?node-id=0%3A1&t=85UDGkLaBHO6YC2q-1" target="_blank">Figma</a> 로 간략하게나마 레이아웃을 구성하게 되었다.</p>
<p width="100%">
  <img src="https://velog.velcdn.com/images/sangheon-k/post/eb88f2e6-3bf7-4c8e-abf3-29d10e289076/image.png" width="70%" />
</p>


<p>레이아웃 구성은 작업이 진행되며 하나씩 추가로 디자인하는 방식으로 유연하게 진행하였다.
기존에 있던 레이아웃이 아니고 디자이너가 없는 상황에서 불가피한 선택이었다고 생각되었으나 
미리 레이아웃이 나와있었다면 조금 더 안정적으로 코드를 작성할 수 있었을 텐데 하는 아쉬움이 남아있다.</p>
<br />

<h3 id="개인적으로-작업한-영역">개인적으로 작업한 영역</h3>
<p>팀프로젝트를 진행하며 개인적으로 스터디 및 공부한 경험이 있어 PM과 같은 직책 대신 
관련 문서(<a href="https://resisted-geese-d3f.notion.site/1-c829e7cf0ed84d4491f32a9aa52ef725" target="_blank">노션</a>, <a href="https://www.figma.com/file/Tk0KATo7qNLvDgLVmMUkvx/%5B%EC%9C%84%EC%BD%94%EB%93%9C-41%EA%B8%B0%5D-A.C.P---1%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-(%EC%9D%B4%EC%86%9D--%3E-%ED%94%8C%EB%9E%9C%ED%85%8C%EB%A6%AC%EC%96%B4)?node-id=0%3A1&t=85UDGkLaBHO6YC2q-1" target="_blank">피그마</a>)를 작성하며 해당 프로젝트의 윤활유 역할이 되기로 마음 먹었다.</p>
<p>개인적으로 맡게된 페이지는 <strong>로그인 / 회원가입 페이지</strong>와 <strong>장바구니 페이지</strong> 였는데
기획이 추가됨에 따라 메인페이지에 있는 <strong>필터 및 더보기 버튼 기능</strong>을 추가로 구현하였다.</p>
<h4 id="로그인--회원가입-페이지">로그인 / 회원가입 페이지</h4>
<p>자체 상품을 판매하고, 회원들의 충성도가 중요한 우리 사이트의 특성상 
가볍게 로그인 및 회원가입이 진행되는 방식보다는 
조금 더 진정성 있는 고객들이 유입되고 회원으로 가입되는 방향으로 진행 되기를 원했다.</p>
<p>따라서 기존 이솝의 팝업형식의 로그인 / 회원가입 형식을 페이지로 리뉴얼하여 
왼쪽에는 상품을 암시하는 Plant 이미지를, 오른쪽에는 가입 및 로그인 양식이 보여지도록 디자인하였다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/8185d98d-cb73-46c8-bf10-6b82ce75096e/image.gif" alt=""></p>
<p>페이지를 구현하며 여러가지 고민할 사항이 있었다.</p>
<ol>
<li>이솝의 Label 애니메이션을 우리의 페이지에도 구현할 것인가?</li>
<li>실시간 유효성 검사를 적용할 것인가?</li>
<li>로그인 / 회원가입 페이지를 별개의 페이지로 볼 것인가, 
비슷한 양식처럼 보이므로 하나의 컴포넌트로 관리 할 것인가?</li>
</ol>
<p>결론부터 이야기하자면 세가지 질문에 대한 대답은 모두 Yes였다. </p>
<p>이솝의 Label 애니메이션 효과는 우리의 상품성과도 잘 어울리는 고급스러운 인터렉션으로 
Plait의 브랜드 이미지와도 잘 맞아 떨어진다고 생각하여 적용하게 되었고, 
실시간 유효성 검사는 유저의 편리함 및 효과적인 상호작용을 위해, 
로그인 회원가입 페이지를 하나의 컴포넌트로 관리하는 것은 개인적으로 구현하고 싶었기에 반영하였다.</p>
<p><del>각 항목에 대한 기술적인 의견은 <a href="">추가 포스팅</a>을 참조 바랍니다.</del> <a href="https://url.kr/i84elx">리팩토링 진행</a></p>
<h4 id="장바구니">장바구니</h4>
<p>생각보다 많은 시간을 들여야 했고, 한번 구현 한 뒤 사소한 버그로 인해 주말동안 다시 처음부터 구현해야 했던 페이지다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/98636314-3df4-4e12-a429-28b140755e93/image.png" alt=""></p>
<p>장바구니 페이지에 필요한 기능은 </p>
<ul>
<li>아이템 리스트 fetch</li>
<li>수량 변경 -&gt; 아이템 가격 변경</li>
<li>상품 개별 선택 및 해제 -&gt; 결제 금액에 선택된 아이템 가격 반영</li>
<li>전체 선택 및 해제 -&gt; 결제 금액에 반영</li>
</ul>
<p>여기서 문제가 되었던 부분은 상품 선택이 선택되어있는 상태에서 수량을 변경하게 되면 
결제 금액에 변경된 아이템 가격이 반영되지 않는다는 점이었다.</p>
<p>그래서 기존에 상품을 선택한 경우 해당 아이템의 ID와 수량만 관리하던 로직에서
아이템의 가격까지 포함하도록 구조를 변경하여 다시 작업하였다.</p>
<p><strong>As-is</strong></p>
<pre><code>selectedItems = [{ cartId }]
상품 체크 상태, 수량 변경 -&gt; 아무런 변경도 일어나지 않음</code></pre><p><strong>To-be</strong></p>
<pre><code>selectedItems = [{ cartId, data:{ id, quantity }, itemPrice }]
상품 체크 상태, 수량 변경 -&gt; data의 quantity 및 itemPrice 변경</code></pre><ul>
<li>체크된 상태에서 수량이 변경된다 -&gt; 선택된 상품들이 보관된 배열(selectedItems)에서 해당상품의 quantity 및 아이템 가격이 변경된다.</li>
</ul>
<p>선택된 상품의 데이터를 관리하던 배열(<code>selectedItems</code>)의 구조를 변경하니
수량을 변경해도 아이템 가격이 반영되고, 아이템 가격(<code>itemPrice</code>)만 따로 연산하여 총 결제 금액 또한 반영(update)할 수 있었다.</p>
<p>이래저래 고민이 많았던 페이지 이지만
덕분에 상품을 장바구니에 담고, 담겨진 상품을 주문하는 과정을 통신을 포함하여 이해할 수 있었던 작업이어서 많이 배운 작업이었다고 생각된다.</p>
<h3 id="개인-회고">개인 회고</h3>
<p>맡겨진 페이지를 작업하는데 여러가지 고민을 하다보니 조금씩 페이지가 완성도 있어지는 모습을 실시간으로 보게 된 것 같다.
이전에 공부를 하면서는 코드의 가독성과 백엔드와의 통신로직을 생각하며 데이터를 어떤식으로 받아야하는지까지는 고민하지는 않았다면,
이번 프로젝트를 진행하며 조금 더 다양한 요소를 고민하며 작업하지 않으면 안되겠다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 커밋 로그 합치기]]></title>
            <link>https://velog.io/@sangheon-k/TIL-%EC%BB%A4%EB%B0%8B-%EB%A1%9C%EA%B7%B8-%ED%95%A9%EC%B9%98%EA%B8%B0</link>
            <guid>https://velog.io/@sangheon-k/TIL-%EC%BB%A4%EB%B0%8B-%EB%A1%9C%EA%B7%B8-%ED%95%A9%EC%B9%98%EA%B8%B0</guid>
            <pubDate>Wed, 18 Jan 2023 05:19:50 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하며 다른 브랜치에 이동하며 작업했던 커밋로그가 남아 정리가 필요해 보였다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/f97b3a50-a9c7-44ce-be14-f89575f8876e/image.png" alt=""></p>
<p><code>git rebase -i HEAD~수량</code> </p>
<p>해당 명령어로 커밋메세지를 하나로 합치려다보니 <code>cannot &#39;squash&#39; without a previous commit</code> 라는 에러 메세지가 나타났다. </p>
<p>구글링을 해보니 squash를 적용하며 남겨놓을 (혹은 사용할) 커밋을 지정하지 않고 모두 처리하게 되어 나타난 에러문구였다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/34629d95-9c63-4cef-8a3f-87c1b21cb708/image.png" alt=""></p>
<p>남겨놓을 메세지를 아래와 같이 수정하니 다음 과정으로 잘 넘어가 지는 것이 보인다.
<img src="https://velog.velcdn.com/images/sangheon-k/post/1da892d7-362e-493a-afdf-e17957044d0c/image.png" alt=""></p>
<p>주의할 점은 squash로 변경한 커밋항목은 앞에 합쳐질 대상이 꼭 있어야 한다는 것이다.</p>
<p><a href="https://stackoverflow.com/questions/39595034/git-cannot-squash-without-a-previous-commit-error-while-rebase">https://stackoverflow.com/questions/39595034/git-cannot-squash-without-a-previous-commit-error-while-rebase</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 운영서버 아키텍처와 클라우드 컴퓨팅]]></title>
            <link>https://velog.io/@sangheon-k/AWS-%EC%9A%B4%EC%98%81%EC%84%9C%EB%B2%84-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%BB%B4%ED%93%A8%ED%8C%85</link>
            <guid>https://velog.io/@sangheon-k/AWS-%EC%9A%B4%EC%98%81%EC%84%9C%EB%B2%84-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%BB%B4%ED%93%A8%ED%8C%85</guid>
            <pubDate>Tue, 17 Jan 2023 11:17:17 GMT</pubDate>
            <description><![CDATA[<h2 id="운영-서버-아키텍쳐">운영 서버 아키텍쳐</h2>
<br />

<h3 id="1-단일-서버">1. 단일 서버</h3>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/7d6faff6-e1d4-4ec0-97ac-ef8c0e1deabe/image.png" alt=""></p>
<p>데이터베이스와 애플리케이션을 같은 서버에서 실행, 별도의 네트워크 설정을 할 필요 없음</p>
<p><em>단점</em></p>
<ol>
<li>애플리케이션과 DB 둘 중 하나라도 장애가 발생할 경우 전체 서비스의 장애로 이어짐</li>
<li>서버 자원을 효율적으로 사용하기가 어려움</li>
<li>DB와 애플리케이션이 한 서버에 구축되어 있기에 보안이 취약함</li>
<li>애플리케이션과 데이터베이스가 하나의 서버로 구성되면 똑같은 데이터를 여러 서버에 복제해야 하므로 관리가 매우 힘들어지고 이는 scale-out 서버 확장의 어려움으로 이어짐.</li>
</ol>
<br />

<h3 id="2-애플리케이션과-데이터베이스-서버를-분리">2. 애플리케이션과 데이터베이스 서버를 분리</h3>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/5a7aec68-46cf-4ca0-8aee-522803a85eaf/image.png" alt=""></p>
<p>애플리케이션과 데이터베이스를 각각의 서버로 구성하는 방법
단일 서버의 단점을 어느정도 해소해주지만 두개의 서버를 관리하므로 구성이 다소 복잡해집니다. 
클라이언트는 여전히 하나의 애플리케이션 서버를 바라보고 있기 때문에 Scale-out은 어려운 구조입니다.</p>
<br />

<h3 id="3-서버-단위의-로드-밸런서">3. 서버 단위의 로드 밸런서</h3>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/1652a3c0-d3aa-4904-b97d-ff1c2c95ccf0/image.png" alt=""></p>
<ul>
<li>클라이언트는 애플리케이션 서버와 직접 통신하는게 아닌 로드 밸런서 서버와 통신하며, 
그 뒤에 여러대의 애플리케이션 서버를 두는 방식입니다.</li>
<li>이러한 로드 밸런서를 두면서 Scale-out을 통한 확장이 가능해지고 특정 서버에 장애 발생시, 
로드 밸런서가 정상 서버에 요청을 넘기면 되므로 서버 다운을 최소화 할 수 있습니다. </li>
</ul>
<br />

<h3 id="4-서버-내-앱-단위의-로드-밸런서">4. 서버 내 앱 단위의 로드 밸런서</h3>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/5977f6b5-cc28-430e-b279-ec9ea8b981e6/image.png" alt=""></p>
<p>기존에 하나의 로드 밸런서가 여러개의 서버로 요청을 분산해주었던 방식에서 
서버 내에 앱 단위의 로드 밸런서가 추가 된 방식</p>
<p>하나의 서버에 여러 개의 프로세스를 실행해 하나의 서버에서 여러 개의 요청을 동시에 처리 할 수 있기에 서버의 자원을 최대한으로 사용할 수 있습니다.</p>
<p><br /><br /></p>
<h2 id="온프레미스란">온프레미스란?</h2>
<p>IT 서비스를 기업이 자체적으로 보유한 <strong>물리적인 서버에 직접 설치</strong>해 운영하는 방식</p>
<h3 id="특징">특징</h3>
<ul>
<li>인프라를 물리적으로 직접 구축하여 운영하는 방식</li>
<li>서비스에 필요한 시스템을 구축하기 위해 기업이 직업 하드웨어를 구입하거나 대여해야 함</li>
<li>필요한 자원의 예측과 실제가 달라 불필요한 비용이 사용될 수 있음</li>
<li>서버 확장이 필요할 때 여러 제약 사항이 많음</li>
<li>지속적인 모니터링 필요 (24/7)</li>
<li>천재 지변에 대응하기 어려움</li>
</ul>
<p><br /><br /></p>
<h2 id="클라우드-컴퓨팅이란">클라우드 컴퓨팅이란?</h2>
<p>IT 리소스를 인터넷을 통해 온디맨드로 제공받고 사용한 만큼만 비용을 지불하는 방식</p>
<p>클라우드 컴퓨팅은 관리 주체와 수준에 따라 3가지로 구분하여 설명할 수 있다.</p>
<ul>
<li>서비스형 인프라스트럭처(IaaS)</li>
<li>서비스형 플랫폼(PaaS)</li>
<li>서비스형 소프트웨어(SaaS)</li>
</ul>
<p>Iass -&gt; PaaS -&gt; SaaS 순으로 갈수록 자유도가 낮아지며 
SaaS는 우리가 쉽게 접하는 여러 서비스(슬랙, 피그마, 지라 등) 들이 여기에 포함된다.</p>
<br />

<h2 id="aws">AWS</h2>
<p>AWS는 컴퓨팅, 스토리지, 데이터베이스를 포함하여 
이외에도 수많은 종류의 서비스를 제공하는 플랫폼입니다.
그 중에서도 자주 쓰이고 중요한 몇가지 서비스의 기능을 정리합니다.</p>
<h3 id="ec2">EC2</h3>
<p>Elastic Compute Cloud
단순 클릭 몇번을 통해 쉽게 사양을 고르고, 
원하는 시간만큼 EC2 인스턴스를 생성해서 해당 서버로 API를 배포할 수 있음</p>
<h3 id="rds">RDS</h3>
<p>Relational Database Service
클라우드에서 간편하게 데이터베이스를 설치, 운영 및 확장할 수 있는 서비스
사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요 시 즉시 사용할 수 있도록 만들어주는 서비스
사용자가 직접 서버를 생성해서 데이터베이스를 설치하거나 설정하고 관리 하지 않아도 된다.</p>
<h3 id="s3">S3</h3>
<p>Simple Storage Service
일을 쉽게 저장할 수 있는 공간을 제공하는 서비스</p>
<h3 id="vpc">VPC</h3>
<p>Virtual Private Cloud
클라우드 환경을 계정 별로 독립된 네트워크 환경을 구성할 수 있게 도와주는 서비스
VPC를 활용하면 온프레미스 방식으로 데이터 센터에서 직접 네트워크 환경을 만드는 것과 같이 클라우드 환경에서도 네트워크를 구축 할 수 있습니다.</p>
<h3 id="cloudfront">CloudFront</h3>
<p>AWS에서 제공하는 CDN(Content Delivery Network) 서비스
사용자가 콘텐츠를 요청하면 지연시간(latency)이 가장 낮은 엣지 로케이션으로 라우팅하여 빠르고 뛰어난 성능을 보여줌</p>
<h3 id="route-53">Route 53</h3>
<p>AWS의 DNS 서비스로 가용성과 확장성이 뛰어나고
AWS 내의 다른 서비스들과 호환성이 뛰어나 연동이 잘 된다는 장점이 있습니다. </p>
<h3 id="elb">ELB</h3>
<p>Elastic Load Balancer
이름 그대로 로드 밸런서를 제공하는 서비스로 서버로 들어오는 높은 애플리케이션 트래픽을 여러 대상에 적절하게, 자동으로 분산시켜줌으로써 안정적으로 서버를 운영할 수 있게 도와줍니다.</p>
<h3 id="iam">IAM</h3>
<p>AWS의 리소스에 대해 개별적으로 접근제어를 하거나 권한을 가지도록 계정 또는 그룹을 생성 및 관리하는 서비스</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 2023.01.15]]></title>
            <link>https://velog.io/@sangheon-k/TIL-2023.01.15</link>
            <guid>https://velog.io/@sangheon-k/TIL-2023.01.15</guid>
            <pubDate>Sun, 15 Jan 2023 13:41:16 GMT</pubDate>
            <description><![CDATA[<p>1차프로젝트 장바구니 페이지를 구현하며 
상품의 총 가격을 반영해야하는 일이 생겼다. 
state로 관리하여 해당하는 상품의 가격을 모두 더해주어도 괜찮지만
reduce 함수를 사용하여 간단하게 처리하기로 해보았다. </p>
<pre><code class="language-js">const totalPrice = selectedItems.reduce(
  (acc, curr) =&gt; acc + curr.itemPrice,
  0
);</code></pre>
<p>선택된 상품의 배열을 reduce함수로 탐색하며 
이전값과 현재값을 계속해서 더해주는 로직을 작성하여 totalPrice를 상수로 관리할 수 있게 되었다.</p>
<p>결과적으로 state의 변동에 따른 리렌더링이 감소하게 되어 프로덕트가 조금 더 효율적으로 동작하게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useSearchParams를 이용한 쿼리스트링 적용]]></title>
            <link>https://velog.io/@sangheon-k/React-useSearchParams%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@sangheon-k/React-useSearchParams%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sun, 15 Jan 2023 07:03:42 GMT</pubDate>
            <description><![CDATA[<h3 id="필터-중복선택">필터 중복선택</h3>
<pre><code class="language-js">const handleChkClick = (category, id) =&gt; {
    const getAll = searchParams.getAll(category);
    const idStr = id.toString();

    if (getAll.includes(idStr)) {
      const filteredIds = getAll.filter(item =&gt; item !== idStr);
      searchParams.delete(category);
      filteredIds.forEach(id =&gt; {
        searchParams.append(category, id);
      });
      setSearchParams(searchParams);
    } else {
      searchParams.append(category, id);
      setSearchParams(searchParams);
    }

    const queryString = searchParams.toString();
    console.log(queryString);
    fetchQueryData(queryString);
  };


&lt;checkBox onClick={handleChkClick} /&gt; </code></pre>
<p>note) searchParams.remove 메서드의 경우 키값에 해당하는 모든 value가 삭제되므로 
체크 토글기능 구현의 어려움이 있음.
따라서 위와같이 커스텀 로직으로 해당 기능을 구현하게 됨.</p>
<p>//gif</p>
<h3 id="더보기-버튼">더보기 버튼</h3>
<pre><code class="language-js">const handleMoreClick = () =&gt; {
  if (maxProductLength &gt; limit) {
    limit += 6;
    searchParams.set(&#39;offset&#39;, 0);
    searchParams.set(&#39;limit&#39;, limit);
    setSearchParams(searchParams);
    fetchQueryData(searchParams);
  }
};

&lt;button onClick=(handleMoreClick)&gt;더보기&lt;/button&gt;</code></pre>
<p>더보기 버튼을 클릭할 때 마다 limit 값을 원하는 갯수만큼 증가시켜 
parameter값으로 넘겨주는 방식이다. 
일반적인 페이지네이션이 아닌 계속해서 추가로 보여주는 방식을 원했기 때문에
start값-(offset)은 0으로 고정되도록 작업하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 리액트 쿼리스트링]]></title>
            <link>https://velog.io/@sangheon-k/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81</link>
            <guid>https://velog.io/@sangheon-k/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81</guid>
            <pubDate>Wed, 11 Jan 2023 15:08:20 GMT</pubDate>
            <description><![CDATA[<h2 id="쿼리스트링이란">쿼리스트링이란?</h2>
<p>URL의 한 부분으로 요청하고자 하는 URL에 부가적인 정보를 포함하고 싶은 경우 사용</p>
<h3 id="쿼리스트링의-사용-예시">쿼리스트링의 사용 예시</h3>
<p><code>http://myhomepage.com/products?sort=popular&amp;sort=new</code></p>
<ul>
<li>url에서 ?를 통해 쿼리스트링이 시작된다는 표현을 할 수 있다 </li>
<li>쿼리스트링은 <code>key=value</code> 형태의 문자열로 표현된다</li>
<li>쿼리스트링 내부에서 <code>key=value</code> 페어를 구분할 때는 &amp; 연산자를 이용한다.</li>
</ul>
<p>리액트에서 쿼리스트링을 사용하는 경우
<code>&lt;Link to=&quot;/products?sort=new&quot; /&gt;</code> 
<code>navigate(&quot;/products?sort=new&quot;)</code> 와 같이 사용 가능하며 
렌더링은 <code>/products</code> URL에 해당하는 컴포넌트를 그려준다.</p>
<h3 id="리액트에서-쿼리스트링-가져오기">리액트에서 쿼리스트링 가져오기</h3>
<p>앞선 <a href="https://velog.io/@sangheon-k/React-useLocation-useParams">포스팅</a>과 같이 <code>react-router-dom</code> 에서 제공하는 _<code>useLocation() Hook</code>_을 사용하면 <code>/products</code> 라는 <strong>pathname</strong>과 함께 <code>?sort=new</code> 라는 <strong>search</strong> 값도 가져올 수 있게됩니다.</p>
<h4 id="uselocation">useLocation</h4>
<pre><code class="language-js">import { useLocation } from &#39;react-router-dom&#39;

// ex) url : https://velog.io/products?queryString

function Products() {
  const location = useLocation();
  console.log(location) // {pathname: &#39;/products&#39;, search: &#39;?queryString&#39;, hash: &#39;&#39;, state: null, key: &#39;cfkmpx77&#39;}
  console.log(location.pathname) // &#39;/products&#39;
  console.log(location.search) // &#39;?queryString&#39;

  return (    
    &lt;div&gt;Products&lt;/div&gt;
  )
}</code></pre>
<p>그러나 우리가 가져오기 원하는 쿼리스트링 부분에 <code>key=value</code> 페어가 많아지게 되면 
원하는 정보만 파싱하는 것이 어려워지게 됩니다.</p>
<p><code>useSearchParams Hook</code>은 이러한 문제를 해결해 주며 더 나은 기능을 제공해줍니다.</p>
<h4 id="usesearchparams">useSearchParams</h4>
<pre><code class="language-js">
import { useSearchParams } from &#39;react-router-dom&#39;

// ex) url : https://velog.io/products?queryString

function Products() {
  const [ searchParams, setSearchParams ] = useSearchParams();

  return (    
    &lt;div&gt;Products&lt;/div&gt;
  )
}
</code></pre>
<p><code>useSearchParams Hook</code> 은 useState와 비슷하게 
_searchParams_와 _setSearchParams_를 배열의 형태로 리턴해주는데 
위의 예시코드와 같이 사용하면 원하는 쿼리스트링 값을 얻을수도, 설정해 줄 수도 있습니다.</p>
<blockquote>
</blockquote>
<p><strong>searchParams</strong> : URLSearchParams 객체, 쿼리스트링을 다루기 위한 여러 메서드 제공
<strong>setSearchParams</strong> : 인자에 객체 or 문자열을 넣어 <strong>URL의 쿼리스트링 변경</strong></p>
<h3 id="searchparams의-다양한-기능">searchParams의 다양한 기능</h3>
<ul>
<li>get
searchParams.get(key) : 특정한 key의 value 값 가져오기
searchParams.getAll(key) : 특정한 key의 모든 value 값 가져오기
searchParams.toString() : 쿼리스트링 전체를 문자열로 리턴</li>
<li>set
searchParams.set(key, value) : 전달한 key값을 value로 설정
searchParams.append(key, value) : 기존 값을 유지한 채 (key=value) 페어를 추가함
searchParams.delete(key) : 전달한 key값에 해당하는 모든 쿼리문 삭제</li>
</ul>
<p>searchParams로 쿼리스트링을 세팅해주는 경우 
setSearchParams 메서드로 적용을 시켜주어야 url에 반영이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 001.  fetch 함수 재활용]]></title>
            <link>https://velog.io/@sangheon-k/TIL-001.-fetch-%ED%95%A8%EC%88%98-%EC%9E%AC%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@sangheon-k/TIL-001.-fetch-%ED%95%A8%EC%88%98-%EC%9E%AC%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Sun, 08 Jan 2023 12:01:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sangheon-k/post/c85621e2-f01b-476e-84b0-d66da7ee7bc1/image.png" alt=""></p>
<p><del><code>2023. 01. 08 (일)</code></del>
<code>업데이트 - 2023. 02. 04 (토)</code> </p>
<p>*<em>백엔드와 통신하는 fetch 함수를 재사용이 가능한 형태로 구현하였다. *</em></p>
<pre><code class="language-js">// fetch 함수
export const fetchApi = async (url, method = &#39;GET&#39;, fetchData, auth = false) =&gt; {
  try {
    if (method === &#39;GET&#39;) {
      const res = await fetch(url, {
        method: &#39;GET&#39;,
        headers: {
          &#39;Content-Type&#39;: &#39;application/json;charset=utf-8&#39;,
          Authorization: auth ? localStorage.getItem(&#39;accessToken&#39;) : null,
        },
      });
      return res.json();
    }
    const res = await fetch(url, {
      method,
      headers: {
        &#39;Content-Type&#39;: &#39;application/json;charset=utf-8&#39;,
        Authorization: auth ? localStorage.getItem(&#39;accessToken&#39;) : null,
      },
      body: JSON.stringify(fetchData),
    });
    return res.json();
  } catch (e) {
    return e;
  }
};


// 사용할 때
const data = await fetchData(GET_USER_INFO_API, &#39;POST&#39;, userData);
</code></pre>
<p>함수의 결과값으로 response를 return하여 
사용하는 곳에서 변수로 저장한 뒤 다음 로직을 상황에 따라 다르게 작성해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useLocation / useParams ]]></title>
            <link>https://velog.io/@sangheon-k/React-useLocation-useParams</link>
            <guid>https://velog.io/@sangheon-k/React-useLocation-useParams</guid>
            <pubDate>Fri, 06 Jan 2023 04:37:16 GMT</pubDate>
            <description><![CDATA[<h3 id="uselocation">useLocation</h3>
<p>일반 자바스크립트에서 url 정보를 가져오고 싶은 경우 location 인터페이스를 사용합니다.</p>
<pre><code class="language-js">// url : https://velog.io/write?queryString
location.href // &#39;https://velog.io/write?queryString&#39;
location.pathname // &#39;/write&#39; </code></pre>
<p>(참조) <a href="https://developer.mozilla.org/ko/docs/Web/API/Location">Location - Web API | MDN</a></p>
<p><strong><code>react-router-dom</code></strong> 에서 제공하는 여러가지 Hook들은 
이러한 작업을 편하게 할 수 있도록 도와줍니다. </p>
<pre><code class="language-jsx">import { useLocation } from &#39;react-router-dom&#39;

// ex) url : https://velog.io/products?queryString

function Products() {
  const location = useLocation();
  console.log(location) // {pathname: &#39;/products&#39;, search: &#39;?queryString&#39;, hash: &#39;&#39;, state: null, key: &#39;cfkmpx77&#39;}
  console.log(location.pathname) // &#39;/products&#39;
  console.log(location.search) // &#39;?queryString&#39;

  return (    
    &lt;div&gt;Products&lt;/div&gt;
  )
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/66a5dd47-5f9f-40e6-ba82-54c0e9217ed4/image.png" alt=""></p>
<h3 id="useparams">useParams</h3>
<h4 id="라우팅">라우팅</h4>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/e5b880d4-cec9-444e-ac98-459439f2886a/image.png" alt=""></p>
<h4 id="코드">코드</h4>
<pre><code class="language-jsx">import { useParams } from &#39;react-router-dom&#39;

function Products() {
  const params = useParams();
  console.log(params) // {id: 1}

  return (    
    &lt;div&gt;Products&lt;/div&gt;
  )
}</code></pre>
<p>우리가 작업한 사이트의 상품 상세 페이지로 들어갈 때 
위와같은 라우팅 상황에서 
htttp://<a href="http://www.myhomepage.com/product/1">www.myhomepage.com/product/1</a> 와 같은 url로 라우팅 되고
<code>useParams()</code> Hook을 사용하면 아래와 같이 동적으로 라우팅이 된 정보를 담은 객체값을 전달받을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/3d71a233-9695-4c52-a2e1-ddf96055a3e3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 컴포넌트 재사용]]></title>
            <link>https://velog.io/@sangheon-k/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%9E%AC%EC%82%AC%EC%9A%A9-l1jf5w32</link>
            <guid>https://velog.io/@sangheon-k/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%9E%AC%EC%82%AC%EC%9A%A9-l1jf5w32</guid>
            <pubDate>Fri, 06 Jan 2023 04:20:23 GMT</pubDate>
            <description><![CDATA[<h3 id="컴포넌트의-재사용">컴포넌트의 재사용</h3>
<p>1차 프로젝트를 진행하며 로그인페이지를 맡게 되었는데 
같이 맡게 된 회원가입 페이지와 레이아웃 및 기능이 비슷하여 
<code>&lt;User /&gt;</code> 라는 컴포넌트를 만들어 재사용하게 되었습니다.</p>
<p>로그인, 회원가입 페이지의 기본 구조</p>
<ul>
<li>타이틀</li>
<li>(회원가입) 이름</li>
<li>이메일</li>
<li>패스워드</li>
<li>(회원가입) 휴대폰 번호</li>
<li>(로그인/회원가입)버튼</li>
</ul>
<pre><code class="language-jsx">&lt;div className=&quot;user&quot;&gt;
  &lt;h1 className=&quot;userTitle&quot;&gt;{title}&lt;/h1&gt;
  &lt;form className=&quot;form&quot; onSubmit={handleFormSubmit}&gt;
    {title === &#39;회원가입&#39; &amp;&amp; &lt;input type=&quot;text&quot; name=&quot;userName&quot; /&gt;}
    &lt;input type=&quot;text&quot; name=&quot;email&quot; /&gt;
    &lt;input type=&quot;password&quot; name=&quot;password&quot; /&gt;
    {title === &#39;회원가입&#39; &amp;&amp; &lt;input type=&quot;text&quot; name=&quot;phoneNum&quot; /&gt;}
    &lt;button&gt;{title}&lt;/button&gt;
  &lt;/form&gt;
&lt;/div&gt;</code></pre>
<p>User 컴포넌트의 Props로 받는 데이터의 조건에 따라
추가적으로 이름과 휴대폰 번호를 입력받을 수 있는 <code>&lt;input /&gt;</code> 을 렌더링 해주면
하나의 컴포넌트로 비슷한 구조인 상황에서 로그인과 회원가입 페이지를 만들 수 있게 됩니다.</p>
<h3 id="하위-컴포넌트의-재사용">하위 컴포넌트의 재사용</h3>
<p>작업을 진행하며 <code>&lt;input /&gt;</code> 태그의 경우 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 댓글 리스트 뷰 &  추가, 편집, 삭제 기능구현]]></title>
            <link>https://velog.io/@sangheon-k/React-%EB%8C%93%EA%B8%80-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B7%B0-%EC%B6%94%EA%B0%80-%ED%8E%B8%EC%A7%91-%EC%82%AD%EC%A0%9C-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@sangheon-k/React-%EB%8C%93%EA%B8%80-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B7%B0-%EC%B6%94%EA%B0%80-%ED%8E%B8%EC%A7%91-%EC%82%AD%EC%A0%9C-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 28 Dec 2022 07:40:44 GMT</pubDate>
            <description><![CDATA[<p>*Javascript로 만들었던 인스타그램 프로젝트를 
React로 포팅하며 구현한 기능을 정리한 내용입니다.
상태관리 라이브러리를 사용하지 않고  작업을 진행하였습니다.</p>
<h3 id="댓글-추가">댓글 추가</h3>
<p>Input창에 댓글을 입력하고 <strong>Enter Press</strong> or <strong>게시버튼을 클릭</strong>하면
commentList에 새로운 댓글 데이터가 추가되어 다시 렌더링되는 구조로 작업하였습니다.</p>
<pre><code class="language-js">export default function WrapComments() {
  const [input, setInput] = useState(&#39;&#39;)
  const [commentList, setCommentList] = useState(data)

  const addComment = () =&gt; {
    if (input !== &#39;&#39;) {
      const lastCmtIndex = commentLists.length - 1;
      const addedCmtId = commentLists[lastCmtIndex].id + 1;
      const newComment = {
        id: addedCmtId,
        username: &#39;bibigo&#39;,
        content: input,
      };
      setCommentLists([...commentLists, newComment]);
      setInput(&#39;&#39;);
    }
  };

  return (
    &lt;&gt;
      &lt;ul className=&quot;list-cmt&quot;&gt;
        {commentLists.map(comment =&gt; {
          const commentId = comment.id;
          return (
            &lt;Comment
              key={commentId}
              comment={comment}
            /&gt;
          );
        })}
      &lt;/ul&gt;

      &lt;div className=&quot;box-inp-cmt&quot;&gt;
        &lt;input
          type=&quot;text&quot;
          placeholder=&quot;댓글 달기...&quot;
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          onKeyDown={e =&gt; (e.key === &#39;Enter&#39; ? addComment() : null)}
          /&gt;
        &lt;button className=&quot;btn-submit&quot; disabled=&quot;&quot; onClick={addComment}&gt;
            게시
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>input에 값이 입력되면 onChange의 setInput을 통해 문자열이 저장되고, 
Enter Press 혹은 게시 버튼 클릭시 <code>addComment()</code> 함수가 실행됩니다. </p>
<p>미리 만들어둔 <code>addComment()</code> 함수 안에서 관리중인 댓글데이터의 구조대로 새로운 댓글 데이터를 만들어주고, 새로운 데이터를 commentList에 넣어주어 다시 렌더링 하는 방법입니다. </p>
<p>mockData를 활용하였기에 ID로 넣어주어야할 LastIndex값은 단순하게 마지막 ID값에서 +1하여 처리하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/52183358-1012-4388-9cb7-0f7b48f206c9/image.gif" alt=""></p>
<h3 id="댓글-편집">댓글 편집</h3>
<blockquote>
<p>댓글 컴포넌트의 구조는 다음과 같습니다. 
<code>&lt;WrapComments /&gt;</code> -&gt; <code>&lt;CommentList /&gt;</code> -&gt; <code>&lt;Comment /&gt;</code></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/c4ab6186-1d2a-49f5-a6f1-c82230448a9c/image.png" alt=""></p>
<pre><code class="language-jsx">&lt;WrapComments /&gt; </code></pre>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/8e126f1d-4bda-42ed-b028-b36b670c630d/image.png" alt=""></p>
<pre><code class="language-jsx">&lt;CommentList /&gt; </code></pre>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/e909b7fe-a71d-419b-8493-eab3be56e474/image.png" alt=""></p>
<pre><code class="language-jsx">&lt;Comment /&gt; </code></pre>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/5208fae2-6a06-4371-8e10-c8a03bc89ccd/image.png" alt=""></p>
<p>현재 댓글 데이터의 구조는 하나의 객체 안에 { id, username, content } 가 들어있는 상태입니다. 
로그인 환경을 제외하고 댓글을 편집하기위해서는 다음과 같은 로직을 따르는데</p>
<hr>
<ol>
<li>Edit 버튼을 누른다. <em>(Enter Press 동일)</em></li>
<li>isEditing이 true가 되어 <code>handleEditInput()</code> 함수가 실행되고, 상위컴포넌트에서 전달해준 <code>editComment()</code>안에 인자값으로 &#39;comment id&#39; 값과 &#39;editvalue&#39;값을 넣어주며 실행된다.
(editValue의 초기값은 댓글의 content값입니다)</li>
<li>상위 컴포넌트의 <code>editComment()</code> 로직이 돌며 map으로 새로 반환된 댓글리스트를 setCommentList를 통해 state 업데이트를 해준다.</li>
<li>state가 업데이트 되었으므로 <code>commentList</code>가 새롭게 
<code>&lt;CommentList commentList={commentList} /&gt;</code> 컴포넌트로 전달되고, UI를 다시 그리게 됨으로 편집된 댓글내용이 화면에 나타나게 됩니다. </li>
</ol>
<p>요약하면 
Comment 컴포넌트의 버튼을 클릭함으로 댓글 수정 input창이 나타나고, 
수정이 완료된 뒤 Enter Press 혹은 버튼 클릭으로 
상위에 있는 <code>editComment()</code> 를 실행하게 되고, </p>
<p>전달된 editValue값으로 선택한 댓글의 content가 변경되어 commentList의 state변경이 일어나게 됨으로, 댓글 리스트를 재 렌더링 시켜주어
수정된 댓글이 화면에 나타나게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/cd291baa-9dc6-40b0-abe6-96c4c71df125/image.gif" alt=""></p>
<h3 id="댓글-삭제">댓글 삭제</h3>
<p>삭제 기능 또한 편집과 동일한 로직을 따르게 되는데</p>
<p>상위 컴포넌트에서 전달해준 삭제기능을 
<code>&lt;Comment /&gt;</code> 컴포넌트에서 받아 comment의 <code>id</code> 값만 전달해줍니다. </p>
<pre><code class="language-js">// 상위 컴포넌트(&lt;WrapComments /&gt;)의 삭제 기능
const deleteComment = commentId =&gt; {
  let newCommentLists = commentLists.filter(item =&gt; item.id !== commentId);
  setCommentLists(newCommentLists);
};


// 댓글 컴포넌트(&lt;Comment /&gt;)
export default function Comment({ comment, deleteComment }){

  const handleDeleteBtn = e =&gt; {
    deleteComment(comment.id);
  };

  return (
    &lt;button className=&quot;btn-remove&quot; onClick={handleDeleteBtn}&gt;
      &lt;i className=&quot;fa fa-thin fa-xmark&quot; /&gt;
    &lt;/button&gt;
  )

}</code></pre>
<p>결과적으로 <code>&lt;Comment /&gt;</code> 컴포넌트의 <code>handleDeleteBtn()</code> 이 실행되면 상위에 있는 <code>deleteComment()</code> 함수가 실행되고,
filtering 되어진 새로운 댓글 리스트로 -&gt; commentList의 상태값이 업데이트 됨으로 <code>&lt;CommentList /&gt;</code> 컴포넌트를 다시 그리게 되어 삭제된 댓글을 제외한 나머지 댓글들이 화면에 나타나게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/sangheon-k/post/0e1ba052-cd28-4cd5-ba20-ea898cf01a04/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useEffect와 Side Effect - 2]]></title>
            <link>https://velog.io/@sangheon-k/React-useEffect%EC%99%80-Side-Effect-2</link>
            <guid>https://velog.io/@sangheon-k/React-useEffect%EC%99%80-Side-Effect-2</guid>
            <pubDate>Tue, 27 Dec 2022 08:43:48 GMT</pubDate>
            <description><![CDATA[<h2 id="useeffect를-사용하는-경우">useEffect를 사용하는 경우</h2>
<p>React에서는 Functional Component에서 
sideEffect를 효과적으로 관리하기 위해 <code>useEffect</code> 라는 Hook을 제공합니다.</p>
<p>함수형 컴포넌트의 경우 아래와 같은 기본 구조를 가지기에,
return문이 실행되어 JSX를 반환하기 전 실행되는 함수를 입력하면
Side Effect를 발생시킬 수 있게 됩니다. </p>
<p><em>(참조) <a href="https://velog.io/@sangheon-k/React-useEffect-%EC%99%80-Side-Effect">(이전 포스팅) useEffect와 Side Effect - 1</a></em></p>
<pre><code class="language-jsx">export default function App() {
  console.log(&#39;Side Effect!!&#39;)

  return &lt;h1&gt;Hello World&lt;/h1&gt;
}</code></pre>
<p>그런데 위와같이 UI를 그려주는 렌더링 과정 전에 Side Effect를 발생시키면
두 가지 문제가 발생하게 됩니다.</p>
<blockquote>
<h4 id="렌더링-과정-전에-side-effect가-발생하는-경우">렌더링 과정 전에 Side Effect가 발생하는 경우</h4>
</blockquote>
<ul>
<li>Side Effect가 렌더링을 막습니다. </li>
<li>UI가 그려지는 렌더링 과정마다 Side Effect가 실행되게 됩니다.</li>
</ul>
<p>그런데 리액트에서 제공하는 <code>useEffect Hook</code>을 사용하게 되면 
위와같은 현상을 아래와 같이 바꾸어 줍니다. </p>
<blockquote>
<h4 id="useeffect-hook을-사용하여-side-effect를-관리해주는-경우">useEffect Hook을 사용하여 Side Effect를 관리해주는 경우</h4>
</blockquote>
<ul>
<li>렌더링이 완료되고 난 후 side Effect를 발생시킬 수 있다.</li>
<li>내가 원하는 경우에 따라 조건부로 렌더링을 실행할 수 있게 된다.</li>
</ul>
<p>따라서 함수형 컴포넌트로 어플리케이션을 개발할 때 필요한 Side Effect를 
효과적으로 관리하기 위해 우리는 <code>useEffect Hook</code>을 사용하는 것이 좋습니다.</p>
<h2 id="useeffect의-사용법">useEffect의 사용법</h2>
<p><code>useEffect</code>는 리액트에서 제공해주는 Hook으로써, 
컴포넌트가 렌더링 된 후, Side Effect가 실행되도록 만들어진 Hook입니다.
<code>useEffect</code>는 기본적으로 아래와 같은 방법으로 사용됩니다.</p>
<pre><code class="language-js">// useEffect(콜백 함수, 의존성 배열);

// 1. 의존성 배열이 존재하지 않으므로, 매 렌더링마다 useEffect가 실행된다.
useEffect(() =&gt; {
  // Side Effect 
  console.log(&#39;useEffect test 1&#39;)
})

// 2. 의존성 배열에 빈배열이 들어가있으므로, 렌더링시 1번만 useEffect가 실행된다. 
useEffect(() =&gt; {
  // Side Effect 
  console.log(&#39;useEffect test 1&#39;)
}, [])

// 3. 의존성 배열에 참조하는 state1, state2가 변경될 때 마다, useEffect 내부에 있는 side Effect를 실행시킨다.
useEffect(() =&gt; {
  // Side Effect 
  console.log(&#39;useEffect test 1&#39;)
}, [state1, state2])</code></pre>
<p>우리는 useEffect를 사용하여 
컴포넌트가 렌더링 된 후에 <strong>Side Effect</strong>를 일으킬 수 있으며,
조건에 따라(<em>state가 변경되는 경우, 컴포넌트 마운트가 해제되는경우 등</em>)
적절하게 <strong>Side Effect</strong>를 조절하여 사용할 수 있게 됩니다.</p>
<h2 id="cleanup">cleanUp</h2>
<p>useEffect는 Side Effect를 clean up 할 수 있는 방법을 제공해주는데 
사용법은 아래와 같습니다. </p>
<pre><code class="language-js">useEffect(() =&gt; {
  // Side Effect

  const cleanUpMethod = () =&gt; {
    // clean up 내용
  }
  return cleanUpMethod;
},[])</code></pre>
<p>위의 기본 구조에서 <code>useEffect</code>는 2가지 경우에 useEffect 콜백함수 내부의 <code>return</code>문을 실행시켜주는데 2가지는 다음과 같습니다.</p>
<ul>
<li>*<em>다음 Side Effect를 발생시키기 전 *</em>
(의존성 배열에 state값이 존재하는 경우, state의 변경으로 인해 
Side Effect가 다시 발생되기 전)</li>
<li><strong>컴포넌트가 unmount 될 때</strong>
(다른 페이지로 라우팅 되는 경우, 시간 초과에 따라 컴포넌트의 마운트가 해제되는 경우 등)</li>
</ul>
<p>만약 useEffect의 의존성배열로 어떠한 state값이 들어가있고, 
그 state가 변경됨에 따라 컴포넌트가 업데이트 된다고 가정했을 때 아래와 같이 실행 될 수 있습니다. </p>
<pre><code class="language-jsx">const [count, setCount] = useState();

useEffect(() =&gt; {
  // Side Effect
  console.log(&#39;useEffect SideEffect 1&#39;)

  return () =&gt; {
    // clean up 내용
    console.log(&#39;useEffect clean up 2&#39;)
  };
},[count])

return (
    &lt;button onClick={e =&gt; setCount(prev =&gt; prev + 1)}&gt;count 증가&lt;/button&gt;
)</code></pre>
<p>초기 렌더링이 완료 된 후 useEffect가 실행됨으로</p>
<ul>
<li><ol>
<li>side Effect의 실행으로 console에 
&#39;useEffect SideEffect 1&#39; 가 나타나며</li>
</ol>
</li>
</ul>
<p>버튼을 클릭하여 state가 변경되는 경우</p>
<ul>
<li><ol>
<li>state값이 변경된다</li>
</ol>
</li>
<li><ol start="2">
<li>useEffect 안의 return문이 실행되어 기존의 side Effect가 정리된다. 
(clean up)</li>
</ol>
</li>
<li><ol start="3">
<li>기록된 side Effect가 다시 실행된다.</li>
</ol>
</li>
</ul>
<p>따라서 콘솔에는</p>
<pre><code class="language-js">// 초기 렌더링시
console.log(&#39;useEffect clean up 2&#39;) // 1

// 버튼 클릭 후 state 변경된 후
console.log(&#39;useEffect clean up 2&#39;) // 1
console.log(&#39;useEffect SideEffect 1&#39;) // 2</code></pre>
<p>위와 같이 입력되게 된다.</p>
<h3 id="결론">결론</h3>
<p><code>useEffect()</code> Hook 내부에서 일어나는 일련의 과정을 이해함으로써
리액트의 라이프사이클을 이해할 수 있고,  조건에 맞는 렌더링을 개발자가 원하는 지점에서 작동시킬 수 있게 함으로써 어플리케이션의 품질을 향상 시킬 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useEffect와 Side Effect]]></title>
            <link>https://velog.io/@sangheon-k/React-useEffect-%EC%99%80-Side-Effect</link>
            <guid>https://velog.io/@sangheon-k/React-useEffect-%EC%99%80-Side-Effect</guid>
            <pubDate>Mon, 26 Dec 2022 02:26:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sangheon-k/post/4506c8a0-84d6-46c1-a944-2e9fbc630a12/image.png" alt=""></p>
<h2 id="side-effect란">Side Effect란?</h2>
<p>원래 주된 목적에서 벗어나 다른 결과가 나타나는 현상을 말합니다.
웹 프로그래밍 작업을 하며 우리는 필요한 기능을 함수단위로 작성하게 되는데, 
이러한 <strong>함수의 원작용은 Input을 받아서 output을 산출하는 것</strong>입니다.</p>
<p>따라서, 프로그래밍 언어에서 <strong>Side Effect</strong>가 의미하는 말은
<strong>Input을 받아서 output을 산출하는 것 외의 모든 행위</strong>를 의미하게 됩니다. </p>
<pre><code class="language-js">// 1. Side Effect 발생 X =&gt; 함수 내부에서 원하는 결과 값 도출
const sum = (x) =&gt; {
  return x + 1;
};</code></pre>
<pre><code class="language-js">// 2. Side Effect 발생 O =&gt; 함수 외부의 변수를 읽어옴
const num = 1;

const sum = (x) =&gt; {
  return x + num;
};</code></pre>
<pre><code class="language-js">// 3. Side Effect 발생 O =&gt; 함수 외부의 변수 상태를 변경시킴
let num;

const sum = (x) =&gt; {
  num = x + 1;
};

// 4-1. Side Effect 발생 O =&gt; 함수 외부에 존재하는 console의 상태를 변경시킴
const printNum = (x) =&gt; {
  console.log(x);
};

// 4-2. Side Effect 발생 O =&gt; 함수 외부에 존재하는 DOM의 상태를 변경시킴
const changeTitle = (newTitle) =&gt; {
  const title = document.getElementById(&#39;title&#39;);

  title.innerText = newTitle;
};
</code></pre>
<p><strong>프로그래밍에서 
필요한 기능을 함수단위로 작성하는 경우 
Side Effect는</strong> </p>
<ul>
<li>함수 외부의 값을 읽어오는 행위</li>
<li>함수 외부의 값을 변경하는 행위 를 의미합니다. </li>
</ul>
<h2 id="react에서의-side-effect란">React에서의 Side Effect란?</h2>
<p>React에서 <strong>rendering</strong>이란,
<strong>state, props</strong>를 기반으로 <strong>UI 요소를 그려내는 행위</strong>를 말합니다. </p>
<p>여기에서 리액트는 함수컴포넌트를 활용하여 UI요소를 그려내게 되는데, 
함수컴포넌트의 역할은 <strong>props</strong>와 <strong>state</strong>를 활용하여 <strong>JSX를 return</strong>하는 것입니다. </p>
<p>도식화하면 다음과 같습니다. </p>
<pre><code class="language-jsx">// React에서 함수컴포넌트의 원작용
function (state, props) =&gt; JSX</code></pre>
<p>그러므로 React 함수 컴포넌트에서의 Side Effect란
<strong>state</strong>와 <strong>props</strong>를 이용하여 <strong>JSX를 return하는 <em>활동 외의 모든 행위</em></strong>를 의미하게 됩니다.</p>
<p>대표적으로는 
<strong>Data Fetching</strong> (외부에서 Data를 가져오는 행위),
<strong>DOM 접근 및 조작</strong> (함수 외부의 DOM을 조작하는 행위), 
<strong>구독</strong>(Subscribe - 함수 외부의 어떤 것의 변화를 계속해서 감지하는 행위)
등이 있습니다.</p>
<h2 id="결론">결론</h2>
<p>Side effect는 함수의 동작 결과를 예측하기 어렵게 만들기에 
일반적으로는 기피해야하나, 필요(Data Fetching, DOM 접근 및 조작등)에 따라 
잘 사용하여 예측이 가능한 프로그램을 만들어야 한다.</p>
]]></description>
        </item>
    </channel>
</rss>