<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>protect-me.log</title>
        <link>https://velog.io/</link>
        <description>protect me from what i want</description>
        <lastBuildDate>Sun, 21 Nov 2021 10:19:21 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>protect-me.log</title>
            <url>https://images.velog.io/images/protect-me/profile/82db36e8-5c85-469c-a20a-2b33e764fb2b/coffee.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. protect-me.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/protect-me" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Magazine B] ISSUE No.86 USM]]></title>
            <link>https://velog.io/@protect-me/Magazine-B-ISSUE-No.86-USM</link>
            <guid>https://velog.io/@protect-me/Magazine-B-ISSUE-No.86-USM</guid>
            <pubDate>Sun, 21 Nov 2021 10:19:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/protect-me/post/39feed99-8660-4a2a-89a6-18fb87d52e52/magazineb_usm.jpg" alt="">
이미지 출처 : <a href="https://magazine-b.co.kr/product/usm/">magazine-b USM</a></p>
<blockquote>
<p><strong>취ː사 선ː택, 取捨選擇</strong>
여럿 가운데서 쓸 것은 골라 쓰고 버릴 것은 버림.</p>
</blockquote>
<blockquote>
<p>가끔은 이미 최고인 것을 그대로 유지하는 능력도 필요합니다. 눈 앞의 성취에 중독된 사회에서 이들의 태도는 꽤 신선하게 다가옵니다.</p>
</blockquote>
<blockquote>
<p>&quot;형태는 기능을 따른다(Form follows function)&quot; <strong>by.루이스 설리번</strong>
(화려한 장식을 배제한 기능주의 미학과 모더니즘 디자인을 주창)</p>
</blockquote>
<blockquote>
<p>처음부터 옳은 것에 투자하는 것의 가치를 아는 회사. 
지속 가능성(sustainability)에 대한 메시지를 던진 최초의 브랜드 중 하나라는 사실입니다. 지금은 모든 브랜드가 지속 가능성에 대해 이야기하는 시기입니다만, USM은 존재 자체가 지속 가능성의 정수입니다. <strong>by.타일러 브륄레(모노클 편집장)</strong></p>
</blockquote>
<blockquote>
<p>USM이 지닌 모던한 형태와 무엇이든지 가능하게 만드는 창의적 비전이 ... 그런 관점에서 할러 시스템은 어른들을 위한 레고 Lego 블록 같아요. 성인이 되어서도 레고에 빠지는 이유는 그것을 통해 표현할 수 있는 무한한 가능성 때문일 테니까요. 특유의 컬러와 기하학적 요소도 상상력을 자극하고요. 특유의 컬러와 기하학적 요소도 상상력을 자극하고요. 정리해보면, 할러 시스템을 통해 자신이 원하는 환경을 지어나가고 무한한 가능성을 탐험할 수 있는 점이 창의적인 사람에게 어필하는 것이 아닐까 생각합니다. 사람들마다 동일하지 않은 자신만의 가구를 설계할 수 있는 점에도 끌리는 것 같고요.</p>
</blockquote>
<blockquote>
<p><strong>대안 없는 1순위</strong></p>
</blockquote>
<blockquote>
</blockquote>
<p>미래의 사무 공간에서 점점 더 부각될 가치는 공간의 &#39;가역성&#39;입니다. 최초 설계자의 손에서 구조와 기능이 이미 결정난 고정된 형태의 건축이 아닌 시대의 변화에 민첩하게 적응할 수 있고, 언제든 원래대로 복원할 수 있는 유연한 사무 공간이 요구받는 시대니까요. 미니멀한 조형미, 확장성, 내구성, 지속 가능성은 고루 갖춘 USM의 가구에는 이미 미래형 사무 공간에 대한 숙고가 담겨 있습니다. <strong>by. 프랭클랭 아지</strong></p>
<blockquote>
</blockquote>
<p><strong>가역성</strong> : 물질이 어떤 상태로 변하였다가 본래 상태로 되돌아갈 수 있는 성질. 열역학에서 어떤 계(系)가 처음 상태에서 나중 상태로 변화할 때, 그 과정의 역과정이 존재하고 관련된 계들에 순효과도 없이 계의 처음 상태로 돌아갈 수 있는 과정들의 특성을 말한다.</p>
<blockquote>
<p>글로벌하게 생각하고, 지역적으로 행동하라(Think global, act local) <strong>by.로랑크로세(USM 프랑스 대표)</strong></p>
</blockquote>
<blockquote>
<p><strong>기능주의 미학</strong></p>
</blockquote>
<blockquote>
<p>트렌드에 맞는 신상품을 무분별하게 개발하기보다 눈에 보이지 않는 사용감과 기능성을 향상하는 데 시간과 노력을 투자하는 자세야말로 지극히 클래식한 동시에 모던한 제품을 완성하는 열쇠라고 생각합니다. <strong>by.로랑크로세(USM 프랑스 대표)</strong></p>
</blockquote>
<blockquote>
<p><strong>특정 시대에 등장한 완성형 가구를 좋아하는 고객 중에 USM의 가치를 낮게 보는 이도 존재합니다. 그들에겐 &#39;중고 시장에서 더 비싸게 팔리는가&#39;의 여부가 좋은 가구를 가름하는 기준이 되기도 하는 것 같은데요.</strong>
 사실 유행을 타지 않는 미니멀한 디자인에 사용감이 쉽게 드러나지 않는 USM 제품을 말하기에 &#39;빈티지&#39;라는 단어는 잘 어울리지 않는 수식입니다. 모든 디자인 가구에 해당하는 이야기는 아니지만 한 시대를 풍미한 디자인 가구를 주거 공간에 배치했을 대, 공간이 해당 가구의 시대적 분위기를 드러내는 경우는 흔히 볼 수 있는데요, USM이 공간에서 존재하는 방식은 이와 다릅니다. USM이 놓인 공간의 사진이 여러 장 있다고 가정했을 떼, 그 공간의 시대적 배경을 짐작하게 해주는 요소는 USM 제품이 아니라 주변에 자리한 가구와 오브제들이죠. 그만큼 USM 가구는 한 시대에 국한되지 않는 궁극의 동시대성을 지니고 있습니다. &#39;불변의 모던함&#39;이야말로 USM 가구의 진정한 강점이자 가치죠.</p>
</blockquote>
<blockquote>
<p>USM은 줄곧 자신들의 시작점에서 도전을 계속해왔다. 새로운 것을 창조하기 위함이 아닌, 이미 존재하는 것의 완성도를 높이기 위한 도전이다.</p>
</blockquote>
<blockquote>
<p>전통에 혁신을 더한 제조 공정</p>
</blockquote>
<blockquote>
<p><strong>확장과 변형</strong>
공장에서 생산하는 부품은 크게 세 가지로, 이음매 역할을 하는 크로뮴도금 볼 ball과 뼈대 역할을 하는 스틸 튜브 tube, 뼈대를 채우는 컬러 스틸 팬러 panel이 있다. 우리가 생각하는 USM 할러 시스템은 이 세가지 부품을 조립해 확장한 가구를 말하는데, 조립, 확장하는 방법은 그들의 공장을 통해 유추할 수 있다. 기둥과 대들보를 이어 붙여 건물을 확장하듯, 크로뮴도금 볼과 스틸 튜브를 이어 붙여 확장한 다음 컬러 스틸 패널로 면을 채워나가는 식이다. 여기에 사용자의 목적에 따라 서랍과 문, 바퀴와 유리 선반 같은 옵션을 추가할 수 있어 책상부터 티브이장, 장식장, 옷장, 트롤리 등 원하는 구성으로 변형해서 사용 가능하다. 매우 단순한 방식이지만 할러가 선보인 단순함은 그저 하나의 단어가 아니다. 더욱더 창의적 형태로 진화하는 확장과 변형을 내포한 단어다.</p>
</blockquote>
<blockquote>
<p><strong>지속가능한 도전</strong>
그들은 제품과 소재의 수명을 연장하는 방법을 토대로 친환경을 실천하는 쪽이다.</p>
</blockquote>
<blockquote>
<p>USM의 대표 모듈 시스템인 할러 시스템은 사용자의 생활방식을 대변하는 거울이다.
선택의 기준이 오전히 자신에게 있어서 사용자의 삶과 제품이 완전히 결합한다고 볼 수 있다.</p>
</blockquote>
<blockquote>
</blockquote>
<p>USM 할러 시스템은 무언가의 가치를 제대로 보여줄 수 있는 수납장이라고 확신해요. 단순한 디자인은 오히려 소장품을 돋보이게 만들고, 견고한 만듦새는 자신들이 좋아하는 제품을 평생 잘 보관할 수 있다는 믿음을 심어주죠.
...
단순한 선이 모여 추상적 직조를 만들어내는 듯한 외형과 시대를 바로 가늠할 수 없는 디자인이라는 점이 굊아히 인상적이었습니다. 
...
시간을 초월한 가구를 만들고 있는 거예요. 지금 부터 향후 20년 후에도 변함없이 스스로가 낼 수 있는 느낌 그대로 간직할 가구... 반대로 20년 전에도 지금과 같은 느낌을 ...
<strong>by.로돌프 파랑트</strong></p>
<blockquote>
</blockquote>
<p>... USM의 입방체 구조물은 어린아이의 장난감처럼 단순하다. 이 단순함은 곧 무한한 가능성을 뜻한다. ... 무엇이든 될 수 있다. ***by.이미혜(칼럼니스트 | 꽃술 대표)</p>
<blockquote>
<p>&quot;단순함은 그저 하나의 단어가 아니다.&quot;<strong>by.프리츠 할러</strong></p>
</blockquote>
<blockquote>
<p>USM 제품같이 어떤 목적에 맞게 완벽하게 디자인했다면 그것을 단순함이라고 ㅁ라할 수 있을 것 같아요. 저는 보잉 boeing에서 개발한 대형 여객기 747의 형태에서도 단순함을 느끼곤 합니다. 처음 747을 탔을 때 비행기 조종석 아래 볼록하게 튀어나온 공간ㅇ에 피아노가 놓여있었거든요. 기장이 직접 나와 피아노를 연주하기도 했죠. 요즘 시대에 비행기를 디자인한다면 그 공간에 피아노가 아닌 좌석을 놓을 겁니다. 당시 747은 장시간 여행하는 승객의 불안과 불편함을 풀어주기 위해 피아노를 들여놓았스빈다. 사용자를 위한 디자인, 그것이 바로 단순함이고, 단순함이야말로 디자인의 가치를 높이는 핵심 가치라고 생각합니다. <strong>by.알렉산더 셰러 USM CEO</strong></p>
</blockquote>
<blockquote>
<p>제품을 고객에게 선볼일 때, 그 제품은 이미 완벽한 제품으로 준비되어 있어야 합니다. 고객이 제품의 성능을 테스트하는 것이 아니라 저희가 제품의 성능을 완벽하게 테스트한 후 합격점을 받고나면 고객에게 선보이는 것이죠. <strong>by.알렉산더 셰러 USM CEO</strong></p>
</blockquote>
<blockquote>
<p>당신이 배를 만들고 싶다면, 사람들에게 목재를 가져오게 하거나 일을 지시하지 마라. 그들에게 저 넓고 끝없는 바다에 대한 동경심을 키워줘라. <strong>by.생텍쥐페리</strong></p>
</blockquote>
<blockquote>
<p>모두를 위한 좋은 디자인  <strong>by.바우하우스</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[테니스구인 | tennis9in 🎾]]></title>
            <link>https://velog.io/@protect-me/tennin9in</link>
            <guid>https://velog.io/@protect-me/tennin9in</guid>
            <pubDate>Mon, 01 Nov 2021 11:48:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/protect-me/post/5d1bfac0-d53e-44cf-9c0d-26bb98b4ebeb/image.png" alt=""></p>
<h3 id="🎾-테니스구인">🎾 테니스구인</h3>
<blockquote>
</blockquote>
<h4 id="👉🏻-httpstennis9innetlifyapp">👉🏻 <a href="https://tennis9in.netlify.app/">https://tennis9in.netlify.app/</a></h4>
<blockquote>
</blockquote>
<p><strong>테니스 경기장을 한눈에 보고, 게스트를 모집할 수 있는 모바일 웹앱입니다.
구글 로그인만으로 모든 서비스를 이용하실 수 있습니다!</strong></p>
<blockquote>
</blockquote>
<p>🪄 모바일 화면으로 보실 것을 권장드립니다:)</p>
<ul>
<li>Mac : <code>Command + Option + i</code> ⇒ <code>Command + Shift + m</code></li>
<li>Window : <code>F12</code> ⇒ <code>Ctrl + Shift + m</code></li>
</ul>
<br>

<h3 id="📲-테니스구인-오픈카톡방">📲 테니스구인 오픈카톡방</h3>
<blockquote>
</blockquote>
<h4 id="👉🏻-httpsopenkakaocomogjnjpxhd">👉🏻 <a href="https://open.kakao.com/o/gjNjPXHd">https://open.kakao.com/o/gjNjPXHd</a></h4>
<blockquote>
</blockquote>
<p><img src="https://images.velog.io/images/protect-me/post/9478485b-5380-4c66-8a86-f544e1ba76a6/tennis9in_qr.png" alt=""></p>
<br>
<br>

