<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>minjae-mj</title>
        <link>https://velog.io/</link>
        <description>Front-end Developer.  자바스크립트 파헤치기에 주력하고 있습니다 🌴</description>
        <lastBuildDate>Tue, 08 Nov 2022 15:04:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>minjae-mj</title>
            <url>https://images.velog.io/images/minjae-mj/profile/cf4c23b0-dcbd-48bb-afc8-a2b454beb3ac/IMG_4090.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. minjae-mj. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/minjae-mj" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[하이브리드앱 협업기록 #2단계: 필요사항 준비 1탄]]></title>
            <link>https://velog.io/@minjae-mj/%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%ED%98%91%EC%97%85%EA%B8%B0%EB%A1%9D-2%EB%8B%A8%EA%B3%84-%ED%95%84%EC%9A%94%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@minjae-mj/%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%ED%98%91%EC%97%85%EA%B8%B0%EB%A1%9D-2%EB%8B%A8%EA%B3%84-%ED%95%84%EC%9A%94%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 08 Nov 2022 15:04:50 GMT</pubDate>
            <description><![CDATA[<p>자, 우리의 하이브리드앱은 현재 브라우저 기반의 반응형 웹앱에 flutter 로 layer 를 씌워 만들기로 했다. 이 작업은 외부의 collaborator 개발자가 맡아주시기로 하고, 협업에 필요한 준비를 한다. 그와 별도로, 앱출시 이후 유지•업데이트를 이끌어나가기 위해 flutter 를 스터디하고, 우리가 앱의 최소한이자 필수 기능으로 기대하고 있는 push notification 기능에 대해서도 추가 스터디를 이어가고 있다. 🤓</p>
<h2 id="☕️-킥오프-미팅에서-collaborator-로부터-전달받은-요구사항">☕️ 킥오프 미팅에서 collaborator 로부터 전달받은 요구사항</h2>
<h3 id="1-구글-play-console-과-애플-developer-program-에서-개발자-계정을-생성하여-초대">1. <a href="https://support.google.com/googleplay/android-developer/answer/6112435?hl=ko">구글 Play Console</a> 과 <a href="https://developer.apple.com/kr/programs/enroll/">애플 Developer Program</a> 에서 개발자 계정을 생성하여 초대</h3>
<p>🔸 회사의 대표 개발자 계정을 만들어서 직접 가입 진행
🔸 가격은 구글의 경우, US$ 25 한 번 지불. 애플은 US$ 99 의 연회비가 있다. 
🔸 법인 카드와 (애플의 경우) 영문 사업자등록증이 필요하다. 
🔸 가입/로그인 시에 two-factor 등 개인기기로 인증절차를 거쳐야 하므로, 기기와 연락처는 내 것을 사용
🔸 처음에는 권한을 제한하여 초대드렸으나, 작업에 제약이 발견되어 admin 권한으로 변경해드렸다. </p>
<h3 id="2-빈-repository-를-만들어서-전달">2. 빈 repository 를 만들어서 전달</h3>
<p>🔸 github 에서 생성하여 readme, .gitignore 등 initial commit 만 한 상태로 준비
🔸 collaborator 의 github 계정을 받아서 maintain 권한으로 초대
🔸 repository 명을 지을 때는 naming convention 을 검색하여 참고했다. </p>
<blockquote>
<ul>
<li>타이핑이 어려운 _ 보다는 - 사용을 권장</li>
</ul>
</blockquote>
<ul>
<li>영문 소문자 사용</li>
<li>가능한 구체적으로 정해야, 향후 추가될 수 있는 repo 와 구별되기 쉽다. <ul>
<li>그래서 web 이나 app 같은 제네릭한 이름은 넣지 않았다. </li>
</ul>
</li>
<li>운영 중인 repository 가 있다면, 일관성과 레벨을 잘 맞추어본다. <ul>
<li>flutter 를 넣어서 모바일 repo 라는 걸 암시해볼까.. 는 제일 이상했고, 혹시나 미래의 네이티브앱이 있다면 구분을 위해 hybrid 를 붙여볼까 고민하다가 제일 맘에 들었던 <strong>서비스명-mobile</strong> 형태로 낙점! </li>
</ul>
</li>
</ul>
<h3 id="3-그-외-app-정보에-관한-작성-및-수집">3. 그 외 app 정보에 관한 작성 및 수집</h3>
<p>🔸 개발건으로 구글의 경우, <a href="https://support.google.com/admob/answer/9972781?hl=ko">app package name</a> 을 함께 전달해야 한다. </p>
<ul>
<li>기기, Google Play 스토어, 지원되는 타사 Android 스토어에서 앱을 고유하게 식별하는 unique 한 아이디 역할을 한다. </li>
<li>네이밍 규칙이 있다. 보통 <strong>최상위도메인.회사명.프로젝트명</strong> 과 같은 형태 (참고블로그는 👉🏼 <a href="https://bvc12.tistory.com/339">여기</a>) </li>
</ul>
<p>🔸 그 외 기획/디자이너와 협업하여 어플을 검색했을때 보이는 앱의 이름과 설명, 아이콘과 스크린샷 이미지 등을 준비한다.<br>🔸 앱 심사 전에만 준비되면 되지만, 개발이 진행되는 동안 병렬로 준비할 수 있는 내용들이다. 
🔸 애플이 정리가 잘 되어있어, 애플을 기준으로 준비했다. (참고링크는 👉🏼 <a href="https://help.apple.com/app-store-connect/#/devfc3066644">여기</a>) 구글도 슥- 보니 내용이 얼추 겹쳐서 일타이피를 할 수 있을 것 같았다.<br>🔸 정리가 잘 되어있지만, 설명을 포함한 내용이 매우 방대하여! 아래와 같이 노션에 필수항목들을 뽑아내고, 미리 준비가능한 것들을 착착 준비했다. </p>
<p><img src="https://velog.velcdn.com/images/minjae-mj/post/601b4a83-5ea3-479e-93e9-d16e2e155764/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[하이브리드앱 협업기록 #1단계: 도입검토]]></title>
            <link>https://velog.io/@minjae-mj/%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EC%97%AC%EC%A0%95%EA%B8%B0%EB%A1%9D-%EB%8F%84%EC%9E%85%EA%B2%80%ED%86%A0</link>
            <guid>https://velog.io/@minjae-mj/%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EC%97%AC%EC%A0%95%EA%B8%B0%EB%A1%9D-%EB%8F%84%EC%9E%85%EA%B2%80%ED%86%A0</guid>
            <pubDate>Tue, 25 Oct 2022 14:23:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/minjae-mj/post/4f8d4760-0ac6-41c5-9e1d-9617883559d7/image.png" alt=""></p>
<h2 id="prologue">Prologue</h2>
<p>이제 만 두 살이 되어가는, 빈 페이지에서 시작했던 웹서비스가 무럭무럭 자라 많은 곳에서 선보여지고 있다. 모든 스크린 사이즈에 대응 가능한 반응형 모바일 웹으로 제작되었는데, 현장에서 앱을 찾는다는 얘기가 꾸준히 들려온다. 새로운 기획은 쏟아져나오고, 현재 기능들의 유지 보수도 챙겨야 하고, 리소스는 한정된 상황. 이러한 인적, 시간 경제적, 타겟 유저 등등의 환경을 고려해서 방향을 찾고 있었다. </p>
<p>내부적으로 PWA를 적극 검토하고 있던 와중, 외부 관계자의 도움으로 flutter 를 통해 앱패키징까지 해주실 수 있다는 연락을 받았다. 올레! 그래서 그 분과의 협력 하에 하이브리드앱을 만드는 것으로 얘기가 급진전-</p>
<h2 id="🦒-앱-형태">🦒 앱 형태</h2>
<p>모바일웹, 웹앱, 하이브리드앱, 네이티브앱 등 다양한 용어가 있었는데 찾아보고 크게 Web, Hybrid, Native 세 가지로 축약했다. 장단점을 포함한 특징과 함께 정리하면 이렇다. </p>
<h3 id="web-app">Web app</h3>
<blockquote>
<p>웹 브라우저에 기반한 모든 소프트웨어 어플리케이션. 반응형 웹앱, 모바일 웹앱 등</p>
</blockquote>
<h3 id="native-app">Native app</h3>
<blockquote>
<p>Android 또는 iOS와 같이 특정 플랫폼을 위해 만들어진 소프트웨어 프로그램 </p>
</blockquote>
<p>Android 모바일앱은 Kotlin 또는 Java 로, iOS 는 Swift 또는 Objective C 를 사용하여 만든다. 기기와 플랫폼에 맞춰 개발되는 만큼, 최적화된 성능과 최신 기능을 빠르게 적용할 수 있다. </p>
<ul>
<li>다양한 네이티브 기능을 사용할 수 있다. (GPS, 카메라, 마이크, 푸쉬알람, swipe gesture 등등)</li>
<li>안정적이고 반응이 빠르다.</li>
<li>각 OS의 유저 경험에 잘 맞는 UI 제공이 가능하다. </li>
<li>플랫폼간 호환이 되지 않으므로 별도로 제작해야 한다. </li>
<li>그래서 비용이 많이 들고 빌드하는데 오래 걸린다. </li>
</ul>
<h3 id="hybrid-app">Hybrid app</h3>
<blockquote>
<p>Native app 의 shell 에 쌓인 Web app</p>
</blockquote>
<p>내부 동작은 Web app 과 유사하나, Native app 처럼 설치할 수 있다. 기본적으로 내장된 API (GPS, 카메라 등) 에 접근할 수 있다. 잘 알려진 Yelp 와 인스타그램도 하이브리드 앱이다.  </p>
<ul>
<li>하나의 코드베이스로 멀티 플랫폼에서 동작한다. </li>
<li>안드로이드나 아이폰 등 각 디바이스의 특정 기능을 사용하는 데는 제약이 있다. </li>
<li>비용, 시간, 유지보수 측면에서 용이하고 기본 API까지 사용할 수 있다. </li>
<li>오프라인에서 동작하지 않는다. </li>
</ul>
<p>Hybrid app 역시 각 플랫폼에 맞는 코드를 작성해야 한다는 점에서 Native app 과 비용 차가 크지 않다고 하나, 일반적으로 4-6개월내 개발되어야 하는 경우 더욱 빠르게 만들 수 있는 하이브리드앱이 선호된다고 한다.</p>
<h2 id="🐆-검토-etc">🐆 검토 ETC</h2>
<p>그 외 개발팀 내부에서 앱과 함께 찾아오는 것들(?)도 함께 챙겨봤다. 👀</p>
<ul>
<li>테스트 범위 증가 - 앱전용 기능, 웹뷰 테스트, 모바일 기기 테스트</li>
<li>유지보수 난이도 증가 - 테스트 &amp; 배포를 위한 개발 언어 및 환경의 다양성</li>
<li>평점 관리 - 출시 퀄리티와 유지하기, 그리고 리뷰 CS</li>
<li>가이드 준수 - 심사 reject 를 최소화 하기 위한 가이드 지침 준수 및 추후 가이드 변경에 대한 지속적 모니터링</li>
</ul>
<p>인앱결제를 할 경우, 플랫폼에 지불되는 수수료도 함께 챙겨봐야 하지만 우리는 그런 모델이 아니므로 패쓰- 그리고 애플은 릴리즈 심사가 까다롭다고 하니.. (심사가 거절되어도 어디서! 왜! 거절되었는지 안 알려주고, 어떤 항목을 위반했다고만 알려온다고.. 😨) 스토어 배포 지침서에서 언급하는 Native 구현 요구 조건 등을 잘 살펴보고 만족하도록 준비해야겠다. 첫 릴리즈 이후에는 배포를 최소화하는 금융권 어플을 모방하면 어떨까 한다. </p>
<p><br><br></p>
<p>[참고자료]</p>
<ul>
<li><a href="https://www.techtarget.com/searchsoftwarequality/definition/native-application-native-app">native app</a></li>
<li><a href="https://www.upwork.com/resources/hybrid-app#advantages">What Is a Hybrid App? (Detailed Guide for 2022)</a></li>
<li><a href="https://velog.io/@openhub/%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%95%B1Native-App-vs-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EC%95%B1Hybrid-App-vs-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%A0%88%EC%8B%9C%EB%B8%8C-%EC%9B%B9-%EC%95%B1PWA-%EC%A0%95%EC%9D%98%EC%99%80-%EC%9E%A5%EB%8B%A8%EC%A0%90">네이티브 앱(Native App) vs 하이브리드 앱(Hybrid App) vs 프로그레시브 웹 앱(PWA) – 정의와 장단점</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[깃이 꼬였을 때 ]]></title>
            <link>https://velog.io/@minjae-mj/%EA%B9%83%EC%9D%B4-%EA%BC%AC%EC%98%80%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@minjae-mj/%EA%B9%83%EC%9D%B4-%EA%BC%AC%EC%98%80%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Mon, 21 Mar 2022 10:43:00 GMT</pubDate>
            <description><![CDATA[<h3 id="🤯-방금-커밋-잘못했다-이전-작업상태로-되돌리고-싶다">🤯 방금 커밋 잘못했다. 이전 작업상태로 되돌리고 싶다.</h3>
<p>or 현재 main 작업공간에 feature 브랜치를 잘못 당겨왔다. 그 결과 Merge 한 내용의 커밋 기록이 남았고, 이전 작업 상태로 돌리고 싶다. </p>
<blockquote>
<p>🔥 커밋 취소하기: <code>git reset</code></p>
</blockquote>
<ol>
<li><p>git log 를 살펴본다.</p>
<pre><code>$ git log --oneline
 1938ys9 (HEAD -&gt; main) Add newFile
 lks313s Update Readme.md
 df319eg Initial commit</code></pre></li>
</ol>
<p>가장 최근에 df319eg &gt; lks313s &gt; 1938ys9 순서대로 커밋했고, 마지막에 한 1938ys9 커밋을 취소하고 싶다. </p>
<ol start="2">
<li><p><em>세부 시나리오</em></p>
<h4 id="최근-파일-수정-내용도-지우고-이전-커밋이-막-끝난-상태로-되돌리고-싶다">최근 파일 수정 내용도 지우고, 이전 커밋이 막 끝난 상태로 되돌리고 싶다.</h4>
<p><code>git reset --hard &lt;COMMIT_ID&gt;</code> 의 COMMIT_ID 에 되돌아가고자 하는 이전 커밋 아이디 (lks313s) 를 넣어 HEAD 가 다시 이전 커밋을 가리키도록 한다. </p>
<pre><code class="language-html">  $ git reset --hard lks313s</code></pre>
<h4 id="파일-수정-내용은-남기고-커밋만-취소하고-싶다">파일 수정 내용은 남기고, 커밋만 취소하고 싶다.</h4>
<p><code>git reset HEAD^</code> 를 사용하여 최신커밋을 취소하고, 커밋되었던 파일을 unstaged 시킨다. (working tree에 남는다.) 커밋 여러 개를 되돌릴 경우, <code>git reset HEAD~취소수량</code> 을 입력한다. 주로 직전 커밋을 취소하고 싶은 경우 사용하는 것이 좋다. </p>
<pre><code class="language-html"> $ git reset HEAD^ // 커밋 하나만 뒤로 돌린다. 
 $ git reset HEAD~1 // 취소 수량에 1만 넣었으므로 바로 위와 동일</code></pre>
</li>
<li><p>잘 됐는지 확인한다.
2번의 --hard 옵션은 현재 HEAD 에서 추가된 변경사항들을 모두 되돌린다. (복구 불가하므로 주의)</p>
</li>
</ol>
<pre><code class="language-html">   $ git log --oneline
    lks313s (HEAD -&gt; main) Update Readme.md
    df319eg Initial commit

    $ git status
    On branch main
    nothing to commit, working tree clean</code></pre>
<blockquote>
<p>🔥 working tree 에서 수정한 파일 되돌리기: <code>git restore</code></p>
</blockquote>
<h4 id="🙋🏻♀️-만약-working-tree-에-최근-수정-파일이-돌아왔는데-확인해보니-이전-상태로-되돌리고-싶다면">🙋🏻‍♀️ 만약 working tree 에 최근 수정 파일이 돌아왔는데, 확인해보니 이전 상태로 되돌리고 싶다면?</h4>
<pre><code class="language-html">    $ git checkout -- &lt;file&gt; // 예전에 쓰던 방법
    $ git restore &lt;file&gt; // 새로운 깃버전에서 출시</code></pre>
<h4 id="🙋🏻♀️-수정-파일을-unstaged-시키기">🙋🏻‍♀️ 수정 파일을 unstaged 시키기</h4>
<pre><code class="language-html">    $ git reset HEAD &lt;file&gt; // 예전에 쓰던 방법
    $ git restore --staged &lt;file&gt; // 새로운 깃버전에서 출시</code></pre>
<h3 id="😱-잘못된-커밋을-원격에-push-까지-해버렸다">😱 잘못된 커밋을 원격에 push 까지 해버렸다.</h3>
<p>git reset 은 HEAD 위치를 바꿔버리므로, 로컬 저장소의 깃 상태를 이전 상태로 강제 변경한다. 하지만 커밋을 원격 저장소에 push 한 상태라면, 로컬에서 커밋을 취소했을때 원격의 최신 상태와 어긋나버리게 된다. 이 경우, <code>git revert</code> 로 특정 커밋의 내용을 되돌릴 수 있다. </p>
<blockquote>
<p>🔥 커밋 되돌리기: <code>git revert</code></p>
</blockquote>
<ol>
<li>git log 를 살펴본다.</li>
</ol>
<pre><code class="language-html">   $ git log --oneline
    1938ys9 (HEAD -&gt; main) Add newFile
    lks313s Update Readme.md
    df319eg Initial commit</code></pre>
<ol start="2">
<li><p>여기서 1938ys9 커밋을 되돌리기위해 다음 명령어를 실행한다.</p>
<pre><code class="language-html">$ git revert 1938ys9</code></pre>
<p>커밋 메세지 작성을 위해 에디터 환경변수의 에디터가 실행된다.</p>
<pre><code class="language-html">Revert &quot;Add newFile&quot;
This reverts commit 1938ysbac01f0ef6bee04ae4f40c3dd8e5bf1d552.</code></pre>
</li>
</ol>
<ol start="3">
<li>기본 메세지 그대로 저장후 에디터를 종료한다. 로그를 확인해보면, 다음과 같이 Revert 커밋이 추가되어 있다. </li>
</ol>
<pre><code class="language-html">   $ git log --oneline
    9wksl8l (HEAD -&gt; main) Revert &quot;Add newFile&quot;
    1938ys9 (origin/main) Add newFile
    lks313s Update Readme.md
    df319eg Initial commit</code></pre>
<h3 id="😵-커밋했는데-수정-하나를-빠트렸다">😵 커밋했는데 수정 하나를 빠트렸다.</h3>
<p>or 커밋메세지 잘못 썼다. </p>
<blockquote>
<p>🔥 커밋 덮어쓰기: <code>git commit --amend -m &lt;COMMIT_MESSAGE&gt;</code></p>
</blockquote>
<p>amend 옵션은 스테이징에 추가된 내용을 반영할 뿐 아니라, 커밋메세지도 업데이트 변경한다. 따라서 변경 내용이 없어도, 커밋메세지만 바꾸고 싶을때 사용할 수 있다. </p>
<p><br><br>
[ 참고자료 ]</p>
<ul>
<li><a href="https://www.lainyzine.com/ko/article/git-reset-and-git-revert-and-git-commit-amend">https://www.lainyzine.com/ko/article/git-reset-and-git-revert-and-git-commit-amend</a></li>
<li><a href="https://developer-ankiwoong.tistory.com/773">https://developer-ankiwoong.tistory.com/773</a></li>
<li><a href="https://bskyvision.com/1146">https://bskyvision.com/1146</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NEXT.js 파먹기 #dynamicRoutes]]></title>
            <link>https://velog.io/@minjae-mj/NEXT.js-%ED%8C%8C%EB%A8%B9%EA%B8%B0-dynamicRoutes</link>
            <guid>https://velog.io/@minjae-mj/NEXT.js-%ED%8C%8C%EB%A8%B9%EA%B8%B0-dynamicRoutes</guid>
            <pubDate>Fri, 18 Mar 2022 16:15:50 GMT</pubDate>
            <description><![CDATA[<h3 id="🔥-dynamic-routes">🔥 Dynamic routes</h3>
<ul>
<li>pages 아래의 파일 이름을 <code>[]</code> 에 담으면 dynamic route 가 된다. </li>
<li>그 파일에 반드시 <code>getStaticPaths()</code> 와 <code>getStaticProps()</code> 두 개의 메소드를 포함해야 한다 </li>
<li>참고) 아래 예시에서 id 를 key 로 쓰는 이유는 파일 이름이 [id].js 이기 때문</li>
</ul>
<pre><code class="language-jsx">import Layout from &#39;../../components/layout&#39;

export default function Post() {
  return &lt;Layout&gt;...&lt;/Layout&gt;
}

export async function getStaticPaths() {
  // Return a list of possible value for id under paths
  // Example of paths. {}[]:
  // const paths = [
  //   {
  //     params: {
  //       id: &#39;some-string&#39;
  //     }
  //   }
  // ]

  return {
    paths,
    fallback: false
  }
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
  return {
    props: {
      postData: await getPostData(params.id)
    }
  }
}</code></pre>
<ul>
<li>Note<ul>
<li>In development, getStaticPaths runs <strong>on every request</strong>.
In production, getStaticPaths runs <strong>at build time</strong>.</li>
<li><code>getStaticPaths</code> 안의 <code>fallback: false</code> 는 path 를 찾지 못했을 때, 404 페이지를 반환한다. 이외에 &#39;true&#39;, &#39;blocking&#39; 값을 가질 수 있다. 더 자세한 건 <a href="https://nextjs.org/learn/basics/dynamic-routes/dynamic-routes-details">여기</a>로!</li>
<li>또 다른 예시: pages/products/[id].js 로 상품 페이지를 만든다고 하자. <code>getStaticPaths</code> 에서 전체 product ID 를 배열로 가져오고, <code>getStaticProps</code> 에서 각 상품의 데이터를 가져온다. </li>
</ul>
</li>
</ul>
<h3 id="catch-all-routes">Catch-all Routes</h3>
<ul>
<li><p><a href="https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes">다 잡아내는 라우트</a>를 만들기도 가능하다. 파일의 괄호 [] 안에 쩜쩜쩜만 추가하면 된다</p>
<ul>
<li>pages/posts/[...id].js 로 저장하면 /posts/a 뿐 아니라,<br>/posts/a/b, /posts/a/b/c 등등에 모두 매칭된다 </li>
<li>이 경우, getStaticPaths 안의 id 값은 {}[] 가 아닌, [] 형태로 반환할 것</li>
</ul>
<pre><code class="language-jsx">return [
   {
     params: {
       // Statically Generates /posts/a/b/c
       id: [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
     }
   }
   //...
 ]</code></pre>
</li>
</ul>
<h3 id="router">Router</h3>
<ul>
<li>일반 Next.js router 는 useRouter hook from next/router 사용</li>
</ul>
<h3 id="404">404</h3>
<ul>
<li>자주 발생하는 404 페이지를 모든 visit 마다 서버렌더링 하면 Next.js 서버에 부담이 많이 간다 =&gt; 비용 증가, 지루한 경험으로 이어진다. 그래서 Next.js 는 별도로 추가하지 않아도 기본적으로 404 페이지를 제공하며, 얼마든지 <a href="https://nextjs.org/docs/advanced-features/custom-error-page#404-page">커스터마이징</a>해서 사용 가능하다</li>
<li>500 페이지도 디폴트 제공</li>
</ul>
<p><img src="https://images.velog.io/images/minjae-mj/post/245db084-2490-4f79-8110-36897ab1388b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NEXT.js 파먹기 #preRendering]]></title>
            <link>https://velog.io/@minjae-mj/NEXT.js-%ED%8C%8C%EB%A8%B9%EA%B8%B0</link>
            <guid>https://velog.io/@minjae-mj/NEXT.js-%ED%8C%8C%EB%A8%B9%EA%B8%B0</guid>
            <pubDate>Fri, 18 Mar 2022 04:12:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nextjs.org/learn/basics/data-fetching">공식문서</a>로 Next.js 속성 공부 중. #핵심개념 #요약정리 </p>
<h3 id="pre-rendering">Pre-rendering</h3>
<ul>
<li>장점: <strong>Better performance and SEO</strong></li>
<li>특징: 페이지별 pre-rendering 여부 설정 가능 </li>
</ul>
<h4 id="🌞-1-static-generation">🌞 1. Static Generation</h4>
<ul>
<li>pre-rendering method that generates the HTML <strong>at build time</strong>. The     pre-rendered HTML is then reused on each request</li>
<li><em>getStaticProps()</em></li>
<li>페이지 예시: Marketing pages, Blog posts, E-commerce product listings, Help and documentation, etc</li>
</ul>
<h4 id="🌝-2-server-side-rendering">🌝 2. Server-side Rendering</h4>
<ul>
<li>pre-rendering method that generates the HTML <strong>on each request</strong>.</li>
<li><em>getServerSideProps(context)</em></li>
<li>You should use <code>getServerSideProps</code> only if you need to pre-render a page whose data must be fetched at request time. <code>Time to first byte (TTFB)</code> will be slower than <code>getStaticProps</code> because the server must compute the result on every request, and the result cannot be cached by a CDN without extra configuration</li>
<li>Good when you cannot pre-render a page ahead of a user&#39;s request, shows frequently updated data, and the page content changes on every request</li>
</ul>
<h3 id="client-side-rendering">Client-side Rendering</h3>
<ul>
<li>Combo: Static Generation without Data + Fetch Data on the Client-side</li>
<li>When the page loads (at request time), fetch external data from the client using JavaScript and populate the remaining parts</li>
<li>페이지 예시: User Dashboard pages <ul>
<li>because a dashboard is a private, user-specific page, SEO is not relevant, and the page doesn’t need to be pre-rendered. The data is frequently updated, which requires request-time data fetching</li>
</ul>
</li>
</ul>
<ul>
<li><em><a href="https://swr.vercel.app/">SWR</a></em>: React hook for data fetching on the client side<ul>
<li>handles caching, revalidation, focus tracking, refetching on interval, and more</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[💐 입사 1주년 ]]></title>
            <link>https://velog.io/@minjae-mj/%EC%9E%85%EC%82%AC-1%EC%A3%BC%EB%85%84</link>
            <guid>https://velog.io/@minjae-mj/%EC%9E%85%EC%82%AC-1%EC%A3%BC%EB%85%84</guid>
            <pubDate>Thu, 17 Mar 2022 09:28:04 GMT</pubDate>
            <description><![CDATA[<p>1st anniversary. 새로운 일을 시작한지 1주년을 맞았다. 입사 전까지 열심히 블로그도 작성하고 daily commit 도 하려 노력했는데, 워낙 여러 군데 신경쓰지 못하는 성격이다 보니 일에 집중하느라 그 동안 뜸했다. github 잔디도 열심히 기르고 있었는데, bitbucket 으로 넘어오면서 황무지가 됐다. 🥺</p>
<p>🌸 1인 프론트엔드 개발자라 외로울 뻔 했지만, 다행히 너무 좋은 백엔드 개발자분을 사수로 만나 과외인듯 코드 리뷰를 받아가며, 듀오로 작업하면서 서비스의 골격을 만들었다. 내가 몰랐던 CS 지식이나 데이터 아키텍처, API 구조에서부터 단축키 팁들과 개발자의 본능(?) 등등 다양한 주제에 대한 경험적인 노하우를 들으며 단 시간에 개발에 대한 내 사고에 엄청난 확장을 이룰 수 있었다. 아무리 생각해도 감사한 일이다. 게다가 입사 전 팀 프로젝트를 모두 리액트로만 진행해서, 자바스크립트에 대한 기초가 약한게 아닌지 내심 불안했는데 현재 서비스를 바닐라 JS 로 구성하다보니 이제는 꽤 자신감이 붙었다. (대신 이번엔 리액트를 한 동안 안 써서 불안해지려고 한다 ^_^. 이런 류의 불안함은 계속 안고 가야 하는걸까?)</p>
<p>🌞 아침마다 스크럼을 하는데 아무래도 백엔드 개발자가 다수이다 보니, 서버에 관한 이야기가 많이 오고간다. 나는 꼭 관련 분야가 아니라도 일단 다 배우고 보자는 주의라, 전부 알아듣지는 못해도 집중해서 듣는데 구조 설계 같은 부분은 톡톡히 도움이 된다. 아, 또 이런 것도 있다. 오늘 스크럼에서는 성능 최적화보다 운영 노하우를 구축해놓는게 도움이 되기도 한다는 말을 들었는데 새로웠다. 현재는 작은 규모라 개발•운영이 같이 관리되는데 이걸 따로 떼어서 생각해보지 않은 것 같다. 고객 이슈 대응을 위한 tool, 디버깅을 위한 로그관리, 좋은 서버에 몰빵할 수 있어도 맛이 가는 경우를 위한 분산관리 등등이 여기에 해당된다. + 고객 이슈가 들어올때, 장애상황을 편하게 파악하기 위해 나만 알아볼 수 있는 표식(?) 이라던가, 개발과 운영화면을 구분하기 위해 개발 화면을 이따시만하게(?) 변조해 둔다던가 하는 것도 지금 생각하니 다 꿀팁이다.  </p>
<p>🌷 서비스 킥오프 미팅이 시작된지 두어 달 후에 조인하여, 기획부터 참여했던 서비스가 21년 4월에 클로즈베타, 그리고 세달 뒤 오픈베타를 런칭했다. 그 사이에 내부 정책이 변경되어 화면을 대대적으로 갈아엎기도 했다. 작년 말 사수 분이 떠나시고 😢 새로운 백엔드 개발자와 step up 해주신 우리 CTO와 합을 맞추는 것도 떨렸지만 새로운 경험이었고, bitbucket 에서 github 으로 깃을 이전하면서 관련 세팅을 해 본 덕택에 터미널 작업, 깃 관리등이 한결 편해지기도 했다. 운영팀에서는 서비스가 안정화되었다고 판단했는지 이제 곧 beta 뱃지를 떼어낼 준비 중이다. 크립토를 적립식 구매하는 서비스이다 보니, 회사의 지원으로 다섯 달 정도는 블록체인/크립토 환경에 관한 세미나를 매주 듣기도 했다. 흥미진진. </p>
<p>🌟 온 나라의 친구들과 이야기하면서도 종종 &#39;나는 숫자랑 안 친해~&#39; 를 굳이 굳이 번역해서 말하고 다니던 내가 숫자 데이터를 다루는 일을 하다니, 그리고 그걸 또 해내는 스스로를 지켜보며 역시 내 한계는 내가 만드는 거란 걸 새삼 깨닫는다. 항상 하나라도 더 알려주시려고 하는 CTO와 똑똑하고 젠틀한 동료들, 믿고 지원해주시는 경영진 덕에 즐겁게 일하고 있다. 스스로 부족하다고 생각한 부분들을 채워 성장으로 보답하고 싶은 마음이 앞서는 하루다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript Basic]]></title>
            <link>https://velog.io/@minjae-mj/Javascript-Basic</link>
            <guid>https://velog.io/@minjae-mj/Javascript-Basic</guid>
            <pubDate>Sun, 11 Apr 2021 14:53:03 GMT</pubDate>
            <description><![CDATA[<p>Javascript has dynamic typing. 자바스크립트는 동적 타입의 언어다. 
그 말인 즉슨, 변수의 데이터 타입을 일일이 지정하지 않아도, 변수에 값을 할당할 때에, 값의 타입에 따라 변수의 타입이 자동적으로 결정되는 것을 말한다. 따라서 다음과 같이 한 변수에 여러 번 다른 타입의 데이터 할당이 가능하다. 그래서 느슨한 언어라고도 한다. </p>
<pre><code class="language-js">let drink = &#39;coffee&#39;;
drink = 101; 
drink = true; 
</code></pre>
<p>자유도가 높아 편리할 수 있는 반면, 사실 특정 변수는 한 종류의 데이터 타입만 취급하게 함으로써 코드의 예측 가능성을 높이고, 혹시 모를 실수를 방지할 수도 있다. 그래서 이와 대비되어 정적 타이핑으로 각광받고 있는 언어가 바로 타입 스크립트이다. </p>
<p>(TBD)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 사이트 보안 공격 (XSS, CSRF)]]></title>
            <link>https://velog.io/@minjae-mj/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%B3%B4%EC%95%88-%EA%B3%B5%EA%B2%A9-XSS-CSRF</link>
            <guid>https://velog.io/@minjae-mj/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%B3%B4%EC%95%88-%EA%B3%B5%EA%B2%A9-XSS-CSRF</guid>
            <pubDate>Tue, 02 Mar 2021 06:04:27 GMT</pubDate>
            <description><![CDATA[<p>웹 어플리케이션 보안 이슈에서 자주 언급되는 2가지 공격에 대해 알아본다.  </p>
<h3 id="💡-cross-site-scripting-xss-공격">💡 Cross-Site Scripting (XSS) 공격</h3>
<p>공격자가 클라이언트 코드에 악의적인 스크립트를 주입하는 공격이다. 웹 어플리케이션의 유효성 검사나 인코딩이 충분하지 않을 경우, 브라우저는 스크립트의 악의성을 감별할 수 없다. 공격자는 사용자를 가장하여, 쿠키와 세션 토큰, 사이트에 민감한 정보들에 접근이 가능해지며 클라이언트 코드를 재작성하여 해킹하기도 한다. 이러한 문제는 주로 1) 확인되지 않은 경로를 통해 데이터가 입력되거나 2) 동적인 데이터의 유효성 검사가 적절히 이루어지지 않고 사용자에게 보내지는 경우에 발생한다. </p>
<p>악의적인 콘텐츠는 대개 자바스크립트를 포함하나, 때에 따라 HTML, Flash 등 브라우저가 실행하는 어떤 종류의 코드든 가능하다. XSS 공격은 1) 쿠키나 세션 정보와 같은 기밀 정보를 빼가거나 2) 접속자를 자신들이 의도한 페이지로 리디렉트 하거나 3) 정상적인 사이트 흉내를 내며 사용자의 컴퓨터에 악의적인 공격 (악성코드를 다운로드하거나 개인정보를 입력하도록 유도) 을 하여 해를 입힌다. 대표적인 유형으로 다음 두 가지를 알아본다. </p>
<p><strong>stored / persistent</strong>
XSS 취약점이 있는 타겟 서버에 악성 스크립트를 저장시킨 후, 브라우저가 서버에 데이터를 요청하면 희생자는 이 조작된 스크립트를 받게 된다. 만약 회사 등 조직의 개인 컴퓨터가 해킹될 경우, 조직 내부로 악성 코드가 이동하여 내부의 중요 정보가 탈취될 수 있다. </p>
<p><strong>reflected / non-persistent</strong>
사용자가 악의적으로 조작된 링크를 누르거나, 폼을 제출하거나, 사이트에 방문을 하는 경우에 주입된 코드가 희생 대상이 되는 웹사이트로 퍼진다. 웹 서버는 주입받은 코드를 정상적인 요청으로 인식하여, 브라우저에 그에 대한 응답을 한다. 에러 메세지를 띄우거나, 특정 검색결과를 보여줄 수도 있고, 어떤 데이터를 포함할 수도 있다. 브라우저는 서버가 악의적으로 영향받았다는 사실을 인지하지 못하므로, 정상 응답으로 파악하여 코드를 실행한다. 만약 input 창에 댓글을 입력하면 화면에 보여주는 기능이 있다고 할때, 악의적으로 <code>&lt;script&gt;alert(&#39;this website is hacked!&#39;)&lt;/script&gt;</code> 과 같이 입력하여 개발자의 의도를 무시하고 새로운 동작을 입힐 수 있다.  </p>
<p><strong>🧼 XSS 공격 예방</strong></p>
<ul>
<li><p>User input value 제한
유저 입력값이 한정적인 범주안에서 예측 가능하다면, 드롭다운 등을 사용하여 미리 입력될 데이터값을 통제할 수 있다. </p>
</li>
<li><p>Sanitize value
악성 HTML을 필터링해주는 라이브러리 사용도 가능하다. </p>
</li>
<li><p>스크립트 문자 필터링
DOM 상의 텍스트를 읽을 때 html 태그를 읽는 innerHTML 사용을 지양하고, textContent 등으로 메소드를 대체한다. 메소드 내용은 <a href="https://velog.io/@minjae-mj/Javascript-value-vs-textContent-innerHTML-innerText">이전 블로그</a>에 정리한 바 있다. </p>
</li>
</ul>
<br />

<h3 id="💡-cross-site-request-forgery-csrf-또는-xsrf-공격">💡 Cross-Site Request Forgery (CSRF 또는 XSRF) 공격</h3>
<p>공격자가 &#39;사용자의 동의없이 / 사용자가 인지하지 못하는 상황에서&#39; 브라우저로 하여금 서버에 어떤 요청을 보내도록 한다. XSS 공격으로 탈취한 정보를 이 때 사용할 수 있다. </p>
<p>MDN 이 인용한 위키피디아의 예시를 살펴보자. 공격자는 이미지 태그를 가장하여, 실제로는 은행 서버로부터 돈을 인출하는 코드를 작성하였다. </p>
<pre><code class="language-js">&lt;img src=&quot;https://bank.example.com/withdraw?account=you&amp;amount=1000000&amp;for=me&quot;&gt;</code></pre>
<p>만약 희생자의 컴퓨터가 은행 사이트에 접속한 이후에 쿠키가 유효한 상태라면 (그리고 돈을 인출할 때 필요한 다른 유효성 검사가 없다고 가정하면) 위의 이미지가 포함된 HTML 이 로딩되는 순간, 돈이 인출되도록 할 수 있다. POST 요청을 하는 form 태그는 페이지 로딩이 되는 순간 submit 이 되도록 조작할 수 있기 때문이다. </p>
<pre><code class="language-js">&lt;form action=&quot;https://bank.example.com/withdraw&quot; method=&quot;POST&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;account&quot; value=&quot;you&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;amount&quot; value=&quot;1000000&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;for&quot; value=&quot;me&quot;&gt;
&lt;/form&gt;

&lt;script&gt;
  window.addEventListener(&#39;DOMContentLoaded&#39;, 
    (e) =&gt; { document.querySelector(&#39;form&#39;).submit(); }
&lt;/script&gt;</code></pre>
<p><strong>🧼 CSRF 공격 예방</strong></p>
<p>일반적으로 CSRF 공격 방어는 GET 메소드와 같은 조회성 요청은 제외하고, 데이터 조작이 가능한 POST, PATCH, DELETE 메소드에 적용한다. 때에 따라 기밀 데이터이거나, GET 메소드를 통해 쓰기, 변경 등의 동작을 한다면 GET 메소드도 물론 방어해야 한다. </p>
<ul>
<li><p>Referrer 검증 (SameSite 쿠키 설정)
Back-end 단에서 request의 referrer를 확인하여 domain (e.g. *.instagram.com) 이 일치하는 지 검증하는 방법이다. referrer 검증만으로 대부분의 CSRF 공격을 방어할 수 있다. 민감한 정보를 담고 있는 쿠키 (세션 쿠키 등) 는 유효 시간을 짧게 설정하고, 쿠키의 SameSite 속성을 Strict 또는 Lax 로 설정한다. 이러한 세팅으로, 이를 지원하는 브라우저에서는 cross-site 요청에 세션 쿠키를 보내지 않는다. 하지만 같은 domain 내의 페이지에 XSS 취약점이 있는 경우 CSRF 공격에 취약해질 수 있다. domain 단위 검증에서 좀 더 세밀하게 페이지 단위까지 일치하는지 검증을 하면 도메인 내의 타 페이지에서의 XSS 취약점에 의한 CSRF 공격을 방어할 수 있다. </p>
</li>
<li><p>Security Token 사용 (A.K.A CSRF Token)
Referrer 검증이 불가한 환경이라면, Security Token를 활용할 수 있다. 우선 사용자의 세션에 임의의 난수 값을 저장하고 사용자의 요청 마다 해당 난수 값을 포함 시켜 전송한다. 이후 Back-end 단에서 요청을 받을 때마다 세션에 저장된 토큰 값과 요청 파라미터에 전달되는 토큰 값이 일치하는 지 검증하는 방법이다. 이 방법도 결국 같은 도메인 내에 XSS 취약점이 있다면 CSRF 공격에 취약해진다. 아래는 간략한 샘플 코드이다. </p>
</li>
</ul>
<pre><code class="language-js">// 로그인시, 또는 작업화면 요청시 CSRF 토큰을 생성하여 세션에 저장한다.
session.setAttribute(&quot;CSRF_TOKEN&quot;,UUID.randomUUID().toString());

// 요청 페이지에 CSRF 토큰을 셋팅하여 전송한다
&lt;input type=&quot;hidden&quot; name=&quot;_csrf&quot; value=&quot;${CSRF_TOKEN}&quot; /&gt;

// 파라미터로 전달된 CSRF 토큰 값
String param = request.getParameter(&quot;_csrf&quot;);

// 세션에 저장된 토큰 값과 일치 여부 검증
if (request.getSession().getAttribute(&quot;CSRF_TOKEN&quot;).equals(param)) {
    return true;
} else {
    response.sendRedirect(&quot;/&quot;);
    return false;
}

출처: https://itstory.tk/entry/CSRF-공격이란-그리고-CSRF-방어-방법 [덕&#39;s IT Story]</code></pre>
<ul>
<li>Double Submit Cookie 검증
Security Token 검증의 한 종류로 세션을 사용할 수 없는 환경에서 사용할 수 있는 방법이다. 웹 브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키 값을 확인/수정하지 못한다는 것을 이용한 방어 기법이다. 스크립트 단에서 요청 시 난수 값을 생성하여 쿠키에 저장하고 동일한 난수 값을 요청 파라미터(혹은 헤더)에도 저장하여 서버로 전송한다. 서버 단에서는 쿠키의 토큰 값와 파라미터(혹은 헤더)의 토큰 값이 일치하는 지만 검사하면 된다. 서버에 따로 토큰 값을 저장할 필요가 없어 위에서 살펴본 세션을 이용한 검증보다 개발 공수가 적은 편이다. 피싱 사이트에서는 도메인이 달라 instagram.com 쿠키에 값을 저장하지 못하므로 (Same Origin 정책) 가능한 방어 기법이다. </li>
</ul>
<br />

<p>[참고 자료]</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss">https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss</a></li>
<li><a href="https://sucuri.net/guides/what-is-cross-site-scripting">https://sucuri.net/guides/what-is-cross-site-scripting</a></li>
<li><a href="https://itstory.tk/entry/CSRF-%EA%B3%B5%EA%B2%A9%EC%9D%B4%EB%9E%80-%EA%B7%B8%EB%A6%AC%EA%B3%A0-CSRF-%EB%B0%A9%EC%96%B4-%EB%B0%A9%EB%B2%95">https://itstory.tk/entry/CSRF-공격이란-그리고-CSRF-방어-방법</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[hostname(호스트명), domain name(도메인), same origin VS same site]]></title>
            <link>https://velog.io/@minjae-mj/%ED%98%B8%EC%8A%A4%ED%8A%B8-%EB%84%A4%EC%9E%84%EA%B3%BC-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%84%A4%EC%9E%84</link>
            <guid>https://velog.io/@minjae-mj/%ED%98%B8%EC%8A%A4%ED%8A%B8-%EB%84%A4%EC%9E%84%EA%B3%BC-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%84%A4%EC%9E%84</guid>
            <pubDate>Mon, 01 Mar 2021 16:08:01 GMT</pubDate>
            <description><![CDATA[<p>CORS 를 정리하다보니 hostname 과 domain 의 키워드가 헷갈려서 따로 정리해본다.  </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/cddacd52-b32e-4eaf-9825-d3bc6fa1c247/Group%203.png" alt=""></p>
<p>여기서 <strong>전체 주소 도메인 네임</strong> - FQDN (fully qualified domain name) - 을 추출할 경우, 호스트 네임과 도메인 네임, 탑레벨 도메인을 모두 합친 <code><a href="http://www.example.com">www.example.com</a></code> 가 된다. 이 주소는 DNS 상에서 노드의 정확한 위치를 나타내므로 unique 해야 한다.</p>
<p>host name 은 각 네트워크 디바이스(컴퓨터)에 할당되는 이름이고, domain 은 네트워크에 부여되는 이름이다. 인터넷과 같은 외부에서 네트워크에 접속하려면 domain 이 필요하다. 우리가 잘 아는 www, mail.naver.com 의 mail, en.wikipedia.org 의 en 은 모두 호스트 네임에 해당하고, naver.com 과 wikipedia.org 는 도메인에 해당한다. </p>
<p>홈 네트워크에서 en.wikipedia.org 로 접속하고자 할 때, 도메인 부분이 위키피디아 네트워크로 안내해주고, 호스트 네임 부분이 정확한 지점으로 안내해주는 역할을 한다. 
<br /></p>
<h3 id="💡-same-origin-vs-same-site">💡 same origin VS same site</h3>
<p><strong>same origin</strong>
출처(origin) 의 구성 요소는 프로토콜, 호스트 네임, 포트 라고 정리한 바 있다. 따라서, same origin 이라 함은 이 세가지가 모두 충족되는 경우이다. </p>
<p><strong>same site</strong> 
사이트(site) 는 도메인 네임과 탑레벨 도메인으로 구성된다. 위의 예시에서는 example.com 에 해당한다. 호스트 네임과 같은 sub-domain 이나 포트 번호는 포함하지 않는다. 프로토콜은 사이트의 범주에 해당하지 않았으나, HTTP 가 보안이 약하기 때문에 브라우저에 따라 프로토콜 정의를 강화하여 schemeful same-site 정의를 따르기도 한다. schemeful same-site 에 따를 경우, <code>http://www.example.com</code> 와 <code>https://www.example.com</code> 는 cross site 로 간주된다. </p>
<br />

<p>[참고 자료]</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Hostname">https://en.wikipedia.org/wiki/Hostname</a></li>
<li><a href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name">https://en.wikipedia.org/wiki/Fully_qualified_domain_name</a></li>
<li><a href="https://superuser.com/questions/59093/difference-between-host-name-and-domain-name">https://superuser.com/questions/59093/difference-between-host-name-and-domain-name</a></li>
<li><a href="https://user.com/en/question/domain-name-vs-host-name-what-is-the-difference">https://user.com/en/question/domain-name-vs-host-name-what-is-the-difference</a></li>
<li><a href="https://www.sistrix.com/ask-sistrix/technical-seo/site-structure/what-is-the-difference-between-a-url-domain-subdomain-hostname-etc">https://www.sistrix.com/ask-sistrix/technical-seo/site-structure/what-is-the-difference-between-a-url-domain-subdomain-hostname-etc</a></li>
<li><a href="https://web.dev/same-site-same-origin">https://web.dev/same-site-same-origin</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS(교차 출처 리소스 공유), Same-origin policy(동일 출처 정책)]]></title>
            <link>https://velog.io/@minjae-mj/Cross-Origin-Resource-SharingCORS</link>
            <guid>https://velog.io/@minjae-mj/Cross-Origin-Resource-SharingCORS</guid>
            <pubDate>Mon, 01 Mar 2021 09:42:11 GMT</pubDate>
            <description><![CDATA[<p>CORS 는 <code>Cross-Origin Resource Sharing</code> 의 준말로, <strong>교차 출처 리소스 공유</strong> 라고 해석된다. 브라우저와 서버가 서로 통신할 때 등장하는 개념으로서, &#39;HTTP 헤더&#39;를 기반으로 동작한다. 서버는 어떤 출처가 자신의 리소스를 가져갈 수 있는지 설정하고, HTTP 헤더를 통해 요청의 출처를 확인한다. </p>
<p>헤더와 더불어 &#39;프리플라이트 preflight&#39; 메커니즘도 사용한다. 브라우저가 실제 요청에 앞서 자신이 어떤 HTTP 메소드를 사용할 지 서버에 귀띔을 보내고, 실제 요청이 허락될지 확인하는 과정이다. 모든 HTTP 요청이 프리플라이트를 띄우지는 않는다. 예를 들어, 서버에서 정보를 읽어오기만 하는, 상대적으로 안전한 get 요청은 프리플라이트를 띄우지 않는다. 반면, 서버에 정보를 업데이트하는, 즉 정보 변환의 위험이 있는 post 요청의 경우에는 프리플라이트에 해당하는 http 메소드인 options 가 먼저 서버에 날라가, 허용될 지 여부를 알아온다. 이들을 포함하여 CORS 요청 종류는 다음 4가지로 나뉜다.  </p>
<ul>
<li>Simple Request</li>
<li>Preflight Request</li>
<li>Credential Request</li>
<li>Request without Credential</li>
</ul>
<h3 id="💡-simple-request-단순-요청-preflight-request-프리플라이트-요청">💡 Simple Request 단순 요청, Preflight Request 프리플라이트 요청</h3>
<p>방금 get 요청은 preflight 를 띄우지 않는다고 했는데, 이런 것이 단순 요청에 해당한다. MDN 에 따르면, 아래의 조건 사항을 모두 만족하는 경우 이 단순 요청에 해당한다고 본다. 이외에는 프리플라이트를 띄운다.  </p>
<blockquote>
<p>📌 HTTP 메소드는 다음 셋 중 하나일 것</p>
</blockquote>
<ul>
<li>GET</li>
<li>HEAD</li>
<li>POST<blockquote>
<p>📌 브라우저 기본 세팅으로 설정된 헤더 - 예컨대 Connection, User-Agent 등 - 외에 커스텀 헤더 내용은 아래 항목만 가능</p>
</blockquote>
</li>
<li>Accept</li>
<li>Accept-Language</li>
<li>Content-Language</li>
<li>Content-Type (단, 아래 조건을 만족해야 함)<blockquote>
<p>📌 Content-Type 헤더의 값은 다음 셋 중 하나일 것</p>
</blockquote>
</li>
<li>application/x-www-form-urlencoded</li>
<li>multipart/form-data</li>
<li>text/plain<blockquote>
<p>📌 요청에서 이벤트 리스너가 사용되지 않아야 함 
📌 요청에서 ReadableStream 이 사용되지 않아야 함</p>
</blockquote>
</li>
</ul>
<h3 id="💡-credential-request-request-without-credential">💡 Credential Request, Request without Credential</h3>
<p>서버와 쿠키, 세션 등을 통해 통신한다면 반드시 withCredentials 값을 true 로 설정해주어야 한다. (이 때 서버는 반드시, Access-Control-Allow-Origin 값으로서 와일드 카드 * 를 사용하지 않고, 정확한 출처를 명시해주어야 한다. ) 이를 Credential Request 로 분류한다. withCredentials 값을 별도로 설정하지 않는 경우, 기본값은 non-credential 요청이다.  </p>
<h3 id="💡-동일-출처-정책-same-origin-policy">💡 동일 출처 정책 same-origin policy</h3>
<blockquote>
<p>출처(origin)는 <strong>프로토콜</strong>, <strong>포트(명시된 경우)</strong>, <strong>호스트</strong> 로 구성된다. </p>
</blockquote>
<p>동일 출처는 두 URI 의 프로토콜, 포트, 호스트가 모두 같음을 의미한다. (프로토콜은 URI scheme 으로 불리우기도 한다. 👉🏻 <a href="https://stackoverflow.com/questions/48953298/whats-the-difference-between-a-scheme-and-a-protocol-in-a-url#:~:text=The%20protocol%20being%20the%20agreed,are%20simply%20identifiers%20for%20protocols.">관련링크</a>) </p>
<p>동일 출처 정책은 웹 브라우저 보안을 위하여 출처가 동일한 서버로만 통신을 주고 받도록 하는 보안 정책으로서, 대부분의 브라우저가 따르고 있다. 어떤 이유들 때문일까? </p>
<ul>
<li>스크립트 삽입을 통한 잠재적 <a href="https://velog.io/@minjae-mj/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%B3%B4%EC%95%88-%EA%B3%B5%EA%B2%A9-XSS-CSRF">XSS 공격</a> 위험 차단</li>
<li>데이터 기밀 유지를 위해, 관계없는 사이트에서 제공된 콘텐츠는 클라이언트 단에서 차단</li>
<li>현대 웹 어플리케이션은 authenticated 된 사용자의 세션을 유지하기 위해 http cookie 에 의존</li>
<li>웹 어플리케이션이 authorised 된 사용자의 세션 정보를 http cookie 에 담아 광범위하게 사용하는 경우, 출처가 다른 페이지에서 스크립트를 이용해 해당 쿠키정보를 추출할 수 있기 때문<br />

</li>
</ul>
<h4 id="🌿-동일-출처-예시">🌿 동일 출처 예시</h4>
<p>아래는 기준이 되는 URI 이다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/07cb9cfe-88e3-4624-8f02-e43736bb20dd/Group%201.png" alt="">
프로토콜 http://  의 포트는 80이 기본값이다. 즉 위 URI 는 <code><a href="http://diy.market.com:80/dir/page.html">http://diy.market.com:80/dir/page.html</a></code> 과 같다. 이 URI 와 비교하여 동일 출처 URI 들을 구분해보면 다음과 같다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/c3a7bed0-b380-4ba9-bf2e-0735a64c14b0/Group%202.png" alt=""></p>
<blockquote>
<h4 id="❗️-internet-explorer-두-가지-예외사항">❗️ Internet Explorer 두 가지 예외사항</h4>
<p>동일 출처 정책은 브라우저에서 동작하는 제약이기 때문에, 브라우저마다 상이하게 적용될 수 있으며 대표적으로 Internet Explorer 는 아래 예외사항을 둔다. </p>
<p>1) 신뢰할 수 있는 사이트: 양쪽 도메인 모두 &#39;높음&#39; 단계의 보안 수준인 경우 동일 출처 제약을 적용하지 않는다. 
2) 포트: 포트 검사를 하지 않는다. 즉 Internet Explorer 에서 위의 네 번재 예시는 동일 출처로 간주된다. </p>
<p>이와 같은 두 예외는 모두 비표준이며, 다른 브라우저는 따르지 않는다. </p>
</blockquote>
<br />

<h4 id="🌿-동일-출처-정책-범위">🌿 동일 출처 정책 범위</h4>
<p>동일 출처 정책은 스크립트에만 적용된다. 이미지, CSS 나 동적으로 로딩된 스크립트들은 그에 대응하는 HTML 태그, 이를테면 <code>img</code>, <code>video</code>, <code>embed</code>, <code>iframe</code> 등을 사용함으로써 교차 출처가 허용된다.</p>
<blockquote>
<p><a href="https://velog.io/@minjae-mj/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%B3%B4%EC%95%88-%EA%B3%B5%EA%B2%A9-XSS-CSRF">CSRF 공격</a>은 이런 점을 악용한 공격이다. 동일 출처 정책은 cross-site 읽기만 방어할 뿐, 쓰기를 예방하지 못한다. 동적으로 로딩된 스크립트가 어떤 요청을 보내는 것을 막을 수는 없다. 만약 cross-site 가 사용자의 인증 정보가 담긴 쿠키를 채가서 서버에 있는 정보를 탈취하려고 한다면, 서버에서는 SameSite 쿠키 설정을 통해 이를 예방할 수 있다. </p>
</blockquote>
<p>동일 출처 정책은 중요한 보안 정책이지만, 타이트한 기준을 가지고 있기 때문에 로컬 작업을 하다보면 종종 아래와 같은 에러를 마주치기도 한다. </p>
<blockquote>
<p>🚨 XMLHttpRequest cannot load &#39;<a href="http://localhost:3000&#39;">http://localhost:3000&#39;</a>. No &#39;Access-Control-Allow-Origin&#39; header is present on the requested resource. Origin &#39;<a href="http://localhost:4000&#39;">http://localhost:4000&#39;</a> is therefore not allowed access.</p>
</blockquote>
<p>이를 완화하기 위해서, 출처가 다른 도메인에서의 AJAX 요청이라도 서버단에서 데이터 접근 권한을 허용할 수 있도록 CORS 가 등장하게 되었다. </p>
<p>👩🏻‍🎓 코드스테이츠에서 학습 당시, 기본 Web API 인 fetch API 를 사용했을 때는 CORS 에러가 나고, third-party 라이브러리인 axios 를 설치해서 사용하면 별도의 에러가 나지 않았던 경험이 있다. fetch API 는 동일 출처 정책을 따르기 때문에, 로컬에서 작업할 때 위와 같은 에러를 유발할 수 있다. 임시 방편으로 서버단에서 Access-Control-Allow-Origin: * 로 설정하여 해결할 수 있지만, 이후에는 반드시 정확한 출처로 변경해주어야 한다. </p>
<br />

<p>[ 참고자료 ]</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy</a></li>
<li><a href="https://velog.io/@yejinh/CORS-4tk536f0db">https://velog.io/@yejinh/CORS-4tk536f0db</a></li>
<li><a href="https://kimkoungho.github.io/security/same-origin-policy">https://kimkoungho.github.io/security/same-origin-policy</a></li>
<li><a href="https://evan-moon.github.io/2020/05/21/about-cors">https://evan-moon.github.io/2020/05/21/about-cors</a></li>
<li><a href="https://security.stackexchange.com/questions/226007/doesnt-samesite-cookie-and-sameorigin-policy-effectively-does-the-same-job">https://security.stackexchange.com/questions/226007/doesnt-samesite-cookie-and-sameorigin-policy-effectively-does-the-same-job</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 에서 DOM 에 접근하기 (Refs)   ]]></title>
            <link>https://velog.io/@minjae-mj/React-%EC%97%90%EC%84%9C-DOM-%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0-Refs</link>
            <guid>https://velog.io/@minjae-mj/React-%EC%97%90%EC%84%9C-DOM-%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0-Refs</guid>
            <pubDate>Mon, 22 Feb 2021 14:25:24 GMT</pubDate>
            <description><![CDATA[<p>검색어를 입력하면 Unsplash API를 사용해 사진들을 렌더링하는 간단한 앱을 만드려고 한다. (위의 사진은 완성본이다.) CSS Grid 속성을 사용해 각 사진에 똑같은 넓이를 부여했는데, 사진의 높이가 제각각이다. 이를 어떻게 해결할까? </p>
<p>다음과 같은 절차를 통해 해결할 것이다. </p>
<blockquote>
<ol>
<li>ImageCard 컴포넌트를 통해 개별 이미지를 렌더링한다. </li>
<li>DOM에 접근해서 이미지의 높이를 구한다. </li>
<li>이미지 높이를 상태에 저장해서, ImageCard 를 리렌더링한다. </li>
<li>리렌더링할때, CSS Grid 의 grid-row-end 속성값을 부여하여, 각 이미지에 맞는 적절한 공간을 차지하도록 설정한다. </li>
</ol>
</blockquote>
<h3 id="💡-리액트에서는-dom에-어떻게-접근할까">💡 리액트에서는 DOM에 어떻게 접근할까?</h3>
<p>일단 document.querySelector(&#39;img&#39;) 와 같이 직접적인 돔조작은 하지 않는다. React way가 아니다. 리액트에서는 Ref 시스템을 제공한다. Ref는 reference 의 준말로, 컴포넌트가 렌더링할 jsx 에 붙여서 그 돔에 접근 가능하도록 해주는 기능이다. 사용법은 다음과 같다. </p>
<ol>
<li>constructor 함수 내에서 변수를 하나 만든다. (this.imageRef)</li>
<li>그 변수를 엘리먼트의 ref 속성에 할당한다. </li>
</ol>
<pre><code class="language-jsx">class ImageCard extends React.Component {
  constructor(props) {
    super(props);

    this.imageRef = React.createRef();
  }

  componentDidMount() {
    console.log(this.imageRef);
  }

  render() {
    const { description, urls } = this.props.image;

    return (
      &lt;div&gt;
        &lt;img ref={this.imageRef} alt={description} src={urls.regular} /&gt;
      &lt;/div&gt;
    );
  }
}</code></pre>
<p>위와 같은 형태로 img 엘리먼트를 렌더링하면, 콘솔에서 다음과 같이 확인할 수 있다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/148f837b-fec4-4b56-ab82-dfdc84bea069/Screen%20Shot%202021-02-22%20at%2010.52.30%20PM%201.png" alt=""></p>
<p>여기서 clientHeight 속성을 통해 이미지의 높이를 알 수 있다. 재밌는 것은, <code>this.imageRef.current.clientHeight</code> 으로 콘솔 로그를 해보면 0이 찍힌다는 사실이다. 왜 객체를 열어볼 땐 높이가 찍히는데, clientHeight을 바로 찍으면 0으로 보일까? </p>
<p>왜냐하면 콘솔에 이미지 높이를 로깅하는 바로 그 순간, 사실 이미지 로딩이 끝나지 않았기 때문이다. 개발자 도구는 사실 fancy 한 측면이 있어서, 객체는 우리가 화살표를 눌러서 펼쳐보는 순간에 찾아서 보여지는 것이다. 우리가 화살표를 누를 쯤에는 이미지 로딩이 끝났으므로 정확한 높이값을 확인할 수 있다. 그럼 컴포넌트에서는 어떻게 clientHeight 값에 접근할 수 있을까?
<br /></p>
<h3 id="💡-이미지-로딩이-끝난-후-dom-에-접근하기">💡 이미지 로딩이 끝난 후, DOM 에 접근하기</h3>
<p>로딩이 끝나는 걸 기다렸다가 어떤 작업을 실행하고 싶다면, 간단히 &#39;load&#39; 이벤트 리스너를 사용하고 콜백함수를 할당하면 된다. 아래와 같이 코드가 변경될 것이다. </p>
<pre><code class="language-jsx">componentDidMount() {
    this.imageRef.current.addEventListener(&quot;load&quot;, this.setSpans);
  }

setSpans = () =&gt; {
    console.log(this.imageRef.current.clientHeight);
  };</code></pre>
<p>콜백함수인 this.setSpans 의 this 값을 올바르게 설정하기위해, setSpans 를 화살표 함수로 처리하였다. </p>
<br />

<h3 id="💡-부록-css-grid-를-활용해-각-이미지에-알맞은-높이-설정하기">💡 부록: CSS Grid 를 활용해 각 이미지에 알맞은 높이 설정하기</h3>
<p>기본적인 CSS 는 다음과 같이 설정되어 있다. 한 grid의 폭은 250px 이상 1fr 이하의 공간을 차지하고, 높이는 10px 씩 차지한다. </p>
<pre><code class="language-css">.image-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-gap: 0 10px;
  grid-auto-rows: 10px;
}

.image-list img {
  width: 250px;
}</code></pre>
<p>이 상태에서 개별 이미지에 <code>grid-row-end: span 3</code> 과 같이 할당하면, 그 이미지는 위에서부터 세 개의 grid 공간을 차지하게 된다. 따라서 우리의 목적은 이미지 높이에 맞게 span 개수 설정을 하는 것이다. 컴포넌트에 완성된 코드는 다음과 같다. </p>
<pre><code class="language-jsx">class ImageCard extends React.Component {
  constructor(props) {
    super(props);

    this.state = { spans: 0 };
    this.imageRef = React.createRef();
  }

  componentDidMount() {
    this.imageRef.current.addEventListener(&quot;load&quot;, this.setSpans);
  }

  setSpans = () =&gt; {
    const height = this.imageRef.current.clientHeight;
    // height 은 개별 이미지의 높이 
    // 10은 grid-auto-rows 설정값
    const spans = Math.ceil(height / 10);

    this.setState({ spans });
  };

  render() {
    const { description, urls } = this.props.image;

    return (
      &lt;div style={{ gridRowEnd: `span ${this.state.spans}` }}&gt;
        &lt;img ref={this.imageRef} alt={description} src={urls.regular} /&gt;
      &lt;/div&gt;
    );
  }
}</code></pre>
<p>간단한 사진앱이 완성되었다. 
<img src="https://images.velog.io/images/minjae-mj/post/0b58c373-ef3b-4fab-a3d3-9adf42e94275/Screen%20Shot%202021-02-22%20at%2011.23.30%20PM.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React AJAX call (비동기 작업)]]></title>
            <link>https://velog.io/@minjae-mj/React-AJAX-call-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85</link>
            <guid>https://velog.io/@minjae-mj/React-AJAX-call-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85</guid>
            <pubDate>Mon, 22 Feb 2021 10:06:34 GMT</pubDate>
            <description><![CDATA[<h3 id="💡-ajax-client--axios-fetch-">💡 AJAX Client { axios, fetch }</h3>
<p>AJAX 는 <strong>Asynchronous JavaScript and XML</strong> 의 준말로, AJAX 콜을 보낸다고 하면 비동기 요청을 보낸다고 이해할 수 있다. 비동기 요청을 보낸다함은, 외부 서버의 API에 요청하여 데이터를 불러들이는 작업을 하는 것이다. 예를 들어 youtube API를 사용해서 동영상을 불러올 수도 있고, unsplash API를 사용하여 사진을 불러올 수도 있다. </p>
<p>이러한 비동기 요청은 리액트가 아니라, 별도의 AJAX client 로 하여금 보내게 되는데 대표적으로 third party 라이브러리인 axios 와 대부분의 모던 브라우저에 Web API 로 깔려있는, 즉 별도의 설치없이 작업 가능한 fetch가 사용된다. </p>
<p>&#39;비동기&#39; 라는 말은, 외부 데이터를 불러들이는 동안 화면이 멈추지 않고 렌더링되며, 데이터가 다 불러와지면 화면에 데이터가 나타나게끔 하는 일이다. 이렇게 하여 화면을 끊김없이 보여줄 수 있게된다.<br><br /></p>
<h3 id="💡-비동기-요청이-끝나면-결과물을-어떻게-핸들링할까">💡 비동기 요청이 끝나면 결과물을 어떻게 핸들링할까?</h3>
<p>대표적인 두 가지 문법을 아래 Unsplash API 에 요청하는 예제를 통해 알아본다. </p>
<p><strong>1. then 체이닝</strong>
비동기 작업이 끝나면 Promise 객체가 반환된다. 우리는 그 객체를 then 으로 받아서 then 안에 콜백함수를 작성함으로써 결과값을 핸들링한다. </p>
<pre><code class="language-jsx">  onSearchSunmit(term) {
    axios.get(&quot;https://api.unsplash.com/search/photos&quot;, {
      params: { query: term },
      headers: {
        Authorization: `Client-ID ${YOUR_API_KEY}`
      },
    }).then((response) =&gt; {
      console.log(response); 
    })
  }</code></pre>
<p><strong>2. async await</strong>
함수 이름 앞에 async 를 붙여 비동기 함수화한 뒤 axios 요청 앞에 await 을 붙여 변수에 담는다. 이 변수는 일반 객체처럼 읽어낼 수 있다.   </p>
<pre><code class="language-jsx">  async onSearchSunmit(term) {
    const response = await axios.get(&quot;https://api.unsplash.com/search/photos&quot;, {
      params: { query: term },
      headers: {
        Authorization: `Client-ID ${YOUR_API_KEY}`
      },
    });

    console.log(response);
  }</code></pre>
<br />

<h3 id="💡-each-child-in-a-list-should-have-a-unique-key-prop">💡 Each child in a list should have a unique &quot;key&quot; prop</h3>
<p>리액트로 map 작업을 하며 리스트를 만들 때 만날 수 있는 경고 문구다. 
<img src="https://images.velog.io/images/minjae-mj/post/38cbae46-c59b-4158-bbc2-889f7a426452/Screen%20Shot%202021-02-22%20at%207.35.42%20PM.png" alt=""></p>
<p>어떤 기능을 막지는 않기때문에 에러는 아니지만, 리액트가 보내는 경고 문구이다. 여러 개의 리스트를 렌더링할 때 반드시 identity 역할을 할 수 있는 key 값을 부여할 것을 권장한다. 왜 key 값이 필요할까? 예를 들어 아래와 같이 할 일 목록을 만든다고 생각해보자. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/ced835f4-eb7f-455b-a429-3a9cad7dc857/Untitled%20Diagram%20(1).jpg" alt=""></p>
<p>이제 우리는 여기에 하나의 할 일을 더 추가하려고 한다.</p>
<p><img src="https://images.velog.io/images/minjae-mj/post/7ad966b7-33d1-49a9-b36b-b5f6e84232a8/Untitled%20Diagram%20(3).jpg" alt=""></p>
<p>여기서 리액트가 하는 일은, 새로 렌더링된 목록과 현재 DOM상의 목록을 비교하여 업데이트 된 부분만 DOM에 appendChild 하는 것이다. 바로 이 비교 작업을 할 때 key 가 필요하다. 리액트로 하여금 더욱 정확하게 리스트를 비교하게 함으로써 퍼포먼스를 돕는다. </p>
<p>따라서 이 key 값은 중복되지 않는 유니크한 속성을 가져야 하는데, 대개 데이터의 경우 id 속성이 이런 역할을 하고 있다. 따라서 그 id를 key 값으로 부여해주면 좋다. 아래는 key 를 설정하기 전과 후를 보여주는 예시이다. </p>
<pre><code class="language-jsx">// key 설정 전
  const images = props.images.map((image) =&gt; {
    return &lt;img src={image.urls.regular} /&gt;;
  });

// key 설정 후
  const images = props.images.map((image) =&gt; {
    return &lt;img key={image.id} src={image.urls.regular} /&gt;;
  });

// 주의: key 는 리스트의 최상단 엘리먼트에 부여한다. 
// 만약 img 를 감싼 li 가 있다면 아래처럼 바뀐다. 
  const images = props.images.map((image) =&gt; {
    return (
      &lt;li key={image.id}&gt;
          &lt;img src={image.urls.regular} /&gt;
      &lt;/li&gt;
    );
  });</code></pre>
<p>key 는 리액트가 자체적으로 활용하는 read only 속성이므로 개발자가 조작할 수 없다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React User Input (이벤트 핸들러, 진실의 원천, this)]]></title>
            <link>https://velog.io/@minjae-mj/React-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%9F%AC-%EC%A7%84%EC%8B%A4%EC%9D%98-%EC%9B%90%EC%B2%9C</link>
            <guid>https://velog.io/@minjae-mj/React-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%9F%AC-%EC%A7%84%EC%8B%A4%EC%9D%98-%EC%9B%90%EC%B2%9C</guid>
            <pubDate>Mon, 22 Feb 2021 04:47:04 GMT</pubDate>
            <description><![CDATA[<p>리액트에서 User Input 과 관련해 마주칠 수 있는 기본 이슈들에 대한 정리이다. </p>
<h3 id="💡-이벤트-관련-special-property-names-3종">💡 이벤트 관련 special Property names 3종</h3>
<p>각각의 이벤트 상황에 맞게 아래 스페셜 속성을 사용한다. </p>
<ul>
<li>onClick: 유저가 클릭했을 때</li>
<li>onSubmit: 유저가 폼 제출했을 때</li>
<li>onChange: 유저가 인풋창에 텍스트 변경했을 때</li>
</ul>
<pre><code class="language-jsx">// onChange에 이벤트 핸들러를 콜백함수로 할당
class SearchBar extends React.Component {
  onInputChange(e) {
    console.log(e.target.value)
  }

  render() {
    return (
      &lt;div className=&quot;ui segment&quot;&gt;
        &lt;input type=&quot;text&quot; onChange={this.onInputChange} /&gt;
      &lt;/div&gt;
    );
  }
}

export default SearchBar;</code></pre>
<ul>
<li>이벤트 핸들러 함수 네이밍 컨벤션:
&quot;on/handle&quot; + &quot;엘리먼트&quot; + &quot;이벤트&quot;
(e.g. onFormSubmit, handleFormSubmit )</li>
</ul>
<p>간단한 이벤트 핸들러의 경우, 아래와 같이 화살표 함수로 처리해도 무방하다.</p>
<pre><code class="language-jsx">// inline 화살표 함수로 할당
class SearchBar extends React.Component {
  render() {
    return (
      &lt;div className=&quot;ui segment&quot;&gt;
        &lt;input type=&quot;text&quot; onChange={(e) =&gt; console.log(e.target.value)} /&gt;
      &lt;/div&gt;
    );
  }
}

export default SearchBar;</code></pre>
<br />

<h3 id="💡-controlled-element-vs-uncontrolled-element">💡 Controlled element VS Uncontrolled element</h3>
<p>위의 이벤트핸들러 설명에서 input 예시는 &#39;uncontrolled element&#39;의 예시이다. &#39;controlled element&#39; 의 형태로 리팩토링하면 아래와 같다. </p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

class SearchBar extends React.Component {
  state = { term: &quot;&quot; };

  render() {
    return (
      &lt;div className=&quot;ui segment&quot;&gt;
        &lt;input
          type=&quot;text&quot;
          value={this.state.term}
          onChange={(e) =&gt; this.setState({ term: e.target.value })}
        /&gt;
      &lt;/div&gt;
    );
  }
}

export default SearchBar;</code></pre>
<p>리액트 개발자로서 항상 controlled element 작성을 유념해두어야한다. controlled, uncontrolled element 는 무슨 차이를 바탕으로 할까? 바로 Single source of truth, 즉 진실(데이터)의 원천이 어디에 있느냐 하는 것이다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/4d231bbd-e477-493c-af08-5f27b2070ec3/Untitled%20Diagram%20(2).jpg" alt=""></p>
<p>리팩토링 전에는 어떤 랜덤한 순간에 input 값을 알고자 할때, 항상 html 에서 정보를 가져와야 했다. 리액트는 유저가 input에 정보를 넣는 순간과 콜백이 일어날때만 정보에 접근이 가능했다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/809bfb51-08b3-48ce-9584-9e769b21249a/Untitled%20Diagram.jpg" alt=""></p>
<p>리팩토링 후에는 모든 진실의 원천을 리액트 컴포넌트에서 통제하게 된다. 더 이상 정보가 돔에서 관리되지 않는다. (다른 프레임워크는 다를 수 있지만, 리액트 공식문서에서 권장하는 single source of truth 관리 방법은 이와 같다.)</p>
<p>controlled element 가 되면, 돔 조작없이 UI 가공도 수월하다. 아래는 그 예시이다. </p>
<pre><code class="language-jsx">// placeholder 를 넣고 싶을 때
state = { term: &quot;이메일을 입력해주세요.&quot; };

// 모든 input 값을 대문자로 처리하고자 할 때
onChange={(e) =&gt; this.setState({ term: e.target.value.toUpperCase() })}</code></pre>
<br />

<h3 id="💡-form-이벤트-핸들러에서-preventdefault-의-역할">💡 form 이벤트 핸들러에서 preventDefault() 의 역할</h3>
<p>이벤트 핸들러에서 아래와 같이 preventDefault 함수를 본 적이 종종 있을 것이다. 브라우저가 가지는 default 동작을 막는 메소드이다. </p>
<p>html에서 특수한 성격을 갖는 엘리먼트들이 있는데 대표적으로 form 이 그렇다. form 에 있는 인풋에 유저가 정보를 입력하고 엔터를 치면, 브라우저는 디폴트 동작으로 정보를 백엔드 서버에 보내고 페이지를 새로고침한다. 이 동작을 원하지 않을 경우, e.prevetDefault 를 통해서 제어할 수 있다. </p>
<pre><code class="language-jsx">// 간략화 예시
  onFormSubmit(e) {
    e.preventDefault();
  }

  render() {
    return (
        &lt;form onSubmit={this.onFormSubmit} &gt;
            &lt;input type=&quot;text&quot; /&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    );
  }</code></pre>
<br />

<h3 id="💡cannot-read-property-state-of-undefined">💡Cannot read property &#39;state&#39; of undefined</h3>
<p>리액트 클래스 컴포넌트에서 this 가 제대로 설정되어 있지 않을 때 마주치는 문구이다. 위의 onFormSubmit 을 예시로, this 를 설정하는 세 가지 방법을 정리해본다.</p>
<p>1번 this 바인딩의 경우 레거시 코드에서 많이 볼 수 있으며, ES2015 의 화살표 함수 도입 후에는 2, 3번을 흔히 볼 수 있다.</p>
<p><strong>1. constructor 내에서 bind 로 정의해주기</strong></p>
<pre><code class="language-jsx">constructor() {
  this.onFormSubmit = this.onFormSubmit.bind(this)
}

onFormSubmit(e) {
  e.preventDefault();
}

render() {
  return (
      &lt;form onSubmit={this.onFormSubmit} &gt;
          &lt;input type=&quot;text&quot; /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>2. 화살표 함수로 바꾸기</strong></p>
<pre><code class="language-jsx">onFormSubmit = (e) =&gt; {
  e.preventDefault();
}

render() {
  return (
      &lt;form onSubmit={this.onFormSubmit} &gt;
          &lt;input type=&quot;text&quot; /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>3. jsx 상에서 inline 화살표 함수로 바꾸기</strong></p>
<pre><code class="language-jsx">onFormSubmit(e) {
  e.preventDefault();
}

render() {
  return (
      &lt;form onSubmit={(e) =&gt; this.onFormSubmit(e)} &gt;
          &lt;input type=&quot;text&quot; /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디폴트 페이지와 로딩스피너]]></title>
            <link>https://velog.io/@minjae-mj/%EB%94%94%ED%8F%B4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%99%80-%EB%A1%9C%EB%94%A9%EC%8A%A4%ED%94%BC%EB%84%88</link>
            <guid>https://velog.io/@minjae-mj/%EB%94%94%ED%8F%B4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%99%80-%EB%A1%9C%EB%94%A9%EC%8A%A4%ED%94%BC%EB%84%88</guid>
            <pubDate>Fri, 19 Feb 2021 15:18:48 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드 개발자는 화면 구현을 담당하는 만큼, 유저와의 상호작용 최접점에 있다. 그래서 작은 디테일에 센스를 발휘할 수록 그 능력이 빛이 나는 것 같다. 그 디테일이 꼭 대단히 어려운 로직일 필요도 없다. 아래는 그 예시이다. </p>
<h3 id="☝🏻-디폴트-페이지">☝🏻 디폴트 페이지</h3>
<p>이번 프로젝트에서 내가 맡았던 북마크리스트 페이지이다. 리스트를 렌더링하고, hover 했을 때 삭제나 상세보기 페이지로 이동하게 한다. 
<img src="https://user-images.githubusercontent.com/65207835/108521029-a7fc5380-730e-11eb-9831-379fc7828276.png" alt="example">
하지만 만약 북마크된 제품이 하나도 없다면?<br>아래와 같이 가볍게 북마크를 권해본다. (원래는 예쁜 전통주 일러스트도 넣고 싶었지만 시간 관계상😅) 
<img src="https://user-images.githubusercontent.com/65207835/108520466-0c6ae300-730e-11eb-8f04-a16da5a48066.png" alt="default"></p>
<h3 id="✌🏻-로딩-스피너">✌🏻 로딩 스피너</h3>
<p>페이스북에서 스크롤을 내리는데 다음 화면이 나타나는데 1초 이상 걸렸을 때, 빈 화면을 보고있는 그 1초가 은근히 갑갑하다. 빚다 프로젝트에서도 서버에서 데이터 로딩을 해오는 구간이 많았는데, 우리의 컨셉에 맞게 술잔에 술이 차오르는 gif 를 만들어 지루함을 조금이나마 줄여보았다. 
<img src="https://user-images.githubusercontent.com/65207835/108521963-9ff0e380-730f-11eb-98ec-ca916039473d.gif" alt="default"></p>
<h3 id="💡-defaultprops">💡 defaultProps</h3>
<p>아래는 sementic-ui-css    에서 로딩 스피너를 빌려와 모듈화해본 것이다. 빙글빙글 돌아가는 동그라미만 보이고 메세지는 상황에 맞게 props 로 내려준다. props 를 누락할 경우에 대비하거나 기본 메세지를 정해두고 싶다면, 두 가지 방법으로 핸들링할 수 있다.  </p>
<p>1) <code>{props.message || &quot;Loading...&quot;}</code> 로 처리한다. 
2) 디폴트값을 가지고 싶은 props가 여러 개라면, 아래와 같이 defaultProps 메소드를 사용하여 정의한다.  </p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

const Spinner = (props) =&gt; {
  return (
    &lt;div className=&quot;ui active dimmer&quot;&gt;
      &lt;div className=&quot;ui big text loader&quot;&gt;{props.message}&lt;/div&gt;
    &lt;/div&gt;
  );
};

Spinner.defaultProps = {
  message: &quot;Loading...&quot;,
};

export default Spinner;</code></pre>
<p><img src="https://user-images.githubusercontent.com/65207835/108522483-3ae9bd80-7310-11eb-9a37-f3fe978dc0da.png" alt="spinner"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Class-Based Component]]></title>
            <link>https://velog.io/@minjae-mj/%EB%A6%AC%EC%95%A1%ED%8A%B8-Class-Based-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@minjae-mj/%EB%A6%AC%EC%95%A1%ED%8A%B8-Class-Based-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Fri, 19 Feb 2021 08:00:21 GMT</pubDate>
            <description><![CDATA[<p>Hooks 도입 이후, 과거 Class 컴포넌트에서만 가능하던 상태 시스템, 라이프사이클 메소드가 Functional 컴포넌트에서도 구현이 가능해졌다. 하지만 많은 레거시 코드들이 Class 컴포넌트로 작성되어 있음을 생각해보면, Class 컴포넌트와 Functional 컴포넌트를 둘 다 이해할 수 있어야 한다. </p>
<h3 id="💡-class-컴포넌트-장점">💡 Class 컴포넌트 장점</h3>
<ul>
<li>Easier code organisation: helper method 들이 class 내부에 존재하므로 가독성이 좋음</li>
<li>상태 시스템: Easier to handle user input</li>
<li>라이프사이클 메소드: Easier to do things when the app first starts</li>
</ul>
<h3 id="💡-페이지-로딩-시-react-타임-라인">💡 페이지 로딩 시, React 타임 라인</h3>
<p>아래 functional 컴포넌트로 작성된 코드에서 실행 순서는 다음과 같다. </p>
<blockquote>
<p>1) JS 파일이 브라우저에 의해 실행된다. 
2) App component 가 생성된다. 
3) geolocation service 를 호출한다. 
4) App 이 JSX 를 반환하고, HTML로 렌더링된다. 
5) (잠시 후) geolocation 정보를 얻는다. </p>
</blockquote>
<pre><code class="language-jsx">const App = () =&gt; {
  window.navigator.geolocation.getCurrentPosition(
    (position) =&gt; console.log(position),
    (err) =&gt; console.log(err)
  );

  return &lt;div&gt;Hi there&lt;/div&gt;;
};</code></pre>
<p>위의 코드를 라이프사이클 메소드를 통해 핸들링해보자. 
아래 코드는 Class 컴포넌트로 리팩토링 후 변화된 실행 순서이다. </p>
<blockquote>
<p>1) JS 파일이 브라우저에 의해 실행된다. 
2) App component 의 인스턴스가 생성되고, 가장 먼저 constructor 함수가 실행된다. 
3) State 객체가 생성되고, this.state 에 할당된다. 
4) render method 가 실행된다. 
5) App 이 JSX 를 반환하고, HTML로 렌더링된다.
6) componentDidMount가 실행되고, geolocation service 를 호출한다. 
7) geolocation 정보를 얻은 후, this.setState 를 통해 업데이트된다. 
8) render method 가 다시 실행된다. 
9) update 된 정보를 담아 JSX가 반환된다. </p>
</blockquote>
<pre><code class="language-jsx">class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = { lat: null };
  }

  componentDidMount() {
      window.navigator.geolocation.getCurrentPosition(
        (position) =&gt; this.setState({ lat: position.coords.latitude }),
        (err) =&gt; console.log(err)
        );
  }

  render() {
    return &lt;div&gt;Latitude: {this.state.lat}&lt;/div&gt;;
  }
}</code></pre>
<h4 id="💡-라이프사이클-타임라인">💡 라이프사이클 타임라인</h4>
<ul>
<li><code>constructor()</code></li>
<li><code>render()</code> 스크린에 내용이 나타남</li>
<li><code>componentDidMount()</code> render() 실행 후 한 번 실행됨</li>
<li><code>componentDidUpdate()</code> state 업데이트때마다, render() 재실행후 실행됨</li>
<li><code>componentWillUnmount()</code> 컴포넌트가 화면에서 사라질 때 실행됨</li>
</ul>
<h4 id="💡-라이프사이클-use-case">💡 라이프사이클 use case</h4>
<ul>
<li><code>constructor</code> state 초기화 등 one-time setup </li>
<li><code>render</code> 자주 실행되므로, JSX 반환 외에 별도의 작업 (api request 등)은 하지 않는 것이 좋음</li>
<li><code>componentDidMount</code> 초기 데이터 로딩</li>
<li><code>componentDidUpdate</code> 상태값/props 변화에 따른 추가 데이터 로딩</li>
<li><code>componentWillUnmount</code> cleanup 이 필요할 때</li>
<li>이외의 라이프사이클 메소드도 있지만 자주 사용되지 않으므로 필요시 별도로 찾아서 사용</li>
</ul>
<h4 id="💡-constructor-를-생략해도-될까">💡 constructor 를 생략해도 될까?</h4>
<p>consturctor 를 생략해도 된다. 왜냐하면 babel 이 constructor 를 생성하기 때문에, babel 이 컴파일링한 후에는 결과값이 동일하다. 따라서, 아래의 두 코드 역시 동일하게 작동한다. </p>
<pre><code class="language-jsx">// with constructor
class App extends React.Component {
 constructor(props) {
    super(props);

    this.state = { message: &#39;hello world&#39; }; 
  }

  render() { 
    return &lt;div&gt;Hello world&lt;/div&gt; 
  }
}

// without constructor
class App extends React.Component {
  state = { message: &#39;hello world&#39; }; 

  render() { 
    return &lt;div&gt;Hello world&lt;/div&gt; 
  }
}
</code></pre>
<h4 id="💡-번외-컴포넌트-파일-안에서-css-파일은-어떻게-로딩될까">💡 번외) 컴포넌트 파일 안에서 CSS 파일은 어떻게 로딩될까?</h4>
<p>예컨대, SeasonDisplay.js 안에서 아래와 같이 CSS 파일을 import 하면, Webpack 이 하나로 묶어내는 과정에서 CSS 파일을 발견하고 index.html 로 가져다 붙인다. </p>
<pre><code class="language-jsx">import &#39;./SeasonDisplay.css&#39;;
import React from &#39;react&#39;;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React foundation]]></title>
            <link>https://velog.io/@minjae-mj/React-%ED%8C%8C%EC%9A%B4%EB%8D%B0%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@minjae-mj/React-%ED%8C%8C%EC%9A%B4%EB%8D%B0%EC%9D%B4%EC%85%98</guid>
            <pubDate>Fri, 19 Feb 2021 05:25:05 GMT</pubDate>
            <description><![CDATA[<h2 id="🏰-에필로그">🏰 에필로그</h2>
<p>React 로 프로젝트를 마치고 코드스테이츠 수료를 완료한 지금, Vanilla JS 대비 React를 사용하며 개인적으로 좋았던 점을 생각해보았다. </p>
<blockquote>
<ol>
<li>데이터에 방향성이 생겨서, 데이터 흐름이 예측 가능해진다. </li>
<li>목업페이지를 만들고 UI에 컴포넌트를 매핑하니, 머릿 속에서 각 컴포넌트들의 상호작용이 더욱 직관적으로 이해가 된다. </li>
<li>매번 돔조작을 할 필요가 없으니, 시간이 덜 든다. html 태그 생성, 라이프사이클 메소드 등.</li>
<li>React 생태계가 발달되어 있어, 관련 라이브러리를 조합해서 사용하기가 수월하다. Typescript, Redux 등.</li>
<li>JSX를 비롯하여 ES6 문법도 Babel 을 통해 ES5 로 컴파일링되어 각종 브라우저에 대응하기 편리하다. 이건 Babel 의 역할이지만, 
create-react-app 의 경우 Webpack, Babel 이 올인원으로 설치되어 build 작업까지 간편하게 처리한다.   </li>
</ol>
</blockquote>
<p>오늘부터는 Udemy 에서 수강했던 수업을 다시 복습하면서 Javascript 와 React 기초 동작 원리에 대한 지식을 공고히 하려고 한다. 공부하면서 요약 정리한 내용을 이번 시리즈에 담아본다. </p>
<p>🔗  <strong>강의 정보: <a href="https://www.udemy.com/course/react-redux/">Modern React with Redux</a> by Stephen Grider</strong></p>
<h3 id="💡--react-component">💡  React Component</h3>
<p>JSX를 반환하는 plain javascript function. 두 가지 일을 하는데, </p>
<p>1) App component 로 하여금 유저 이벤트를 핸들링하고
2) JSX로 하여금 리액트가 스크린에 어떤 컨텐트를 보여줄지 지시한다. </p>
<h3 id="💡-react가-jsx를-어떻게-핸들링할까">💡 React가 JSX를 어떻게 핸들링할까?</h3>
<p>1) return 문의 div 를 만나면 묻는다.<br>2) is this a DOM element? 
3) if yes, OK, show a div on the screen to the user. 
4) if no - it is a component, then call the component function and inspect all the JSX we get back. </p>
<h3 id="💡-어떻게-브라우저-화면에-내용들이-나타날까">💡 어떻게 브라우저 화면에 내용들이 나타날까?</h3>
<p>1) browser 가 http request 를 보낸다. 
2) server 가 index.html 를 response 한다. 
3) browser 가 index.html 을 로딩하고, 그 안에 script 태그를 파싱한다. 다시 http request 를 보낸다. 
4) server 가 bundle.js 를 response 한다. </p>
<h3 id="💡-react-vs-reactdom">💡 React VS ReactDOM</h3>
<p>리액트를 다룰 때, 실제로 우리는 2개의 리액트 라이브러리를 다룬다. 리액트와 리액트 돔. </p>
<ul>
<li>React: &quot;Reconciler&quot;. Knows how to work with components. how to call component function, how to get back JSX, decide weather to call html elements or to call some other component functions. </li>
<li>ReactDOM: &quot;Renderer&quot;. Knows how to take instructions on what we want to show and turn it into HTML. 실제로 HTML 로 바꿔서 DOM에 붙이는 역할. </li>
</ul>
<h3 id="💡-es2015-modules-vs-commonjs-modules">💡 ES2015 Modules VS CommonJS Modules</h3>
<p>파일들 간 코드들이 어떻게 쉐어링되어야 하는지 명시한 모듈시스템</p>
<ul>
<li>ES2015 Molues: &quot;import&quot; ES2015 import statement</li>
<li>CommonJS Modules: &quot;require&quot; CommonJS import statement</li>
</ul>
<h3 id="💡-babel">💡 Babel</h3>
<ul>
<li>브라우저는 JSX 를 이해하지 못하고, ES2015 를 이해하지 못할 수도 있다. </li>
<li>배블(Babel) 이 ES5 언어로 변환하는 역할을 한다. <pre><code class="language-jsx">// before Babel
return &lt;div&gt;Hi!&lt;/div&gt;
</code></pre>
</li>
</ul>
<p>// after Babel
return React.createElement(&quot;div&quot;, null, &quot;Hi!&quot;); 
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저 (Closure)]]></title>
            <link>https://velog.io/@minjae-mj/%ED%81%B4%EB%A1%9C%EC%A0%80-Closure</link>
            <guid>https://velog.io/@minjae-mj/%ED%81%B4%EB%A1%9C%EC%A0%80-Closure</guid>
            <pubDate>Sun, 14 Feb 2021 12:53:03 GMT</pubDate>
            <description><![CDATA[<h3 id="🙋🏻♀️--closure란">🙋🏻‍♀️  Closure란?</h3>
<p>외부함수의 변수에 접근할 수 있는 내부 함수 (혹은 이러한 작동 원리). 아래 예시에서 inner() 는 클로저 함수다. </p>
<pre><code class="language-js">function outer() {
    let outerCall = &#39;full&#39;
    console.log(outerCall);

    function inner(){
    let innerCall = &#39;tomatoes&#39;
    console.log(innerCall); 

    return inner; 
}

// tricky questions!
console.log(outer()()); // &#39;full&#39;, &#39;tomatoes&#39;
let inner = outer(); // &#39;full&#39;, definition of inner
console.log(inner()) // &#39;tomatoes&#39;

// note: 두 번째 inner 변수에 할당하는 순간, outer() 함수가 호출된다.  </code></pre>
<br />

<h3 id="🙋🏻♀️--closure-는-어떻게-많이-쓰이나요">🙋🏻‍♀️  Closure 는 어떻게 많이 쓰이나요?</h3>
<p>💡  <strong>커링</strong>: 함수 하나가 n 개의 인자를 받는 대신, n 개의 함수를 만들어 각각 인자를 받게 하는 방법</p>
<p>함수형 프로그래밍의 대표적인 예제로서, 두 번 연속 실행을 통해 사용한다. 외부함수 인자 값을 고정한 후, 재사용 할 수 있어서 유용하다. (마치 템플릿처럼 사용)</p>
<pre><code class="language-js">function adder(x) {
    return function(y) {
        return x + y; 
    }
}

adder(5)(10) // 15; 
let adder3 = adder(3)
adder3(7) // 10; </code></pre>
<br />

<p>💡  <strong>클로저 모듈 패턴</strong>: 변수를 스코프 안쪽에 가두어 함수 밖으로 노출시키지 않는 방법</p>
<p>함수를 새로운 변수에 할당함으로써, 클로저 함수 밖에 있는 외부변수의 값 (아래 예시에서는 initVal) 을 노출시키지 않으면서도 얼마든지 독립적인 변수로서 재사용이 가능하다. </p>
<pre><code class="language-js">function makeCounter() {
    let initVal = 0; 

    return {
        add: function() { initVal++ }
        minus: function() { initVal -- }
        finalVal: function () { return initVal }
    }
}

let counterOne = makeCounter(); 
let counterTwo = makeCounter(); 

// counter 를 몇 개 생성하든지 간에, 각 변수는 독립적인 값을 갖는다.  

// 아래와 같이 type 변수를 함수 내에 설정해서, 외부 요소에 의해 변경되지 않도록 보호할 수 있다.
function makePayment() {
    const type = [&#39;cash&#39;, &#39;card&#39;]

    return {
        payByCash: function(amount) { 
            console.log(`About to pay ${amount} by ${type[0]}`);  
        },
        payByCash: function(amount) { 
            console.log(`About to pay ${amount} by ${type[1]}`);  
        },
    }
}</code></pre>
<p>👉🏼  [<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures">관련 MDN 자료</a>]</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클라우드 컴퓨팅 - IaaS, SaaS, PaaS]]></title>
            <link>https://velog.io/@minjae-mj/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%BB%B4%ED%93%A8%ED%8C%85-IaaS-SaaS-PaaS</link>
            <guid>https://velog.io/@minjae-mj/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%BB%B4%ED%93%A8%ED%8C%85-IaaS-SaaS-PaaS</guid>
            <pubDate>Wed, 10 Feb 2021 15:50:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/minjae-mj/post/b6e87e92-f4df-4a8e-8cc8-dd8a5f35ece6/elcarito-MHNjEBeLTgw-unsplash.jpg" alt=""></p>
<p>회사 리서치를 하다가 <a href="https://jmagazine.joins.com/forbes/view/329259">Sendbird 김동신 대표님의 인터뷰</a>를 보게 됐다. 그 중 아래 답변의 SaaS, PaaS 와 같은 키워드가 눈에 띄어 찾아 정리해본다. 소프트웨어를 하나의 프로덕트로 볼 때, 부품을 공급하는 구조를 API 경제에 빗대어 표현한 점이 와닿았다.</p>
<blockquote>
<p><strong>한국은 아직 SI 중심적인 소프트웨어 경제가 주축을 이루고 있는데, 한국의 소프트웨어 산업은 앞으로 어떤 방향으로 나아가고 변화할 것으로 보는가.</strong></p>
<p>API 경제는 전 세계에서 일어나는 거대한 파도다. 본질적으로 모든 산업이 시작할 때는 작지만 성장하면서 더 복잡해지고 번들에 포함되지 않은 요소들을 가치사슬 내내 가지고 오게 된다. 제조업이 좋은 사례다. 예전에는 자동차 회사가 모든 부품을 자체 제작했다면, 지금은 자동차 제조업자들에게 부품을 파는 10억 달러 시가 총액을 가지고 있는 공기업 형태로 부품 제조업자들이 존재한다.</p>
<p>이는 전체적인 소프트웨어 산업에서 발생하는 현상이다. 20~30년 전을 되돌아보면 모든 것은 자체 개발됐다. 하지만 10년 전쯤부터 SaaS 또는 오픈소스와 같은 모델들 덕분에 변화가 일고 있다. 최근 들어 PaaS나 API와 같은 더 발전된 형태를 볼 수 있다. 지금은 회사들이 특수화된 소프트웨어 요소들과 서비스 제공자들에게 의존하고 있다. 그래서 API들은 회사가 핵심적인 것에 집중할 수 있는, 그 위에 입힐 수 있는 추상적 레이어를 만든다. 센드버드의 경우 최상의 채팅 API를 제공한다.</p>
</blockquote>
<h2 id="🌤-클라우드-컴퓨팅">🌤 클라우드 컴퓨팅?</h2>
<p>인터넷을 통해 컴퓨터 리소스에 접근할 수 있는 사용자 중심의 컴퓨터 환경이다. 여기서 리소스는 데이터 저장소일수도 있고, 서버, 데이터베이스, 네트워킹, 소프트웨어일 수도 있다. 물리적으로 개인용 컴퓨터, 기업용 서버 등에 보관하는 것과 대조적으로, 웹에 접속할 수만 있다면 이들 컴퓨터 자원에 접근이 가능하다.</p>
<h2 id="🙋🏻♀️-iaas-saas-paas">🙋🏻‍♀️ IaaS, SaaS, PaaS?</h2>
<p>IT인프라의 여러 구성요소 들 중 일정 부분은 사용자가 관리하고, 나머지 서비스는 클라우드로부터 제공받게 되는데, 이 때 <strong>얼마만큼을 클라우드로 분리할지에 따라</strong> 나누어지는 개념이다.  </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/c800ea71-79b4-4b64-832d-1fb2801651eb/Cloud%20computing.jpg" alt=""></p>
<p>민트색으로 클라우드로 구분되는 영역을 나누어 보았다. </p>
<ul>
<li>IaaS: Infrastructure as a Service</li>
<li>PaaS: Platform as a Service</li>
<li>SaaS: Software as a Service</li>
</ul>
<h3 id="☁️-packaged-software">☁️ Packaged Software</h3>
<ul>
<li>사용자가 하드웨어 구매부터, O/S 설치, 네트환경 구성, 서버 관리 등을 다 한다. <br />

</li>
</ul>
<h3 id="☁️-iaas">☁️ IaaS</h3>
<ul>
<li>기업(클라우드)으로부터 인프라스트럭처 레벨의 서비스를 제공받는다.</li>
<li>가상 서버 하위 레벨에 대한 고려없이, OS와 어플리케이션을 직접 관리한다. </li>
</ul>
<p><u>예시: AWS 의 EC2</u>
프로젝트에서 사용한 경험이 있는데, 아마존에서 가상 머신을 제공한다. 사용자는 원하는 OS를 고르고 스펙 설정 후 어플리케이션을 올리기만 하면 된다. 
<br /></p>
<h3 id="☁️-paas">☁️ PaaS</h3>
<ul>
<li>개발자가 응용프로그램을 작성할 수 있도록 플랫폼 및 환경을 제공받는다.</li>
<li>node.js 등과 같은 런타임과 컴파일 기능 등을 제공하며, 개발자는 소스만 작성한다. </li>
<li>가장 이상적인 어플리케이션 플랫폼 관점의 클라우드 모델로 받아들여지고 있다. </li>
<li>IaaS와 같은 취약점으로서, 어플리케이션이 플랫폼에 종속되어 개발되기 때문에 다른 플랫폼으로의 이동이 어려울 수 있다. </li>
</ul>
<p><u>예시: AWS Elastic Beanstalk, Heroku, Windows Azure (주로 PaaS 로 사용됨), Force.com, OpenShift, Apache Stratos 등</u>
<br /></p>
<h3 id="☁️-saas">☁️ SaaS</h3>
<ul>
<li>별도의 설치 부담 없이, 클라우드를 통해 소프트웨어를 제공받는다. </li>
<li>구독이나 트래픽 기반으로 수익을 창출한다. </li>
<li>반드시 인터넷에 접속해야만 사용 가능하고, 외부에 데이터 노출에 대한 위험이 있다는 것이 단점으로 꼽힌다.</li>
</ul>
<p><u>예시: Google Apps (Gmail 등), Dropbox, Slack, Netflix</u>
<br /></p>
<p>💡 정리해보면, 위의 개념은 각각 host, build, consume 에 대응된다고 할 수 있다.
PaaS 와 SaaS 는 자주 혼동되기도 하는데, 가장 큰 차이점은 SaaS 는 소프트웨어 자체를 &#39;소비&#39;하는데에 중심이 있는 반면, PaaS 는 플랫폼을 기반으로 개발자들이 소프트웨어를 &#39;만드는&#39; 데에 역점을 둔다는 것이다. </p>
<h2 id="🌤-클라우드-컴퓨팅의-장점">🌤 클라우드 컴퓨팅의 장점?</h2>
<ul>
<li>앱이나 브라우저를 통해, 어떤 기기에서든지 접속 가능하다. </li>
<li>사용자는 클라우드에 정보를 업로드/백업할 수도 있으며, 여러 기기에서도 자신의 파일을 seamless 하게 관리할 수 있다. </li>
<li>사용자가 개인 컴퓨터에 큰 용량을 가지고 있을 필요가 없다. </li>
<li>훨씬 수월하게 업데이트된 서비스를 이용할 수 있다. 업데이트 버전을 다운받으면 끝! 💾 ❌</li>
<li>하드웨어 구매, 유지 등에 필요한 비용 절감 효과가 있다. (이런 데에 쓰던 비용을 인터넷 속도를 높이는 데에 쓴다.)</li>
</ul>
<h2 id="🌤-클라우드-컴퓨팅의-단점">🌤 클라우드 컴퓨팅의 단점?</h2>
<ul>
<li>보안상의 문제. 암호화를 하더라도 만약 암호키가 사라지면, 데이터도 사라진다. </li>
<li>클라우드 컴퓨팅으로 관리되는 서버는 자연재해, 내부 버그, 정전 등에 영향을 받는다. 지역을 가로질러서 캘리포니아에 정전이 오면, 한국 사는 우리에게 영향을 끼칠 수도 있다. <br />

</li>
</ul>
<p>[ 참고자료 ]</p>
<ul>
<li><a href="https://wnsgml972.github.io/network/2018/08/14/network_cloud-computing">https://wnsgml972.github.io/network/2018/08/14/network_cloud-computing</a></li>
<li><a href="https://www.investopedia.com/terms/c/cloud-computing.asp">https://www.investopedia.com/terms/c/cloud-computing.asp</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[클라이언트 환경변수]]></title>
            <link>https://velog.io/@minjae-mj/%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@minjae-mj/%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98</guid>
            <pubDate>Thu, 04 Feb 2021 13:57:05 GMT</pubDate>
            <description><![CDATA[<p>당연하지만 우리에게는 당연하지 않았던 - </p>
<p>첫 프로젝트 회고에도 썼지만 개선하고 싶었던 것 중 하나가 환경변수 처리였다. 마지막에 CSS 작업을 하며 로컬환경, 배포환경에서 여러 번 번갈아가면서 테스트하는데 각 파일에 있는 localhost:8080 주소를 배포한 주소로 일일이 바꿔서 썼다. 이를 해결하기 위한 방법을 강구해보았다.  </p>
<p>1) 내가 적용한 방법은 주소 부분을 환경 변수 처리하기 위해, 따로 모듈 파일을 만든 것이다. 그래서 파이널 프로젝트에서는 axios.create 을 이용해 base url 을 정의하고, 이 파일을 import 해서 사용함으로써 해결했다. 로컬, 배포 환경에 따라 base url 만 바꿔서 테스트하면 되었다. </p>
<p>2) dev, production 환경변수를 정의해두고, npm script 에 따로 빼서 써도 된다. npm run dev 하면 NODE_ENV=dev 이런 식으로. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[빚다 (BITDA) 프로젝트 회고]]></title>
            <link>https://velog.io/@minjae-mj/%ED%8C%8C%EC%9D%B4%EB%84%90-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@minjae-mj/%ED%8C%8C%EC%9D%B4%EB%84%90-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 02 Feb 2021 16:11:05 GMT</pubDate>
            <description><![CDATA[<p>_이번 회고록도, <a href="https://bit.ly/retrospect_waf">이전 회고록</a>처럼, 영국 에든버러 대학의 <a href="https://www.ed.ac.uk/reflection/reflectors-toolkit/reflecting-on-experience/four-f">툴킷</a>을 참고하여, 다음 4f 에 기반하여 작성되었습니다. _</p>
<blockquote>
<p><strong>Facts 사실</strong> | An objective account of what happened
<strong>Feelings 느낌</strong> | The emotional reactions to the situation
<strong>Findings 교훈</strong> | The concrete learning that you can take away from the situation
<strong>Future 앞으로</strong> | Structuring your learning such that you can use it in the future </p>
</blockquote>
<h1 id="facts-사실">Facts (사실)</h1>
<h2 id="🍊-시작">🍊 시작</h2>
<p>새로운 네 명이 모였다. 동환님과는 지난 WAF 프로젝트에 이어 또 같은 팀이 되었다. 아이디어 스케치를 했는데, 팀장님께서 전통주를 소개하는 웹서비스를 꽤 구체적으로 생각해오셔서 이 아이디어를 발전시켜보기로 했다. 채택되지 못했지만 내가 준비했던 아이디어 두 개도 함께 적어본다. </p>
<p><strong>사이드 프로젝트가 된 아이디어</strong>
하나는 일반인의 미술작품 대중경매 사이트였다. 미술, 옥션이라는 주제가 마치 와인처럼 하이엔드 문화로 여겨지는게 항상 아쉬웠다. DIY와 공예를 즐기는 나로서, 보다 가볍게 접근할 수 있는 플랫폼을 만들고 싶었다. 좋은 가격에 팔리면 좋고, 아니어도 재미는 볼 수 있는(?). 하지만 팀원 중 한 분이 이전 프로젝트에서 이와 비슷한 중고물품 대중경매 플랫폼을 만드셨기에, 이건 스킵되었다.   </p>
<p>다른 하나는 생애주기 통합건강관리 앱이었다. 먹거리, 입을거리 하나하나 몸에 좋은 것을 찾는 요즘 시대, 헬스케어는 국민소득 3만달러 시대에 더 진화할 시장이다. 물론 이런 거창한 이유보다는 개인적인 이유로 구상한 서비스였다. 치과 등 정기 검진 다니는 곳이 많고, 한 질환으로 여러 병원을 다니기도 하고, 어쩌면 같이 먹으면 안 되는 약들이 있을 수 있는데 대개 받아온 약 봉지를 보관하지 않으면 손 안의 관리가 어렵고, 마지막으로 이런 기록들은 보험사에 청구할 때도 요긴한데 적어두지 않으면 까먹기 일쑤였다. 이런 걸 한 방에! 관리할 수 있는 앱을 만들고 싶었다. 하지만 4주차 프로젝트로 선정하기에는 규모가 작다는 의견이 있어서, 이들은 모두 나의 사이드 프로젝트 명단에 오르며 물러났다. </p>
<p><strong>기술스택</strong>
이전에 백엔드를 담당했던 나는, 이번에는 동환님과 함께 프론트엔드를 맡았다. 4주차는 새로운 스택, 모두가 써보고 싶던 스택을 적극 반영하여 기술 스택을 구성했다. 프론트 엔드에서는 <em><strong>Typescript, React hooks, Styled-components, Redux</strong></em> 백엔드에서는 <em><strong>Node.js, Express.js, TypeORM, MySql, Kakao Oauth, Google Oauth, AWS S3, EC2, RDS</strong></em> 로 구성했다. </p>
<h2 id="🍊-진행">🍊 진행</h2>
<p>프로젝트 4주 기간 동안 첫 일주일은 기술 스택 공부 및 복습, 그 다음 일주일은 SR 미팅 및 초기 환경 세팅을 하고, 나머지 2주 동안 열나게 코드를 썼다. </p>
<h3 id="1주차--새로운-기술-스택-공부--기존에-배운-스택-복습">1주차 : 새로운 기술 스택 공부 || 기존에 배운 스택 복습</h3>
<ol>
<li><strong>typescript</strong> 
udemy 로 강의를 끊어서 하루 반나절 동안 1.2배속으로 들었다. 그리고 공식문서 좀 읽고, 팀원 분들과 배운 내용 공유. 결론적으로 말하면 typescript 를 굳이 돈 내고 들을 필요는 없는 것 같지만, 나는 자료 찾을 시간도 없이 공부해야한다 하면 만 원내고 들어볼만 하다.  </li>
<li><strong>redux, react-redux</strong>
하루 동안 공식문서만 팠다. 처음 챕터부터 순서대로 읽었는데 누가 정리했는지.. 여러 챕터에서 똑같은 말을 계속 반복하고 있어서, 잘못 눌러서 뒤로가기 한 줄 알았다. 하지만 나는 사실 복습이 되서 좋긴 했다. 😅 
mapStateToProps, connect 이 문법은 볼 때마다 부담스럽고 거추장스러운데가 있었는데 이번에 보니 hooks 문법을 도입해서 useSelector, useDispatch로 업그레이드 되었다. 훨씬 직관적이고 쉽게 사용할 수 있어서 너무 행복했다. 한편으로, 내가 리덕스를 처음 공부한 지 불과 1년 도 안 지났는데 이런 변화가 일어나다니, 개발 세계의 속도에 새삼 놀랐다.</li>
<li><strong>react-hooks</strong>
hooks API는 매력적인 게 많은데, 우선 useState, useEffect 만 알아도 기존 클래스 컴포넌트들에서 사용하던 state 와 life cycle method 들은 무리없이 대체해서 사용할 수 있기 때문에 react-hooks를 시작해 볼 만하다. hooks 도 하루 정도 공식 문서를 읽었다. </li>
<li><strong>styled-components</strong>
개인적으로 udemy 강좌에서 sass 를 배운 적이 있어서, sass 에 대해 긍정적인 인식이 있었는데 이번 코드스테이츠에서는 스프린트 때도 그렇고, 팀원들 간에도 styled-components 얘기가 많았다. 핫한 건 또 써봐야지.. 이건 반나절 공부하고, 필요한 게 있으면 공식 API 문서를 보면서 필요한 걸 찾아썼다. </li>
<li><strong>atomic pattern</strong>
코드스테이츠의 현규님께서 좋다고 매주 영업하던 아토믹 패턴.. 거기에 솔깃한 나와 동환님은 귀가 팔랑팔랑.. 은 (반만) 농담이고. 기분이 다운 되면 집안 청소, 방 정리, 책상 정리부터하고 마음의 안정을 찾는 나는 깔끔한 컴포넌트 설계와 효율적인 파일 구조 세팅같은 것에 항상 관심이 많은 편인데, 클라이언트 사이드에도 아토믹이라는 디자인 패턴을 적용할 수 있다는 얘기를 듣고 도입해보기로 했다. 이건 개념적인 부분이라, 블로그들도 중복된 설명이 대부분이다. 우선 button, logo, input, label 이런 건 atoms 에 속하고, input 하고 button 합치면 molecules 에 속하고~ 이렇게 느낌만 잡고 시작했는데, 물론 그렇게 모든게 쉬울리 없고 암흑의 서막이 시작된다.  </li>
</ol>
<h3 id="2주차--프로젝트-기획-및-환경-설정">2주차 : 프로젝트 기획 및 환경 설정</h3>
<p>팀원들 모두 디테일 작렬하는 프로젝트 기획의 중요성을 잘 느끼고 있어서, 거의 일주일을 할애하여 프로젝트 기획을 했다. git flow 는 다들 익숙했고, figma 로 와이어 프레임, UI 목업 설정, 대강의 콘텐츠를 생각해보고, drawio 로 플로우 차트를 만들고, dbdiagram 으로 데이터 스키마 구상, gitbook 으로 각 엔드포인트와 주고 받을 데이터에 대해 스케치해보았다.</p>
<p><strong>작업 환경설정</strong></p>
<p><img src="https://images.velog.io/images/minjae-mj/post/347b3fa9-e314-4100-9219-4014a853fca7/Untitled%20Diagram.jpg" alt=""></p>
<p>새벽에 혼자 데이터 흐름을 생각해보며 위와 같은 컴포넌트 트리를 그리고, react-router-dom 문서를 훑어보며 useParams 를 통해 동적 라우팅을 구상하며 목업 프로젝트를 만들어서 테스트해보았다. 다음 날 다행히 다른 팀원 분의 동의를 얻어서 그대로 적용할 수 있었다.  </p>
<p>파일 구조를 만들 때에는 리덕스, 아토믹 패턴을 함께 고려해야했기에, 우선 리덕스용 store, actions, reducers 디렉토리를 만들었다. 그리고 App 디렉토리 안에 아토믹 패턴을 담을 atoms, molecules, organisms, templates, pages 를 디렉토리를 만들었다. </p>
<p>마지막으로 로컬 서버, 배포 서버 주소를 환경 변수처럼 관리하기 위해 apis 디렉토리에 server 파일을 만들고 axios.create 의 base url 을 지정한 후, 계속 이 파일을 import 하여 axios 요청을 했다. 배포 후에는 base url을 EC2 서버 주소로 바꾸기만 했다. 최종 src 파일 구조는 아래와 같이 완성되었다. </p>
<p><img src="https://images.velog.io/images/minjae-mj/post/d8eb2c8c-366c-4563-90b6-c4d93cb1c42f/Screen%20Shot%202021-02-08%20at%203.06.17%20am.png" alt=""></p>
<h3 id="3주차--각-페이지-비즈니스-로직-구현">3주차 : 각 페이지 비즈니스 로직 구현</h3>
<p><strong>협업</strong>
우선 NavBar, 랜딩페이지, Footer 를 페어 프로그래밍 형식으로 작업하면서, 우리 파일 구조에 어떻게 파일명을 짓고 기능 분할을 할 지 세부적인 가닥을 잡았다. 그리고 페이지를 나눠서 맡되, 프론트엔드 협업은 항상 줌을 연결하여 모르는 것은 함께 풀어가는 페어코딩 방식을 택했다. </p>
<p>** 🍉 전통주 상세페이지 **</p>
<p><strong>svg를 리액트 컴포넌트로 바꿔서 icon 으로 사용</strong>
리액트에서 리뷰에 들어갈 별 icon 작업을 위해 이미지를 다운받으려고 보니, svg 옵션이 있었다. HTML5 에서 svg 가 생긴 것은 알고 있는데, 리액트에서는 어떻게 활용할 수 있는지 궁금했다. stackoverflow 를 통해 svg 파일을 하나의 리액트 컴포넌트로 사용할 수 있다는 것을 알고, atoms 디렉토리에 만들어서 사용했다.</p>
<pre><code class="language-tsx">import { ReactComponent as StarSvg } from &#39;/images/star.svg&#39;;</code></pre>
<p>위와 같이 svg 파일을 import 해주면 끝이다. svg 컴포넌트는 자체 fill 속성을 사용해서 쉽게 색상 변경을 할 수 있다. 리액트 컴포넌트이다 보니 때에 따라 props 로 색깔을 내려주면 되서 일반 png, jpg에 비해 훨씬 용도에 맞게 사용할 수 있었다. </p>
<p><strong>useState, styled-components 로 CSS 효과주기</strong>
이전 프로젝트의 프론트엔드 분들은 돔을 조작하여 :hover selector 를 쓰거나 active 클래스를 만들고 classList.add/remove 로 마우스 이벤트에 대한 효과를 컨트롤했는데 이번에는 주어진 기술 환경부터가 달랐다.  </p>
<p>react hooks 와 styled-components 를 조합해서 hover, active 효과를 주는 게 도전 과제였다. 여기서는 useState 를 사용했다. hover 의 경우 onMouseEnter 와 onMouseLeave 이벤트 리스너를, active 의 경우 onClick 이벤트 리스너를 사용해서 setState 하였다. 그리고 state에 따라 별도의 styled-components 를 렌더링하도록 하였다. </p>
<p><strong>리뷰 별점 기능</strong>
<a href="https://medium.com/javascript-in-plain-english/how-to-build-a-star-rating-component-in-react-dad06b05679b">해외 블로그</a>를 보고 거의 그대로 가져다 적용해볼 수 있었다. 특히 useMemo 를 사용해볼 수 있어서 좋았다. memo는 내가 <a href="https://velog.io/@minjae-mj/Memoization-%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98-feat.-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98-%EC%88%98%EC%97%B4">이전 블로그</a>에도 정리해본 적 있는 memoization 의 준말이다. 이전에 계산한 값을 저장하여 동일한 계산은 건너뜀으로서 프로그램 실행을 빠르게 해주는 기술인데, 별 아이콘을 빠르게 hover over 할 때마다 위치에 맞춰서 별을 노랗게 바꿀지 말지를 연산하는데에 적용되었다.</p>
<p><strong>리뷰 리스트</strong>
맡은 페이지에서 UX 최적화를 위해 노력했다. 예컨대 클릭을 유도하는 버튼에 진한 색깔을 주고, 리스트는 컨텐츠에 집중할 수 있도록 최대 4개까지만 보이도록 구현했다. 반응형에 따라 3개까지 줄어들기도 한다. </p>
<p><strong>유효성검사</strong>
게스트가 만약 개인화된 기능을 사용하려면 로그인을 해야한다. 대표적으로 상세페이지에 있는 &#39;북마크 등록&#39; 과 &#39;리뷰 등록&#39; 이다. 로컬스토리지 내의 토큰을 확인하고, 토큰이 없다면 로그인 요청 팝업이 뜬다.  </p>
<p>** 🍉 마이페이지 **</p>
<p><strong>사이드바</strong>
마이페이지의 탑레벨 컴포넌트에 사이드바와 사이드바 클릭에 따라 렌더링될 페이지들을 담았다. 
<img src="https://user-images.githubusercontent.com/65207835/108521029-a7fc5380-730e-11eb-9831-379fc7828276.png" alt="example">
<strong>북마크 리스트</strong>
flexbox 를 사용하여 레이아웃을 잡고, 하나의 북마크는 하나의 카드 형태로 보여주었다. 카드에 많은 정보는 담지 않고, hover 하는 경우 북마크 삭제 또는 상세보기 페이지로 이동할 수 있는 버튼이 보인다. 역시 UX를 생각했을 때, 북마크에서 많은 정보를 보여줄 필요는 없다고 생각했다. 만약 북마크에 등록된 전통주가 없다면, 가볍게 <a href="https://velog.io/@minjae-mj/%EB%94%94%ED%8F%B4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%99%80-%EB%A1%9C%EB%94%A9%EC%8A%A4%ED%94%BC%EB%84%88">디폴트 메세지</a>를 보여주었다. </p>
<p><strong>formData, 모달 생성</strong>
사용자는 로그인 후에 이미지, 닉네임, 비밀번호 수정을 할 수 있다. 이미지의 경우 formData 형태로 보내면 서버에서 multer 를 사용해 업데이트를 한다. 그리고 닉네임과 비밀번호 수정을 해야하는데, 개인적으로 팝업 모달은 광고 생각이 나서 좋아하지 않기 때문에, input 이 나타날 공간을 미리 확보해두고 클릭해따라 display 를 나타내는 식으로 구현했다.  </p>
<h3 id="4주차--각-페이지-반응형-css-콘텐츠-구성-데이터-입력">4주차 : 각 페이지 반응형 CSS, 콘텐츠 구성, 데이터 입력</h3>
<p>본격적으로 팀원 네 명이 머리를 맞대고 협업을 했다. 공교롭게도 우리가 13인치, 14인치, 27인치 등 다양한 사이즈의 모니터를 사용하고 있어서, 개발자도구와 각자의 로컬 환경에서 테스트 해보는 것으로 반응형 테스트를 진행해볼 수 있었다.  </p>
<p><strong>글로벌 CSS 파일 세팅</strong>
udemy에서 CSS 심화 수업을 들은 적이 있는데, 그 때 요긴했던 base 세팅을 가져와서 우리 글로벌 CSS 파일을 만들어보았다. 기본적으로 px 대신, rem 을 사용하기로 하고 em, rem 의 차이 등에 대해 복습했다. em 은 부모요소 대비, rem 은 root 요소 대비로 계산한다. 크롬 기준 브라우저 디폴트 폰트 크기가 16px 이고, 나는 계산의 편의를 위해 1rem = 10px 로 설정하고자 했으므로, root font-size 는 62.5% 로 설정했다. (10px/16px * 100)</p>
<p>그리고 우리 메인 컬러가 있었기 때문에, 매번 hexcode 를 적는 불편함을 덜고자 custom variable 을 사용했다. 그리고 flexbox 와 미디어쿼리로 반응형을 구현했다. </p>
<h2 id="🍊-결과">🍊 결과</h2>
<p>이번에는 도메인 주소를 Router53 에 연결하여, 비교적 예쁜 주소로 배포까지 마무리하였다. </p>
<h1 id="feelings-느낌">Feelings (느낌)</h1>
<p>분명히 코딩 프로젝트인데, 하루 정도는 꼬박 콘텐츠 구상에 쏟기도 했다. 로그인에 들어갈 그림, 랜딩에 넣을 문구들 등등 이런 부분들 하나하나 의견 교환이 많이 필요했던 작업이었는데, 팀원들 모두 콘텐츠에 각종 인터넷 드립들을 섞어보며.. 😂 즐겁게 작업했다!  </p>
<p><strong>나의 건강은 팀의 자산</strong>
프로젝트를 만드는 것도 재밌었고, 무엇보다 너무 좋은 팀원들과 함께했기 때문에 마음은 하나도 힘들지 않았는데, 오히려 그랬기에 몸이 힘든 걸 대수롭지 않게 여겼던 것 같다. 약 두 달 가까이, 특히 파이널 프로젝트 기간 동안 점심시간 1시간, 저녁시간 2시간 외 자는 시간을 빼고 정말 항상 화상채팅을 켜 놓고 프로젝트를 하는 생활을 하다보니, 프로젝트 마지막 전 날 내내 원인불명의 두통과 속 울렁거림으로 병이 나서 울다가 자다가 했다. 끝까지 마무리를 하는 팀원들에게 어찌나 미안하던지.. 협업을 할 때에는 내 건강이 나의 자산일 뿐만 아니라, 팀의 자산이기도 하다는 것을 깨달았다.</p>
<p><strong>인간 프리티어</strong>
이번에 나는 자칭 인간 프리티어였다. 나는 평소에도 깔끔하고 주석없이도 알아볼 수 있는 직관적인 코드, 일명 clean code에 열광한다. 그리고 아직 내 지식이 얕고 배우는 단계에서는 가급적 convention 이나 best practice 를 찾고 따르는 편이다. 그런데 이번 개발 환경 세팅 당시 모두 prettier 를 사용하기로 했는데, 동환님과 나의 컴퓨터에선 어쩐 일인지 적용이 되지 않았다. 프론트엔드인 우리는 화면 공유를 해서 의논할 때가 많았기 때문에, 내가 종종 동환님에게 몇 가지 부탁을 드리곤 했다. 예를 들어 isClicked 와 같은 변수명에 대한 값은 textContent 문자열이 아닌 불리언 값으로 바꿔달라든지.. 너무 컨벤션 코드를 봐온 탓에, 문자열을 값으로 가진 isClicked 로 로직 고민을 해야하는데 머리가 버벅대는 것 같았다. 결국 불리언 값으로 바꿨다. 여러 사람이 읽는 코드이기때문에 다수가 용이하다고 합의한 컨벤션을 참조하는 것도 좋지만, 여러 사람의 스타일을 받아들이고 유연하게 작업할 수 있는 능력 또한 필요하다고 느꼈다. 많이 귀찮았을 요구를 웃으며 받아준 동료에게 다시 한 번 감사함을 전한다. </p>
<p><strong>디테일, 디테일, 디테일...</strong>
꼼꼼한 성격을 스스로 피곤해하는 편이지만, 이 성격이 발휘될 수 있는 일을 맡으면 물만난 물고기가 된다. 프론트엔드 개발자는 기능 구현뿐 아니라, 사용자가 직접 보는 화면을 폴리싱하는 담당자인만큼, 디테일을 섬세하게 다듬을수록 프로덕트는 빛이 난다. 우리 빚다 사이트를 예로 들면 데이터 로딩이 오래 걸리는 페이지 전환의 경우 <a href="https://velog.io/@minjae-mj/%EB%94%94%ED%8F%B4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%99%80-%EB%A1%9C%EB%94%A9%EC%8A%A4%ED%94%BC%EB%84%88">로딩 애니메이션</a>을 넣었다. 그 밖에도 같은 레벨의 타이틀에는 같은 폰트 사이즈를 사용해야한다든지. 이런 류의 사소한 디테일을 다 챙겨야 하는데 다행히도 성격에 위배되지 않아서(?) 즐거운 작업이었다. </p>
<p><strong>🎉 첫 프로젝트의 findings 에 적었던 개선점 반영</strong>
개인적으로 이전 프로젝트에서 기술적으로 아쉬웠던 부분들 - <em><strong>컴포넌트 기초 설계, 상태관리 라이브러리 사용, 반응형 CSS 구현</strong></em> - 을 이번 프로젝트에서 모두 개선시켰다. 이번 프로젝트가 만족스러운 이유 중의 하나이다. </p>
<h1 id="findings-교훈">Findings (교훈)</h1>
<p><strong>styled-components 와 아토믹 패턴의 조합</strong>
우리 규모의 프로젝트에서 꿀조합이라고는 할 수 없었다. 스타일드 컴포넌트들 자체도 하나의 리액트 컴포넌트들인데, 점점 컴포넌트들 갯수가 증폭되면서 재사용성의 장점을 넘어 오버킬하는 느낌이었다. 거의 모든 jsx 가 일회용 스타일드 컴포넌트화되었다. 그리고 아토믹 패턴으로 구성된 수많은 리액트 컴포넌트들도 필연적으로 props 를 몇 번씩 거쳐 내리는데, 스타일드 컴포넌트들에도 props 를 내리면서 props hell이 펼쳐졌다. </p>
<p>물론 스타일드 컴포넌트의 장점이 확실히 있었다. 기능 컴포넌트에 밀착 사용하기때문에 컴포넌트를 삭제할 경우 쉽게 스타일링과 함께 삭제 가능하고, 클래스 이름의 중복으로 인한 문제점을 피할 수 있으며, 동적 스타일링이 가능하다는 점이 특히 매력적이었다. 동적 스타일링의 경우, 나는 버튼에서 유용하게 사용했다. 버튼을 누르면 항상 어떤 이벤트가 발생하는 점을 고려하여 항상 handleClick 이라는 props 를 받도록 해두고, 버튼 스타일드 컴포넌트를 사용할 때마다 다른 클릭 이벤트 함수를 내려줬다. Sass 처럼 &amp;:hover, &amp;:focus 의 형태로 중첩해서 사용할 수도 있고, 기존 스타일드 컴포넌트를 extend 해서 사용하는 기능도 편리했다. </p>
<p>이렇게 버튼만 따로 떼서 컴포넌트를 만든 건 아토믹 디자인 패턴에 따른 것이었다. 아토믹 패턴은 하나의 기능 컴포넌트 요소들을 그 역할 또는 기능에 따라 잘게 쪼개어서 <code>atoms</code>, <code>molecules</code>, <code>organisms</code>, <code>templates</code>, <code>pages</code> 에 나누는 것이다. 더 이상 잘게 쪼갤 수 없는 버튼, 인풋창 등은 atoms, 인풋과 버튼이 만나면 molecules, 거기에 라벨까지 씌우면 organisms 가 된다. 여러 개의 organisms 가 template 에 정의한 레이아웃 안에 배치되고, 최상단의 pages 는 그 template 을 렌더링하는 역할을 한다. 자연스레 여러 컴포넌트의 중첩 구조를 상상해볼 수 있다. 아토믹 패턴은 프로젝트 규모가 더 커서 재사용 가능한 컴포넌트들이 많이 있을 때 훨씬 더 유용하게 사용할 수 있을 것 같다. 우리 프로젝트에서는 일회용 컴포넌트들이 많아 제대로 효용을 발휘하지 못한 것 같아 아쉽지만, 그 장단점을 체험해볼 수 있어서 좋았다. </p>
<p><strong>styled-components 변수명 규칙</strong>
변수명 규칙은 camelCase 로 정했지만, 스타일드 컴포넌트 변수명 규칙도 정했어야 한다는 것을 간과했다. 나는 스타일드 컴포넌트의 경우 &#39;Style + 기능&#39; 의 패턴으로 &#39;StyleReview&#39; 라는 식으로 네이밍했다면, 다른 프론트엔드 팀원 분은 &#39;CSS특징 + jsx&#39; 의 패턴으로 &#39;FlexBoxDiv&#39; 라는 식으로 네이밍을 하셨다. </p>
<p>처음 우리 코드를 리뷰 하는 사람이라면, 이렇게 통일되지 않은 네이밍 방식에 분명 혼란스럽고 가독성이 떨어진다고 느낄 것이다. 혼란스러운 코드는 개발자로 하여금 불필요한 추측에 시간을 소요하게 한다. 사소해보이지만, 이런 부분들이 쌓여 향후 디버깅 등에 있어 문제 해결속도를 지연시키므로 결과적으로 기술적 부채에 해당한다고 본다. 꼭 개선해야할 부분이다.  </p>
<p><strong>typescript의 사용</strong>
타입스크립트의 유용성은 <a href="https://github.com/codestates/BITDA_Client/issues/5">깃헙 회고</a>에도 작성한 바 있다.</p>
<blockquote>
<p>타입스크립트의 장점은  개발자로 하여금, type 충돌로 발생 가능한 에러를 런타임에 실행하기 전 &quot;개발 단계&quot; 에서 감지하고 수정하게 함으로써, 런타임 에러를 줄여준다는 것이다. 예전에는 브라우저에서 실행시킨 후 콘솔창을 보고 에러를 감지했다면, typescript 는 코드 에디터 상에서, 혹은 typescript 컴파일링 단계에서 에러를 감지할 수 있게 한다. 이를 테면, 사후약처방에서 사전예방으로 에러 핸들링을 할 수 있게된 것이다. </p>
</blockquote>
<p>프로젝트 4주차에 접어들자 코드 작성을 하면서 바로 타입 지정을 할만큼, 타입스크립트를 선제적으로 사용할 수 있었다. 즉, 나 스스로도 훨씬 예측 가능한 코드를 작성 가능하게 된 것이다. 타입스크립트가 유용한 또 다른 경우도 있었다. 우리 사이트는 관리자 모드가 있는데 처음에는 관리자인지 여부를 boolean 으로 데이터를 주고 받기로 했으나, 서버에서  0, 1 의 값으로 대체해야 한다고 했다. 곧바로 타입스크립트의 enum 기능이 생각나서 써먹었다. 0 이 true인지, false인지 모를 때 enum 으로 미리 정의해두면 누구나 쉽게 알 수 있었다. </p>
<p><strong>token 상태관리는 로컬스토리지에서</strong>
글로벌 상태관리를 위해 redux 를 도입하기로 하고, 가장 먼저 token과 같은 로그인 상태를 저장하였으나 새로고침 후에 리덕스가 reset 되어 로그인이 풀리는 현상이 발생했다. 이런 고질적인 문제를 어떻게 해결할까하다가 결국 로컬스토리지를 사용했다. 그러나 모든 정보를 로컬스토리지에 저장하기에는 한계가 있었고, 리덕스 사용성이 현격히 떨어진다는 생각이 들었다. 결국 토큰만 로컬스토리지에 저장하고, 항상 렌더링되는 NavBar에서 새로고침 때마다 로컬스토리지에 토큰이 있는지 확인하고, 토큰이 있으면 유저 정보를 불러와 리덕스 스토어에 저장하여 사용하는 방식을 택했다. 프로젝트 당시에는 이렇게 해결했고, 프로젝트 발표 후 리서치를 통해 이런 점을 보완해주는 라이브러리 redux-persist 에 대해 알게 됐다. 다음에는 이 라이브러리를 적용해서 해결해보고 싶다. </p>
<h1 id="future-앞으로">Future (앞으로)</h1>
<p>이번 프로젝트를 토대로 다음 프로젝트에 적용해보고 싶은 것들이 생겼다. </p>
<p><strong>Storybook 사용</strong>
스토리북은 컴포넌트 UI 개발을 위한 오픈소스 툴이라고 하는데, 아토믹 패턴을 찾아볼 때 거의 모두가 사용하고 있었다. 미리 계획하고 사용하지 못해서 아쉽지만, 얼마나 좋은지 다음 번엔 꼭 사용해봐야겠다. </p>
<p><strong>Custom hook</strong>
hooks 의 꽃은 커스텀 훅이라고 생각하는데, 이번 프로젝트에서 활용해보지 못해서 아쉽다. 프로젝트 끝으로 갈수록, 리덕스 스토어에 접근하기위해 아래 문법들이 반복 사용되었는데 이 부분을 커스텀 훅으로 리팩토링해볼 수 있을 것 같다.</p>
<pre><code class="language-tsx">const state = useSelector((state: RootState) =&gt; state.signintReducer);
const dispatch = useDispatch();
</code></pre>
<p><strong>container presenter 패턴 적용</strong>
컨테이너 프레젠터 패턴은 리액트의 가장 기본 패턴이기도 한데, 비즈니스 로직을 핸들링하는 container 컴포넌트와 UI만을 렌더링하는 presenter 컴포넌트로 분리하여 설계하는 것이다. 컴포넌트 설계에 다양한 아키텍처를 적용해보면서, 향후에 프로젝트 특성에 맞는 패턴들을 직접 골라 사용할 수 있으리라 기대한다. </p>
<h1 id="bitda-로-내-취향-전통주-찾기-start-🥂">BITDA 로 내 취향 전통주 찾기 Start 🥂</h1>
<p>취향기반 전통주 추천 서비스, 직접 체험해보세요 - <a href="https://www.bitda.ga/">빚다</a></p>
]]></description>
        </item>
    </channel>
</rss>