<hr>
<h3 id="💻-version-log">💻 Version Log</h3>
<h4 id="v-100-_-테니스구인-모바일웹앱-서비스-출시-_-20211101">V 1.0.0 _ 테니스구인 모바일웹앱 서비스 출시 _ 2021.11.01</h4>
<ul>
<li><strong>게스트 모집</strong> : 게스트 모집하기, 경기 참가 신청, 참가자 영입/방출</li>
<li><strong>테니스장 지도</strong> : 서울 테니스장 지도에서 보기, 테니스장별 현재 모집중인 공고 숫자 표시, 마커 클릭 시, 상세 페이지로 이동</li>
<li><strong>테니스장 리스트</strong> : 서울 테니스장 검색하기, 코트정보 및 모집 공고 확인 가능, 필터링 기능 추가 예정</li>
<li><strong>마이페이지</strong> : 구글 로그인/로그아웃, 내가 모집 중인 공고 보기, 게스트 참가 요청/취소 알림, 내가 참가 신청한 공고 보기, 피드백, 회원 정보 수정, 개인정보취급방침, 다크모드(🌑🌕)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue] iOS Mobile  Safari & Chrome X 100vh Issue (Feat. Vuetify)]]></title>
            <link>https://velog.io/@protect-me/Vue-iOS-Mobile-Safari-Chrome-X-100vh-Issue</link>
            <guid>https://velog.io/@protect-me/Vue-iOS-Mobile-Safari-Chrome-X-100vh-Issue</guid>
            <pubDate>Sun, 31 Oct 2021 12:49:02 GMT</pubDate>
            <description><![CDATA[<h2 id="💣-issue">💣 issue</h2>
<hr>
<ul>
<li><p>iOS Mobile Safari 또는 Chroime 사용 시,
100vh로 높이를 맞추면 하단의 내용이 잘리는 현상.
특히, 하단에 배치해둔 <code>button</code>, <code>alert</code> 등의 내용이 잘리는 상황</p>
</li>
<li><p>이미지 출처 : <a href="https://maxschmitt.me/posts/css-100vh-mobile-browsers/">CSS: Watch out for 100vh height in mobile browsers</a>
<img src="https://images.velog.io/images/protect-me/post/a7becdeb-a733-4ae4-bbc9-8e2c49a7d0ff/image.png" alt=""></p>
</li>
</ul>
<br>
<br>

<h2 id="👨🏻💻-solve">👨🏻‍💻 Solve</h2>
<hr>
<h3 id="🚀-public--indexhtml">🚀 <code>public &gt; index.html</code></h3>
<blockquote>
<p>현재 <code>window</code>의 <code>innerHeight</code>를 구해서 <code>--customVH</code>변수에 담아 <code>styleProperty</code>에 세팅함</p>
</blockquote>
<pre><code class="language-js">  &lt;head&gt;
    ...

    &lt;style&gt;
      body {
        height: calc(var(--vh, 1vh) * 100);
      }
    &lt;/style&gt;
  &lt;/head&gt;

  &lt;body&gt;
    ...

    &lt;script&gt;
      let customVH = window.innerHeight * 0.01
      document.documentElement.style.setProperty(&#39;--vh&#39;, customVH + &#39;px&#39;)
      window.addEventListener(&#39;resize&#39;, () =&gt; {
        let customVH = window.innerHeight * 0.01
        document.documentElement.style.setProperty(
          &#39;--vh&#39;,
          customVH + &#39;px&#39;,
        )
      })
    &lt;/script&gt;
  &lt;/body&gt;</code></pre>
<br>

<h3 id="🚀-사용">🚀 사용</h3>
<blockquote>
<p><code>container</code>와 <code>content</code>에서 <code>customVH</code>를 이용하여 계산함</p>
</blockquote>
<pre><code class="language-js">// app.vue
  &lt;v-app id=&quot;app-container&quot;&gt;
    ...
  &lt;/v-app&gt;
...
&lt;style&gt;
.app-container {
  height: calc(var(--vh, 1vh) * 100);
  overflow: scroll;
}
&lt;/style&gt;</code></pre>
<pre><code class="language-js">// CourtList.vue
&lt;style&gt;
.court-list-container {
  width: 100%;
  height: calc(var(--vh, 1vh) * 100 - 48px);
  display: flex;
  flex-direction: column;
  .court-list-content {
    width: 100%;
    height: calc(var(--vh, 1vh) * 100 - 134px);
    overflow: scroll;
  }
}
&lt;/style&gt;</code></pre>
<br>


<h3 id="🚀-vuetify-환경">🚀 <code>Vuetify</code> 환경</h3>
<blockquote>
</blockquote>
<p><code>v-application--wrap</code>의 <code>min-height</code> 기본값이 <code>100vh</code>로 설정되어있기 때문에 
<code>App.vue</code>의 <code>style</code> 태그에서 <code>scoped</code>를 떼고 <code>!important</code>로 덮어씌워야함.</p>
<pre><code class="language-js">// App.vue
&lt;template&gt;
  &lt;v-app id=&quot;app&quot;&gt;
    ...
  &lt;/v-app&gt;
&lt;/template&gt;
...
&lt;style lang=&quot;scss&quot;&gt; // `scoped` 제거
#app {
  height: calc(var(--vh, 1vh) * 100);
  overflow: scroll;
  .v-application--wrap {
    min-height: calc(var(--vh, 1vh) * 100) !important; // 덮어씌우기
  }
}</code></pre>
<br>
<br>

<h2 id="📚-ref">📚 REF</h2>
<hr>
<p><a href="https://dmstjq92.medium.com/%EB%AA%A8%EB%B0%94%EC%9D%BC-ios-safari-chrome-%EC%97%90%EC%84%9C-100vh-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%83%9D%EA%B8%B0%EB%8A%94-%EB%AC%B8%EC%A0%9C-%EC%A2%85%EA%B2%B0-682c5e9d068d">모바일(iOS) Safari, Chrome 에서 100vh 스크롤 생기는 문제 종결</a>
<a href="https://sub0709.tistory.com/73">script와 css의 선언 위치는 어디가 좋을까</a>
<a href="https://itinerant.tistory.com/192">[Vue.js] Vue.js 에서 css variable 사용하기</a>
추가 : <a href="https://css-tricks.com/the-trick-to-viewport-units-on-mobile/">The trick to viewport units on mobile</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<Danger> vue-tennis main img ]]></title>
            <link>https://velog.io/@protect-me/Danger-vue-tennis-main-img</link>
            <guid>https://velog.io/@protect-me/Danger-vue-tennis-main-img</guid>
            <pubDate>Sat, 23 Oct 2021 12:57:56 GMT</pubDate>
            <description><![CDATA[<h3 id="home">/home</h3>
<h4 id="og">og</h4>
<p><img src="https://images.velog.io/images/protect-me/post/526de137-5aee-4710-90e9-2107c9c784c4/tennis.jpeg" alt=""></p>
<h4 id="11">1:1</h4>
<p><img src="https://images.velog.io/images/protect-me/post/7ab49d45-78cb-4dd6-bdb1-4b716145736d/tennis_main_square.jpeg" alt=""></p>
<h3 id="indexhtml--meta--ogimage">index.html &gt; meta | og:image</h3>
<p><img src="https://images.velog.io/images/protect-me/post/0677140c-8f07-4202-a560-d9191b079ee9/tennis_meta_og_img.jpeg" alt=""></p>
<h3 id="kim-3-meals-img">kim-3-meals img</h3>
<p><img src="https://images.velog.io/images/protect-me/post/c6ea3472-c8f9-46e1-93ae-ea854d371c22/kim3meals_meta_img.png" alt=""></p>
<br>
<br>

<hr>
<p>Photo by <a href="https://unsplash.com/@renithr17?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Renith R</a> on <a href="https://unsplash.com/s/photos/tennis?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Netlify | Change Github Repository]]></title>
            <link>https://velog.io/@protect-me/Netlify-Change-Github-Repository</link>
            <guid>https://velog.io/@protect-me/Netlify-Change-Github-Repository</guid>
            <pubDate>Sat, 23 Oct 2021 07:16:40 GMT</pubDate>
            <description><![CDATA[<h2 id="path">Path</h2>
<p><code>project &gt; Site Settings &gt; Build &amp; Deploy &gt; Build Setting &gt; Edit Settings</code>
<code>&gt; Repository : Link to a different repository</code></p>
<h2 id="image">Image</h2>
<p><img src="https://images.velog.io/images/protect-me/post/ac6d65e5-dd17-42bf-92ba-4b7d8ee8c19f/image.png" alt="">
<img src="https://images.velog.io/images/protect-me/post/fe82580a-f5c2-4c09-ae05-e034eb61dcc8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎾 vue-tennis | SSR Migration(Nuxt + Firebase) | 2. Firebase Usage]]></title>
            <link>https://velog.io/@protect-me/vue-tennis-ssr-2</link>
            <guid>https://velog.io/@protect-me/vue-tennis-ssr-2</guid>
            <pubDate>Thu, 21 Oct 2021 13:33:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>SSR Migration(Nuxt + Firebase) Series
<a href="https://velog.io/@protect-me/vue-tennis-ssr-1">1. Set Environment</a>
<a href="https://velog.io/@protect-me/vue-tennis-ssr-2">2. Firebase Usage</a> 👈🏻</p>
</blockquote>
<br>
<br>


<h2 id="🔥-firestore-usage">🔥 firestore usage</h2>
<hr>
<blockquote>
<p>REF : <a href="https://firebase.nuxtjs.org/guide/usage">https://firebase.nuxtjs.org/guide/usage</a></p>
</blockquote>
<ul>
<li><code>pages &gt; index.vue</code> 내용 추가<pre><code class="language-js">&lt;script&gt;
export default {
async mounted() {
  console.log(&#39;mounted&#39;)
  const db = await this.$fire.firestore
  // const users = this.$fire.firestore.collection(&#39;users&#39;).doc(uid).get()
  console.log(&#39;here&#39;, db)
},
}
&lt;/script&gt;</code></pre>
</li>
<li><code>console</code>
<img src="https://images.velog.io/images/protect-me/post/0e9cb38f-3873-42ae-ad48-ba8f0da165bb/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>주의 사항</strong></p>
</blockquote>
<ol>
<li><code>this.$firestore</code> (x) ⇒ <code>this.$fire.firestore</code> (o) </li>
</ol>
<p>*버전에 따라 다름(필자의 환경 =&gt; <code>&quot;@nuxtjs/firebase&quot;: &quot;^7.6.1&quot;</code>)
2. <code>nuxt.config.js &gt; firebase &gt; services</code> ⇒ <code>firestore: true</code> 확인</p>
<br>
<br>


<h2 id="👨🏻🚒-functions-usage">👨🏻‍🚒 functions usage</h2>
<blockquote>
<p>REF : 로컬에서 함수 실행
<a href="https://firebase.google.com/docs/functions/local-emulator">https://firebase.google.com/docs/functions/local-emulator</a>
REF : 자바스크립트 프로젝트에 Firebase 추가
<a href="https://firebase.google.com/docs/web/setup">https://firebase.google.com/docs/web/setup</a>
REF : Functions
<a href="https://firebase.google.com/docs/functions/manage-functions?hl=ko">https://firebase.google.com/docs/functions/manage-functions?hl=ko</a></p>
</blockquote>
<h3 id="1-선행">1. 선행</h3>
<p><code>$ npm install -g firebase-tools</code> : firebase-tools 설치
<code>$ firebase login</code> : firebase에 로그인</p>
<h3 id="2-firebase-초기화">2. firebase 초기화</h3>
<p><code>$ firebase init</code> : <strong><code>functions</code>만 선택한 후 설치</strong>
(프로젝트 내에 <code>functions</code> 폴더가 생성된 것을 확인할 수 있음)</p>
<h3 id="3-nuxtconfigjs-내용-수정">3. <code>nuxt.config.js</code> 내용 수정</h3>
<pre><code class="language-js">  firebase: {
    config: {
      ...
    },
    services: {
      ...
      functions: {
        location: &#39;asia-northeast3&#39;, // firebase project 설정에 따름
        // Cloud Functions 위치
        // https://firebase.google.com/docs/functions/locations
      },</code></pre>
<h3 id="4-firebase-admin-sdk-설정">4. firebase admin SDK 설정</h3>
<blockquote>
</blockquote>
<p><code>local</code>에서 <code>myFirebaseProject</code>를 다룰 수 있게 됨
그만큼 리스크가 크기 때문에 <code>key</code> 관리에 신경써야 함</p>
<h4 id="1-firebase--myproject--프로젝트-설정--서비스-계정--firebase-admin-sdk-구성-스니펫-확인">1. <code>firebase &gt; myProject &gt; 프로젝트 설정 &gt; 서비스 계정 &gt; firebase admin SDK 구성 스니펫</code> 확인</h4>
<p><img src="https://images.velog.io/images/protect-me/post/34484db9-1ccf-4367-bb52-f58711e537d1/image.png" alt=""></p>
<h4 id="2-하단의-새-비공개-키-생성을-통해서-키-파일을-다운받은-후-myproject--functions-경로로-이동">2. 하단의 <code>새 비공개 키 생성</code>을 통해서 키 파일을 다운받은 후 <code>myProject &gt; functions</code> 경로로 이동</h4>
<p>(key 파일명을 알맞게 수정함 / 필자의 경우 <code>vue-tennis-key.json</code>)
<img src="https://images.velog.io/images/protect-me/post/04c2a3e8-bad1-4317-af00-1c48e75f3984/image.png" alt=""></p>
<h4 id="3-firebase-console-내-firebase-admin-sdk-구성-스니펫을-복사하여">3. <code>firebase console</code> 내 <code>firebase admin SDK 구성 스니펫</code>을 복사하여</h4>
<p><code>myProject &gt; functions &gt; index.js</code>에 붙여넣음</p>
<pre><code class="language-js">const functions = require(&#39;firebase-functions&#39;)
var admin = require(&#39;firebase-admin&#39;)
var serviceAccount = require(&#39;./vue-tennis-key.json&#39;)

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: &#39;myDatabaseURL&#39;,
})

const db = admin.database()
const fdb = admin.firestore()</code></pre>
<h4 id="4-gitignore-등록-중요❗️❗️">4. gitignore 등록 (중요❗️❗️)</h4>
<ul>
<li><code>root &gt; functions &gt; .gitignore</code>
<code>root &gt; .gitignore</code>과 구분됨에 주의 🚨<pre><code class="language-js">node_modules
vue-tennis-key.json
.env</code></pre>
</li>
</ul>
<h4 id="5-새로운-테스트-함수function-작성">5. 새로운 테스트 함수(function) 작성</h4>
<p>: <code>root &gt; functions &gt; index.js</code> 내용 수정</p>
<pre><code class="language-js">exports.createUser = functions.auth.user().onCreate(async (user) =&gt; {
  console.log(&#39;firebase funtions : createUser&#39;)
  const { uid, email, displayName, photoURL } = user
  const time = new Date()
  const userInfo = {
    email,
    displayName,
    photoURL,
    nickName: displayName,
    sex: &#39;&#39;,
    birth: &#39;&#39;,
    location: &#39;&#39;,
  }
  await fdb.collection(&#39;users&#39;).doc(uid).set(userInfo) // set user at Firestore
  userInfo.createdAt = time.getTime()
  db.ref(&#39;users&#39;).child(uid).set(userInfo) // set user at RealTime Database
})

exports.deleteUser = functions.auth.user().onDelete(async (user) =&gt; {
  console.log(&#39;firebase funtions : deleteUser&#39;)
  const { uid } = user
  await db.ref(&#39;users&#39;).child(uid).remove()
  await fdb.collection(&#39;users&#39;).doc(uid).delete()
})</code></pre>
<h4 id="6-함수-배포deploy-function">6. 함수 배포(deploy function)</h4>
<p>: <code>$ firebase deploy --only functions:createUser</code>
: <code>$ firebase deploy --only functions:deleteUser</code></p>
<h4 id="7-테스트-함수-작동-확인">7. 테스트 함수 작동 확인</h4>
<p>: <code>firebase &gt; myProject &gt; Functions &gt; 로그</code></p>
<ul>
<li>위에서 작성한 <code>function</code>에서 <code>console</code>로 찍은 내용을 확인할 수 있음</li>
<li>여기서 예시로 작성한 <code>createUser</code> 함수의 경우, 
<code>auth</code>에서 <code>user</code>가 새로 생성되거나 삭제되는 경우를 트리거로 하고 있기 때문에,
아래 <code>auth usage</code> 파트에서 작성한 코드를 기반으로 작동함. 
<strong>따라서 <code>auth usage</code> 파트를 작성한 후에 <code>7. 테스트 함수 작동 확인</code> 내용을 확인 바람</strong></li>
</ul>
<p><img src="https://images.velog.io/images/protect-me/post/5ab5c69d-01f2-4bc1-9913-12fb298d0c86/image.png" alt=""></p>
<br>
<br>


<h2 id="🚒-auth-usage">🚒 auth usage</h2>
<h3 id="1-nuxtconfigjs-내용-수정">1. <code>nuxt.config.js</code> 내용 수정</h3>
<p>: 아래와 같이 작성할 경우, <code>Auth State</code>가 변경될 때마다 <code>onAuthStateChanged</code><strong>Action</strong>이 실행됨.</p>
<pre><code class="language-js">  firebase: {
    config: {
      ...
    },
    services: {
      ...
      auth: {
        initialize: {
          onAuthStateChangedAction: &#39;onAuthStateChanged&#39;,
        },
        ssr: true,
        disableEmulatorWarnings: false,
      },
    }
  }</code></pre>
<br>

<h3 id="2-pages--mypagevue-내용-수정">2. <code>pages &gt; mypage.vue</code> 내용 수정</h3>
<pre><code class="language-js">&lt;template&gt;
  &lt;v-container&gt;
    &lt;v-card&gt;
      &lt;v-card-text&gt;
        &lt;v-btn @click=&quot;login&quot; :loading=&quot;isProcessing&quot;&gt;login&lt;/v-btn&gt;
        &lt;v-btn @click=&quot;logout&quot; :loading=&quot;isProcessing&quot;&gt;logout&lt;/v-btn&gt;
      &lt;/v-card-text&gt;
    &lt;/v-card&gt;
  &lt;/v-container&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  data() {
    return {
      isProcessing: false,
    }
  },
  methods: {
    async login() {
      if (this.isProcessing) return
      this.isProcessing = true
      const provider = new this.$fireModule.auth.GoogleAuthProvider()
      try {
        const snapshot = await this.$fire.auth.signInWithPopup(provider)
      } catch (err) {
        alert(&#39;로그인 실패&#39;, err)
        console.log(err)
      } finally {
        this.isProcessing = false
      }
    },

    async logout() {
      if (this.isProcessing) return
      this.isProcessing = true
      try {
        await this.$fire.auth.signOut()
        console.log(&#39;로그아웃 성공&#39;)
      } catch (err) {
        alert(&#39;로그아웃 실패.&#39;, err)
        console.log(err)
      } finally {
        this.isProcessing = false
      }
    },
  },
}
&lt;/script&gt;</code></pre>
<ul>
<li><code>http://localhost:3000/mypage</code>
<img src="https://images.velog.io/images/protect-me/post/764b9d19-fc93-473e-b742-98ac8f335a3e/image.png" alt=""></li>
</ul>
<br>

<h3 id="3-vuexstore">3. Vuex(store)</h3>
<h4 id="store--statejs">store &gt; state.js</h4>
<pre><code class="language-js">export default () =&gt; ({
  user: null,
  fireUser: null,
})</code></pre>
<h4 id="store--mutationsjs">store &gt; mutations.js</h4>
<blockquote>
<p><code>mutations</code>에서 <code>state.user = payload</code>와 같이 직접 할당을 하면 에러 발생 🚨</p>
</blockquote>
<pre><code class="language-js">import initialState from &#39;./state&#39;

export default {
  RESET_STORE: (state) =&gt; {
    Object.assign(state, initialState())
  },

  SET_FIRE_USER: (state, fireUser) =&gt; {
    const { uid, email, displayName, photoURL } = fireUser
    state.fireUser = { uid, email, displayName, photoURL }
  },

  SET_USER: (state, user) =&gt; {
    if (user) {
      const {
        email,
        displayName,
        photoURL,
        nickName,
        sex,
        birth,
        location,
      } = user
      state.user = {
        email,
        displayName,
        photoURL,
        nickName,
        sex,
        birth,
        location,
      }
    } else {
      state.user = null
    }
  },
}</code></pre>
<h4 id="store--actionsjs">store &gt; actions.js</h4>
<pre><code class="language-js">export default {
  async onAuthStateChanged({ commit }, { authUser }) {
    let unsubscribe = null
    if (!authUser) {
      if (unsubscribe) unsubscribe()
      return
    }
    commit(&#39;SET_FIRE_USER&#39;, authUser)

    const subscribe = (authUser) =&gt; {
      const ref = this.$fire.firestore.collection(&#39;users&#39;).doc(authUser.uid)
      unsubscribe = ref.onSnapshot((doc) =&gt; {
        if (doc.exists) commit(&#39;SET_USER&#39;, doc.data())
      }, console.error)
    }
    subscribe(authUser)
  },
}
</code></pre>
<br>
<br>



<h2 id="🧯🧑🏻🚒👩🏻🚒🕯">🧯🧑🏻‍🚒👩🏻‍🚒🕯</h2>
<br>
<br>


<p>REF : <a href="https://github.com/lupas/nuxt-firebase-demo">nuxt-firebase-demo</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎾 vue-tennis | SSR Migration(Nuxt + Firebase) | 1. Set Environment]]></title>
            <link>https://velog.io/@protect-me/vue-tennis-ssr-1</link>
            <guid>https://velog.io/@protect-me/vue-tennis-ssr-1</guid>
            <pubDate>Fri, 15 Oct 2021 12:46:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>SSR Migration(Nuxt + Firebase) Series
<a href="https://velog.io/@protect-me/vue-tennis-ssr-1">1. Set Environment</a> 👈🏻
<a href="https://velog.io/@protect-me/vue-tennis-ssr-2">2. Firebase Usage</a></p>
</blockquote>
<blockquote>
<p><strong>CSR</strong> : Client Side Rendering 
<strong>SSR</strong> : Server Side Rendering
* <strong>CSR</strong>은 <strong>SPA</strong>(Single Page Application)을 포함하는 개념</p>
</blockquote>
<blockquote>
</blockquote>
<p>기존에 <code>CSR</code> 방식으로 구현된 프로젝트를 <code>SSR</code> 방식으로 <code>Migration</code>하는 과정</p>
<blockquote>
</blockquote>
<p><strong>*마이그레이션(migration)</strong> : 하나의 운영환경으로부터 더 나은 운영환경으로 옮아가는 과정을 뜻하는 정보통신 용어</p>
<br>
<br>



<h2 id="🧩-create-nuxt-project-with-vuetify">🧩 create nuxt project with vuetify</h2>
<hr>
<ul>
<li><code>$ npx create-nuxt-app yourProjectName</code><pre><code class="language-js">create-nuxt-app v3.7.1
✨  Generating Nuxt.js project in tennis-9-in-nuxt
? Project name: tennis-9-in-nuxt
? Programming language: JavaScript
? Package manager: Npm
? UI framework: Vuetify
? Nuxt.js modules: (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selectio
n)
? Linting tools: (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)
</code></pre>
</li>
</ul>
<p>? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert select
ion)
? What is your GitHub username? yourName
? Version control system: Git</p>
<pre><code>- `$ npm run serve`
- `localhost:3000`
![](https://images.velog.io/images/protect-me/post/13039746-b85d-4f8d-aedd-d16ded515c24/image.png)

&lt;br&gt;
&lt;br&gt;

## 🔥 install firebase
---
&gt; __REF__ : https://firebase.nuxtjs.org/guide/getting-started

- `$ npm install firebase`
- `$ npm install @nuxtjs/firebase`


- `firebase &gt; 프로젝트 설정 &gt; 일반 &gt; 내 앱 &gt; SDK 설정 및 구성 &gt; 구성(firebaseConfig 내용 복사)`
![](https://images.velog.io/images/protect-me/post/a7fb0e0a-5a66-49c8-ae37-c80b4748e05a/image.png)
- `nuxt.config.js` 내용 수정
```js
modules: [&#39;@nuxtjs/firebase&#39;],

firebase: {
     {
        config: {
          apiKey: &#39;&lt;apiKey&gt;&#39;,
          authDomain: &#39;&lt;authDomain&gt;&#39;,
          projectId: &#39;&lt;projectId&gt;&#39;,
          storageBucket: &#39;&lt;storageBucket&gt;&#39;,
          messagingSenderId: &#39;&lt;messagingSenderId&gt;&#39;,
          appId: &#39;&lt;appId&gt;&#39;,
          measurementId: &#39;&lt;measurementId&gt;&#39;
        },
        services: {
          // Just as example. Can be any other service.
          auth: true,
          firestore: true,
          functions: true,
          storage: true,
          database: true,
          analytics: true,
        }
      }
}</code></pre><blockquote>
<p><strong>Error</strong> : Cannot read property &#39;apps&#39; of undefined</p>
</blockquote>
<ul>
<li><strong>원인 파악</strong>
<code>$ npm install --save</code> ⇒ <code>npm WARN @nuxtjs/firebase@7.6.1 requires a peer of firebase@^8.3.1 but none is installed. You must install peer dependencies yourself.</code>
즉, <code>@nuxtjs/firebase@7.6.1</code>은 <code>firebase@^8.3.1</code>을 필요로 하는데, 설치되어있지 않다.</li>
<li><strong>해결</strong>
<code>package.json</code> 수정 
<code>&quot;firebase&quot;: &quot;^9.1.2&quot;,</code> ⇒ <code>&quot;firebase&quot;: &quot;^8.3.1&quot;,</code>
수정 후 <code>npm install</code></li>
</ul>
<br>
<br>

<h2 id="📦-dotenv">📦 dotenv</h2>
<hr>
<blockquote>
<p><strong>중요한 키를 <code>git</code>에 올리지 않고 보호하기 위한 조치</strong></p>
</blockquote>
<ol>
<li><code>dotenv</code>에 키를 이관</li>
<li><code>require(&#39;dotenv&#39;).config()</code>를 import</li>
<li><code>process.env.yourKey</code>를 통해 사용</li>
<li><code>.gitignore</code>에 <code>dotenv</code> 추가</li>
</ol>
<ul>
<li><p><code>$ npm install dotenv</code></p>
</li>
<li><p><code>$ npm install @nuxtjs/dotenv</code></p>
</li>
<li><p><code>root &gt; dotenv</code> : 기존 <code>nuxt.config.js</code> 내용 이관
(주석, 따옴표, 마지막 빈 줄 등 제거)</p>
<pre><code class="language-js">VUE_APP_apiKey=&lt;apiKey&gt;
VUE_APP_authDomain=&lt;authDomain&gt;
VUE_APP_projectId=&lt;projectId&gt;
VUE_APP_storageBucket=&lt;storageBucket&gt;
VUE_APP_messagingSenderId=&lt;messagingSenderId&gt;
VUE_APP_appId=&lt;appId&gt;
VUE_APP_measurementId=&lt;measurementId&gt;</code></pre>
</li>
<li><p><code>nuxt.config.js</code> 수정</p>
<pre><code class="language-js">require(&#39;dotenv&#39;).config()
modules: [..., &#39;@nuxtjs/dotenv&#39;],

firebase: {
  // REQUIRED: Official config for firebase.initializeApp(config):
  config: {
    apiKey: process.env.VUE_APP_apiKey,
    authDomain: process.env.VUE_APP_authDomain,
    databaseURL: process.env.VUE_APP_databaseURL,
    projectId: process.env.VUE_APP_projectId,
    storageBucket: process.env.VUE_APP_storageBucket,
    messagingSenderId: process.env.VUE_APP_messagingSenderId,
    appId: process.env.VUE_APP_appId,
    measurementId: process.env.VUE_APP_measurementId,
  },</code></pre>
</li>
<li><p><code>.gitignore</code> 내용 추가 : <code>.env</code></p>
</li>
</ul>
<br>
<br>

<h2 id="🗒-packagejson-확인">🗒 package.json 확인</h2>
<hr>
<ul>
<li>현재까지의 <code>package.json</code> 확인<pre><code class="language-js">{
&quot;name&quot;: &quot;tennis-9-in-ssr&quot;,
&quot;version&quot;: &quot;1.0.0&quot;,
&quot;private&quot;: true,
&quot;scripts&quot;: {
  &quot;dev&quot;: &quot;nuxt&quot;,
  &quot;build&quot;: &quot;nuxt build&quot;,
  &quot;start&quot;: &quot;nuxt start&quot;,
  &quot;generate&quot;: &quot;nuxt generate&quot;
},
&quot;dependencies&quot;: {
  &quot;@nuxtjs/dotenv&quot;: &quot;^1.4.1&quot;,
  &quot;@nuxtjs/firebase&quot;: &quot;^7.6.1&quot;,
  &quot;core-js&quot;: &quot;^3.15.1&quot;,
  &quot;dotenv&quot;: &quot;^10.0.0&quot;,
  &quot;firebase&quot;: &quot;^8.3.1&quot;,
  &quot;nuxt&quot;: &quot;^2.15.7&quot;,
  &quot;vuetify&quot;: &quot;^2.5.5&quot;
},
&quot;devDependencies&quot;: {
  &quot;@nuxtjs/vuetify&quot;: &quot;^1.12.1&quot;
}
}</code></pre>
<br>
<br>


</li>
</ul>
<blockquote>
<p><strong>buildmodules vs modules</strong>
<code>buildmodules</code>은 개발 중에만 필요하거나 빌드를 실행하는 데만 필요하므로 프로덕션 번들에서 제외됨</p>
</blockquote>
<br>
<br>


<p>REF : <a href="https://github.com/lupas/nuxt-firebase-demo">nuxt-firebase-demo</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[programmers] Lv2. 수식 최대화 | ? | protect-me]]></title>
            <link>https://velog.io/@protect-me/programmers-Lv2.-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94-protect-me</link>
            <guid>https://velog.io/@protect-me/programmers-Lv2.-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94-protect-me</guid>
            <pubDate>Tue, 05 Oct 2021 06:26:32 GMT</pubDate>
            <description><![CDATA[<h1 id="🕊-link">🕊 Link</h1>
<blockquote>
<p>Lv2. 수식 최대화 Javascript
<a href="https://programmers.co.kr/learn/courses/30/lessons/67257">https://programmers.co.kr/learn/courses/30/lessons/67257</a></p>
</blockquote>
<h1 id="🧑🏻💻-codejavascript">🧑🏻‍💻 Code(javascript)</h1>
<pre><code class="language-javascript">const getPermutations = function (arr, selectNumber) {
  const results = []
  if (selectNumber === 1) return arr.map((value) =&gt; [value])
  arr.forEach((fixed, index, origin) =&gt; {
    const rest = [...origin.slice(0, index), ...origin.slice(index + 1)]
    const permutations = getPermutations(rest, selectNumber - 1)
    const attached = permutations.map((permutation) =&gt; [fixed, ...permutation])
    results.push(...attached)
  })
  return results
}

function solution(exp) {
  const spread = []
  const expLen = exp.length
  for (let i = 0, j = 0; i &lt; expLen; i++) {
    if (Number.isInteger(Number(exp[i]))) {
      spread[j] = (spread[j] || &#39;&#39;) + exp[i]
    } else {
      spread.push(exp[i])
      j += 2
    }
  }
  const types = [&#39;-&#39;, &#39;+&#39;, &#39;*&#39;]
  const perm = getPermutations(types, 3)
  const answer = []
  let ans = 0

  for (let i = 0; i &lt; perm.length; i++) {
    const clone = spread.slice()
    for (let j = 0; j &lt; perm[i].length; j++) {
      for (let k = 0; k &lt; clone.length; k++) {
        if (perm[i][j] === clone[k]) {
          switch (perm[i][j]) {
            case &#39;-&#39;:
              ans = Number(clone[k - 1]) - Number(clone[k + 1])
              break
            case &#39;+&#39;:
              ans = Number(clone[k - 1]) + Number(clone[k + 1])
              break
            case &#39;*&#39;:
              ans = Number(clone[k - 1]) * Number(clone[k + 1])
              break
          }
          clone.splice(k - 1, 3, ans)
          k--
        }
      }
    }
    answer.push(clone[0])
  }
  const abs = answer.map((v) =&gt; Math.abs(v))
  return Math.max(...abs)
}</code></pre>
<h1 id="💡-solution">💡 Solution</h1>
<pre><code class="language-javascript">const getPermutations = function (arr, selectNumber) {
  const results = []
  if (selectNumber === 1) return arr.map((value) =&gt; [value])
  arr.forEach((fixed, index, origin) =&gt; {
    const rest = [...origin.slice(0, index), ...origin.slice(index + 1)]
    const permutations = getPermutations(rest, selectNumber - 1)
    const attached = permutations.map((permutation) =&gt; [fixed, ...permutation])
    results.push(...attached)
  })
  return results
}

function solution(exp) {
  const spread = []
  const expLen = exp.length
  for (let i = 0, j = 0; i &lt; expLen; i++) {
    if (Number.isInteger(Number(exp[i]))) {
      spread[j] = (spread[j] || &#39;&#39;) + exp[i]
    } else {
      spread.push(exp[i])
      j += 2
    }
  }
  const types = [&#39;-&#39;, &#39;+&#39;, &#39;*&#39;]
  const perm = getPermutations(types, 3)
  const answer = []
  let ans = 0

  for (let i = 0; i &lt; perm.length; i++) {
    const clone = spread.slice()
    for (let j = 0; j &lt; perm[i].length; j++) {
      for (let k = 0; k &lt; clone.length; k++) {
        if (perm[i][j] === clone[k]) {
          switch (perm[i][j]) {
            case &#39;-&#39;:
              ans = Number(clone[k - 1]) - Number(clone[k + 1])
              break
            case &#39;+&#39;:
              ans = Number(clone[k - 1]) + Number(clone[k + 1])
              break
            case &#39;*&#39;:
              ans = Number(clone[k - 1]) * Number(clone[k + 1])
              break
          }
          clone.splice(k - 1, 3, ans)
          k--
        }
      }
    }
    answer.push(clone[0])
  }
  const abs = answer.map((v) =&gt; Math.abs(v))
  return Math.max(...abs)
}
</code></pre>
<h1 id="👨👦👦-others">👨‍👦‍👦 Others</h1>
<pre><code class="language-js"></code></pre>
<h1 id="👨🏻💻💭-self-feedback">👨🏻‍💻💭 Self Feedback</h1>
<blockquote>
</blockquote>
<hr>
<ul>
<li>2021.10.05 - 최초 작성</li>
</ul>
<hr>
<p><code>댓글 환영</code> <code>질문 환영</code>
<code>by.protect-me</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Favicon | 파비콘]]></title>
            <link>https://velog.io/@protect-me/Favicon-%ED%8C%8C%EB%B9%84%EC%BD%98</link>
            <guid>https://velog.io/@protect-me/Favicon-%ED%8C%8C%EB%B9%84%EC%BD%98</guid>
            <pubDate>Mon, 04 Oct 2021 18:27:42 GMT</pubDate>
            <description><![CDATA[<h2 id="circle-crop-with-mac">Circle Crop with Mac</h2>
<p><a href="https://lovesome-gunbbang.tistory.com/194">맥에서 사진을 원으로 자르기 by.시골아빠</a></p>
<hr>
<h2 id="favicon-generator">FavIcon Generator</h2>
<ol>
<li><p>아래 링크 접속
<a href="http://tools.dynamicdrive.com/favicon/">http://tools.dynamicdrive.com/favicon/</a></p>
</li>
<li><p>파일 선택</p>
</li>
<li><p>Optional &gt;  Merge with a 32x32 desktop icon 체크</p>
</li>
<li><p>Download FavIcon
<img src="https://images.velog.io/images/protect-me/post/399b64fc-1c72-45d5-b1d4-266f9a2a66ca/image.png" alt=""></p>
</li>
<li><p>프로젝트에 파일 추가 
ex) vue project =&gt; public 폴더</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎾 vue-tennis | 서울 테니스 클럽 웹앱 : Project Summary]]></title>
            <link>https://velog.io/@protect-me/vue-tennis-summary</link>
            <guid>https://velog.io/@protect-me/vue-tennis-summary</guid>
            <pubDate>Mon, 04 Oct 2021 14:59:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="🎾-서울-테니스-클럽-웹앱">🎾 서울 테니스 클럽 웹앱</h3>
</blockquote>
<h4 id="주변-테니스장-찾기--게스트-모집-플랫폼-웹앱-개발">주변 테니스장 찾기 &amp; 게스트 모집 플랫폼 웹앱 개발</h4>
<p><strong>Link</strong> : <a href="https://tennis9in.netlify.app">https://tennis9in.netlify.app</a>
<strong>Github</strong> : <a href="https://github.com/protect-me/vue-tennis">https://github.com/protect-me/vue-tennis</a>
<strong>Project Summary</strong> : <a href="https://velog.io/@protect-me/vue-tennis-summary">https://velog.io/@protect-me/vue-tennis-summary</a> (<code>Current</code>)
<strong>Development Log</strong> : <a href="https://velog.io/@protect-me/vue-tennis-log">https://velog.io/@protect-me/vue-tennis-log</a></p>
<br>
<br>

<h2 id="💻-구현-화면">💻 구현 화면</h2>
<h3 id="홈-화면-마이페이지">홈 화면, 마이페이지</h3>
<p><img src="https://images.velog.io/images/protect-me/post/8eb9d978-3e27-49f9-8846-6ab3a2f93d27/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="테니스-지도--상세페이지--게스트-모집">테니스 지도 | 상세페이지 | 게스트 모집</h3>
<p><img src="https://images.velog.io/images/protect-me/post/7c8a8403-5a1f-474f-af50-f287e8491456/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="참가-신청취소--게스트-영입방출--알림">참가 신청/취소 + 게스트 영입/방출 + 알림</h3>
<h3 id="youtube-link-15배속-시청-권장-👈🏻👈🏻👈🏻👈🏻👈🏻"><a href="https://www.youtube.com/watch?v=kzJjKvXBBzg">Youtube Link</a> (1.5배속 시청 권장) 👈🏻👈🏻👈🏻👈🏻👈🏻</h3>
<p><img src="https://images.velog.io/images/protect-me/post/7d70e617-29a5-4e85-848b-f5a837ebba5c/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="테니스장-리스트--신규-코트-등록--테니스장-상세-정보">테니스장 리스트 | 신규 코트 등록 | 테니스장 상세 정보</h3>
<p><img src="https://images.velog.io/images/protect-me/post/5d1bd46f-1d31-40cc-902c-604c8b79cc0a/image.png" alt=""></p>
<hr>
<br>
<br>

<h3 id="신규-게스트-모집-등록">신규 게스트 모집 등록</h3>
<p><img src="https://images.velog.io/images/protect-me/post/10b32be9-2aab-4baf-93ea-218255980717/image.png" alt=""></p>
<hr>
<br>
<br>


<h3 id="마이페이지-회원-정보-수정">마이페이지, 회원 정보 수정</h3>
<p><img src="https://images.velog.io/images/protect-me/post/d33e3135-4c9a-43df-9099-7d3779418e15/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-05%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.48.14.png" alt=""></p>
<hr>
<br>
<br>






<h3 id=""></h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[포트폴리오 ]]></title>
            <link>https://velog.io/@protect-me/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4</link>
            <guid>https://velog.io/@protect-me/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4</guid>
            <pubDate>Tue, 28 Sep 2021 17:50:05 GMT</pubDate>
            <description><![CDATA[<h2 id="👨🏻💻-github-profileprotect-me">👨🏻‍💻 Github Profile(protect-me)</h2>
<hr>
<ul>
<li><a href="https://github.com/protect-me">https://github.com/protect-me</a></li>
</ul>
<br>
<br>
<br>

<h2 id="🐟-kim-3-meals">🐟 kim-3-meals</h2>
<ul>
<li>유튜브 김사원세끼 채널 맛집 지도 웹사이트 구축(<a href="https://www.youtube.com/channel/UC-x55HF1-IilhxZOzwJm7JA">유튜브 김사원세끼 채널</a>)</li>
</ul>
<hr>
<h3 id="1-specs">1. Specs</h3>
<ul>
<li><code>Vue.js : v3</code> + <code>Firebase</code> + <code>Bootstrap</code> + <code>Netlify : Deploy</code><h3 id="2-link--👈🏻">2. Link  👈🏻</h3>
</li>
<li><a href="https://kim3meals.com/">https://kim3meals.com/</a> (link1)</li>
<li><a href="https://kim3meals.netlify.app/">https://kim3meals.netlify.app/</a> (link2)</li>
<li>각종 포털에서 <code>ㄱㅅㅇㅅㄲ</code> 초성 검색으로 접근 가능<h3 id="3-github">3. Github</h3>
</li>
<li><a href="https://github.com/protect-me/kim-3-meals">https://github.com/protect-me/kim-3-meals</a><h3 id="4-project-summary-👈🏻">4. Project Summary 👈🏻</h3>
</li>
<li><a href="https://velog.io/@protect-me/kim-3-meals-summary">https://velog.io/@protect-me/kim-3-meals-summary</a><h3 id="5-developement-log">5. Developement Log</h3>
</li>
<li><a href="https://velog.io/@protect-me/kim-3-meals-log">https://velog.io/@protect-me/kim-3-meals-log</a></li>
</ul>
<br>
<br>
<br>


<h2 id="🎾-vue-tennis">🎾 vue-tennis</h2>
<ul>
<li>주변 테니스장 찾기 &amp; 게스트 모집 플랫폼 웹앱 개발</li>
</ul>
<hr>
<h3 id="1-specs-1">1. Specs</h3>
<ul>
<li><code>Vue.js : v2</code> + <code>Firebase</code> + <code>Vuetify</code> + <code>Netlify : Deploy</code><h3 id="2-link--👈🏻-1">2. Link  👈🏻</h3>
</li>
<li><a href="https://tennis9in.netlify.app">https://tennis9in.netlify.app</a> (모바일 화면 권장)<h3 id="3-github-1">3. Github</h3>
</li>
<li><a href="https://github.com/protect-me/vue-tennis">https://github.com/protect-me/vue-tennis</a><h3 id="4-project-summary--👈🏻">4. Project Summary  👈🏻</h3>
</li>
<li><a href="https://velog.io/@protect-me/vue-tennis-summary">https://velog.io/@protect-me/vue-tennis-summary</a><h3 id="5-developement-log-1">5. Developement Log</h3>
</li>
<li><a href="https://velog.io/@protect-me/vue-tennis-log">https://velog.io/@protect-me/vue-tennis-log</a></li>
</ul>
<br>
<br>
<br>


<h2 id="💪🏿-gain-muscle">💪🏿 gain-muscle</h2>
<ul>
<li>근력 운동을 계획·기록하고 점진적 과부하를 돕는 웹앱 개발</li>
</ul>
<hr>
<h3 id="1-specs-2">1. Specs</h3>
<ul>
<li><code>Vue.js : v2</code> + <code>MySQL</code> + <code>Vuetify</code> + <code>Node.js(Experss.js)</code><h3 id="2-link--👈🏻-2">2. Link  👈🏻</h3>
</li>
<li><a href="https://www.youtube.com/watch?v=pZJsCdUcpqM&amp;list=PL0CXk5IHCZYIMJpEFfo2s9WudGBJwHnuH">시연 영상 Youtube (2배속 시청 권장)</a> (배포 작업중)<h3 id="3-github-2">3. Github</h3>
</li>
<li><a href="https://github.com/protect-me/gm-front">https://github.com/protect-me/gm-front</a> (front)</li>
<li><a href="https://github.com/protect-me/gm-back">https://github.com/protect-me/gm-back</a> (back)<h3 id="4-project-summary">4. Project Summary</h3>
</li>
<li>-<h3 id="5-developement-log-2">5. Developement Log</h3>
</li>
<li><a href="https://velog.io/@protect-me/gain-muscle">https://velog.io/@protect-me/gain-muscle</a></li>
</ul>
<br>
<br>
<br>


<hr>
<hr>
<p>Photo by <a href="https://unsplash.com/@alesnesetril?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ales Nesetril</a> on <a href="https://unsplash.com/s/photos/portfolio?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🐟 kim3meals | 유튜브 김사원세끼 맛집 지도 웹 : Project Sammary]]></title>
            <link>https://velog.io/@protect-me/kim-3-meals-summary</link>
            <guid>https://velog.io/@protect-me/kim-3-meals-summary</guid>
            <pubDate>Tue, 28 Sep 2021 06:14:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h3 id="🐟-유튜브-김사원세끼-맛집-지도-웹">🐟 유튜브 김사원세끼 맛집 지도 웹</h3>
<p><a href="https://kim3meals.com/">https://kim3meals.com/</a> (link1)
<a href="https://kim3meals.netlify.app/">https://kim3meals.netlify.app/</a> (link2)
각종 포털에서 <code>ㄱㅅㅇㅅㄲ</code> 초성 검색으로 접근 가능</p>
<br>
<br>
<br>


<h2 id="📲-누적-방문자-수">📲 누적 방문자 수</h2>
<p>*누적 방문자 수 산정 기준: 1시간 이내 접속 기록이 없는 IP의 경우 Count</p>
<hr>
<ul>
<li><p><strong>프로젝트 오픈 1일차 (2021.08.20)
누적 방문자 수: 1만 8천여 명</strong></p>
</li>
<li><p><strong>프로젝트 오픈 5일차 (2021.08.24)
누적 방문자 수: 2만 5천여 명</strong></p>
</li>
<li><p><strong>프로젝트 오픈 40일차(2021.09.10)
누적 방문자 수: 7만 1천여 명</strong></p>
</li>
</ul>
<h3 id="일-평균-방문자-수--약-1775명-🤭"><strong>일 평균 방문자 수 : 약 1,775명 🤭</strong></h3>
<br>
<br>
<br>

<h2 id="📈-firestore-사용량-기록">📈 Firestore 사용량 기록</h2>
<hr>
<ul>
<li><p>2021.08.19~2021.08.20
<img src="https://images.velog.io/images/protect-me/post/74e93732-0a7e-46de-ba19-49bda26a895c/image.png" alt=""></p>
</li>
<li><p>2021.08.19~2021.09.06
<img src="https://images.velog.io/images/protect-me/post/a9ed6128-c318-420e-a1ce-3b963f2e15ec/image.png" alt=""></p>
</li>
<li><p>2021.09.04~2021.10.03
<img src="https://images.velog.io/images/protect-me/post/7b750f00-10f7-4894-b865-20597862bb17/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.21.04.png" alt=""></p>
</li>
</ul>
<br>
<br>
<br>

<h2 id="💻-구현-화면">💻 구현 화면</h2>
<ul>
<li><p>검색 화면(지도)
<img src="https://images.velog.io/images/protect-me/post/b0e09d14-c3a3-4225-bfca-766b5d936315/image.png" alt=""></p>
</li>
<li><p>검색 화면(카드)
<img src="https://images.velog.io/images/protect-me/post/3b9eb6ab-f516-48d6-9ace-cf641476cf6d/image.png" alt=""></p>
</li>
<li><p>출장 요청 화면
<img src="https://images.velog.io/images/protect-me/post/3c40c739-15c9-4cdc-a20f-64c6a91423d6/image.png" alt=""></p>
</li>
<li><p>출장 요청 화면(주소 검색)
<img src="https://images.velog.io/images/protect-me/post/d0ca28e0-5607-4360-b141-47427220382e/image.png" alt=""></p>
</li>
<li><p>관리자 페이지 리스트
<img src="https://images.velog.io/images/protect-me/post/b414cc35-bd8a-4f32-b0ec-a85af9f7d51f/image.png" alt=""></p>
</li>
<li><p>관리자 페이지 신규 상호 등록
<img src="https://images.velog.io/images/protect-me/post/443b6ac7-addb-44fc-b9c2-e2b877a8119b/image.png" alt=""></p>
</li>
<li><p>관리자 페이지 기존 상호 수정
<img src="https://images.velog.io/images/protect-me/post/b1cf45c0-9a5f-47e8-94f9-86a6132ee71b/image.png" alt=""></p>
</li>
</ul>
<br>
<br>
<br>


<h2 id="🔔-유튜브-소개">🔔 유튜브 소개</h2>
<h3 id="커뮤니티-소개link">커뮤니티 소개(<a href="https://www.youtube.com/post/UgyV3CyJslrhZpFTLcd4AaABCQ">Link</a>)</h3>
<ul>
<li>좋아요 2.2천 + 댓글 281개
<img src="https://images.velog.io/images/protect-me/post/b9189d4a-1f76-4700-ba15-f324632fc09f/image.png" alt=""><img src="https://images.velog.io/images/protect-me/post/e425944e-3dfa-4c37-9bc0-a284f884f11a/image.png" alt=""></li>
</ul>
<h3 id="동영상-소개link">동영상 소개(<a href="https://www.youtube.com/watch?v=r8sx4inP96s">Link</a>)</h3>
<ul>
<li><img src="https://images.velog.io/images/protect-me/post/1d564a16-45c4-45db-af1a-6fcf15108b4b/image.png" alt=""></li>
</ul>
<br>
<br>
<br>


<h2 id="✍🏻-후기">✍🏻 후기</h2>
<h3 id="발단">발단</h3>
<ul>
<li>맛집 소개를 해주는 유튜브는 많지만, 정작 밖에서 내 주변의 맛집을 찾기는 힘들다는 포인트</li>
<li>실생활의 작은 불편함에 시작된 토이프로젝트가 유튜버 김사원세끼님과 만나며 폭발적인 반응을 이끌어냄</li>
</ul>
<h3 id="발전-가능성">발전 가능성</h3>
<ul>
<li>데이터를 시각화하고 활용성을 높였다는데에 의의가 있었으며, 
타 맛집 유튜버들의 맛집 지도를 통합하는 방식도 고려해볼만 함.</li>
<li>유튜버와 구독자 간의 소통을 조금 더 원활하게 할 수 있도록 맛집 방문 요청 페이지를 추가 제작하였으며, 
차후 이 기능 또한 지도로 표시하여 구독자 맛집 지도를 구현 가능할 수 있음.</li>
</ul>
<h3 id="아쉬운-점">아쉬운 점</h3>
<ul>
<li>웹사이트 오픈 직전, 급하게 캐시를 통한 누적방문자 수, 페이지당 방문자 수를 기록하는 로직을 구현하였으나,
기회가 된다면 <code>SSR</code>을 구현하고, <code>Google Analytics</code>를 추가하여 유입경로 분석 혹은 나아갈 방향을 예측해보고자 함.</li>
</ul>
<br>
<br>

<hr>
<hr>
<br>

<p><strong>🌿아는 것과 실행하는 것의 차이.</strong>
<strong>🌿여러 시행착오를 겪으며 내가 &#39;무엇을 모르는지를 알아가는&#39; 과정.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎾 vue-tennis | 서울 테니스 클럽 웹앱 : Development Log]]></title>
            <link>https://velog.io/@protect-me/vue-tennis-log</link>
            <guid>https://velog.io/@protect-me/vue-tennis-log</guid>
            <pubDate>Fri, 17 Sep 2021 09:57:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="🎾-서울-테니스-클럽-웹앱">🎾 서울 테니스 클럽 웹앱</h3>
</blockquote>
<h4 id="주변-테니스장-찾기--게스트-모집-플랫폼-웹앱-개발">주변 테니스장 찾기 &amp; 게스트 모집 플랫폼 웹앱 개발</h4>
<p><strong>Link</strong> : <a href="https://tennis9in.netlify.app">https://tennis9in.netlify.app</a>
<strong>Github</strong> : <a href="https://github.com/protect-me/vue-tennis">https://github.com/protect-me/vue-tennis</a>
<strong>Project Summary</strong> : <a href="https://velog.io/@protect-me/vue-tennis-summary">https://velog.io/@protect-me/vue-tennis-summary</a>
<strong>Development Log</strong> : <a href="https://velog.io/@protect-me/vue-tennis-log">https://velog.io/@protect-me/vue-tennis-log</a> (<code>Current</code>)</p>
<h2 id="1-프로젝트-환경설정">1. 프로젝트 환경설정</h2>
<h3 id="1-1-프로젝트-생성">1-1. 프로젝트 생성</h3>
<ul>
<li><code>$ vue create vue-tennis</code> : <code>vue-tennis</code>라는 이름으로 vue 프로젝트 생성</li>
</ul>
<pre><code>Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, Lint
er
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in producti
on) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? No</code></pre><h3 id="1-2-경로-이동-및-서버-테스트-실행">1-2. 경로 이동 및 서버 테스트 실행</h3>
<ul>
<li><code>$ cd vue-tennis</code> : 경로 이동</li>
<li><code>$ yarn serve</code> : 서버 실행</li>
<li><a href="http://localhost:8080/">http://localhost:8080/</a> : LOCAL URL</li>
<li><code>$ Control + C</code> : 서버 내리기</li>
<li><code>editor(VScode)</code>를 해당 경로로 다시 열기</li>
</ul>
<h3 id="1-3-vuetify-설치">1-3. Vuetify 설치</h3>
<p><code>$ vue add vuetify</code>
(해당 프로젝트 내부에서 명령어 입력)</p>
<pre><code>? Choose a preset: Configure (advanced)
? Use a pre-made template? (will replace App.vue and HelloWorld.vue) Yes
? Use custom theme? No
? Use custom properties (CSS variables)? No
? Select icon font Material Design Icons
? Use fonts as a dependency (for Electron or offline)? No
? Use a-la-carte components? Yes
? Select locale English</code></pre><h3 id="1-4-gitignore-파일-수정">1-4. .gitignore 파일 수정</h3>
<pre><code>.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules</code></pre><hr>
<h2 id="2-firebase-설정">2. Firebase 설정</h2>
<blockquote>
<p><a href="https://console.firebase.google.com/u/0/">Firebase</a></p>
</blockquote>
<ul>
<li>firebase 구글 로그인</li>
<li>프로젝트 추가</li>
</ul>
<h3 id="authentication">Authentication</h3>
<ul>
<li>이메일/비밀번호: 사용 설정</li>
<li>Google: 사용 설정</li>
</ul>
<h3 id="firestore-database">Firestore Database</h3>
<ul>
<li>Database에 적합</li>
<li>Cloud Firestore의 보안 규칙: 테스트 모드</li>
<li>Cloud Firestore 위치 설정: <code>asia-northeast3</code>(Seoul)</li>
</ul>
<h3 id="realtime-database">Realtime Database</h3>
<ul>
<li>설정값에 적합</li>
<li>미국으로 설정(차후 가까운 지역이 나오면 그쪽으로 설정)</li>
<li>테스트모드로 설정</li>
</ul>
<h3 id="프로젝트-설정">프로젝트 설정</h3>
<ul>
<li>일반 &gt; 내 프로젝트 &gt;공개 설정 &gt; 지원 이메일 설정</li>
<li>일반 &gt; 내 앱 &gt; 플랫폼 선택: 웹(<code>&lt;/&gt;</code>)
: 웹 앱에 Firebase 추가 &gt; 앱 등록</li>
</ul>
<h3 id="firebase-설치">Firebase 설치</h3>
<ul>
<li><p><code>$ Firebase login</code></p>
</li>
<li><p><code>$ Firebase init</code></p>
<pre><code>firebase init

   ######## #### ########  ######## ########     ###     ######  ########
   ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
   ######    ##  ########  ######   ########  #########  ######  ######
   ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
   ##       #### ##     ## ######## ########  ##     ##  ######  ########
</code></pre></li>
</ul>
<p>You&#39;re about to initialize a Firebase project in this directory:</p>
<p>  /Users/***/Documents/Develope/vue-tennis</p>
<p>? Which Firebase CLI features do you want to set up for this folder? Press Space to select 
features, then Enter to confirm your choices. Database: Configure Firebase Realtime Databas
e and deploy rules, Firestore: Deploy rules and create indexes for Firestore, Functions: Co
nfigure and deploy Cloud Functions, Hosting: Configure and deploy Firebase Hosting sites, S
torage: Deploy Cloud Storage security rules</p>
<p>=== Project Setup</p>
<p>First, let&#39;s associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we&#39;ll just set up a default project.</p>
<p>? Please select an option: Use an existing project
? Select a default Firebase project for this directory: vue-tennis (vue-tennis)
i  Using project vue-tennis (vue-tennis)</p>
<p>=== Database Setup
i  database: ensuring required API firebasedatabase.googleapis.com is enabled...
✔  database: required API firebasedatabase.googleapis.com is enabled</p>
<p>Firebase Realtime Database Security Rules allow you to define how your data should be
structured and when your data can be read from and written to.</p>
<p>? What file should be used for Realtime Database Security Rules? database.rules.json
✔  Database Rules for vue-tennis-default-rtdb have been written to database.rules.json.
Future modifications to database.rules.json will update Realtime Database Security Rules when you run
firebase deploy.</p>
<p>=== Firestore Setup</p>
<p>Firestore Security Rules allow you to define how and when to allow
requests. You can keep these rules in your project directory
and publish them with firebase deploy.</p>
<p>? What file should be used for Firestore Rules? firestore.rules</p>
<p>Firestore indexes allow you to perform complex queries while
maintaining performance that scales with the size of the result
set. You can keep index definitions in your project directory
and publish them with firebase deploy.</p>
<p>? What file should be used for Firestore indexes? firestore.indexes.json</p>
<p>=== Functions Setup</p>
<p>A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.</p>
<p>? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
✔  Wrote functions/package.json
✔  Wrote functions/index.js
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes</p>
<blockquote>
<p><a href="mailto:protobufjs@6.11.2">protobufjs@6.11.2</a> postinstall /Users/***/Documents/Develope/vue-tennis/functions/node_modules/protobufjs
node scripts/postinstall</p>
</blockquote>
<p>npm notice created a lockfile as package-lock.json. You should commit this file.
added 227 packages from 204 contributors and audited 227 packages in 6.657s</p>
<p>8 packages are looking for funding
  run <code>npm fund</code> for details</p>
<p>found 0 vulnerabilities</p>
<p>=== Hosting Setup</p>
<p>Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build&#39;s output directory.</p>
<p>? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No
✔  Wrote dist/index.html</p>
<p>=== Storage Setup</p>
<p>Firebase Storage Security Rules allow you to define how and when to allow
uploads and downloads. You can keep these rules in your project directory
and publish them with firebase deploy.</p>
<p>? What file should be used for Storage Rules? storage.rules</p>
<p>i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...</p>
<p>✔  Firebase initialization complete!</p>
<pre><code>
- `$ yarn add firebase@8.2.3`

&gt; 버전별로 import하는 방식이 다르게 나와서 꽤나 혼란스러움.
`v8`로 설치 시, 본 블로그 글의 내용과 같이 `import`가 가능하나,
`v7` 이하 혹은 `v9` 이상에서는
`import * as firebase from &#39;firebase/app&#39;`과 같이 `import`
=&gt; 자세한 사항은 [firebase Document](https://firebase.google.com/docs/web/setup?authuser=0) 참고


### firebase 연결

#### main.js
```js
import Vue from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;
import store from &#39;./store&#39;
import vuetify from &#39;./plugins/vuetify&#39;
import &#39;./plugins/firebase.js&#39; // plugin 설정

Vue.config.productionTip = false

new Vue({
  router,
  store,
  vuetify,
  render: (h) =&gt; h(App),
}).$mount(&#39;#app&#39;)</code></pre><h3 id="pluginsfirebasejs">plugins/firebase.js</h3>
<pre><code class="language-js">import Vue from &#39;vue&#39;
import firebase from &#39;firebase/app&#39;
import firebaseConfig from &#39;../../firebaseConfig&#39;
import &#39;firebase/auth&#39;
import &#39;firebase/firebase-firestore&#39;

firebase.initializeApp(firebaseConfig)
Vue.prototype.$firebase = firebase</code></pre>
<h3 id="appjs">App.js</h3>
<pre><code class="language-js">&lt;script&gt; 
  (...)
  mounted() {
    console.log(&#39;here&#39;, this.$firebase)
  },
  (...)</code></pre>
<h3 id="env-설정">.env 설정</h3>
<p><code>$ npm install dotenv</code></p>
<ul>
<li><code>.env</code> 파일 생성
: <code>firebaseConfig.js</code>의 내용을 기입<pre><code class="language-js">VUE_APP_apiKey=&#39;&#39;
VUE_APP_authDomain=&#39;&#39;
VUE_APP_databaseURL=&#39;&#39;
VUE_APP_projectId=&#39;&#39;
VUE_APP_storageBucket=&#39;&#39;
VUE_APP_messagingSenderId=&#39;&#39;
VUE_APP_appId=&#39;&#39;
VUE_APP_measurementId=&#39;&#39;</code></pre>
</li>
<li><code>firebaseConfig.js</code> 내용 수정<pre><code class="language-js">export default {
apiKey: process.env.VUE_APP_apiKey,
authDomain: process.env.VUE_APP_authDomain,
databaseURL: process.env.VUE_APP_databaseURL,
projectId: process.env.VUE_APP_projectId,
storageBucket: process.env.VUE_APP_storageBucket,
messagingSenderId: process.env.VUE_APP_messagingSenderId,
appId: process.env.VUE_APP_appId,
measurementId: process.env.VUE_APP_measurementId,
}</code></pre>
</li>
</ul>
<h3 id="gitignore-추가">.gitignore 추가</h3>
<pre><code class="language-js">.env</code></pre>
<hr>
<h2 id="3-netlify-배포">3. Netlify 배포</h2>
<h3 id="netlify-deploy">Netlify deploy</h3>
<p><code>New site from Git</code> &gt; <code>Github</code> &gt; 로그인 및 권한 설정 &gt; 해당 <code>repository</code> 선택 &gt; 기본 설정 유지, <code>Deploy site</code></p>
<h3 id="환경변수-설정">환경변수 설정</h3>
<p><code>site Settings</code> &gt; <code>Build &amp; deploy</code> &gt; <code>Environment</code> &gt; <code>Environment variables</code> &gt; <code>.env</code>의 환경변수 기입</p>
<h3 id="functions-bundling-error">functions bundling error</h3>
<pre><code class="language-js">2. Functions bundling                                         
11:16:27 PM: ────────────────────────────────────────────────────────────────
11:16:27 PM: ​
11:16:27 PM: Packaging Functions from functions directory:
11:16:27 PM:  - index.js
11:16:27 PM: ​
11:16:27 PM: ​
11:16:27 PM: ────────────────────────────────────────────────────────────────
11:16:27 PM:   Dependencies installation error                               
11:16:27 PM: ────────────────────────────────────────────────────────────────
11:16:27 PM: ​
11:16:27 PM:   Error message
11:16:27 PM:   A Netlify Function is using &quot;firebase-functions&quot; but that dependency has not been installed yet.</code></pre>
<blockquote>
<p>위와 같은 에러를 만난 경우</p>
</blockquote>
<ul>
<li>원인 : Netlify와 firebase의 각각의 funtions가 있는데, 
Netlify가 배포할 때 fireabase의 funtions 폴더를 찾아들어가서 함수를 찾기 때문에 에러가 발생.</li>
<li>해결 : 최상위 경로에 <code>netlify.toml</code> 파일 생성 후 아래와 같이 작성<pre><code class="language-js">[build]
functions=&quot;NetlifyFunctions&quot;</code></pre>
즉, Netlify 배포 시, netlify의 funtions는 NetlifyFunctions 경로에 들어가서 찾도록 설정함.
물론 폴더가 없거나 만들더라도 빈 폴더이기 때문에 아래와 같은 log를 남기고 넘어갈 수 있음
(추가로 netlify funtions를 작성한 경우 제외)
<code>The Netlify Functions setting targets a non-existing directory: NetlifyFunctions</code></li>
</ul>
<h2 id="4-kakaomap-설정">4. KakaoMap 설정</h2>
<p><a href="https://developers.kakao.com/console/app">kakao developers</a></p>
<h3 id="앱-키-설정">앱 키 설정</h3>
<ul>
<li><p>애플리케이션 추가 &gt; 앱 키 확인
: REST API 키
: JavaScript 키</p>
</li>
<li><p><code>.env</code> 파일에 내용 추가</p>
<pre><code class="language-js">VUE_APP_KAKAO_ADDRESS_REST_API_KEY=&#39;&#39;
VUE_APP_KAKAO_MAP_JAVASCRIPT_KEY=&#39;&#39;</code></pre>
</li>
</ul>
<h3 id="플랫폼-설정">플랫폼 설정</h3>
<p>: 플랫폼 &gt; web &gt; Netlify에서 배포한 주소 추가</p>
<h2 id="error">ERROR</h2>
<h3 id="mixed-content">Mixed Content</h3>
<ul>
<li>Error
<code>Mixed Content: The page at &#39;https://tennis9in.netlify.app/Map&#39; was loaded over HTTPS, but requested an insecure script &#39;http://dapi.kakao.com/v2/maps/sdk.js?autoload=false&amp;appkey=*****&#39;. This request has been blocked;</code></li>
<li>Solve
<code>public &gt; index.html &gt; head</code> 아래 내용 추가
<code>&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;upgrade-insecure-requests&quot;&gt;</code></li>
<li>Ref
<a href="https://wellsw.tistory.com/34">Mixed content 문제 해결(https 사이트에서 http 사이트 요청 시 발생하는 보안 문제)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚛️ React - Web Game(1)]]></title>
            <link>https://velog.io/@protect-me/React-Web-Game1</link>
            <guid>https://velog.io/@protect-me/React-Web-Game1</guid>
            <pubDate>Fri, 17 Sep 2021 05:47:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>참고: <a href="https://www.youtube.com/watch?v=V3QsSrldHqI&amp;list=PLcqDmjxt30RtqbStQqk-eYMK8N-1SYIFn">리액트 무료 강좌(웹게임) ZeroCho TV</a></p>
</blockquote>
<h2 id="1-리액트를-왜-쓰는가">1. 리액트를 왜 쓰는가</h2>
<ol>
<li>사용자 경험</li>
<li>재사용 컴포넌트</li>
<li>데이터-화면 일치</li>
<li>유지보수에 용이</li>
</ol>
<h2 id="2-첫-리액트-컴포넌트">2. 첫 리액트 컴포넌트</h2>
<p><code>react</code>: react가 동작하는데 필요한 코드
<code>react-dom</code>: react코드를 DOM에 붙여주는 역할</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Quora Clone]]></title>
            <link>https://velog.io/@protect-me/React-Quora-Clone</link>
            <guid>https://velog.io/@protect-me/React-Quora-Clone</guid>
            <pubDate>Fri, 17 Sep 2021 05:08:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.youtube.com/watch?v=j3-4QTcAcNk&amp;list=PLX8nUGGurseFyyElSCTjU01iFC47DOran&amp;index=1">[React 강의] 리액트(react)로 Quora-Clone 웹사이트 만들기 by.리액트 깎는 노인</a></p>
</blockquote>
<ul>
<li><p>react app 설치(+ redux)
<code>$ npx create-react-app quora-clone --template redux</code></p>
</li>
<li><p>material icon 설치
<code>$ npm install @material-ui/core</code>
<code>$ npm install @material-ui/icons</code></p>
</li>
<li><p><a href="https://abcdqbbq.tistory.com/9">reset css</a>
=&gt; index.css 상단</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Hooks & Context]]></title>
            <link>https://velog.io/@protect-me/React-Hooks-Context</link>
            <guid>https://velog.io/@protect-me/React-Hooks-Context</guid>
            <pubDate>Thu, 16 Sep 2021 06:56:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>참고</strong> <a href="https://slides.com/woongjae/react2021">https://slides.com/woongjae/react2021</a></p>
</blockquote>
<h2 id="1-basic-hooks">1. Basic Hooks</h2>
<h3 id="1-1-usestate">1-1. useState</h3>
<ul>
<li>state를 대체할 수 있음</li>
</ul>
<blockquote>
<p>기존에는 state를 변경하려면 class 문법을 통해서만 가능했고, functional 컴포너늩에서는 구현이 불가능했지만, hook이 새롭게 업데이트 되면서 functional 컴포넌트에서도 state를 가질 수 있게 되었다.</p>
</blockquote>
<h4 id="1-1-1-class-구현기존">1-1-1. class 구현(기존)</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;

export default class Example2 extends React.Component {
  state = {
    count: 0,
  }
  render() {
    const { count } = this.state
    return (
      &lt;div&gt;
        &lt;p&gt;You Clicked {count} times&lt;/p&gt;
        &lt;button onClick={this.click}&gt;BTN&lt;/button&gt;
      &lt;/div&gt;
    )
  }
  click = () =&gt; {
    this.setState({
      count: this.state.count + 1,
    })
  }
}</code></pre>
<h4 id="1-1-2-functional-구현1">1-1-2. functional 구현(1)</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;

export default function Example2() {
  const [count, setCount] = React.useState(0)

  return (
    &lt;div&gt;
      &lt;p&gt;You Clicked {count} times&lt;/p&gt;
      &lt;button onClick={click}&gt;BTN&lt;/button&gt;
    &lt;/div&gt;
  )

  function click() {
    setCount(count + 1)
  }
}</code></pre>
<h4 id="1-1-3-functional-구현2">1-1-3. functional 구현(2)</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;

// (기존) useState =&gt; count
// (수정) useState =&gt; {count: 0}
export default function Example3() {
  const [state, setState] = React.useState({ count: 0 })

  return (
    &lt;div&gt;
      &lt;p&gt;You Clicked {state.count} times&lt;/p&gt;
      &lt;button onClick={click}&gt;BTN&lt;/button&gt;
    &lt;/div&gt;
  )

  // function click() {
  //   setState({ count: state.count + 1 })
  // }
  function click() {
    setState((state) =&gt; {
      return {
        count: state.count + 1,
      }
    })
  }
}</code></pre>
<blockquote>
</blockquote>
<p><code>state hook</code>이 나오면서 ...</p>
<ul>
<li>(기존) 
: <code>Functional Component</code> = <code>Stateless Component</code> = <code>Stateless Functional Component</code></li>
<li>(현재)
: <code>Functional Compoent</code> !== <code>Stateless Component</code></li>
</ul>
<blockquote>
</blockquote>
<ul>
<li>컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어려움
: 컨테이너 방식 말고 상태에 관련된 로직</li>
<li>복잡한 컴포넌트들은 이해하기 어려움</li>
<li>Class는 사람과 기계를 혼동시킴
: 컴파일 단계에서 코드 최적화가 어려워짐</li>
<li><code>this.state</code>는 로직에서 레퍼런스를 공유하기 때문에 문제 발생 가능성이 있음
: functional 컴포넌트는 <code>this.state</code>를 공유하지 않음
: <strong>따라서, 어떠한 것이 맞다 틀리다가 아니라, 상황에 맞게 class 혹은 function을 적절하게 활용해야함</strong></li>
</ul>
<h3 id="1-2-useeffect">1-2. useEffect</h3>
<ul>
<li>라이프 사이클 훅을 대체할 수 있음
: componentDidMount
: componentDidUpdate
: componentWillUnmount<pre><code class="language-js">// useEffect
import React from &#39;react&#39;
</code></pre>
</li>
</ul>
<p>export default function Example5() {
  const [count, setCount] = React.useState(0)</p>
<p>  // React.useEffect(() =&gt; {
  //   console.log(&#39;componentDidMount &amp;&amp; componentDidUpdate&#39;, count)
  // })
  React.useEffect(() =&gt; {
    console.log(&#39;componentDidMount&#39;)</p>
<pre><code>return () =&gt; {
  // cleanup
  // componentWillUnmount
}</code></pre><p>  }, [])</p>
<p>  React.useEffect(() =&gt; {
    console.log(&#39;componentDidMount &amp;&amp; componentDidUpdate by count&#39;, count)
    return () =&gt; {
      // cleanup
      console.log(&#39;cleanup by count&#39; count)
    }
  }, [count])</p>
<p>  return (
    <div>
      <p>You Clicked {count} times</p>
      <button onClick={click}>BTN</button>
    </div>
  )</p>
<p>  function click() {
    setCount(count + 1)
  }
}</p>
<pre><code>&gt; [[번역] useEffect 완벽 가이드 | rinae&#39;s devlog](https://rinae.dev/posts/a-complete-guide-to-useeffect-ko)


### 1-3. userContext
`6. Context API` 에서 다룸

---
## 2. Custom Hooks
```js
// useWindowWidth.js
import { useState, useEffect } from &#39;react&#39;

export default function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth)
  useEffect(() =&gt; {
    const resize = () =&gt; {
      setWidth(window.innerWidth)
    }
    window.addEventListener(&#39;resize&#39;, resize)
    return () =&gt; {
      window.removeEventListener(&#39;resize&#39;, resize)
    }
  }, [])

  return width
}</code></pre><h3 id="usehasmounted-vs-widthhasmounted">useHasMounted vs widthHasMounted</h3>
<p>(...)</p>
<h2 id="3-additional-hooks">3. Additional Hooks</h2>
<h2 id="4-react-router-hooks">4. React Router Hooks</h2>
<h2 id="5-컴포넌트-간-통신">5. 컴포넌트 간 통신</h2>
<h2 id="6-context-api">6. Context API</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 실전 활용]]></title>
            <link>https://velog.io/@protect-me/React-%EC%8B%A4%EC%A0%84-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@protect-me/React-%EC%8B%A4%EC%A0%84-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Thu, 16 Sep 2021 06:22:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>참고</strong> <a href="https://slides.com/woongjae/react2021">https://slides.com/woongjae/react2021</a></p>
</blockquote>
<h2 id="1-hoc--higher-order-component">1. HOC | Higher Order Component</h2>
<ul>
<li>리액트에서 컴포넌트 안에 있는 로직을 재사용할 수 있는 진보된 기술</li>
<li>최근에는 <code>Hook</code>을 더 많이 활용하는 추세</li>
<li><code>컴포넌트</code>를 인자로 받아 <code>새로운 컴포넌트</code>를 <code>리턴</code>하는 <code>함수</code></li>
<li>HOC는 앞에 <code>with</code>를 붙이는 경우가 많음</li>
<li>횡단 관심사
ex) 로그인이 되어있는지 확인하는 로직
ex) 로그 수집</li>
<li>컴포지션</li>
</ul>
<p><img src="https://images.velog.io/images/protect-me/post/2bdeaeab-f703-41c3-bc38-855e3890e319/image.png" alt=""></p>
<blockquote>
<p><strong>주의</strong></p>
</blockquote>
<ul>
<li>render method 안에 HOC를 사용하지말자
: render될 때마다 새로운 컴포넌트를 만들기 때문</li>
<li>static method는 카피해서 써라(Feat. hoist-non-react-static)</li>
<li>refs 통과시킬 수 없음(feat. React.forwardRef)</li>
</ul>
<h2 id="2-controlled-component-uncontrolled-component">2. Controlled Component, Uncontrolled Component</h2>
<blockquote>
<ul>
<li><strong>상태를 가지고 있는 엘리먼트</strong>
: input, select, textarea, ...</li>
</ul>
</blockquote>
<ul>
<li><strong>Controlled</strong>
: 엘리먼트를 가지고 있는 컴포넌트가 관리</li>
<li><strong>Uncontrolled</strong>
: 엘리먼트의 상태를 관리하지 않고, 엘리먼트의 참조만 컴포넌트가 소유</li>
</ul>
<h3 id="2-1-controlled-component">2-1. Controlled Component</h3>
<pre><code class="language-jsx">// ControlledComponent.jsx
import React from &#39;react&#39;

class ControlledComponent extends React.Component {
  state = {
    value: &#39;&#39;,
  }
  render() {
    const { value } = this.state
    return (
      &lt;div&gt;
        &lt;input type=&quot;text&quot; value={value} onChange={this.change} /&gt;
        &lt;button onClick={this.click}&gt;버튼&lt;/button&gt;
      &lt;/div&gt;
    )
  }
  change = (e) =&gt; {
    console.log(e.target.value)
    this.setState({ value: e.target.value })
  }
  click = () =&gt; {
    console.log(this.state.value)
  }
}

export default ControlledComponent</code></pre>
<h3 id="2-2-uncontrolled-component--createref">2-2. Uncontrolled Component | createRef</h3>
<pre><code class="language-jsx">// UncontrolledComponent.jsx
import React from &#39;react&#39;

class UncontrolledComponent extends React.Component {
  // 방법2
  inputRef = React.createRef()
  render() {
    console.log(&#39;initail render&#39;, this.inputRef)
    return (
      &lt;div&gt;
        {/* &lt;input id=&quot;my-input&quot; /&gt; 방법1 */}
        &lt;input ref={this.inputRef} /&gt;
        &lt;button onClick={this.click}&gt;전송&lt;/button&gt;
      &lt;/div&gt;
    )
  }

  componentDidMount() {
    console.log(&#39;componentDidMount&#39;, this.inputRef)
  }

  click = () =&gt; {
    // 목표
    // input 엘리먼트의 현재 상태 값 (value)를 꺼내서 전송함
    // 방법 1 =&gt; real DOM에 직접적인 변경을 가함 =&gt; 지양
    // const input = document.querySelector(&#39;#my-input&#39;)
    // console.log(input.value)
    // 방법 2
    console.log(this.inputRef.current.value)
    // inputRef
  }
}

export default UncontrolledComponent</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Component Styling]]></title>
            <link>https://velog.io/@protect-me/React-Component-Styling</link>
            <guid>https://velog.io/@protect-me/React-Component-Styling</guid>
            <pubDate>Thu, 16 Sep 2021 02:02:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>참고</strong> <a href="https://slides.com/woongjae/react2021">https://slides.com/woongjae/react2021</a></p>
</blockquote>
<h2 id="1-style-loaders">1. Style Loaders</h2>
<p><img src="https://images.velog.io/images/protect-me/post/65c3c268-7cb6-466c-9501-00e0a6a11162/image.png" alt=""></p>
<blockquote>
<p><strong>BEM</strong>: Block, Elment, Modifier
React에서 컴포넌트별로 스코핑이 되지 않기 때문에 BEM 방법론을 채택하기도 함</p>
</blockquote>
<h2 id="2-scss-적용">2. SCSS 적용</h2>
<ol>
<li>App.scss 파일 생성 및 작성</li>
<li><code>$ npm i sass</code></li>
<li>App.js
<code>import &#39;./App.scss&#39;</code></li>
</ol>
<h2 id="3-css-modules">3. CSS Modules</h2>
<ol>
<li>App.js
<code>import styles from &#39;./App.modules.css&#39;</code></li>
<li>src &gt; App.css 생성 및 작성</li>
<li><code>styles</code>가 자동으로 선택자에 해시값을 추가해서 맵핑</li>
<li><code>function App()</code>의 return 값 수정
<code>&lt;div className={styles[&quot;App&quot;]}&gt;&lt;/div&gt; ...</code><blockquote>
<p><strong>전역적으로 오염되지 않은 스코프를 가짐</strong></p>
</blockquote>
</li>
</ol>
<h4 id="button-기본">Button 기본</h4>
<pre><code class="language-js">// Button.jsx
import styles from &#39;./Button.module.css&#39;

const Button = (props) =&gt; &lt;button className={styles[&#39;button&#39;]} {...props} /&gt;

export default Button</code></pre>
<pre><code class="language-js">// Button.module.css
.button {
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
  cursor: pointer;
}</code></pre>
<h3 id="button-로딩-추가">Button 로딩 추가</h3>
<pre><code class="language-js">// Button.module.css
import React from &#39;react&#39;
import styles from &#39;./Button.module.css&#39;

// const Button = (props) =&gt; &lt;button className={styles[&#39;button&#39;]} {...props} /&gt;
class Button extends React.Component {
  state = {
    loading: false,
  }
  render() {
    return (
      &lt;button
        onClick={this.startLoading}
        className={
          this.state.loading
            ? `${styles[&#39;button&#39;]} ${styles[&#39;loading&#39;]}`
            : styles[&#39;button&#39;]
        }
        {...this.props}
      /&gt;
    )
  }
  startLoading = () =&gt; {
    this.setState({
      loading: true,
    })
    setTimeout(() =&gt; {
      this.setState({
        loading: false,
      })
    }, 1000)
  }
}

export default Button</code></pre>
<h3 id="button-로딩-추가---classnames-추가">Button 로딩 추가 - classnames 추가</h3>
<blockquote>
<p><strong>classnames test</strong></p>
</blockquote>
<pre><code class="language-js">import classNames from &#39;classnames&#39;
    console.log(classNames(&#39;foo&#39;, &#39;bar&#39;))
    console.log(classNames(&#39;foo&#39;, &#39;bar&#39;, &#39;baz&#39;))
    console.log(classNames({ foo: true }, { bar: false }))
    console.log(
      classNames(null, false, &#39;bar&#39;, undefined, 0, 1, { baz: null }, &#39;&#39;),
    )
    console.log(classNames(styles[&#39;button&#39;], styles[&#39;loading&#39;]))</code></pre>
<p><code>$ npm i classnames</code></p>
<pre><code class="language-jsx">import React from &#39;react&#39;
import styles from &#39;./Button.module.css&#39;
import classNames from &#39;classnames/bind&#39; // bind 추가

const cx = classNames.bind(styles)
console.log(cx(&#39;button&#39;, &#39;loading&#39;))

// const Button = (props) =&gt; &lt;button className={styles[&#39;button&#39;]} {...props} /&gt;
class Button extends React.Component {
  state = {
    loading: false,
  }
  render() {

    const { loading } = this.state

    return (
      &lt;button
        onClick={this.startLoading}
        className={cx(&#39;button&#39;, { loading })}
        {...this.props}
      /&gt;
    )
  }
  startLoading = () =&gt; {
    this.setState({
      loading: true,
    })
    setTimeout(() =&gt; {
      this.setState({
        loading: false,
      })
    }, 1000)
  }
}

export default Button</code></pre>
<h2 id="4-styled-components">4. Styled Components</h2>
<ul>
<li>별도 라이브러리를 통해 스타일링을 좀더 손쉽게 가능</li>
<li><code>$  npm i styled-components</code></li>
</ul>
<h3 id="기본-사용법">기본 사용법</h3>
<pre><code class="language-jsx">// StyledButton.jsx
import styled from &#39;styled-components&#39;

const StyledButton = styled.button`
  background: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
  cursor: pointer;
`

export default StyledButton</code></pre>
<pre><code class="language-js">// 사용
import StyledButton from &#39;../components/StyledButton&#39;
      &lt;StyledButton&gt;스타일버튼&lt;/StyledButton&gt;</code></pre>
<h3 id="다양한-활용법">다양한 활용법</h3>
<pre><code class="language-jsx">import Button from &#39;../components/Button.jsx&#39;
import StyledButton from &#39;../components/StyledButton&#39;
import StyledA from &#39;../components/StyledA&#39;
import styled, { createGlobalStyle } from &#39;styled-components&#39;

const PrimaryStyledButton = styled(StyledButton)`
  background: palevioletred;
  color: white;
`

const UppercaseButton = (props) =&gt; (
  &lt;button {...props} children={props.children.toUpperCase()} /&gt;
)
const MyButton = (props) =&gt; (
  // &lt;button {...props} children={`MyButton ${props.children}`} /&gt;
  &lt;button className={props.className} children={`MyButton ${props.children}`} /&gt;
)

const StyledMyButton = styled(MyButton)`
  background: transparent;
  border-radius: 3px;
  border: 2px solid ${(props) =&gt; props.color || &#39;palevioletred&#39;};
  color: ${(props) =&gt; props.color || &#39;palevioletred&#39;};
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
  cursor: pointer;

  :hover {
    border: 2px solid red;
    font-size: 25px;
  }

  ::before {
    content: &#39;@&#39;;
  }
`

const GlobalStyle = createGlobalStyle`
button {
  color: yellow;
}
`

export default function Home() {
  return (
    &lt;div&gt;
      &lt;GlobalStyle /&gt;
      &lt;h1&gt;HOME&lt;/h1&gt;
      &lt;Button&gt;BTN&lt;/Button&gt;
      &lt;StyledButton&gt;스타일버튼&lt;/StyledButton&gt;
      &lt;StyledButton primary&gt;스타일버튼&lt;/StyledButton&gt;
      &lt;PrimaryStyledButton&gt;스타일버튼&lt;/PrimaryStyledButton&gt;
      &lt;StyledButton as=&quot;a&quot; href=&quot;/profile&quot;&gt;
        a버튼
      &lt;/StyledButton&gt;
      &lt;StyledButton as={UppercaseButton}&gt;button&lt;/StyledButton&gt;
      &lt;StyledMyButton&gt;button&lt;/StyledMyButton&gt;
      &lt;StyledMyButton color=&quot;green&quot;&gt;button&lt;/StyledMyButton&gt;
      &lt;StyledA href=&quot;https://google.com&quot;&gt;구글로&lt;/StyledA&gt;
    &lt;/div&gt;
  )
}</code></pre>
<pre><code class="language-jsx">// StyledA.jsx
import styled from &#39;styled-components&#39;
const StyledA = styled.a.attrs((props) =&gt; ({
  target: &#39;_BLANK&#39;,
}))`
  color: ${(props) =&gt; props.color};
`
export default StyledA</code></pre>
<h2 id="5-react-shadow">5. React Shadow</h2>
<ul>
<li><code>$ npm i react-shadow</code></li>
<li>컴포넌트마다 격리시켜 style을 독립적으로 적용시킬 수 있음</li>
</ul>
<h2 id="6-ant-design">6. Ant Design</h2>
<ul>
<li><a href="https://ant.design/docs/react/introduce">Ant Design</a></li>
<li><code>$ npm i antd</code></li>
</ul>
<pre><code class="language-js">// index.js
import &#39;antd/dist/antd.css&#39;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Router]]></title>
            <link>https://velog.io/@protect-me/React-Router</link>
            <guid>https://velog.io/@protect-me/React-Router</guid>
            <pubDate>Wed, 15 Sep 2021 05:43:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>참고</strong> <a href="https://slides.com/woongjae/react2021">https://slides.com/woongjae/react2021</a></p>
</blockquote>
<h2 id="react의-라우팅-이해하기">React의 라우팅 이해하기</h2>
<h3 id="비교">비교</h3>
<ul>
<li>기존 방식
<img src="https://images.velog.io/images/protect-me/post/06108f8a-8038-4802-a171-f226e7aff711/image.png" alt=""></li>
<li>SPA | Single Page Application
<img src="https://images.velog.io/images/protect-me/post/efb596d3-72ed-49c1-825d-ae69110417ce/image.png" alt=""></li>
</ul>
<h3 id="spa-라우팅-과정">SPA 라우팅 과정</h3>
<ol>
<li>브라우저에서 최초에 &#39;/&#39;경로로 요청을 하면</li>
<li>React Web App을 내려줌</li>
<li>내려받은 React App에서 &#39;/&#39;경로에 맞는 컴포넌트를 보여줌</li>
<li>ReactApp에서 다른 페이지로 이동하는 동작을 수행하면</li>
<li>새로운 경로에 맞는 컴포넌트를 보여줌</li>
</ol>
<blockquote>
<p>React는 작성, 렌더, 업데이트에 관여하는데, 
route는 기본적으로 react의 범주를 넘어서는 작업
⇒ <code>React Router Dom</code> Library가 그 일을 대신함</p>
</blockquote>
<p><code>$npm i react-router-dom</code>
CRA에 기본 내장된 패키지도, 공식 패키지도 아님
가장 대표적인 라우팅 패키지임</p>
<hr>
<h2 id="기본-라우팅">기본 라우팅</h2>
<pre><code class="language-js">// App.js
import { BrowserRouter, Route } from &#39;react-router-dom&#39;
import Home from &#39;./pages/Home&#39;
import Profile from &#39;./pages/Profile&#39;
import About from &#39;./pages/About&#39;
import &#39;./App.css&#39;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Route path=&quot;/&quot; exact component={Home}&gt;&lt;/Route&gt;
      &lt;Route path=&quot;/profile&quot; component={Profile}&gt;&lt;/Route&gt;
      &lt;Route path=&quot;/about&quot; component={About}&gt;&lt;/Route&gt;
    &lt;/BrowserRouter&gt;
  )
}

export default App</code></pre>
<pre><code class="language-js">export default function Profile() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Profile&lt;/h2&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<hr>
<h2 id="동적-라우팅-1">동적 라우팅 (1)</h2>
<ul>
<li><code>params</code>: 필수 요소</li>
<li><code>http://localhost:3000/profile/10</code></li>
</ul>
<pre><code class="language-js">// App.js
(...)
&lt;Route path=&quot;/profile&quot; exact component={Profile}&gt;&lt;/Route&gt;
&lt;Route path=&quot;/profile/:id&quot; component={Profile}&gt;&lt;/Route&gt;
(...)
</code></pre>
<pre><code class="language-js">// profile.jsx
export default function Profile(props) {
  const id = props.match.params.id
  console.log(id, typeof id)
  return (
    &lt;div&gt;
      &lt;h2&gt;Profile&lt;/h2&gt;
      {id &amp;&amp; &lt;p&gt;id 는 {id} 입니다.&lt;/p&gt;}
    &lt;/div&gt;
  )
}</code></pre>
<hr>
<h2 id="동적-라우팅-2">동적 라우팅 (2)</h2>
<ul>
<li><code>query string</code>: 선택적 요소</li>
<li><code>http://localhost:3000/about?name=hell</code></li>
</ul>
<h3 id="basic">basic</h3>
<pre><code class="language-js">export default function About(props) {
  const searchParams = props.location.search
  console.log(searchParams)
  const obj = new URLSearchParams(searchParams)
  const name = obj.get(&#39;name&#39;)
  console.log(name)
  return (
    &lt;div&gt;
      &lt;h2&gt;About&lt;/h2&gt;
      {name &amp;&amp; &lt;p&gt;name은 {name}입니다.&lt;/p&gt;}
    &lt;/div&gt;
  )
}</code></pre>
<h3 id="query-string-library">query-string library</h3>
<pre><code class="language-js">import queryString from &#39;query-string&#39;

export default function About(props) {
  const searchParams = props.location.search
  console.log(searchParams)
  // const obj = new URLSearchParams(searchParams)
  // const name = obj.get(&#39;name&#39;)
  // console.log(name)
  const query = queryString.parse(searchParams)
  console.log(query)
  return (
    &lt;div&gt;
      &lt;h2&gt;About&lt;/h2&gt;
      {query.name &amp;&amp; &lt;p&gt;name은 {query.name}입니다.&lt;/p&gt;}
    &lt;/div&gt;
  )
}</code></pre>
<hr>
<h2 id="switch와-notfound">Switch와 NotFound</h2>
<ul>
<li>여러 Route 중 순서대로 먼저 맞는 하나만 보여줌</li>
<li>로직에서 <code>exact</code>를 뺄 수 있게 해줌</li>
<li>가장 마지막에 어느 <code>path</code>에도 맞지 않는 경우를 설정해서 <code>Not Found</code> 페이지를 만들 수 있음<pre><code class="language-js">// App.js
import { BrowserRouter, Route, Switch } from &#39;react-router-dom&#39;
import Home from &#39;./pages/Home&#39;
import Profile from &#39;./pages/Profile&#39;
import About from &#39;./pages/About&#39;
import NotFound from &#39;./pages/NotFound.jsx&#39;
import &#39;./App.css&#39;
</code></pre>
</li>
</ul>
<p>function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/profile/:id" component={Profile}></Route>
        <Route path="/profile" component={Profile}></Route>
        <Route path="/about" component={About}></Route>
        <Route path="/" exact component={Home}></Route>
        <Route component={NotFound}></Route>
      </Switch>
    </BrowserRouter>
  )
}</p>
<p>export default App</p>
<pre><code>
---

## JSX 링크로 라우팅 이동(Link)
&gt; `&lt;a&gt;&lt;/a&gt;` 태그를 통해 이동하면 React Application의 SPA 특성을 담은 페이지 이동이 아니라 서버에 새로운 요청을 일으킴. 즉, 새로고침이 일어남
⇒ `&lt;Link&gt;&lt;/Link&gt;` 컴포넌트를 활용

### Link Component - 직접 사용
```js
import { BrowserRouter, Route, Switch, Link } from &#39;react-router-dom&#39; // Link import
import Home from &#39;./pages/Home&#39;
import Profile from &#39;./pages/Profile&#39;
import About from &#39;./pages/About&#39;
import NotFound from &#39;./pages/NotFound.jsx&#39;
import &#39;./App.css&#39;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt;
      &lt;Switch&gt;
        &lt;Route path=&quot;/profile/:id&quot; component={Profile}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/profile&quot; component={Profile}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/about&quot; component={About}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/&quot; exact component={Home}&gt;&lt;/Route&gt;
        &lt;Route component={NotFound}&gt;&lt;/Route&gt;
      &lt;/Switch&gt;
    &lt;/BrowserRouter&gt;
  )
}

export default App</code></pre><h3 id="link-component---links-분리">Link Component - Links 분리</h3>
<pre><code class="language-js">import { Link } from &#39;react-router-dom&#39;

export default function Links() {
  return (
    &lt;ul&gt;
      &lt;li&gt;
        &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link to=&quot;/profile&quot;&gt;Profile&lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link to=&quot;/profile/1&quot;&gt;Profile/1&lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link to=&quot;/about&quot;&gt;About&lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link to=&quot;/about?name=hell&quot;&gt;About?name=&quot;hell&lt;/Link&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  )
}</code></pre>
<hr>
<h2 id="jsx-링크로-라우팅-이동navlink">JSX 링크로 라우팅 이동(NavLink)</h2>
<ul>
<li><code>import {NavLink} from &#39;react-router-dom&#39;</code></li>
<li>activeClassName, activeStyle 처럼 active 상태에 대한 스타일 지정이 가능</li>
<li>Route의 path 처럼 동작하기 때문에 exact가 있음</li>
</ul>
<pre><code class="language-js">// NavLinks.js
import { NavLink } from &#39;react-router-dom&#39;
const activeStyle = { color: &#39;green&#39; }

export default function NavLinks() {
  return (
    &lt;ul&gt;
      &lt;li&gt;
        &lt;NavLink to=&quot;/&quot; exact activeStyle={activeStyle}&gt;
          Home
        &lt;/NavLink&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;NavLink to=&quot;/profile&quot; exact activeStyle={activeStyle}&gt;
          Profile
        &lt;/NavLink&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;NavLink to=&quot;/profile/1&quot; activeStyle={activeStyle}&gt;
          Profile/1
        &lt;/NavLink&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;NavLink
          to=&quot;/about&quot;
          activeStyle={activeStyle}
          isActive={(match, location) =&gt; {
            console.log(&#39;match&#39;, match)
            console.log(&#39;location&#39;, location)

            return match !== null &amp;&amp; location.search === &#39;&#39;
          }}
        &gt;
          About
        &lt;/NavLink&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;NavLink
          to=&quot;/about?name=hell&quot;
          activeStyle={activeStyle}
          isActive={(match, location) =&gt; {
            console.log(&#39;location&#39;, location)
            return match !== null &amp;&amp; location.search === &#39;?name=hell&#39;
          }}
        &gt;
          About?name=hell
        &lt;/NavLink&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  )
}</code></pre>
<hr>
<h2 id="js로-라우팅-이동">JS로 라우팅 이동</h2>
<h3 id="기본">기본</h3>
<pre><code class="language-js">// Login.jsx
export default function Login(props) {
  console.log(&#39;props&#39;, props)

  function login() {
    setTimeout(() =&gt; {
      props.history.push(&#39;/&#39;) // go, goback, push, replace...
    }, 1000)
  }

  return (
    &lt;div&gt;
      &lt;h2&gt;Login&lt;/h2&gt;
      &lt;button onClick={login}&gt;로그인하기&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<h3 id="component-분리-1---지양">component 분리 (1) - 지양</h3>
<ul>
<li>부모 컴포넌트에서 자식 컴포넌트로 직접 props를 넘겨주는 방식<pre><code class="language-js">// Login.jsx
import LoginButton from &#39;../components/LoginButton&#39;
export default function Login(props) {
return (
  &lt;div&gt;
    &lt;h2&gt;Login&lt;/h2&gt;
    &lt;LoginButton {...props} /&gt;
  &lt;/div&gt;
)
}</code></pre>
</li>
</ul>
<pre><code class="language-js">// LoginButton.jsx
export default function LoginButton(props) {
  console.log(&#39;LoginButton Props&#39;, props)
  function login() {
    setTimeout(() =&gt; {
      props.history.push(&#39;/&#39;)
    }, 1000)
  }
  return &lt;button onClick={login}&gt;로그인하기&lt;/button&gt;
}</code></pre>
<h3 id="component-분리-2---hoc">component 분리 (2) - HOC</h3>
<ul>
<li><code>Higher Order Component</code>인 <code>withRouter</code> 활용<pre><code class="language-js">// Login.jsx
import LoginButton from &#39;../components/LoginButton&#39;
export default function Login() {
return (
  &lt;div&gt;
    &lt;h2&gt;Login&lt;/h2&gt;
    &lt;LoginButton /&gt;
  &lt;/div&gt;
)
}</code></pre>
<pre><code class="language-js">// LoginButton.jsx
import { withRouter } from &#39;react-router-dom&#39;
</code></pre>
</li>
</ul>
<p>export default withRouter(function LoginButton(props) {
  console.log(&#39;LoginButton Props&#39;, props)
  function login() {
    setTimeout(() =&gt; {
      props.history.push(&#39;/&#39;)
    }, 1000)
  }
  return <button onClick={login}>로그인하기</button>
})</p>
<pre><code>
### component 분리 (3) - Hook
- hook을 통한 방법도 있음(차후 학습)


---

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

```js
// App.js
import { BrowserRouter, Redirect, Route, Switch } from &#39;react-router-dom&#39;
// import Links from &#39;./components/Links.jsx&#39;
import NavLinks from &#39;./components/NavLinks.jsx&#39;
import Login from &#39;./pages/Login&#39;
import Home from &#39;./pages/Home&#39;
import Profile from &#39;./pages/Profile&#39;
import About from &#39;./pages/About&#39;
import NotFound from &#39;./pages/NotFound.jsx&#39;
import &#39;./App.css&#39;

const isLogin = false // isLogin에 따라서 home으로 갈지 login으로 갈지 결정됨

function App() {
  return (
    &lt;BrowserRouter&gt;
      {/* &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt; */}
      {/* &lt;Links&gt;&lt;/Links&gt; */}
      &lt;NavLinks&gt;&lt;/NavLinks&gt;
      &lt;Switch&gt;
        &lt;Route
          path=&quot;/login&quot;
          render={() =&gt; (isLogin ? &lt;Redirect to=&quot;/&quot; /&gt; : &lt;Login /&gt;)}
        &gt;&lt;/Route&gt;
        &lt;Route path=&quot;/profile/:id&quot; component={Profile}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/profile&quot; component={Profile}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/about&quot; component={About}&gt;&lt;/Route&gt;
        &lt;Route path=&quot;/&quot; exact component={Home}&gt;&lt;/Route&gt;
        &lt;Route component={NotFound}&gt;&lt;/Route&gt;
      &lt;/Switch&gt;
    &lt;/BrowserRouter&gt;
  )
}

export default App</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Creating React Project]]></title>
            <link>https://velog.io/@protect-me/React-CompomnentcreateElement</link>
            <guid>https://velog.io/@protect-me/React-CompomnentcreateElement</guid>
            <pubDate>Tue, 14 Sep 2021 07:41:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>참고</strong> <a href="https://slides.com/woongjae/react2021">https://slides.com/woongjae/react2021</a></p>
</blockquote>
<h2 id="cra">CRA</h2>
<p><a href="https://create-react-app.dev/">Create React App</a>
<code>$ npx create-react-app my-project</code></p>
<h2 id="scripts-명령어">scripts 명령어</h2>
<h3 id="npm-start">npm start</h3>
<ul>
<li>react-scripts start</li>
<li>starting the development server<h3 id="npm-run-build">npm run build</h3>
</li>
<li>react-scripts build</li>
<li>creating and optimized production build</li>
<li>build 폴더에 결과물 생성</li>
<li>배포
: <code>$ npm install server -g</code>
: <code>$ serve -s build</code><h3 id="npm-test">npm test</h3>
</li>
<li>react-scripts text</li>
<li>jest를 통해 test code를 실행<h3 id="npm-run-eject">npm run eject</h3>
</li>
<li>rect-scripts eject</li>
</ul>
<h2 id="webpack">webpack</h2>
<p><img src="https://images.velog.io/images/protect-me/post/d88e4bbc-5a84-4502-81ec-de72b0ede323/image.png" alt=""></p>
<h2 id="eslintlinter">ESLint(linter)</h2>
<ul>
<li>잘못입력한 문법을 자동으로 수정</li>
</ul>
<h2 id="prettierformatter">Prettier(formatter)</h2>
<ul>
<li>팀원간의 코딩 컨벤션을 맞추기 위해서 사용</li>
</ul>
<blockquote>
<h2 id="eslint--prettier">ESLint &amp; Prettier</h2>
</blockquote>
<p>ESlint는 보통 잘못입력한 문법을 자동으로 수정하기 위해서 사용된다.</p>
<blockquote>
</blockquote>
<p>Prettier는 팀원간의 코딩 컨벤션을 맞추기 위해서 사용된다.</p>
<blockquote>
</blockquote>
<p>즉, eslint는 포매팅 기능이 포함되어 있기 때문에 eslint와 prettier를 같이 사용하는 경우에는 충돌이 나게 된다. 따라서, eslint 포메팅 기능을 종료시키고 문법 기능만 사용하게 한다.</p>
<blockquote>
</blockquote>
<p>eslint-config-prettier은 eslint에서 prettier의 포매팅과 겹치는 것을 삭제하고 eslint-plugin-prettier는 eslint에서 prettier의 포메팅 기능을 추가한다.</p>
<blockquote>
</blockquote>
<p>출처: <a href="https://zereight.tistory.com/994">https://zereight.tistory.com/994</a> [Zereight&#39;s Blog]</p>
<pre><code class="language-js">// package.json
  &quot;eslintConfig&quot;: {
    &quot;extends&quot;: [
      &quot;react-app&quot;,
      &quot;react-app/jest&quot;,
      &quot;prettier&quot; // 여기에 추가하여 충돌 방지
    ]
  },</code></pre>
<h2 id="create-react-app-시작코드-이해하기">Create React App 시작코드 이해하기</h2>
<h2 id="react-developer-tools">React Developer Tools</h2>
<ul>
<li>디버깅에 용이
<a href="https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi/related?hl=ko">확장프로그램 설치</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>