<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>const_yang</title>
        <link>https://velog.io/</link>
        <description>Log</description>
        <lastBuildDate>Wed, 21 Feb 2024 05:36:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>const_yang</title>
            <url>https://images.velog.io/images/jonghwan2_y/profile/3e054dbd-563b-4921-8064-16b8d3d1b7d3/fairy-tale-4057424_1920.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. const_yang. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jonghwan2_y" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[사이즈 조정]]></title>
            <link>https://velog.io/@jonghwan2_y/%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%A1%B0%EC%A0%95</link>
            <guid>https://velog.io/@jonghwan2_y/%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%A1%B0%EC%A0%95</guid>
            <pubDate>Wed, 21 Feb 2024 05:36:13 GMT</pubDate>
            <description><![CDATA[<h3 id="하려는-거">하려는 거</h3>
<p>마우스 이벤트를 통해 드래그하여 특정 엘레멘트의 사이즈를 조정해 볼 거다.</p>
<h3 id="필요한-거">필요한 거</h3>
<p>먼저, 마우스는 아래의 세가지 동작을 한다는 걸 이해하자</p>
<ul>
<li>마우스를 누른다 (mousedown)</li>
<li>마우스를 움직인다 (mousemove)</li>
<li>마우스에서 손을 뗀다 (mouseup)</li>
</ul>
<h3 id="작업할-거">작업할 거</h3>
<ol>
<li>내가 마우스를 눌렀다는 사실을 상태로 기억하고 있자. 그리고 누른 그 지점이 어딘지도 상태로 기억하고 있자.<blockquote>
<p>우리는 마우스를 누를 때 많은 걸 참조할 수 있다. 
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/edc4bd4e-2e17-49f2-aa44-4c06094914c3/image.png" alt=""></p>
</blockquote>
</li>
</ol>
<p>암튼, 코드로 작성해 보자.</p>
<pre><code class="language-jsx">const [isResizing, setIsResizing] = useState&lt;boolean&gt;(false);

const [initialX, setInitialX] = useState&lt;number&gt;(0);

const handleMouseDown = (e: React.MouseEvent) =&gt; {
    setIsResizing(true);
    setInitialX(e.clientX);
};</code></pre>
<ol start="2">
<li>이제는 마우스를 움직여 볼건데... 가로 너비의 사이즈를 드래스해서 조정해 볼거다. 필요한게 뭔지 하나씩 생각해보자.<ul>
<li>현재 엘레멘트의 너비가 필요하다.</li>
<li>아까 마우스를 누를때 지점의 값이 필요하다(<code>initialX</code>)</li>
<li>마우스를 이동하고서의 지점의 값이 필요하다(1번을 참조해서 <code>e.clientX</code>)</li>
<li>마우스를 이동하고서의 지점 - 마우스 누를 때 지점 = 이동한 거리 (<code>initialX - e.clientX</code>)</li>
<li>현재 너비에서 이동한 거리를 더해준 값이 새로운 너비가 될 거다.</li>
</ul>
</li>
</ol>
<p>코드로 작성해 보자.</p>
<pre><code class="language-jsx">  const handleMouseMove = useCallback(
    (e: MouseEvent) =&gt; {
      if (isResizing) {
        const newWidth = width + e.clientX - initialX;
        setInitialX(e.clientX);
        if (newWidth &gt;= 408) {
          setWidth(newWidth);
        }
      }
    },
    [isResizing, initialX, width],
  );</code></pre>
<ol start="3">
<li>마지막이다. 마우스에서 손을 뗴보자. 아까 누를 때의 상태의 반대로 상태를 바꾸기만 하면 된다.</li>
</ol>
<p>코드로 작성해 보자.</p>
<pre><code class="language-jsx">  const handleMouseUp = useCallback(() =&gt; {
    setIsResizing(false);
  }, []);</code></pre>
<h3 id="전체-코드이고-실제-화면이다">전체 코드이고 실제 화면이다.</h3>
<pre><code class="language-jsx">  const [isResizing, setIsResizing] = useState&lt;boolean&gt;(false);
  const [initialX, setInitialX] = useState&lt;number&gt;(0);
  const [width, setWidth] = useState&lt;number&gt;(408);
  const handleMouseDown = (e: React.MouseEvent) =&gt; {
    e.preventDefault();
    setIsResizing(true);
    setInitialX(e.clientX);
  };

  const handleMouseUp = useCallback(() =&gt; {
    setIsResizing(false);
  }, []);

  const handleMouseMove = useCallback(
    (e: MouseEvent) =&gt; {
      if (isResizing) {
        const newWidth = width + e.clientX - initialX;
        setInitialX(e.clientX);
        if (newWidth &gt;= 408) {
          setWidth(newWidth);
        }
      }
    },
    [isResizing, initialX, width],
  );

  useEffect(() =&gt; {
    if (isResizing) {
      document.addEventListener(&#39;mousemove&#39;, handleMouseMove);
      document.addEventListener(&#39;mouseup&#39;, handleMouseUp, { once: true });
    } else {
      document.removeEventListener(&#39;mousemove&#39;, handleMouseMove);
      document.removeEventListener(&#39;mouseup&#39;, handleMouseUp);
    }

    return () =&gt; {
      document.removeEventListener(&#39;mousemove&#39;, handleMouseMove);
      document.removeEventListener(&#39;mouseup&#39;, handleMouseUp);
    };
  }, [isResizing, handleMouseMove, handleMouseUp]);
</code></pre>
<p><img src="https://velog.velcdn.com/images/jonghwan2_y/post/3104c67e-b2dd-4a57-833e-a17b7f73ee7e/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ST_A] 알고리즘 정리 - DFS와 BFS (feat.자료구조)]]></title>
            <link>https://velog.io/@jonghwan2_y/DFS%EC%99%80-BFS-feat.%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jonghwan2_y/DFS%EC%99%80-BFS-feat.%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sun, 28 May 2023 02:27:54 GMT</pubDate>
            <description><![CDATA[<h4 id="dfs와-bfs를-공부하기-전에-자료구조를-살짝-들여다보자">DFS와 BFS를 공부하기 전에 자료구조를 살짝 들여다보자.</h4>
<h2 id="자료구조의-정의는-아래와-같다">자료구조의 정의는 아래와 같다.</h2>
<blockquote>
<p>무수한 상황에서 데이터를 효율적으로 다룰 수 있는 방법</p>
</blockquote>
<p>익숙한 자료구조로는 Stack, Queue, Tree, Graph 등이 있다.</p>
<h4 id="stack">Stack</h4>
<ul>
<li>LIFO (last in, first out) / 후입선출 (가장 늦게 들어온 것부터 먼저 나간다)</li>
<li>회전 초밥 집에 쌓인 접시처럼 생겼다.</li>
</ul>
<h4 id="queue">Queue</h4>
<ul>
<li>FIFO (first in, first out) / 선입선출 (가장 먼저 들어온 것부터 먼저 나간다)</li>
<li>톨게이트를 그려보면 쉽게 이해가 된다.</li>
</ul>
<h4 id="graph">Graph</h4>
<ul>
<li>여러 개의 점들이 서로 복잡하게 연결되어 있는 관계를 표현한 자료 구조</li>
<li>방향성을 가진다: 무방향 / 단방향</li>
<li>진입차수 / 진출차수: 정점에 들어오는 간선의 수 / 정점에서 나가는 간선의 수</li>
<li>자기루프: 정점에서 나간 간선이 곧바로 진입하는 경우</li>
<li>사이클: 서울 → 대전 → 부산 → 서울</li>
</ul>
<h4 id="tree">Tree</h4>
<ul>
<li>루트(Root) 라는 하나의 꼭짓점 데이터를 시작으로 여러 개의 데이터를 간선(edge)으로 연결한 자료 구조</li>
<li>노드: 모든 개별 데이터</li>
<li>루트: 트리 구조의 시작</li>
<li>부모 노드: 상하 관계의 두 노드 중 루트에 가까운 노드</li>
<li>자식 노드: 상하 관계의 두 노드 중 루트에 먼 노드</li>
<li>리프 노드: 트리 구조의 끝 노드(자식 구조가 없는 노드)</li>
</ul>
<h2 id="탐색">탐색</h2>
<p>탐색은 다양한 알고리즘을 적용하기 좋은 테마 중 하나이다. 
추상적일 수 있지만 우리가 맞이하는 다양한 문제 등을 탐색을 통해 해결할 수 있다. 
미로게임이, 로봇청소기, 최단 거리 또는 최단 시간 경로 찾기 등이 탐색을 통해서 문제를 해결해 볼 수 있다.</p>
<p>탐색에도 여러 방법이 있겠지만 
오늘은 tree, stack, queue 등의 자료구조를 통해 DFS와 BFS를 다루어 보기로 한다.</p>
<h3 id="bfs-breath-first-search">BFS (Breath-First Search)</h3>
<ul>
<li>너비 우선 탐색</li>
<li>노드 레벨 순서에 따라 순회:
부모 노드 기준으로 바로 자신의 자식 노드 레벨(또는 인접 노드)을 모두 살피고 해당 자식 노드들의 바로 아래의 자식 노드 레벨(또는 인전 노드)을 탐색한다.</li>
<li>자료구조 <strong><span style="color:#228B22">Queue</span></strong>를 활용하여 문제 해결할 수 있다.</li>
<li>최단 경로를 알아내야 하는 경우 또는 찾아야 하는 노드가 인접한 경우 사용한다.</li>
</ul>
<p><img src="https://images.velog.io/images/jonghwan2_y/post/6f740524-3c1a-4eaf-a060-2ac8bc0004b3/%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-09-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.59.41.png" alt=""></p>
<blockquote>
<p>Javascript로 구현한 BFS 문제 풀이이다.</p>
</blockquote>
<pre><code class="language-jsx">const bfs = (graph, start, visited = []) =&gt; {
  let queue = [start]
  visited.push(start)

  while (queue.length &gt; 0) {
    node = queue.shift()
    for (value of graph[node]) {
      if (!visited.includes(value)) {
        queue.push(value)
        visited.push(value)
      }
    }
  }
  return visited
}

const graph = [
  [],
  [2, 3, 8],
  [1, 7],
  [1, 4, 5],
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]

bfs(graph, 1)
// [ 1, 2, 3, 8, 7, 4, 5, 6 ]</code></pre>
<h3 id="dfsdepth-first-search">DFS(Depth-First Search)</h3>
<ul>
<li>깊이 우선 탐색</li>
<li>연결되어 있는 노드를 우선하여 순회:
부모 노드 기준으로 자신의 자식 노드 중 하나를 탐색하고 해당 자식 노드의 자식 노드를 탐색하는 순서로 탐색한다.</li>
<li>자료구조 <strong><span style="color:#228B22">stack</span></strong>과 <strong><span style="color:#228B22">재귀</span></strong>를 활용하여 문제 해결할 수 있다.</li>
<li>경로를 완벽하게 탐색해야 할 때 사용한다.</li>
</ul>
<p><img src="https://images.velog.io/images/jonghwan2_y/post/c2211949-05fd-49df-ad62-246cc318119f/%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-09-29%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.10.39.png" alt=""></p>
<blockquote>
<p>Javascript로 구현한 DFS 문제 풀이이다. Stack과 재귀를 활용하였다.</p>
</blockquote>
<pre><code class="language-jsx">const dfs_recussion = (graph, start, visited=[]) =&gt; {
  visited.push(start);
  for (node of graph[start]) {
    if (!visited.includes(node)) {
      dfs_recussion(graph, node, visited)
    }
  }
  return visited
}

const graph = {
  &#39;A&#39;:[&#39;B&#39;,&#39;C&#39;], 
  &#39;B&#39;:[&#39;A&#39;,&#39;D&#39;,&#39;E&#39;], 
  &#39;C&#39;:[&#39;A&#39;,&#39;G&#39;,&#39;H&#39;],
  &#39;D&#39;:[&#39;B&#39;], 
  &#39;E&#39;:[&#39;B&#39;,&#39;F&#39;], 
  &#39;F&#39;:[&#39;E&#39;], 
  &#39;G&#39;:[&#39;C&#39;], 
  &#39;H&#39;:[&#39;C&#39;]
}

dfs_recussion(graph, &#39;A&#39;)
// [ &#39;A&#39;, &#39;B&#39;, &#39;D&#39;, &#39;E&#39;, &#39;F&#39;, &#39;C&#39;, &#39;G&#39;, &#39;H&#39; ]</code></pre>
<h3 id="두-알고리즘의-tree-탐색-경로를-비교해보자">두 알고리즘의 Tree 탐색 경로를 비교해보자</h3>
<p><img src="https://images.velog.io/images/jonghwan2_y/post/729946b6-0c05-4356-a6ba-7a866149df00/DFS-BFS-comparison.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React-Native] 로딩 중에 Swipe Back을 막아보자]]></title>
            <link>https://velog.io/@jonghwan2_y/React-Native-%EB%A1%9C%EB%94%A9-%EC%A4%91%EC%97%90-Swipe-Back%EC%9D%84-%EB%A7%89%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@jonghwan2_y/React-Native-%EB%A1%9C%EB%94%A9-%EC%A4%91%EC%97%90-Swipe-Back%EC%9D%84-%EB%A7%89%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 30 Jan 2023 06:35:08 GMT</pubDate>
            <description><![CDATA[<h2 id="고민의-시작">고민의 시작</h2>
<h3 id="데이터를-보내고--뒤로가기">데이터를 보내고 + 뒤로가기</h3>
<p>설정 중에 특정 데이터를 쿼리로 보내고 + firebase로 메시지를 보내는 이벤트가 있었다. 원래는 뒤로가기 버튼에 이 이벤트 핸들러를 넣어서 뒤로 가기가 한참 뒤에야 뒤로 가졌다. 그 부분은 해결을 했지만, Swipe로 뒤로 가기를 매우 급하게(?) 할 경우 사용자가 저장한 설정이 제대로 서버로 전달을 주지 못하고 navigation이 이뤄지는 바람에 제대로 설정 정보를 불러오지 못하는 이슈가 있었다.</p>
<h2 id="해결">해결</h2>
<h3 id="loading-ui">Loading UI</h3>
<p>첫 번째로 생각난 것이 &quot;일단 Loading UI를 사용자에게 제공해주자&quot;였다. 페이지를 개발하다 보면 다양한 상황에서 서버에서 데이터를 fetch하여 사용자에게 정보를 제공한다. fetch하는 시간동안 나은 UX를 제공하기 위한 방법으로 Loading UI를 사용자에게 제공한다.</p>
<h3 id="loading-중에는-뒤로가기를-막자">Loading 중에는 뒤로가기를 막자</h3>
<p>두 번째로 생각난 것이 Loading 중에 뒤로가기를 막아서 데이터가 서버로 보내지기 전에 Swipe로 뒤로 가는 것은 막되, 서버로 전달이 끝나면 다시 Swipe가 되도록 해야했다.</p>
<h2 id="시도-1-fail">시도 #1 (fail)</h2>
<p>React Navigation 공식 문서에서 제공하는 <a href="https://reactnavigation.org/docs/preventing-going-back">뒤로 가기 방지</a>의 예시를 참조하여 navigation에 이벤트를 붙여보았다. <code>beforeRemove</code>이벤트를 붙이면, 현재 stack이 삭제되고 다른 stack으로 이동하는 순간 해당 이벤트가 트리거된다.</p>
<p>나의 경우 stack에서 tab으로 이동을 하는 거 때문이었는지 몰라도 (아직도 잘 확인이 안된다😭) 자꾸만 에러를 뿜었다. 
<code>The screen was removed natively but didn&#39;t get removed from JS state.</code>
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/b2bd971e-6f02-4c4a-b3f7-8a71765a5b46/image.png" alt=""></p>
<p>해석하자면 현재 화면은 iOS에서 사라졌지만 JS 상태에서는 삭제가 되지 않았다고 한다. (이해하려면 더 공부가 필요하지만) 공식 문서에서도 사실 비슷한 이야기가 나온다.
<code>The event is only triggered whenever a screen is being removed due to a navigation state change.</code></p>
<p>이런 에러로 인해 위에서 말했던 <code>beforeRemove</code> 이벤트를 navigation에 붙여서 사용할 수 없었다.</p>
<h2 id="시도-2-사실상-fail">시도 #2 (사실상 fail)</h2>
<p>위 에러 사진에서 친절하게 대안을 제시해준다. <code>gestureEnabled</code>를 false로 주면 된다고 한다.</p>
<h3 id="gestureenabled"><a href="https://reactnavigation.org/docs/stack-navigator/">gestureEnabled</a></h3>
<p>stack-navigator의 옵션으로 false로 설정할 경우 (기본값은 true임) swipe와 같은 gesture가 막히게 된다. 근데 이건, stack 자체의 swipe를 막은거라 loading 중에만 막고 loading 아닌 경우에는 열어 놓는다는 원래 아이디어와 맞지 않았기 때문에 사실상 시도하지 않았다.</p>
<h2 id="시도-3-pass">시도 #3 (Pass)</h2>
<p>useEffect를 이용하여 의존성 배열에는 loading의 state를 넣어 주었다. 그리고 <code>navigation.setOptions()</code>으로 <code>gestureEnabled</code> 값을 변경해 주는 함수를 실행시켰더니 원하던 아이디어대로 구현이 되었다.</p>
<pre><code class="language-js">  useEffect(() =&gt; {
    navigation.setOptions({ gestureEnabled: !isLoading })
  }, [isLoading])</code></pre>
<p>isLoading이 true일때 gestureEnabled가 false로 setter함수가 옵션을 보내주고, 반대의 경우 true로 보내주기 때문에 loading state에 따라서 해당 옵션값이 잘 변경되었다.</p>
<h2 id="마무리">마무리</h2>
<p>React Native는 iOS와 안드로이드에 맞게 상태를 관리하고 옵션 등을 잘 설정해 주어야 한다는 것을 다시 한번 깨닳았다. 구글링을 아무리해도 해외 개발자 형님들도 이 부분에 대해 명확하게 설명하고 있지 않은 것 같았다. 끝내 <a href="https://reactnavigation.org/docs/navigation-prop"><code>setOption</code></a>이라는 <code>navigtaiton</code>이 제공하는 프로퍼티를 이용하여  이 이슈를 해결할 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tailwind? Tailwind!]]></title>
            <link>https://velog.io/@jonghwan2_y/Tailwind-Tailwind</link>
            <guid>https://velog.io/@jonghwan2_y/Tailwind-Tailwind</guid>
            <pubDate>Thu, 14 Jul 2022 02:34:33 GMT</pubDate>
            <description><![CDATA[<h2 id="tailwind">Tailwind?</h2>
<p>Tailwind는 CSS 프레임워크 중 하나입니다. <code>Atomic CSS</code> 방법론과 함께 Tailwind CSS가 많은 주목을 받고 있습니다.</p>
<blockquote>
<p>Atomic CSS란, class 지정과 같은 legacy한 방식이 아닌 inline styling 또는 기능적인 방법론입니다.</p>
</blockquote>
<p>Legacy한 Styling 방식은 HTML 파일과 CSS 파일을 따로 관리하며, CSS 파일에 많은 Class 별 Styling을 작성하여 해당 Class를 HTML에 적용하는 식이었습니다. 하지만 Utility/Atomic CSS 방법론이 등장하면서 HTML 파일에 바로 Styling을 적용하는 프레임워크가 많이 등장하게 되었습니다. </p>
<p>사실 이러한 방식의 단점은 매우 분명했습니다. Web의 기본적인 철학(?)인 &#39;관심사 분리&#39;가 되지 않는다는 점이죠. 그럼에도 Class 선택자를 통한 작업을 하는 것보다 많은 장점이 있기에 그런 단점이 보완되는 것 같습니다.</p>
<h3 id="em-stylecolor-40bf10본격-탐구em"><em style="color: #40BF10">본격 탐구</em></h3>
<p>Tailwind Docs에서 제공하는 몇 가지 Core concepts을 간단히 소개합니다.</p>
<p><strong><div style="color: #6495ED">Utility-First</div></strong></p>
<blockquote>
<p>Utility Class란, 관련된 메서드를 사용하는 클래스를 모아 사용하여 중복된 코드가 발생하지 않고 효율적으로 관리하는 클래스를 의미합니다.</p>
</blockquote>
<p>Tailwind는 그 실용성을 &quot;Class 파일을 따로 사용하지 않는다&quot;는 점에서 찾고 있습니다. Class 파일을 따로 사용하지 않으면 얻게 되는 이점은 아래와 같습니다.</p>
<ul>
<li>클래스 이름을 지정하는데 시간 낭비를 하지 않아도 됩니다.</li>
<li>CSS 파일이 커지는 것을 막을 수 있습니다. 하지만 Tailwind의 경우 중복 Styling은 재사용이 가능합니다.</li>
<li>Styling을 변경할 때 어느 부분이 영향을 받게 될지 신경쓰지 않아도 됩니다.</li>
</ul>
<p>이렇게 utility class를 이용한 styling은 관리가 용이하여 유지 보수가 쉽기 때문에 이점이 많습니다.</p>
<p><strong><div style="color: #6495ED">Hover, Focus 외 다른 조건값</div></strong></p>
<p>Tailwind가 inline styling과 별반 다를 것 없다는 인상을 받았다면, 이 기능을 통해 차별성을 느끼게 될 것입니다. inline styling에서는 Hover나 Focus와 같은 조건에 따르는 Styling이 불가능하기 때문입니다. Tailwind에서는 아래처럼 utility class내에 <code>hover:</code> 일 때의 styling을 작성해 주면 됩니다.</p>
<div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/568fc661-2db5-4691-b291-d0c9779ed316/image.png" /></div>

<p>또한 다양한 스크린 환경에 따른 UI 구성을 위한 Responsive Design 또한 inline styling으로는 불가능하지만, (당연히) utility class로 작업이 가능합니다.</p>
<p><strong><div style="color: #6495ED">Mobile First</div></strong></p>
<p>위에서 언급된 Responsive Design에서 Tailwind의 컨셉을 조금 더 이해할 수 있는데요. 그것은 바로 <code>Mobile First</code> 입니다. media query를 통해 스크린 사이즈 별로 styling을 할 수 있는데, default 값은 mobile에서 사용하는 화면으로 값을 지정하는 것입니다. 만약 Web App를 만든다면 작은 사이즈에 적용될 styling이 기본이고, lg나 md 같은 media query를 지정하여 큰 사이즈에 적용될 styling을 작성하면 됩니다.</p>
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/e9962ab6-94cc-4b33-9abb-c6b7a1d245ef/image.png" />

<p><strong><div style="color: #6495ED">사용자 정의 1</div></strong>
Tailwind는 기본적으로 4px을 1단위로 정의된 컨벤션을 사용합니다. <code>p-2</code>는 <code>padding: 8px</code>의 styling을 할 수 있습니다. 만약 UI가 4px 단위로 사용되지 않을 경우 <code>[]</code>를 사용하여 정의해 주면 됩니다. <code>15px</code>의 padding을 주기 원할 경우 <code>p-[15px]</code> 이렇게 작성할 수 있습니다.</p>
<p><strong><div style="color: #6495ED">사용자 정의 2</div></strong>
Tailwind를 설치 후 <code>npx tailwindcss init</code>를 실행하면 <code>tailwind.config.js</code> 파일이 생성됩니다. Tailwind는 기본적으로 가장 많이 사용하는 styling을 기본 Theme로 제공하지만, 사용자가 해당 파일에서 Theme를 설정할 수 있습니다.</p>
<div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/ddfde737-9b9f-443f-a3e1-72bff2951d80/image.png"/></div>

<p><strong><div style="color: #6495ED">At-rules (a.k.a @)</div></strong></p>
<p>Tailwind를 처음 설정할 때 <code>global.css</code> 파일 내에 아래 세 가지를 작성해 줘야합니다.</p>
<div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/8b0c4e39-4746-4d99-ba12-197393787de8/image.png"/></div>

<p>위의 의미는 Tailwind의 base, components, utilities를 CSS에 적용하겠다는 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule">디렉티브</a>와 같은 기능을 합니다.</p>
<p>그럼 <code>@layer</code> 와 <code>@apply</code> 디렉티브를 알아볼까요?</p>
<ul>
<li><code>@layer</code>: 사용자 정의 styling을 base, components, utilities에 적용할 수 있고, 이는 <code>@tailwind</code> 디렉비트에 자동으로 포함됩니다.</li>
<li><code>@apply</code>: 기존에 사용하던 CSS styling과 비슷하게 class를 선언하고 styling을 정의할 수 있습니다. 문법은 Tailwind 문법 그대로 사용할 수 있어 유용하게 활용할 수 있습니다.</li>
</ul>
<p><strong><div style="color: #6495ED">Low Level 스타일</div></strong></p>
<p>Tailwind는 low-level 스타일링을 기본적으로 제공하기 때문에 사용자는 자신이 원하는 수준의 styling을 세밀하게 할 수 있는 장점이 있습니다. 다른 말로 하면 Tailwind의 usgae를 모두 외워야 한다는 의미인데 이를 보완하기 위해 Tailwind CSS IntelliSense를 제공하고 있습니다.
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/f256c5c0-b705-4021-b194-442da397ccba/image.png" alt=""></p>
<p>해당 플러그인을 활용하면 아래 그림과 같이 클래스명의 자동 완성을 제공하기 때문에 모든 usage를 외우지 않아도 됩니다.
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/f6c2751c-d97a-488d-9ec9-4a5a3563f538/image.png" alt=""></p>
<h2 id="tailwind-1">Tailwind!</h2>
<h3 id="em-stylecolor-40bf10설치해-봅시다em"><em style="color: #40BF10">설치해 봅시다.</em></h3>
<p><strong>1. 먼저 아래 명령어로 Tailwind와 구성에 필요한 설치를 진행합니다.</strong>
<code>npm install -D tailwindcss postcss autoprefixer</code></p>
<p>auto pixer와 같은 유용한 플러그인을 활용하기 위해 Post CSS도 설치하는 것을 권장합니다.</p>
<p><strong>2. 다음으로 postcss.confing.js 파일에 아래와 같이 플러그인 추가합니다.</strong></p>
<div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/d1194b70-bc38-42c0-a97c-763eff19251e/image.png" /></div>

<p><strong>3. 그리고 Tailwind 설정을 위해 아래 명령어를 실행합니다.</strong>
<code>npx tailwindcss init</code></p>
<p>위 명령어를 실행하면 tailwind.config.js 파일이 루트 위치에 생성됩니다. 해당 파일에서 다양한 theme 옵션을 활용하여 사용자 정의 styling을 작성할 수 있습니다. 위에서 언급한 바 와 같이 해당 파일에서는 아래 그림과 같이 Tailwind의 기본 구성 및 사용자 정의를 작성할 수 있습니다. 더 자세한 구성 방법은 <a href="https://tailwindcss.com/docs/configuration">여기</a>에서 참조할 수 있습니다.</p>
<div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/6c50ccd4-aea6-46ec-bc8f-526bec29f875/image.png" /></div>

<p><strong>4. 마지막으로 main CSS 파일에 아래와 같이 디렉티브(<a href="https://developer.mozilla.org/ko/docs/Web/CSS/At-rule">At Rules</a>)를 작성해 줍니다.</strong>
이렇게 작성해 두면 <code>@tailwind</code> 디렉티브를 통해 Tailwind로 작성된 모든 코드가 빌드 시에 CSS 코드로 변환이 됩니다.
  <div><img src="https://velog.velcdn.com/images/jonghwan2_y/post/8b0c4e39-4746-4d99-ba12-197393787de8/image.png"/> </div></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL. React Lifecycle]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.-React-Lifecycle</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.-React-Lifecycle</guid>
            <pubDate>Thu, 30 Jun 2022 09:25:41 GMT</pubDate>
            <description><![CDATA[<h2 id="중요하다-react-생명주기">중요하다 React 생명주기!</h2>
<p>React를 공부하면서 생명주기가 중요하다는 내용은 모두 안다. 생명주기 메서드 9개가 어떤 단계에서 쓰이고 있는지 정리되어 있는 블로그를 많이 보았다. 대강 이렇다.
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/56f862f6-dbd6-4c19-8913-748674efe2ae/image.png" alt=""></p>
<h3 id="왜-중요한거지">왜 중요한거지?</h3>
<p>React는 컴포넌트로 UI가 구분된다. 각 컴포넌트는 보여지고, 업데이트되고, 없어지는 단계를 거치는데 마치 생명주기를 보는 것 같다. React는 각 단계별로 자동으로 필요한 메서드를 호출하여 각 생애 단계를 잘 관리할 수 있기 때문에 중요한 것이다. 일단 생명주기 메서드는 클래스형 컴포넌트에서만 사용된다.</p>
<h3 id="mount-update-unmount">Mount, Update, Unmount</h3>
<h4 id="1-mount부터-알아보자">1) Mount부터 알아보자.</h4>
<blockquote>
<p>Mount = 컴포넌트가 실행</p>
</blockquote>
<p>메서드는,</p>
<ul>
<li>컴포넌트를 새로 만들 때 <strong><code>constructor</code></strong> 메서드를 실행한다. </li>
<li><strong><code>getDerivedStateFromProps</code></strong> 메서드가 실행되는데, Props에 따라 State의 값이 바뀌는 메서드인데, 거의 사용을 하지 않는다고 하고 React 공식 문서에서는 다른 대안이 많이 있으니 그거를 사용하라고도 한다.</li>
<li><strong><code>Rendering</code></strong> 메서드가 실행되면서 UI가 렌더링된다.</li>
<li>컴포넌트가 브라우저에 나타난 이후 <strong><code>componentDidMount</code></strong> 메서드가 실행된다.</li>
</ul>
<h4 id="2-아래-4가지-경우에-update된다">2) 아래 4가지 경우에 Update된다.</h4>
<blockquote>
<ul>
<li>props가 바뀔 때</li>
</ul>
</blockquote>
<ul>
<li>state가 바뀔 때</li>
<li>부모 컴포넌트가 리렌더링될 때</li>
<li>this.foceUpdate로 강제로 렌더링 트리거할 때</li>
</ul>
<p>메서드는,</p>
<ul>
<li><strong><code>getDerivedStateFromProps</code></strong> 메서드가 실행된다. Props에 의해 State 값이 업데이트될때 사용한다.</li>
<li><strong><code>shouldComponentUpdate</code></strong> 메서드를 통해 진위여부를 따진다. false라면 업데이트를 중지한다.</li>
<li><strong><code>Rendering</code></strong> 메서드가 실행되면서 UI가 렌더링된다.</li>
<li>업데이트가 되기 직전에 <strong><code>getSnapshotBeforeUpdate</code></strong> 메서드가 실행된다.</li>
<li>컴포넌트가 브라우저에 나타난 이후 <strong><code>componentDidMount</code></strong> 메서드가 실행된다.</li>
</ul>
<h4 id="3-unmount도-알아보자">3) Unmount도 알아보자.</h4>
<blockquote>
<p>Unmount = 컴포넌트를 DOM에서 제거</p>
</blockquote>
<p>메서드는,</p>
<ul>
<li>컴포넌트가 브라우저에서 사라지기 직전에 <strong><code>componentWillUnmount</code></strong> 메서드가 실행된다.</li>
</ul>
<h3 id="9개-메서드를-정리해보자면">9개 메서드를 정리해보자면,</h3>
<p><strong>1. constructor</strong> 
constructor 는 컴포넌트의 생성자 메서드이다. 컴포넌트가 만들어지면 가장 먼저 실행된다.</p>
<blockquote>
<p>1) this.state에 객체를 할당하여 지역 state를 초기화
2) 인스턴스에 이벤트 처리 메서드를 바인딩
this.props 와 this.state 에 접근할 수 있다.</p>
</blockquote>
<pre><code class="language-jsx">constructor(props) {
  super(props);
  // 여기서 this.setState()를 호출하면 안 됩니다!
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}</code></pre>
<p><strong>2. render</strong>
컴포넌트를 렌더링하는 메서드이다.</p>
<blockquote>
<p>1) this.props 와 this.state 에 접근할 수 있다.
2) 리액트 요소를 반환한다.</p>
</blockquote>
<p><strong>3. getDerivedStateFromProps</strong>
getDerivedStateFromProps 는 props 로 받아온 것을 state 에 넣어줄 때 실행되는 메서드이다. 컴포넌트가 Mount, Update 될 때 호출된다.</p>
<pre><code class="language-jsx">static getDerivedStateFromProps(nextProps, prevState) {
  console.log(&quot;getDerivedStateFromProps&quot;);
  if (nextProps.color !== prevState.color) {
    return { color: nextProps.color };
  }
  return null;
}</code></pre>
<p><strong>4. componentDidMount</strong>
컴포넌트의 첫 렌더링이 마치고 나면 호출되는 메서드이다.
이벤트 등록, <code>setTimeout</code>, <code>setInterval</code> 및 <code>axios</code>, <code>fetch</code>등과 같은 네트워크 요청 비동기 작업을 처리한다</p>
<p><strong>5. shouldComponentUpdate</strong>
컴포넌트를 리렌더링 할지 말지를 결정하는 메서드이고 성능최적화를 위해 필요하다. </p>
<blockquote>
<p>1) props나 state의 값을 비교하며 변경점을 확인한다.
2) 이 메서드 안에서 props 와 state 는 <code>this.props</code>, <code>this.state</code> 로 접근하고, 새로 설정될 props, state 는 <code>nextProps</code>, <code>nextState</code> 로 접근한다.</p>
</blockquote>
<pre><code class="language-jsx">shouldComponentUpdate(nextProps, nextState) {
  console.log(&quot;shouldComponentUpdate&quot;, nextProps, nextState);
  // 숫자의 마지막 자리가 4면 리렌더링하지 않습니다
  return nextState.number % 10 !== 4;
}</code></pre>
<p><strong>6. getSnapshotBeforeUpdate</strong>
컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져온다.이 메서드를 통해 컴포넌트가 DOM으로부터 스크롤 위치 등과 같은 정보를 이후 변경되기 전에 얻을 수 있다. 이 생명주기 메서드가 반환하는 값은 componentDidUpdate()에 인자로 전달된다.</p>
<pre><code class="language-jsx">class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length &lt; this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we&#39;ve just added new items.
    // Adjust scroll so these new items don&#39;t push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      &lt;div ref={this.listRef}&gt;{/* ...contents... */}&lt;/div&gt;
    );
  }
}</code></pre>
<p><strong>7. componentDidUpdate</strong>
컴포넌트 리렌더링 이후에 호출되는 메서드이다. DOM 관련 처리를 할 수 있다.</p>
<blockquote>
<p><code>prevProps</code>, <code>prevState</code> 를 통해 이전 props, state 값을 확인할 수 있다.
<code>getSnapshotBeforeUpdate</code>에서 반환한 값은 여기에서 <code>snapshot</code> 인자로 받을 수 있다.</p>
</blockquote>
<p><strong>8. componentWillUnmount</strong>
컴포넌트가 화면에서 제거되기 직전에 호출되는 메서드이다.</p>
<blockquote>
<p>이벤트, 타이머, 직접 생성한 DOM같은 경우, 제거 작업된다.</p>
</blockquote>
<p><strong>9. componentDidCatch5</strong>
리액트 16 이후에 추가된 메서드이고 컴포넌트 렌더링 도중에 에러가 발생했을 때 커스텀한 오류 UI를 보여 줄 수 있다.</p>
<p>참고:
<a href="https://velog.io/@youngminss/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0-%EB%A9%94%EC%84%9C%EB%93%9C">[React] 컴포넌트 생명주기 메서드 
</a><a href="https://react.vlpt.us/basic/25-lifecycle.html">LifeCycle Method
</a><a href="https://ko.reactjs.org/docs/react-component.html">React.Component
</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.클로저(Closure)]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.%ED%81%B4%EB%A1%9C%EC%A0%80Closure</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.%ED%81%B4%EB%A1%9C%EC%A0%80Closure</guid>
            <pubDate>Tue, 14 Jun 2022 15:40:38 GMT</pubDate>
            <description><![CDATA[<h3 id="정의-🙄">정의 🙄</h3>
<blockquote>
<p>함수와 함수가 선언된 어휘적 환경의 조합이다(<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures">MDN</a>)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jonghwan2_y/post/9623894b-86a2-4ca1-bd43-8e338df03eec/image.gif" alt="">
<em>그래..그렇구나..그런거구나..</em></p>
<h3 id="1-어휘적-환경-lexical-environment">1. 어휘적 환경 (Lexical Environment)</h3>
<h4 id="코드로-보자">코드로 보자.</h4>
<pre><code class="language-js">let one;
one = 1;

function addOne(num) {
  console.log(one + num);
}

addOne(5);</code></pre>
<ol>
<li><p>첫번째 어휘적 환경이다. (전역 Lexical Env)</p>
<blockquote>
<ol>
<li>변수 <code>one</code> 이 선언되었다. (초기화 전이라 사용 못함, 초기화 이후 사용 가능)</li>
<li>함수 <code>addOne</code> 이 선언되었다. (함수는 선언+초기화+할당을 동시에 하므로 사용은 가능하다)</li>
</ol>
</blockquote>
</li>
<li><p>새로운 어휘적 환경이 생겼다. (내부 Lexical Env)</p>
<blockquote>
<p>addOne 함수를 실행하면서 넘긴 매개변수 num = 5이 저장되었다.</p>
</blockquote>
</li>
</ol>
<h4 id="두-어휘적-환경은-관계">두 어휘적 환경은 관계!</h4>
<p><span style="color: #c7011e">내부 Lexical 환경은 외부 &gt; 외부 &gt; ... &gt; 전역 Lexical 환경에 이르기까지 참조한다.</span></p>
<h4 id="다른-코드를-보자">다른 코드를 보자.</h4>
<pre><code class="language-js">function makeAdder (x) {
  return function(y) {
    return x + y;
  }
}

const add3 = makeAdder(3);
console.log(add3(2));</code></pre>
<ol>
<li><p>전역 Lexical 환경에는 아래 2가지가 저장된다.</p>
<blockquote>
<ol>
<li>makeAdder</li>
<li>add3</li>
</ol>
</blockquote>
</li>
<li><p>add3은 makeAdder를 호출하고 있기 때문에 <code>makeAdder</code> Lexical 환경이 생겨난다.</p>
<blockquote>
<p>x = 3</p>
</blockquote>
</li>
<li><p>console.log를 통해 <code>익명함수</code> Lexical 환경이 생겨난다.</p>
<blockquote>
<p>y = 2</p>
</blockquote>
</li>
</ol>
<h3 id="정리하자">정리하자.</h3>
<p><code>makeAdder</code> 함수 내에 있던 익명함수는 자신의 매개변수 y를 지역변수로 가지고, 상위함수인 <code>makeAdder</code>의 매개변수 x에도 접근이 가능하다. </p>
<pre><code class="language-js">const add10 = makeAdder(10);
console.log(add10(3)); // 13 - add10함수가 생성된 이후에도 10이라는 외부 지역변수에 접근이 계속 된다.</code></pre>
<h2 id="이것이-클로저이다">이것이 <strong>클로저</strong>이다.</h2>
<blockquote>
<ol>
<li>함수와 렉시컬 환경의 조합</li>
<li>함수가 생성될 당시의 외부 변수를 기억하고 생성 이후에도 계속 접근 가능</li>
<li>add10이 외부 변수 10을 참조하고 있는지 아무도 모른다. <strong>은닉화</strong>가 이뤄졌다.</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jonghwan2_y/post/4bde234e-e570-4704-9911-c85b50c2b777/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.호이스팅과 TDZ]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.%EB%B3%80%EC%88%98%EC%99%80-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.%EB%B3%80%EC%88%98%EC%99%80-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Tue, 14 Jun 2022 14:35:17 GMT</pubDate>
            <description><![CDATA[<h2 id="변수">변수</h2>
<table>
<thead>
<tr>
<th>var</th>
<th>let</th>
</tr>
</thead>
<tbody><tr>
<td>같은 이름으로 두 번 선언 가능</td>
<td>같은 이름으로 두 번 선언 불가</td>
</tr>
</tbody></table>
<h2 id="호이스팅이란">호이스팅이란?</h2>
<p>스코프 내부 어디서든 변수 선언이 최상위에서 선언된 것처럼 행동하는 것</p>
<h3 id="1-var는-선언하기-전에-사용할-수-있다">1. var는 선언하기 전에 사용할 수 있다.</h3>
<pre><code class="language-js">console.log(name); // undefined

var name = &#39;Mike&#39;</code></pre>
<p>위 코드는 아래처럼 동작한다.</p>
<pre><code class="language-js">var name; // 호이스팅

console.log(name); // undefined 

name = &#39;Mike&#39;;</code></pre>
<p><strong>var</strong>로 선언한 변수는 선언하기 전에도 사용할 수 있다. 호이스팅 되기 때문이다. 그러나 선언은 호이스팅되지만 할당된 값은 호이스팅 되지 않기 때문에 undefinded가 나온다. </p>
<h3 id="2-let도-호이스팅이-된다-그러나">2. let도 호이스팅이 된다. 그러나...</h3>
<pre><code class="language-js">console.log(name); // ReferenceError

let name = &#39;Mike&#39;</code></pre>
<p>let이나 const 모두 호이스팅된다. 근데 왜 위 코드처럼 <strong>let</strong>에는 ReferenceError가 뜰까?</p>
<h3 id="3-tdztemporal-dead-zone">3. TDZ(Temporal Dead Zone)</h3>
<p>변수는 생명주기는 세 가지로 구분된다.</p>
<ul>
<li>선언단계: 실행 컨텍스트의 변수 객체에 등록하는 단계</li>
<li>초기화단계: 등록된 변수를 위한 메모리를 만들고 &#39;undefined&#39;로 초기화하는 단계</li>
<li>할당단계: 사용자가 정의한 값으로 &#39;undefined&#39;가 교체되는 단계</li>
</ul>
<h4 id="그렇다면-var의-생명주기는">그렇다면, var의 생명주기는?</h4>
<p><img src="https://velog.velcdn.com/images/jonghwan2_y/post/7fb5891f-8a16-40a8-9a2d-4cfedb5944fa/image.png" alt=""></p>
<blockquote>
<p>선언과 초기화가 동시에 이뤄진다. 그래서 var로 변수 선언 전에 호출해도 undefined가 뜬다.</p>
</blockquote>
<h4 id="그렇다면-let의-생명주기는">그렇다면, let의 생명주기는?</h4>
<p><img src="https://velog.velcdn.com/images/jonghwan2_y/post/20b509af-bccc-42ca-aee8-54052dcc856a/image.png" alt=""></p>
<blockquote>
<p>선언과 초기화가 분리되어 진행된다. 초기화가 되지 않아 ReferenceError가 나오게된다. (호이스팅이 안된게 아니다!!) 선언과 초기화 사이의 상태가 TDZ이다.</p>
</blockquote>
<h4 id="그렇다면-const-생명주기는">그렇다면, const 생명주기는?</h4>
<blockquote>
<p>선언 + 초기화 + 할당이 동시에 일어난다. 함수도 동시에 일어난다.</p>
</blockquote>
<h3 id="추가">추가</h3>
<table>
<thead>
<tr>
<th>var</th>
<th>let</th>
<th>const</th>
</tr>
</thead>
<tbody><tr>
<td>함수 스코프</td>
<td>블록 스코프</td>
<td>블록 스코프</td>
</tr>
</tbody></table>
<blockquote>
<p>var의 경우 블록 밖에서도 사용이 가능하지만, let과 const는 블록 밖에서 사용할 수 없다 (지역변수). 그러나 함수 스코프는 함수 내에서 선언한 변수만 지역변수가 된다. 고로 함수 내부에서 var로 선언된 변수는 바깥에서 사용할 수 없다.</p>
</blockquote>
<p>참조:
<a href="https://noogoonaa.tistory.com/78">https://noogoonaa.tistory.com/78</a>
<a href="https://www.youtube.com/watch?v=ocGc-AmWSnQ">https://www.youtube.com/watch?v=ocGc-AmWSnQ</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Final Project_2nd회고
]]></title>
            <link>https://velog.io/@jonghwan2_y/Final-Project2nd%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jonghwan2_y/Final-Project2nd%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 05 May 2022 04:35:35 GMT</pubDate>
            <description><![CDATA[<h2 id="span-stylecolorf7dc6fprojectspan"><span style="color:#F7DC6F">Project</span></h2>
<h3 id="em-stylecolorgraygitbookem"><em style="color:gray"><a href="https://www.gitbook.com/">Gitbook</a></em></h3>
<p>우리가 어떤 API를 사용할 것인지 정리하는 플랫폼이다. 뿐만 아니라, 블로그 용도로도 쓰일 수 있다고 한다.
이번 프로젝트는 화장실 리뷰 서비스인 만큼, 기본적인 CRUD API를 작성했다.</p>
<h3 id="em-stylecolorgrayeslint-prittier-설정em"><em style="color:gray">ESLINT, Prittier 설정</em></h3>
<p>협업 과정에서 서로 다른 코드 스타일링의 충돌을 줄여주는 설정이 필요했다. 또한 효율적인 코드 작성을 위해 linter 설정도 필요했다.
우리는 ESlint와 Prettier을 사용했다. </p>
<ul>
<li>VS code 익스텐션 설치</li>
<li>Node.js 모듈 설치</li>
</ul>
<h2 id="span-stylecolord7bde2kptspan"><span style="color:#D7BDE2">KPT</span></h2>
<h3 id="keep">Keep</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> 새로운 언어에 대한 공부!</li>
<li><input checked="" disabled="" type="checkbox"> 공식 문서 두려움 없이 읽기!</li>
<li><input checked="" disabled="" type="checkbox"> 목표한 것을 이루기 위해 포기하지 않기!</li>
</ul>
<h3 id="problem">Problem</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> Kakao Map API 파악🤔</li>
</ul>
<h3 id="try">Try</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> Redux Persist 라이브러리로 지도 데이터를 상태값으로?</li>
<li><input checked="" disabled="" type="checkbox"> Typescript + React 제대로 이해하기🔥</li>
</ul>
<h2 id="span-stylecolora2d9cepersonalspan"><span style="color:#A2D9CE">Personal</span></h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[Final Project_1st회고]]></title>
            <link>https://velog.io/@jonghwan2_y/First-Project1st%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jonghwan2_y/First-Project1st%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 27 Apr 2022 13:33:52 GMT</pubDate>
            <description><![CDATA[<h2 id="span-stylecolorf7dc6fprojectspan"><span style="color:#F7DC6F">Project</span></h2>
<p>Final Project가 시작되었다.
First Project가 극찬을 받았다. 팀 멤버 모두가 고생했다. </p>
<h3 id="em-stylecolorgray뭐하지em"><em style="color:gray">뭐하지?</em></h3>
<p>공공 데이터 API를 활용하고 싶었다. 화장실 리뷰 서비스를 제안했다. 팀원 모두 너무 좋다했다. 
찾아보니 서울시에서 제공하는 공공 데이터 API가 있었다.
카카오 지도 API를 활용하여 <span style="color: gray">호갱노노</span> 비스무레한 서비스를 만들어 보자!</p>
<h3 id="em-stylecolorgray공부em"><em style="color:gray">공부</em></h3>
<p>Typescript와 TypeORM을 적용하기로 했다. 한 주동안 TS공부를 했다. 
아직 걸음마 단계이고, 아직 프로젝트에 적용해 보지 않았지만 TS의 강력함은 어느 정도 이해했다.</p>
<h2 id="span-stylecolord7bde2kptspan"><span style="color:#D7BDE2">KPT</span></h2>
<h3 id="keep">Keep</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> 새로운 언어에 대한 공부!</li>
<li><input checked="" disabled="" type="checkbox"> 공식 문서 두려움 없이 읽기!</li>
<li><input checked="" disabled="" type="checkbox"> 목표한 것을 이루기 위해 포기하지 않기!</li>
</ul>
<h3 id="problem">Problem</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> Typescript와 React 동시 작업에 대한 이해 부족🤔</li>
</ul>
<h3 id="try">Try</h3>
<ul>
<li><input checked="" disabled="" type="checkbox"> TypeORM 개념 정리</li>
<li><input checked="" disabled="" type="checkbox"> Typescript + React 제대로 이해하기🔥</li>
</ul>
<h2 id="span-stylecolora2d9cepersonalspan"><span style="color:#A2D9CE">Personal</span></h2>
<p>지난 40주가 어떻게 지나갔나 싶을 정도로 정신없이 흘러갔다.
프로젝트까지 할 수 있을까 걱정이 많았는데 감사하게 Final Project를 진행하고 있다.</p>
<p>체력이 부칠 때도 있지만, 마지막에 유종의 미를 얻고자
정말 열심히 공부하고 있다.</p>
<p>끝까지 잘하자...👌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.Typescript_0415]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.Typescript0415</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.Typescript0415</guid>
            <pubDate>Sat, 16 Apr 2022 02:22:04 GMT</pubDate>
            <description><![CDATA[<h3 id="typescript는-왜-공부해야-할까">Typescript는 왜 공부해야 할까?</h3>
<p><em style="color:gray">Superset of Javascript</em></p>
<p>동적 언어인 Javascript는 Runtime에서 타입을 결정하지만, 정적 언어인 Typescript는 Complie 과정에서 타입을 결정합니다.</p>
<p>즉, Javascript의 경우 에러를 user가 발견하는 확율이 높고, Typescript는 개발 환경에서 에러를 발견할 수 있는 확율이 높습니다. </p>
<p>코드를 작성하는 중에 에러를 핸들링하는 것과 모든 코드를 작성하고 에러를 확인하는 것은 생산성 측면에서 대단한 차이를 줍니다.</p>
<p>간단한 코드를 작성하는 경우 human error가 발생할 확율이 그리 높지 않지만 코드가 방대해 지는 경우 코드도 결국 사람이 작성하는 것이기 때문에 human error가 발생할 확율이 높습니다. 이럴 때 Typescript는 매우 강력한 도구가 될 수 있습니다.</p>
<h3 id="타입-정의">타입 정의</h3>
<p>1) Javascript에서 우리는 아래처럼 변수를 선언했습니다.</p>
<pre><code class="language-js">let myName = &quot;peter&quot;</code></pre>
<p>하지만 Typescript에서는 아래와 같이 변수를 선언합니다.</p>
<pre><code class="language-ts">let myName: string = &quot;peter&quot;</code></pre>
<p>2) 객체 타입의 변수의 경우 <code>interface</code>를 사용합니다.</p>
<pre><code class="language-ts">interface User {
  name: string;
  age: number;
}

const user: User = {
  name: &quot;peter&quot;,
  age: 22
}</code></pre>
<p>3) Javascript는 class를 지원하고 객체 지향 프로그래밍을 지원하기 때문에 Typescript에서도 아래와 같이 class를 작성할 수 있습니다.</p>
<pre><code class="language-ts">interface User {
  name: string;
  age: number;
}

class UserAccount {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const user: User = new UserAccount(&quot;peter&quot;, 22)</code></pre>
<h3 id="union">Union</h3>
<p>단순한 타입을 조합하여 <code>literal</code>한 string, number 등을 정의할 수 있습니다.</p>
<pre><code class="language-ts">// 문자열 리터럴 타입 (Union types)
type Score = &#39;A&#39; | &#39;B&#39; | &#39;C&#39; | &#39;F&#39;

interface User {
    name: string
    age: number
    [grade: number]: Score

let user: User = {
    name: &quot;peter&quot;,
    age: 22,
    1: &quot;A&quot;,
    2: &quot;B&quot;
}</code></pre>
<h3 id="generic">Generic</h3>
<p>아래와 같은 함수가 있다고 생각해 봅시다. 
숫자로 이뤄진 배열만 parameter로 받고 있기 때문에 <code>[&quot;1&quot;, &quot;2&quot;]</code>와 같이 문자로 이뤄진 배열을 함수에 넣으면 에러가 발생합니다.</p>
<pre><code class="language-ts">function getSize (arr: number[]) : number {
  return arr.length
}</code></pre>
<p>이럴 때 우리는 <code>Generic</code>를 사용할 수 있습니다. <code>&lt;T&gt;</code>를 함수 이름에 붙여서 다양한 Type을 사용할 수 있도록 합니다.</p>
<pre><code class="language-ts">function getSize&lt;T&gt; (arr: T[]) : number {
  return arr.length
}

// 에러 없이 아래 두 함수를 호출할 수 있습니다.
const arr1 = [1, 2, 3]
getSize&lt;number&gt; (arr1)

const arr2 = [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]
getSize&lt;string&gt; (arr2)
</code></pre>
<p>Generic을 활용하면 하나의 interface를 설정하면 다양한 모습의 객체를 만들 수 있습니다.</p>
<pre><code class="language-ts">interface Mobile &lt;T&gt; {
  name: string
  price: number
  option: T
}

const mobile1: Mobile &lt;string&gt; = {
  name: &quot;iPhon12&quot;,
  price: 1000
  option: &quot;Wifi&quot;
}

const mobile2: Mobile &lt;{color: string coupon: boolean}&gt; = {
  name: &quot;iPhon13&quot;,
  price: 1100
  option: {color: &quot;red&quot;, coupon: false}
}</code></pre>
<h3 id="structural-type-system">Structural Type System</h3>
<p>Typescript의 큰 특징 중 하나는, 타입의 모양을 통해 타입을 checking할 수 있다는 점입니다.</p>
<pre><code class="language-ts">interface Point {
  x: number;
  y: number;
}

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

const point = { x: 12, y: 26 };
logPoint(point);</code></pre>
<p>위 그림에서 <code>point</code> 객체의 경우, 타입을 정해주지 않았지만 <code>Point</code> interface에서 정의된 타입으로 유추하여 에러로 확인하지 않습니다.</p>
<p>다만. 아래와 같이 타입의 모양으로 유추할 수 없는 객체가 parameter로 들어오게 된다면 에러로 확인하게 됩니다.</p>
<pre><code class="language-ts">const color = { hex: &quot;#187ABF&quot; };
logPoint(color);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[First Project 회고]]></title>
            <link>https://velog.io/@jonghwan2_y/First-Project-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jonghwan2_y/First-Project-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 13 Apr 2022 16:01:19 GMT</pubDate>
            <description><![CDATA[<h2 id="devengers의-탄생">Devengers의 탄생</h2>
<p>36주간 교육 세션을 무사히 마치고 꿈에 그렸던 프로젝트를 시작하게 되었다.
과연 내가 3번의 HA를 무사히 통과할 수 있을까 고민하며 열심히 공부해온 결과라 생각이 든다.</p>
<p>팀 선정 기간을 거쳐 나를 포함한 4명의 멤버가 한 팀이 되었다. 
모두 Pair program에서 같이 공부했던 동기분들이라 금방 팀에 녹아들 수 있었다. </p>
<p>나름 사회 생활(?)을 해본 나로서는 프로젝트에 있어서 무엇보다 팀웍이 중요하다는 사실을 알고 있었다.
프로젝트 SR 미팅에서는 나는 서로의 이름이 아닌 nickname을 정해보자 제안했다. </p>
<p>수평적 관계에서 자유롭게 커뮤니케이션을 할 수 있어야 좋은 결과물을 도출해 낼 수 있을 것이라 생각했기 때문이다.</p>
<blockquote>
<center>
  <em>
    서로 nickname으로 불러볼까요?
  </em>
</blockquote>
</center>

<p>그렇게 우리는 Dev + Avengers =&gt; <strong>Devengers</strong>가 되었고, 나는 팀에서 스트레인지로 불리게 되었다.</p>
<p>
  <img src=https://velog.velcdn.com/images/jonghwan2_y/post/896de952-5bf9-4c62-a767-5c8fae77d20f/Asset_4.png>
</img>
<center>
  <em>
    우리의 자랑스런 Team 로고
  </em>
</center>
</p>

<h2 id="이번에는">이번에는...</h2>
<h3 id="첫-software-requirement">첫 Software Requirement</h3>
<p>다양한 아이디어가 나왔다. 첫 프로젝트이기도 하고 4주 정도 그 동안 학습해온 내용을 공부하자는 차원으로 CRUD가 기본적으로 되는 성격의 아이디어를 선정했다. </p>
<blockquote>
<center>
  <em>
    투표
  </em>
</blockquote>
</center>

<p>밸런스 게임에서 착안해 낸 아이디어였다. 둘 중 하나를 선택하는 것은 여간 쉬운 일이 아니다. 그래서 다른 사람들은 어떻게 생각하는지 물어볼 수도 있고, 내가 직접 그런 질문에 답을 해줄 수 있는 그런 서비스를 만들기로 기획했다.
생각보다 기획이 실제 코드 작성 만큼이나 중요한 것이란 걸 많이 배울 수 있었다.</p>
<h3 id="front-end를-해보려고요">Front-end를 해보려고요</h3>
<p>사실 나의 최종 목표는 <em>Front-end도 할 줄 아는 Back-end 개발자</em> 이다.
다른 거창한 말이 따로 있지만, 그 말을 붙이기에는 아직 부족한게 많아서 저렇게 불러본다.</p>
<p>나의 계획은 이렇다.  이번 First-Project에서는 Front-end를 직접 해보면서 중요한 stack들을 다시금 내 것으로 만들어보고, Final Project에서는 Back-end를 제대로 해보는 것이다.</p>
<h3 id="손으로-그린-형편없는-ui-디자인에서-figma까지">손으로 그린 형편없는 UI 디자인에서 Figma까지</h3>
<p>다른 투표 레퍼런스를 참조하여 간단한 wireframe을 만들어 보려고 했다. 
나름 역사적인(?) 우리 서비스의 hand writing wireframe이다.😅 </p>
<center><img src=https://velog.velcdn.com/images/jonghwan2_y/post/c8f8039b-4dc2-4bef-a970-1c7281a0fbbc/%E1%84%90%E1%85%AE%E1%84%91%E1%85%AD%20%E1%84%8C%E1%85%A1%E1%86%A8%E1%84%89%E1%85%A5%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5%20%E1%84%86%E1%85%A9%E1%84%83%E1%85%A1%E1%86%AF%E1%84%8E%E1%85%A1%E1%86%BC.png height="200px" width="500px"></center>
<center>
  <em style="color:gray">
    이렇게 하는 건가요?
  </em>
</center>

<p>우리가 구현해야 하는 각 페이지를 발이 아닌 손으로 직접 그려본 이후에는 <a href="figma.com">Figma</a>를 이용하여 실제 wireframe을 만들었다. 유투브 강의나 블로그 등을 참조했다. </p>
<center><img src=https://velog.velcdn.com/images/jonghwan2_y/post/91b7032d-84b1-4c59-9016-82fa812b32e8/%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%202022-04-09%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.46.56.png height="50px" width="200px"></center>
<center>
  <em style="color:gray">
    Figma로 만들어 본 wireframe
  </em>
</center>

<h3 id="하얀-도화지-위-creat-react-app-">하얀 도화지 위 creat-react-app ...</h3>
<p>그 동안의 Sprint에서는 잘 만들어진 코드에서 학습 과정에 필요한 테스트 등을 많이 해보았지만...
커서만 깜빡이는 vs code에 뭐 부터 시작을 해야할지... 마치 하얀 도화지 위에 그림을 그려야 하는 막막함이 있었다. 그렇게 우리는 타이핑했다. <code>npx creat-react-app</code></p>
<h3 id="git-branch-관리">Git branch 관리</h3>
<p>실제로 팀 프로젝트를 진행해 보니 Git branch의 역할이 얼마나 중요한지 새삼 깨닳았다.
수없이 친 <code>git pull upstream dev</code>도 못 잊을 것 같다.
<img src="https://velog.velcdn.com/images/jonghwan2_y/post/932f4352-c5f7-458c-a6ff-1f0482a87aa3/image.png" alt=""></p>
<p>우리는 위에 정리된 Git flow를 철저하게 지켰다. 각자 작업 전에는 upstream에서 pull로 가져와 작업을 했다. 그리고 개인이 맡은 작업의 경우 새 feature 브랜치를 생성하여 작업 후 나의 remote origin 레포에 push 후 PR을 날리고, Github에서 merge하는 일련의 과정을 꼭 지키려고 노력했다. 그래서였는지 4명의 멤버가 협업하면서 큰 충돌 없이 프로젝트를 마무리할 수 있었다.</p>
<h3 id="tailwind-css이런-거였다니">Tailwind CSS...이런 거였다니</h3>
<p>TailwindCSS는 CSS라이브러리가 아니다. CSS 기술의 방법론적인 차원의 프레임워크이다. 인라인 스타일을 사용하기 때문에 클래스를 지정하고, 수정 시 클래스명을 찾아야하는 수고로움에서 벗어나, 스타일을 빠르게 적용하고 확인할 수 있었다.</p>
<h3 id="brand-color-span-stylecolor-7ce0ae7ce0aespan-그리고-span-stylecolor-ffee93ffee93span">Brand color: <span style="color: #7CE0AE">#7CE0AE</span> 그리고 <span style="color: #FFEE93">#FFEE93</span></h3>
<p>4주 프로젝트라고해서 디자인요소를 대강 만들지 않았다. 서비스만의 brand color를 정하고 기본적인 UI 디자인의 tone&amp;manner를 정했다. <code>투표</code>라는 이미지가 어떻게 보면 딱딱해 보일 수 있는 콘텐츠이기 때문에, 파스텔 톤의 색상을 사용하고 심플한 UI를 사용했다. </p>
<center><img src=https://velog.velcdn.com/images/jonghwan2_y/post/4972754b-8e58-4c1e-a736-bebbe08815af/image.png height="50px" width="200px"></center>
<center>
  <em style="color:gray">
    그리고 로고도 이렇게 만들었다.
  </em>
</center>

<h3 id="달고-살았던-api-문서-feat-gitbook">달고 살았던 API 문서 (feat. GitBook)</h3>
<p>작업을 하면서 Backend에서 작성한 API 문서를 달고 살았다. <code>REST API</code>를 정확하게 따랐는지는 확신이 서지 않았지만, 나름 규칙을 지켜 만든 API 문서였다. 수업에서 해보지 않은 <code>PATCH</code>나 <code>DELETE</code> 메소드를 사용해서 회원 정보 수정이나 탈퇴 등을 구현했다. </p>
<h3 id="살짝-맛만-본-redux-toolkit">살짝 맛만 본 Redux-Toolkit</h3>
<p>우리가 작성한 component 들은 많은 depth의 부모 자식 관계가 존재하지 않기 때문에 props drilling 심각하게 발생하지는 않았다. 다만 jwt로 생성한 accessToken과 refreshToken 중 accessToken을 전역 상태로 관리하여 각 페이지에서 활용하기 위해 <code>redux</code>를 사용했다. 맛을 본 정도로만 사용을 했지만, <code>useDispatch</code>, <code>useStore</code>, <code>slice</code>, <code>store</code> 등을 어떻게 설정하고 활용할 수 있는지 학습할 수 있었다.</p>
<h2 id="다음에는">다음에는...</h2>
<h3 id="토큰은-localstorage로">토큰은 localStorage로...</h3>
<p>새로 고침을 하거나 새 탭을 열었을 때 로그인 및 토큰의 유효성을 유지하고자 했지만, 우리가 작성했던 코드는 토큰을 상태값으로 보관하여 해당 구현에 대한 부침이 있었다. 그러나 app.js 내에 (쿠키에 저장되어 있는) refreshToken으로 accessToken을 갱신하는 콤포넌트를 가장 상단의 위치에 넣어주어 모든 페이지마다 accessToken과 로그인 상태가 유지될 수 있도록 하여 문제를 해결할 수 있었다. 다만, 이런 갱신의 작업을 매 페이지에서 해주는 방법보다는 localStorage에 토큰을 넣어주어 작업을 할 수 있지 않앗을까 하는 질문을 스스로 던지고 마무리했다.</p>
<h3 id="tailwind와-redux를-마스터하자">tailwind와 redux를 마스터하자</h3>
<p>이번에 사용했던 두 stack을 마스터하는 것이 목표이다. (아직 Final 프로젝트에서 Frontend를 하는 것으로 정해지지 않았지만) 궁극적인 목표인 full-stack이 되기위해, 더 파고 들기로 했다. </p>
<h3 id="typesript가-뭐야">typesript가 뭐야</h3>
<p>사실 벌써 우리 팀은 typescript 공부 중이다. JS의 superset이라는 TS는 말그대로 JS의 compiler이다. 런타임 중에 타입을 확인하는 JS아 달리 TS는 컴파일 중에 타입을 확인하기 때문에 Error를 사용 당시가 아닌 개발 당시에서 확인 가능하다는 장점과 오타를 확인해 주어 Editor로서의 역할도 해주는...장점이 많은 언어라고 알고 있다. 이번 기회에 친해지자...</p>
<h2 id="마치며">마치며...</h2>
<p>이렇게 열정적이고 성실하고 팀웍이 좋은 동료들을 만날 수 있을지 몰랐다. 사실 팀 배정 전부터 마음 속으로 바랐다. 좋은 멤버들과 재미있게 공부할 수 있었으면 좋겠다고.</p>
<p>앞으로 우리가 어떻게 성장하고 어느 자리에서 어떤 일을 하게 될지 아직 잘 모르지만...오래동안 함께 즐겁게 개발하자는 약속을 맺으며 첫 번째 프로젝트를 마무리할 수 있어서 감사했다.</p>
<p><strong>Enjoy Coding, Upgrade together.</strong></p>
<table>
<thead>
<tr>
<th align="center">Hulk</th>
<th align="center">Dr.strange</th>
<th align="center">Spiderman</th>
<th align="center">Wanda</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/66931635/161848777-17c6c255-3eb2-4bb0-a24d-b6dbefd99bf3.png" alt="hulk"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/66931635/161849438-36c02e26-e098-4899-bf03-66dcc687b09b.png" alt="strange"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/66931635/161849454-a2fee3ca-1660-44f7-ad09-74a099dc256b.png" alt="spiderman"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/66931635/161849467-a8ebf895-8261-413b-92db-0204c0fb1385.png" alt="wanda"></td>
</tr>
</tbody></table>
<blockquote>
<p>아쉽지만 First project는 두서없는 회고만 적게 되었다. 앞으로 진행하게 될 Final project의 경우 매일 TIL형식으로 기록을 할 예정이다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.Garbage Collection 및 Cashing]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.Garbage-Collection-%EB%B0%8F-Cashing</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.Garbage-Collection-%EB%B0%8F-Cashing</guid>
            <pubDate>Wed, 09 Feb 2022 12:21:43 GMT</pubDate>
            <description><![CDATA[<h3 id="가비지-컬렉션">가비지 컬렉션</h3>
<ul>
<li>더 이상 사용하지 않는 메모리를 자동으로 삭제 (자바, C#, 자바스크립트 등에서 쓰인다)</li>
<li>C++, C에는 가비지 컬렉터가 내장되어있지 않다.</li>
</ul>
<h4 id="대표적인-가비지-컬렉션-종류">대표적인 가비지 컬렉션 종류####</h4>
<ul>
<li>트레이싱: 객채에 in-use flag를 마크하고 가비지 컬렉션 사이클마다 마크되지 않은 객체를 삭제</li>
<li>레퍼런스 카운팅: 변수가 참조될 때 마다 카운트가 올라가고, 참조하고 있는 변수 값이 바뀌거나 변수 스코프를 벗어나면 카운트가 줄어든다. 카운트가 0이 되면 참조가 되지 않고 있다고 보고 관련 객체의 메모리를 지울 수있다.</li>
</ul>
<p><strong>Chrome이나 Node.js v8 엔진</strong>의 경우 어떻게 가비지 컬렉팅?</p>
<h3 id="캐싱">캐싱</h3>
<p>일시적인 특징이 있는 데이터 하위 집합을 저장하는 고속 데이터 스토리지 계층 =&gt; 해당 데이터에 대한 요청이 있을 경우 실제 데이터 접근보다 빠르게 접근이 가능하다</p>
<ul>
<li>저장: RAM(Random Access Memory)과 같이 빠르게 접근할 수 있는 하드웨어에 저장</li>
</ul>
<h4 id="캐싱이-적용되는-예제">캐싱이 적용되는 예제</h4>
<ul>
<li>클라이언트: HTTP 캐시 헤더, 브라우저</li>
<li>네트워크: DNS 서버, HTTP 캐시 헤더, CDN, 리버스 프록시</li>
<li>서버 및 데이터베이스: 키-값 데이터 스토어(e.g. Redis), 로컬 캐시(인-메모리, 디스크)</li>
</ul>
<h4 id="계층과-이점">계층과 이점</h4>
<img width="1234" alt="스크린샷 2022-01-29 오후 11 59 44" src="https://user-images.githubusercontent.com/84922505/151665845-ec0587b8-e873-4080-b28e-928815c1a390.png">
* 데이터베이스의 부하를 줄일 수 있다.
* 인 메모리 캐시를 사용하면 데이터를 읽는 속도를 높일 수 있다.
* 디스크를 사용하는 데이터베이스에 비해, 주로 인메모리를 사용하는 캐시 때문에 더 빠른 읽기 작업을 할 수 있다.

<h4 id="단점">단점</h4>
<ul>
<li>아키텍처 계층이 많아진다.</li>
<li>일시적인 데이터만 저장 가능하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.Encoding 및 UTF-8]]></title>
            <link>https://velog.io/@jonghwan2_y/Encoding-%EB%B0%8F-UTF-8</link>
            <guid>https://velog.io/@jonghwan2_y/Encoding-%EB%B0%8F-UTF-8</guid>
            <pubDate>Wed, 09 Feb 2022 12:15:50 GMT</pubDate>
            <description><![CDATA[<h3 id="인코딩과-유니코드">인코딩과 유니코드</h3>
<p>유니코드란? 전 세계 다양한 문자와 기호를 <strong>컴퓨터</strong>에서 일관되게 표기하기 위한 산업 표준이다.
문자나 기호를 컴퓨터에 보내려면 인코딩(부조화)하여 보내고, 해독할 때는 디코딩하여 처리한다. 인코딩과 디코딩의 기준을 <strong>charset</strong>이라고 부른다.
charset의 국제 표준이 유니코드이다.</p>
<h4 id="ascii-american-standard-code-for-information-interchange">ASCII (American Standard Code for Information Interchange)</h4>
<p>영문 알파벳을 7비트의 이진로 인코딩하는 방식:
<img src="https://user-images.githubusercontent.com/84922505/151649453-00e69bd3-8cce-4c50-bf70-80212c67cb31.png" alt="스크린샷 2022-01-29 오후 2 54 22"></p>
<h4 id="utf-8-가변-길이-인코딩">UTF-8 (가변 길이 인코딩)</h4>
<p>코드 포인트에 따라 1byte 부터 4bytes까지 길이를 <strong>가변</strong>하여 인코딩할 수 있는 방식이다.
각 바이트에 식별자를 넣어 주어 컴퓨터가 인코딩 하려는 문자가 어떤 문자인지 인식하도록 약속한 것이다.</p>
<p>영어 알파벳 A의 유니코드 코드 포인트는 U+0041 이다. 이진 표현은 1000001 이다.
A는 UTF-8 인코딩으로 다음과 같이 표시된다.
<img src="https://user-images.githubusercontent.com/84922505/151653077-bf7ebf6a-aefb-4920-a007-7d79eb813ec4.png" alt="utf-8-1-byte-encoding"></p>
<p>빨간색의 0비트는 현재 1바이트 인코딩이 되고 있고, 나머지는 코드 포인트라는 것을 알려주고 있다.</p>
<p>참고로 ASCII 코드는 7비트로 표현되므로, UTF-8에서 모두 1byte 결과로 만들 수 있다. ASCII 127 문자의 이진 표현은 01111111이다.</p>
<blockquote>
<p>예시
<code>코</code>라는 문자의 유니코드 = <code>U+CF54</code> (이진법 1100-1111-0101-0100) =&gt; UTF-8 3bytes 표현해야한다.</p>
</blockquote>
<pre><code class="language-jsx">1110xxxx 10xxxxxx 10xxxxxx // (3bytes의 식별자들이다. 가장 하위 byte의 마지막 x부터 채워 넣는다
11101100 10111101 10010100</code></pre>
<h4 id="utf-16-가변-바이트-인코딩">UTF-16 (가변 바이트 인코딩)</h4>
<p>모든 현대 언어의 유니코드 대부분을 2bytes로 표현할 수 있다.
바이트의 순서에 따라 UTF-16의 종류도 달라진다.</p>
<blockquote>
<p>참고 <a href="https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/">https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/</a></p>
</blockquote>
<h3 id="이진-파일과-텍스트-파일의-특징">이진 파일과 텍스트 파일의 특징</h3>
<ul>
<li>텍스트 파일이 아닌 파일은 전부 이진 파일로 취급된다.</li>
<li>메모장으로 파일을 열었을 때 깨져 보이는 경우, 인코딩의 문제이거나 혹은 해당 파일이 이진 파일이기 때문이다.</li>
<li>텍스트 파일은 운영체제별로 개행 문자 처리가 다르다.</li>
<li>대표적인 이진 파일로는 비트맵 이미지 파일이나 사운드 파일, 또는 실행 가능한 파일 등이 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL. 프로세스와 스레드]]></title>
            <link>https://velog.io/@jonghwan2_y/TIL.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@jonghwan2_y/TIL.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Wed, 09 Feb 2022 12:14:26 GMT</pubDate>
            <description><![CDATA[<h3 id="프로세스">프로세스</h3>
<ul>
<li>실행 파일, 애플리케이션을 실행하는 상태를 프로세스라고 한다.</li>
<li>운영체제가 여러 프로세스를 함께 진행하기 때문에 <strong>멀티 태스킹</strong>이 가능하게 되었다.</li>
<li>프로세스는 각각 독립된 메모리 영역(Code, Data, Stack, Heap의 구조)을 할당받는다. </li>
<li>스레드는 프로세스 내에서 각각 Stack만 따로 할당받고 Code, Data, Heap 영역은 공유한다.
<img src="https://images.velog.io/images/jonghwan2_y/post/2b200e1f-f84b-463c-b649-22c7dd149fc9/MultiStackThread.gif" alt=""></li>
</ul>
<h3 id="멀티-태스킹">멀티 태스킹</h3>
<p>멀티 태스킹이 가능하려면 프로세스의 동시성 (Concurrency)와 병렬성 (Parallelism) 특징을 각각 사용하거나, 혼합해서 사용해야 한다.</p>
<p><img src="https://user-images.githubusercontent.com/84922505/151655980-e5b71b61-d4b5-4a23-8fcc-5cf67832b362.png" alt="P4WUoOHJE-1623907071461"></p>
<h4 id="동시성-concurrency">동시성 (Concurrency)</h4>
<ul>
<li>하나의 Core에서 Context-Switch를 이용하여 동시에 여러 작업을 하는 것</li>
<li>Context-Switch: 다른 태스크(프로세스, 스레드)가 시작할 수 있도록 이미 실행 중인 태스크(프로세스, 스레드)를 멈추는 것
<img src="https://user-images.githubusercontent.com/84922505/151656016-b79e879b-0d69-4b63-8e28-7cb7c12731ba.png" alt="스크린샷 2022-01-29 오후 6 38 09"></li>
</ul>
<h4 id="병렬성-parallelism">병렬성 (Parallelism)</h4>
<ul>
<li>여러 Core에서 여러 스레드가 동시에 작업을 하는 것!</li>
</ul>
<h3 id="멀티-스레드">멀티 스레드</h3>
<ul>
<li>스레드? 작업의 갈래</li>
<li>멀티스레드? 하나의 프로세스 내에서 여러 작업 갈래로 나눠진 것</li>
</ul>
<p>각 스레드마다 call stack이 존재하지만, 여러 스레드는 하나의 자원을 공유한다. 반면 프로세스는 프로세스마다 자원이 모두 다르다.
여러 스레드가 하나의 자원에 손을 대면 오류가 발생하게 된다. 이것이 멀티 스레드의 단점이다.</p>
<h3 id="nodejs는-싱글-스레드로만-작동하는가">Node.js는 싱글 스레드로만 작동하는가?</h3>
<p>node.js의 Event loop는 싱글 스레드로 작동되지만, Worker pool은 멀티 스레드로 작동한다.
즉, node.js의 초기화와 callback은 Event loop라는 하나의 프로세스, 하나의 스레드에서 작동되지만 I/O intensive, CPU intensive한 모듈은 Worker pool에서 작동한다.</p>
<blockquote>
<p>How Node.js really works
Node.js uses two kinds of threads: a main thread handled by event loop and several auxiliary threads in the worker pool.
Event loop is the mechanism that takes callbacks (functions) and registers them to be executed at some point in the future. It operates in the same thread as the proper JavaScript code. When a JavaScript operation blocks the thread, the event loop is blocked as well.
Worker pool is an execution model that spawns and handles separate threads, which then synchronously perform the task and return the result to the event loop. The event loop then executes the provided callback with said result.
In short, it takes care of asynchronous I/O operations — primarily, interactions with the system’s disk and network. It is mainly used by modules such as fs (I/O-heavy) or crypto (CPU-heavy). Worker pool is implemented in libuv, which results in a slight delay whenever Node needs to communicate internally between JavaScript and C++, but this is hardly noticeable. (<a href="https://blog.logrocket.com/a-complete-guide-to-threads-in-node-js-4fa3898fe74f/">https://blog.logrocket.com/a-complete-guide-to-threads-in-node-js-4fa3898fe74f/</a>)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Toy_#29. binaryHeap && 30.heapSort]]></title>
            <link>https://velog.io/@jonghwan2_y/Toy29.-binaryHeap</link>
            <guid>https://velog.io/@jonghwan2_y/Toy29.-binaryHeap</guid>
            <pubDate>Sun, 06 Feb 2022 05:40:33 GMT</pubDate>
            <description><![CDATA[<h2 id="29-binaryheap">29. binaryHeap</h2>
<h3 id="--문제">- 문제:</h3>
<p>정수를 요소로 갖는 배열을 입력받아 이진 힙(binary heap)*을 리턴해야 합니다.</p>
<h4 id="--참고">- 참고:</h4>
<blockquote>
<ul>
<li>이진 힙(binary heap)은 노드의 값이 특정한 순서를 가지고 있는 완전 이진 트리(Complete Binary Tree)입니다.</li>
</ul>
</blockquote>
<ul>
<li><p>완전 이진 트리는 이진 트리의 (마지막 레벨 또는 마지막 깊이를 제외하고) 모든 레벨이 노드로 가득 채워져 있어야 합니다. 마지막 레벨은 왼쪽부터 차례대로 채워져 있습니다.</p>
</li>
<li><p>이진 힙에서 부모 노드의 값이 (이진 트리이므로 2개의) 자식 노드의 값보다 큰 경우를 최대 힙(max heap), 반대의 경우를 최소 힙(min heap)이라고 합니다.</p>
</li>
<li><p>최대힙
<img src="https://images.velog.io/images/jonghwan2_y/post/996d1690-4e92-4bbe-8c4c-4a2fc8b09406/%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%202022-02-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.23.42.png" alt=""></p>
</li>
<li><p>최소힙 (<a href="https://www.cs.usfca.edu/~galles/visualization/Heap.html">https://www.cs.usfca.edu/~galles/visualization/Heap.html</a>)
<img src="https://images.velog.io/images/jonghwan2_y/post/8b4e54ee-41b5-465f-9545-bf34c42c173f/%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%202022-02-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.24.13.png" alt=""></p>
</li>
</ul>
<h3 id="--입출력-예시">- 입출력 예시:</h3>
<pre><code class="language-js">let output = binaryHeap([5, 4, 3, 2, 1]);
console.log(output); // --&gt; [5, 4, 3, 2, 1]

output = binaryHeap([3, 1, 21]);
console.log(output); // --&gt; [21, 1, 3]

output = binaryHeap([4, 10, 3, 5, 1]);
console.log(output); // --&gt; [10, 5, 3, 4, 1]</code></pre>
<h3 id="--힌트">- 힌트:</h3>
<ul>
<li>이진 힙은 트리 객체를 이용해 구현할 수도 있고, 배열로도 구현할 수 있습니다. 사실 거의 모든 트리를 배열로 구현할 수 있습니다. 트리를 배열로 구현했을 때의 장점은 (데이터가 선형적으로 저장되기 때문에) 저장공간을 절약할 수 있고 노드 접근 시 오버헤드(재귀호출, 반복문 등)가 약간 줄어듭니다. 다만 이를 위해서 매우 복잡한 인덱스 관리가 필요합니다. 반면, 트리 객체를 이용한 구현은 직관적(이해하기 쉬움)입니다. 그 대신 저장 공간과 약간의 오버헤드를 희생해야 합니다. 거의 모든 기술(구현)은 다수의 선택 사이의 트레이드 오프(trade-off)입니다. 무엇을 선택할 지는 요구사항(requirements), 즉 주어진 문제의 제약과 조건을 고려하여 결정해야 합니다. 이 점을 반드시 기억하시기 바랍니다.</li>
<li>완전 이진 트리는 노드가 낮은 레벨부터 채워지고, 같은 레벨에서는 왼쪽부터 채워지기 때문에 배열로 구현하는 것이 쉽습니다.</li>
<li>최대 힙과 이진 검색 트리(binary search tree)는 둘다 완전 이진 트리입니다. 하지만 이진 검색 트리에서는 모든 노드가 정렬되는 것과 달리 최대 힙에서는 오직 부모 노드와 직계 자식들 간의 관계만 유지됩니다. 이는 아래의 사실로부터 바로 알 수 있습니다.<ul>
<li>이진 검색 트리에서 오른쪽 자식 노드의 값은 부모 노드의 값보다 크지만, 최대 힙에서는 부모 노드의 값이 두 자식 노드의 값보다 크다.</li>
</ul>
</li>
</ul>
<h3 id="--풀이1">- 풀이1:</h3>
<ul>
<li>부모 노드에 2개의 자식 노드 (또는 잎)이 들어갈 수 있다.</li>
<li>트리를 배열로 구현한다면, 인덱스 번호로 부모-자식 로직을 구현할 수 있다. 예를 들어 0 인덱스는 root이자 부모일테고 1, 2 인덱스는 자식일 것이다. 1 인덱스의 자식은 3, 4 인덱스 그리고 2 인덱스의 자식은 5, 6 인덱스이다. 이와 같은 로직으로 쉽게 부모 idx는 (자식 idx - 1) / 2의 값의 정수라는 것을 알 수 있다.</li>
<li>자식이 부모보다 큰 수일 경우, 서로의 위치를 swap 함수로 바꿔준다. 위치를 바꾸고 난 후, 부모의 자식의 관계를 재설정해 준다.</li>
</ul>
<h3 id="--풀이2">- 풀이2:</h3>
<pre><code class="language-js">// 아래 코드는 수정하지 마세요.
function swap(idx1, idx2, arr) {
  // 두 변수를 바꾸는 방법

  // 1) 임시 변수를 활용한 방법
  // let temp = arr[idx1];
  // arr[idx1] = arr[idx2];
  // arr[idx2] = temp;

  // 2) Destructuring assignment를 활용한 방법
  // arr이 reference type이라 가능
  [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];

  // 3) XOR 연산을 활용한 방법
  // arr이 reference type이라 가능
  // arr[idx1] ^= arr[idx2];
  // arr[idx2] ^= arr[idx1];
  // arr[idx1] ^= arr[idx2];
}

function getParentIdx(idx) {
  // 부모의 인덱스 번호 확인하기
  // 최대 힙의 경우 왼쪽부터 채워진다.
  // 0의 자식은 1, 2 /  1의 자식은 3, 4 / 2의 자식은 5, 6...
  return Number.parseInt((idx - 1) / 2)
}

function insert(heap, item) {
  heap.push(item);
  childIdx = heap.length - 1;
  parentIdx = getParentIdx(childIdx);
  while (parentIdx &gt;= 0 &amp;&amp; heap[parentIdx] &lt; heap[childIdx]) {
    swap(parentIdx, childIdx, heap);
    childIdx = parentIdx;
    parentIdx = getParentIdx(childIdx);
  }
  return heap
}

// downHeapify 방식으로??
// 아래 코드는 수정하지 마세요.
const binaryHeap = function (arr) {
  return arr.reduce((heap, item) =&gt; {
    return insert(heap, item);
  }, []);
};
</code></pre>
<h2 id="30-heapsort">30. heapSort</h2>
<h3 id="--문제-1">- 문제:</h3>
<p>정수를 요소로 갖는 배열을 입력받아 오름차순으로 정렬하여 리턴해야 합니다.</p>
<h4 id="--참고-1">- 참고:</h4>
<ul>
<li>binaryHeap과 유사하지만, 오름차순 정렬을 위해서 root를 제거하는 알고리즘 하나가 더 필요하다.</li>
<li>최소힙으로 정렬 후, root를 제거한 트리를 다시 최소힙으로 만들어야 한다. 빈 배열에 계속 최소힙을 넣어주면 heapSort가 구현된다.</li>
</ul>
<h3 id="--풀이">- 풀이:</h3>
<pre><code class="language-js">// 아래 코드는 수정하지 마세요.
function swap(idx1, idx2, arr) {
  [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
}

function getParentIdx(idx) {
  return Number.parseInt((idx - 1) / 2)
}

function insert(heap, item) {
  heap.push(item);
  let childIdx = heap.length - 1;
  let parentIdx = getParentIdx(childIdx);

  // binaryHeap에서 부등호만 바꿔줬다.
  while (parentIdx &gt;= 0 &amp;&amp; heap[parentIdx] &gt; heap[childIdx]) {
    swap(parentIdx, childIdx, heap);
    childIdx = parentIdx;
    parentIdx = getParentIdx(childIdx);
  }
  return heap
}

function removeRoot(heap) {
  // 가장 마지막 노드와 root를 교환한 후, 배열 길이를 줄여준 다음 최소 힙 구현을 해준다.
  swap(0, heap.length - 1, heap); // 가장 마지막 노드와 root 교환
  heap.pop(); // 배열 길이 1만큼 축소
  if (heap.length === 0) return [];

  let curIdx; // root부터 아래로 내려가면서 확인할 예정
  let parentIdx = 0; // root idx
  while (curIdx !== parentIdx) {
    curIdx = parentIdx; // root부터 시작!
        let leftChild = curIdx * 2 + 1; 
        let rightChild = curIdx * 2 + 2;
        if (leftChild &lt; heap.length &amp;&amp; heap[leftChild] &lt; heap[parentIdx]) {
            parentIdx = leftChild; // 자식보다 부모가 크면 최소 힙 구현을 위해 자식, 부모 인덱스에 자녀 인덱스 할당 후 swap 진행
        }
        if (rightChild &lt; heap.length &amp;&amp; heap[rightChild] &lt; heap[parentIdx]) {
            parentIdx = rightChild;
        }
        swap(curIdx, parentIdx, heap);
  }
  return heap
}

// 아래 코드는 수정하지 마세요.
const binaryHeap = function (arr) {
  return arr.reduce((heap, item) =&gt; {
    return insert(heap, item);
  }, []);
};

const heapSort = function (arr) {
  let minHeap = binaryHeap(arr);
  const result = [];
  // 큐처럼 활용하는구나
    while (minHeap.length &gt; 0) {
        result.push(minHeap[0]); // 최소힙의 root 값을 넣어주고 다시 최소힙 구현
        minHeap = removeRoot(minHeap);
    }
    return result;
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth 기반 인증 Sprint]]></title>
            <link>https://velog.io/@jonghwan2_y/OAuth-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-Sprint</link>
            <guid>https://velog.io/@jonghwan2_y/OAuth-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-Sprint</guid>
            <pubDate>Wed, 02 Feb 2022 14:26:18 GMT</pubDate>
            <description><![CDATA[<h3 id="github를-활용하여-oauth-인증">Github를 활용하여 OAuth 인증</h3>
<p>1) Github &gt; Settings &gt; Developer Settings에서 Client Id와 Client secret key를 생성한다. <code>Authorization callback URL</code>은 나의 웹 앱 URL을 적으면 된다.
2) 생성한 두 정보를 서버의 .env 파일에 저장한다.</p>
<h3 id="client">Client</h3>
<h4 id="appjs">App.js</h4>
<p>1) Login.js (생략 예정)에서 Github API에 따라 
<code>https://github.com/login/oauth/authorize?client_id={내 Client Id}</code>에서 본인 인증 이후에 callback URL로 리디렉션되면서 autorization code를 받게된다. (이후 서버 callback 요청에 <code>req.body</code>로 확인할 수 있다. </p>
<pre><code class="language-js">class App extends Component {
  constructor() {
    super();
    this.state = {
      isLogin: false,
      accessToken: &quot;&quot;,
    };
    this.getAccessToken = this.getAccessToken.bind(this);
  }
  // accessToken을 클라이언트 App.js내 state로 관리하고,
  // getAcceessToken 메소드로 Github에서 넘겨받은 authorization code로 accessToken을 획득한다.
  async getAccessToken(authorizationCode) {
    await axios
    .post(&#39;http://localhost:8080/callback&#39;, 
    {
      authorizationCode
    })
    .then((data) =&gt; {
      this.setState({
        isLogin: true, 
        accessToken: data.data.accessToken
      })
    })
  }

  // componentDidMount는 랜더링 이후 즉시 호출되는 함수이다. 
  // 함수형 컴포넌트의 useEffect Hook과 비슷하다.
  // 시점은 Login 랜더링 후, Mypage 랜더링 후가 되겠다.
  componentDidMount() {
    const url = new URL(window.location.href)
    const authorizationCode = url.searchParams.get(&#39;code&#39;)
    if (authorizationCode) {
      this.getAccessToken(authorizationCode)
    }
  }
  render() {} // ...생략</code></pre>
<h4 id="mypagejs">Mypage.js</h4>
<p>1) <a href="https://api.github.com/user">https://api.github.com/user</a> 에서 사용자 정보를 받아올 것이다.
2) authorization 헤더에는 Login.js에서 받아온 authorization code를 사용해 획득한 accessToken을 넣어준다.
3) 여러 사용자 정보를 얻을 수 있다.</p>
<pre><code class="language-js">class Mypage extends Component {

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      name: &quot;&quot;, 
      login: &quot;&quot;, 
      html_url: &quot;&quot;, 
      public_repos: null,
    }
  }

  async getGitHubUserInfo() {
    await axios
    .get(&quot;https://api.github.com/user&quot;, 
    {
      headers: {authorization: `token ${this.props.accessToken}`}
    })
    .then(data =&gt; {
      const { name, login, html_url, public_repos } = data.data;
      this.setState({
        name,
        login,
        html_url,
        public_repos
      })
    })
  }

  async getImages() {
    await axios
    .get(`http://localhost:8080/images`, 
    { 
      headers: {authorization: `token ${this.props.accessToken}`}
    })
    .then((data) =&gt; {
      const { images } = data.data
      this.setState({images})
    })
  }</code></pre>
<h3 id="server">Server</h3>
<h4 id="callbackjs">callback.js</h4>
<p>1) <code>https://github.com/login/oauth/access_token</code>의 Body에 client id, client secret, authorization code를 전달한다.
2) 전달받은 내용을 data.data에 접근하여 accessToken을 획득하여, 전달한다.</p>
<pre><code class="language-js">module.exports = async (req, res) =&gt; {
  await axios
  .post(&#39;https://github.com/login/oauth/access_token&#39;, 
  {
    client_id: clientID,
    client_secret: clientSecret,
    code: req.body.authorizationCode
  }, 
  { 
    deaders: {Accept: &#39;application/json&#39;}
  })
  .then((data) =&gt; {
    const accessToken = data.data.access_token;
    res.status(200).json({accessToken})
  })
}</code></pre>
<h4 id="imagesjs">images.js</h4>
<pre><code class="language-js">module.exports = async (req, res) =&gt; {
  if (!req.headers.authorization) {
    res.status(403).json({ message: &quot;no permission to access resources&quot; });
  } else {
    res.status(200).json({ images });
  }
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[토큰 기반 인증 Sprint]]></title>
            <link>https://velog.io/@jonghwan2_y/%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-Sprint</link>
            <guid>https://velog.io/@jonghwan2_y/%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-Sprint</guid>
            <pubDate>Wed, 02 Feb 2022 12:47:34 GMT</pubDate>
            <description><![CDATA[<h3 id="client">Client</h3>
<h4 id="appjs">App.js</h4>
<p>1) App 컴포넌트의 isLogin 상태에 따라 Mypage 혹은 Login 컴포넌트를 렌더링합니다. 적절한 props를 Mypage/Login 컴포넌트에 전달합니다.</p>
<pre><code class="language-js">
import React, { Component } from &quot;react&quot;;

import Login from &quot;./components/Login&quot;;
import Mypage from &quot;./components/Mypage&quot;;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLogin: false,
      accessToken: &quot;&quot;,
    };

    this.loginHandler = this.loginHandler.bind(this);
    this.issueAccessToken = this.issueAccessToken.bind(this);
  }

  // app 화면 전환을 위한 조건
  loginHandler() {
    this.setState({
      isLogin: true,
    })
  }

  // 1) 핸들러를 Login, Mypage에 Props로 전달한다. 
  // 2) 로그인을 통해 accessToken을 획득할 예정이고, 해당 토근을 통해 App.js의 accessToken state값이 변경될 예정이다. 
  // 3) 그 토큰을 Props로 Mypage에 넘겨줄 예정이다.
  issueAccessToken(token) {
    this.setState({
      accessToken: token
    })
  }

  render() {} //...생략
  export default App;
</code></pre>
<p>2) Login 컴포넌트의 loginRequestHandler메소드를 사용하여 상위 컴포넌트인 App 컴포넌트의 state를 적절히 변경시킵니다.</p>
<h4 id="loginjs">Login.js</h4>
<pre><code class="language-js">class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userId: &quot;&quot;,
      password: &quot;&quot;,
    };
    this.inputHandler = this.inputHandler.bind(this);
    this.loginRequestHandler = this.loginRequestHandler.bind(this);
  }

  inputHandler(e) {
    this.setState({ [e.target.name]: e.target.value });
  }

  // accessToken을 획득하는 메소드이다.
  // 사용자 id와 pw를 서버 endpoint로 Body로 보낸다. (서버에서 해당 정보의 사용자가 있는지 확인해서 accessToken을 넘겨줄 예정이다.)
  loginRequestHandler() {
    axios
    .post(&quot;https://localhost:4000/login&quot;, 
    {
      userId: this.state.userId,
      password: this.state.password
    }, 
    {
      &#39;Content-Type&#39;:&#39;application/json&#39;,
      withCredentials: true // cors 요청을 위해 필요하다.
    })
    .then((res) =&gt; {
      this.props.loginHandler(); // 로그인 true로 변경해 주고,
      this.props.accessTokenHandler(res.data.data.accessToken); // 서버에서 data라는 키로 정보를 넘길 예정인데, `res.data`로 접근해야 &#39;data&#39;라는 키에 접근하여 실제 토큰을 획득할 수 있다. 이 토큰은 App으로 전달될 예정이다. 
    }
    ); 
  }
  render() {} //...생략
  export default Login;</code></pre>
<h4 id="mypagejs">Mypage.js</h4>
<p>3) Mypage 컴포넌트 accessTokenRequest, refreshTokenRequest 메소드를 구현합니다.</p>
<pre><code class="language-js">class Mypage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userId: &quot;&quot;,
      email: &quot;&quot;,
      createdAt: &quot;&quot;,
    };
    this.accessTokenRequest = this.accessTokenRequest.bind(this);
    this.refreshTokenRequest = this.refreshTokenRequest.bind(this);
  }

    /*     
    App 컴포넌트에서 내려받은 accessToken props를 authorization header에 담아 요청을 보낸다. 서버에서 해당 토큰을 검증하여, 사용자에게 등록된 토큰이 맞으면 요청하는 정보를 전달해 준다.
    */
  accessTokenRequest() {
    axios
    .get(&quot;https://localhost:4000/accesstokenrequest&quot;, 
    { headers: {
        Authorization: `Bearer ${this.props.accessToken}`
      }
    }, 
    { 
      withCredentials: true 
    })
    .then((res) =&gt; {
      this.setState({
      userId: res.data.data.userInfo.userId,
      email: res.data.data.userInfo.email,
      createdAt: res.data.data.userInfo.createdAt})
    })
  }

    /*
    accessToken이 만료되면 refreshToken을 통해 accessToken을 다시 생성할 수 있어야 한다. 서버에 담긴 refreshToken
    */
  refreshTokenRequest() {
    axios
    .get(&quot;https://localhost:4000/refreshtokenrequest&quot;,
    { withCredentials: true }
      )
    .then((res) =&gt; {
      this.setState({
      userId: res.data.data.userInfo.userId,
      email: res.data.data.userInfo.email,
      createdAt: res.data.data.userInfo.createdAt})
      this.props.accessTokenHandler(res.data.data.accessToken)
    }
    )
  }</code></pre>
<h3 id="server">Server</h3>
<p>1) JWT 활용 방법</p>
<blockquote>
<ol>
<li>JWT의 구조
<img src="https://images.velog.io/images/jonghwan2_y/post/e5b5c671-f1c7-4f31-af2e-5be2ecf7198b/%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%202022-02-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.24.52.png" alt=""></li>
<li>토큰 생성하기<pre><code class="language-js">const jwt = require(&#39;jsonwebtoken&#39;);
const token = jwt.sign(토큰에_담을_값, ACCESS_SECRET, { 옵션1: 값, 옵션2: 값, ... });
// .sign(payload, 비밀키, header)로 토큰 생성</code></pre>
</li>
</ol>
</blockquote>
<pre><code>&gt; 3. 토큰 검증하기
```js 
jwt.verify(token, secretkey, 익명함수)</code></pre><ul>
<li>token: client에게서 받은 token</li>
<li>secretkey : token 생성 시 사용했던 secretKey</li>
<li>3번째 인자로 들어간 익명함수 : 유효성 검사 결과를 처리할 callback 함수</li>
</ul>
<h4 id="loginjs-1">login.js</h4>
<p>1) request로부터 받은 userId, password와 일치하는 유저가 DB에 존재하는지 확인합니다.</p>
<ul>
<li>일치하는 유저가 없을 경우 : 로그인 요청을 거절합니다.</li>
<li>일치하는 유저가 있을 경우 :
필요한 데이터를 담은 두 종류의 JWT(access, refresh)를 생성합니다.
생성한 JWT를 적절한 방법으로 반환합니다.
access token은 클라이언트에서 react state로 다루고 있습니다.
refresh token은 클라이언트의 쿠키에서 다루고 있습니다.</li>
</ul>
<pre><code class="language-js">module.exports = async (req, res) =&gt; {

  // App 클라이언트에서 보낸 id와 pw로 사용자 데이터를 수집
  const userInfo = await Users.findOne({
    where: { userId: req.body.userId, password: req.body.password },
  })

  if (!userInfo) {
    res.status(401).json({data: null, message: &#39;not authorized&#39;})
  } else {
    const { id, userId, email, createdAt, updatedAt } = userInfo

    // jwt.sign(payload, 비밀키값, header)
    const accessToken = jwt.sign({ id, userId, email, createdAt, updatedAt }, process.env.ACCESS_SECRET, {
      algorithm : &quot;HS256&quot;, // 해싱 알고리즘
      expiresIn : &quot;1h&quot;,  // 토큰 유효 기간
    });
    console.log(&#39;accessToken&#39;, accessToken)
    const refreshToken = jwt.sign({ id, userId, email, createdAt, updatedAt }, process.env.REFRESH_SECRET, {
      algorithm : &quot;HS256&quot;, // 해싱 알고리즘
      expiresIn : &quot;3h&quot;,  // 토큰 유효 기간
    });

    // refreshToken은 쿠키에, accessToken은 응답에 답아 전달한다.
    res.status(200)
    .cookie(&#39;refreshToken&#39;, refreshToken, {
      domain: &#39;localhost&#39;,
      path: &#39;/&#39;,
      httpOnly: true,
      secure: true,
      sameSite: &#39;None&#39;,
    })
    .json({data: {accessToken}, message: &#39;ok&#39; })
  }
}</code></pre>
<h4 id="accesstokenrequestjs">accessTokenRequest.js</h4>
<p>1) Login 클라이언트에서 <code>get</code>요청을 해당 end point로 <code>autorization</code> 헤더로 accessToken을 보냈다.
2) accessToken은 &#39;Bearer xxxxxx....&#39;와 같이 전달되어 <code>const token = accessToken.split(&#39; &#39;)[1];</code>로 accessToken값만 추출할 수 있다.
2) 해당 accessToken을 <code>jwt.verify</code>로 검증을 한다. callback으로 들어가는 비동기 함수에 의해 <code>data</code>에 토큰 생성에 보냈던 사용자 정보 (payload)의 내용이 들어가게 된다.</p>
<pre><code class="language-js">module.exports = async (req, res) =&gt; {
  const accessToken = req.headers[&#39;authorization&#39;];

  if (!accessToken) {
    res.status(400).json({ data: null, message: &quot;invalid access token&quot; })
  } else {
    const token = accessToken.split(&#39; &#39;)[1];

    jwt.verify(token, process.env.ACCESS_SECRET, async (err, data) =&gt; {     
      const userInfo = await Users.findOne({
        where: { userId: data.userId },
      })

      if (!userInfo) {
        res.status(400).json({ data: null, message: &quot;access token has been tempered&quot; })
      } else {

        const { id, userId, email, createdAt, updatedAt } = userInfo;

        res.status(200).json({ data: {userInfo: { id, userId, email, createdAt, updatedAt } }, message: &quot;ok&quot; })
      }
    })
  }</code></pre>
<h4 id="refreshtokenrequestjs">refreshTokenRequest.js</h4>
<p>1) 로그인 당시 서버는 클라이언트 cookies에 refreshToken을 넣어 주었다.
2) accessToken과 같이 해당 refreshToken을 검증한다.
3) refreshToken 토큰 검증을 통해 얻은 사용자 정보로 새 accessToken을 생성하는 과정이다.</p>
<pre><code class="language-js">module.exports = (req, res) =&gt; {
  const token = req.cookies.refreshToken;

  if (!token) {
    res.status(400).json({ data: null, message: &quot;refresh token not provided&quot; })
  }
  jwt.verify(token, process.env.REFRESH_SECRET, async(err, data)=&gt;{
    if (err) {
      res.status(400).json({data: null, message: &quot;invalid refresh token, please log in again&quot;})
    } else {
      const userInfo = await Users.findOne({
        where: { userId: data.userId }
      });

      if (!userInfo){ 
        res.status(400).json({ data: null, message: &quot;refresh token has been tempered&quot;})
      } else {
        const { id, userId, email, createdAt, updatedAt } = userInfo;
        const accessToken = jwt.sign(
          {id, userId, email, createdAt, updatedAt}, 
          process.env.ACCESS_SECRET, 
          {expiresIn: &#39;1h&#39;}
          );

        res.status(200).json({ 
          data: {
            accessToken, 
            userInfo: { id, userId, email, createdAt, updatedAt } 
          }, 
          message: &#39;ok&#39;
        });
      }
    }
  });
};</code></pre>
<h3 id="정리">정리</h3>
<p>1) 토큰은 클라이언트에 state로 관리되고 있었다.
2) 서버는 소유하고 있던 acceessSecret, refreshSeceret Key를 가지고 토큰 생성 및 검증만 진행한다.
3) refreshToken을 쿠키로 클라이언트에 심어 놓아서, accessToken이 만료되면, refreshToken의 기한 내에 새 accessToken을 생성할 수 있다.
4) JWT (JSON Web Token)을 통해 sign (생성 또는 토큰 암호화), verify (검증 또는 토큰 복호화)를 통해 사용자 정보를 활용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[연속 부분 배열]]></title>
            <link>https://velog.io/@jonghwan2_y/%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@jonghwan2_y/%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Tue, 11 Jan 2022 14:56:55 GMT</pubDate>
            <description><![CDATA[<h3 id="--문제">- 문제:</h3>
<p>정수를 요소로 갖는 배열을 입력받아 다음의 조건을 만족하는 LSCS*를 리턴해야 합니다.</p>
<ul>
<li>LSCS: 주어진 배열의 연속된 부분 배열*의 합을 구한다고 할 때, 이 중 가장 큰 값(Largest Sum of Contiguous Subarray)</li>
<li>연속된 부분 배열들: 배열 [1,2,3]의 연속 부분 배열은 [1], [1, 2], [1, 2, 3], [2], [2, 3], [3] 입니다.</li>
</ul>
<h4 id="--입출력-예시">- 입출력 예시:</h4>
<pre><code class="language-js">let output = LSCS([1, 2, 3]);
console.log(output); // --&gt; 6

output = LSCS([1, 2, 3, -4]);
console.log(output); // --&gt; 6 ([1, 2, 3])

LSCS([1, 2, 3, -4, 5]);
console.log(output); // --&gt; 7 ([1, 2, 3, -4, 5])

LSCS([10, -11, 11]);
console.log(output); // --&gt; 11 ([11])</code></pre>
<h4 id="--주의">- 주의:</h4>
<p>효율적인 알고리즘(O(N))으로 해결해야 합니다.</p>
<h3 id="--풀이">- 풀이:</h3>
<p>여러 해결 방식이 있겠지만, 주어진 배열의 요소를 요소의 길이만큼만 조회를 하는 방법을 찾아야 한다.
만약 배열의 첫 요소부터 시작해서 모든 부분 배열을 돌아가려면 이중 반복문을 피할 수 없다. 또는 재귀를 사용할 수 밖에 없을 것이다. 하지만 이 방식으로는 문제를 O(N)으로 해결하기 어렵다.</p>
<p>배열의 요소의 수 중에 음수가 없다면, 가장 큰 부분 배열의 합은 배열 모든 요소의 합일 것이다. 그러나 우리는 음수를 고려해야한다.</p>
<p>1)  O(N^2) 풀이 방식</p>
<pre><code class="language-js">const LSCS = function (arr) {
  let max = -100000;
  for (let i = 0; i &lt; arr.length; i++) {
    let sum = arr[i];
    if (sum &gt; max) max = sum;
    for (let j = i + 1; j &lt; arr.length; j++) {
      sum = sum + arr[j];
      if (sum &gt; max) max = sum;
    }
  }
  return max;
}</code></pre>
<p>문제 조건에서 제시한 최소값을 먼저 max로 지정했다. 모든 부분 배열을 돌기위해 0번째 인덱스에서부터 시작해서 마지막 인덱스까지, 배열의 요소마다 반복문을 다시 돌리면서 max 값을 새로 할당했다.</p>
<p>2) O(N) 풀이 방식</p>
<pre><code class="language-js">const LSCS = function (arr) {
  let subArrSum = arr[0];
  let max = arr[0];
  for (let i = 1; i &lt; arr.length; i++) {
    subArrSum = Math.max(subArrSum + arr[i], arr[i]);
    max = Math.max(max, subArrSum);
  }
   return max;
}</code></pre>
<p>연속된 구간의 배열의 합이 음수로 나오는 경우, 해당 구간 이후부터 다시 시작할 수 있는 점을 기억하자.
특정 구간의 요소가 아무리 크다고 하더라도 특정 연속 구간의 합이 음수일 경우 최대합를 다시 다음 수부터 확인해야 할 것이다.
부분 배열의 어떤 특정 구간의 집합이다. 해당 특정 구간의 합이 음수가 나올 경우, 해당 값을 버리고 다음 수부터의 구간의 합을 구해보고, 음수가 나오기 전까지의 max와 비교해서 max를 구하는 것이다.</p>
<p>3) DP 풀이 방식
이 방법은 <a href="https://shoark7.github.io/programming/algorithm/4-ways-to-get-subarray-consecutive-sum">다른 블로그</a>에서 참조한 것이다.
왜 해당 방법이 DP일 수 있을지 고민한 결과, (정확하지 않지만) memoization 방식을 활용해서 DP 알고리즘으로 푼 것이 아닐까 싶다.</p>
<pre><code class="language-js">const LSCS = function (arr) {
  let cache = [];
  cache[0] = arr[0];
  for (let i = 1; i &lt; arr.length; i++) {
    cache[i] = Math.max(0, cache[i-1]) + arr[i]
  }
  return Math.max(...cache)
};</code></pre>
<h3 id="--마무리">- 마무리:</h3>
<p>LSCS...막막했다. 
이번 풀이를 통해, 최대한 발생할 수 있는 가정을 세워보고 문제를 풀어야겠다 생각이 들었다.
수도 코드를 적는 스킬의 개선이 필요했다. 
문제를 나눌 수 있을 때까지 나누어 보는 습관을 더욱 키워야 겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2.MVC_ORM ]]></title>
            <link>https://velog.io/@jonghwan2_y/MVCORM</link>
            <guid>https://velog.io/@jonghwan2_y/MVCORM</guid>
            <pubDate>Sun, 09 Jan 2022 14:45:10 GMT</pubDate>
            <description><![CDATA[<h3 id="orm이란-무엇인가">ORM이란 무엇인가?</h3>
<p>Object Relational Mapping의 약자이다.  Model을 기술하는 도구이다.</p>
<p>개발 환경에서 데이터베이스에 접근할 수 있도록 하는 중간자 역할이라고 생각하자. </p>
<h3 id="sequelize-orm">Sequelize ORM</h3>
<p>비동기 기반의 Node.js ORM이다. 실제로 <code>async</code>, <code>await</code> 비동기가 기본으로 쓰인다.</p>
<p>나는 이번 Sprint를 통해서,</p>
<ul>
<li>Sequelize 설치하기</li>
<li>모델 생성 및 MVC 구조 설정하기</li>
<li>모델 마이그레이션하기</li>
<li>CRUD 중 Create, Update 작업하기</li>
<li>Join 테이블 만들기 (관계 설정)</li>
</ul>
<p>등을 해보았다.</p>
<h2 id="sequelize-설치하기">Sequelize 설치하기</h2>
<p>1) <a href="https://sequelize.org/">공식문서</a>에 나와있는 대로, 먼저 sequelize를 설치한다.</p>
<pre><code class="language-jsx">// Sequlize를 설치하자
npm install --save sequelize</code></pre>
<p>2) Migration을 하려면 sequelize-cli도 설치해야 한다.</p>
<p>우리가 작성한 모델이 실제로 database에 존재하도록 하려면 migration 작업이 꼭 필요하다. </p>
<pre><code class="language-jsx">npm install --save-dev sequelize-cli</code></pre>
<h2 id="프로젝트-준비하기">프로젝트 준비하기</h2>
<p>1) 프로젝트를 시작하기 전, 아래 명령어를 통해 우리는 여러 준비 파일을 생성할 수 있다.</p>
<pre><code class="language-jsx">npx sequelize-cli init</code></pre>
<p>2) 자동으로 생성되는 4개의 파일 중 config 폴더에는 작업 환경에 대한 정보가 담겨 있다. 기본적으로 <code>development</code> 환경이 설정이 되어있고, <code>mysql</code> 을 사용하는 것으로 되어 있다.</p>
<pre><code class="language-jsx">// config/config.json 파일
{
  &quot;development&quot;: {
    &quot;username&quot;: &quot;root&quot;,
    &quot;password&quot;: null,
    &quot;database&quot;: &quot;database_development&quot;,
    &quot;host&quot;: &quot;127.0.0.1&quot;,
    &quot;dialect&quot;: &quot;mysql&quot;
  },
  &quot;test&quot;: {
    &quot;username&quot;: &quot;root&quot;,
    &quot;password&quot;: null,
    &quot;database&quot;: &quot;database_test&quot;,
    &quot;host&quot;: &quot;127.0.0.1&quot;,
    &quot;dialect&quot;: &quot;mysql&quot;
  },
  &quot;production&quot;: {
    &quot;username&quot;: &quot;root&quot;,
    &quot;password&quot;: null,
    &quot;database&quot;: &quot;database_production&quot;,
    &quot;host&quot;: &quot;127.0.0.1&quot;,
    &quot;dialect&quot;: &quot;mysql&quot;
  }
}

// models/index.js에 보면 아래처럼 development가 기본으로 쓰이고 있다는 걸 알 수 있다.
const env = process.env.NODE_ENV || &#39;development&#39;;
const config = require(__dirname + &#39;/../config/config.json&#39;)[env];</code></pre>
<p>3)  데이터 베이스 생성하기</p>
<pre><code class="language-jsx">// 아래 명령어를 입력하면 현재 기본으로 설정된 데이터베이스(database_development)가 자동으로 생성된다.
db:create</code></pre>
<h2 id="모델-생성-및-마이그레이션">모델 생성 및 마이그레이션</h2>
<p>1) url 모델을 먼저 생성하자. <code>model:generate</code> 를 통해 모델과 마이그레이션 파일을 만들 수 있다.</p>
<pre><code class="language-jsx">// name과 attribute를 어떻게 설정하는지 관찰하자
npx sequelize-cli model:generate --name url --attributes 
url:string,title:string,visits:integer</code></pre>
<pre><code class="language-jsx">// 필드의 다른 속성을 추가할 수 있다. 변경 전, 
visits: DataTypes.INTEGER

// 변경 후,
visits: {
        type: DataTypes.INTEGER,
    defaultValue: 0
}</code></pre>
<p><em>point: dataValues를 따로 설정해 주지 않으면 각 컬럼의 기본값은 <code>NULL</code> 이다.</em></p>
<p>2) 마이그레이션 파일을 살펴보자.</p>
<pre><code class="language-jsx">&#39;use strict&#39;;
module.exports = {
  up: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.createTable(&#39;urls&#39;, {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      url: {
        type: Sequelize.STRING
      },
      title: {
        type: Sequelize.STRING
      },
      visits: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.dropTable(&#39;urls&#39;);
  }
};</code></pre>
<p>테이블 이름이 우리가 모델에서 설정한 <code>url</code>과 달리 <code>urls</code>로 복수형이다. 아래 내용 참고하자.</p>
<blockquote>
<p>A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as <code>User</code>) while tables have pluralized names (such as <code>Users</code>), although this is fully configurable.</p>
</blockquote>
<p>3) 마이그레이션 해보자.  </p>
<p>마이그레이션을 진행하면 실제 mysql의 <code>database-development</code> 데이터베이스에 url이라는 테이블이 생성되는 것을 확인할 수 있다. (undo를 진행하면 테이블 사라진다)</p>
<pre><code class="language-jsx">// # 마이그레이션 진행하기
npx sequelize-cli db:migrate

// # 마이그레이션 파일 수정이 필요한 경우, 마이그레이션 해제하기
npx sequelize-cli db:migrate:undo</code></pre>
<h2 id="router-→-controller-연결하자">Router → Controller 연결하자</h2>
<p>1) app.js 파일에 router 파일을 연결하자. </p>
<pre><code class="language-jsx">const express = require(&#39;express&#39;);
const logger = require(&#39;morgan&#39;);

const indexRouter = require(&#39;./routes/index&#39;);
const linksRouter = require(&#39;./routes/links&#39;);

const app = express();

app.use(logger(&#39;dev&#39;));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use(&#39;/&#39;, indexRouter);
app.use(&#39;/links&#39;, linksRouter);

module.exports = app;</code></pre>
<p>2) routes/links.js 파일에 라우터 내용을 작성하자. 각 endpoint에 따라 분기할 메소드의를 연결해 주어야 한다.</p>
<pre><code class="language-jsx">
const express = require(&#39;express&#39;);
const router = express.Router();
const controller = require(&#39;../controllers/links&#39;);

router.get(&#39;/&#39;, controller.get);          // get 메소드 연결
router.post(&#39;/&#39;, controller.post);        // post 메소드 연결
router.get(&#39;/:id&#39;, controller.getId)      // get 메소드 중 redirect를 위한 연결   

module.exports = router;</code></pre>
<h2 id="controller를-작성해-보자">Controller를 작성해 보자</h2>
<p>controller는 작성되어 있는 <code>url</code> model를 통해 데이터베이스의 정보를 조회 또는 작성할 예정이다.</p>
<p>그럼 우선 model를 가져오자. </p>
<pre><code class="language-jsx">const model = require(&quot;../../models&quot;).url;</code></pre>
<p><em>point: 그런데 왜 <code>const model = require(&quot;../../models/url&quot;)</code> 이라고 작성하면 안 되는 걸까?</em></p>
<p><em>사실 url은 ‘models/index.js’ 즉, Model이라는 클래스의 하나의 instance이다.</em></p>
<h3 id="post-links-api-확인하자">post /links API 확인하자</h3>
<ul>
<li>request: payload에 사용자가 입력하는 url을 url 속성에 담아 요청한다.</li>
</ul>
<pre><code class="language-jsx">{
  &quot;url&quot;: &quot;https://www.github.com&quot;
}</code></pre>
<ul>
<li>response: 방금 생성한 모델의 JSON</li>
</ul>
<pre><code class="language-jsx">{
  &quot;id&quot;: 1,
  &quot;url&quot;: &quot;https://www.github.com&quot;,
  &quot;title&quot;: &quot;The world’s leading software development platform · GitHub&quot;,
  &quot;visits&quot;: 1,
  &quot;createdAt&quot;: &quot;2020-07-25T20:07:15.000Z&quot;,
  &quot;updatedAt&quot;: &quot;2020-07-25T20:07:15.000Z&quot;
}</code></pre>
<h3 id="post-컨트롤러-구현">post 컨트롤러 구현</h3>
<p>여기에서 id, createdAt, updatedAt은 자동으로 생성된다. url, title, visits를 레코드로 urls 테이블에 전달해야 한다. </p>
<p>1) url은 req.body를 통해 획득할 수 있으니 pass</p>
<p>2) title은 다른 방법으로 만들어야 한다. sprint에 힌트가 나와있다. </p>
<p>modules/utils.js에 이미 구현되어 있는 두 메소드를 활용할 예정이다. </p>
<pre><code class="language-jsx">const {getUrlTitle, isValidUrl} = require(&#39;../../modules/utils&#39;)</code></pre>
<p><code>isValidUrl</code> 의 경우 url 검사를 한번 해주는 것 같고, <code>getUrlTitle</code> 은 title을 생성해 주는 것 같다.</p>
<pre><code class="language-jsx">const request = require(&#39;request&#39;);

const rValidUrl = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;

exports.getUrlTitle = (url, cb) =&gt; {
  request(url, function (err, res, html) {
    if (err) {
      console.log(&#39;Error reading url heading: &#39;, err);
      return cb(err);
    } else {
      const tag = /&lt;title&gt;(.*)&lt;\/title&gt;/;
      const match = html.match(tag);
      console.log(match.length)
      const title = match ? match[1] : url;
      return cb(err, title);
    }
  });
};

exports.isValidUrl = url =&gt; {
  return url.match(rValidUrl);
};</code></pre>
<pre><code class="language-jsx">// controller에 작성한 post 요청 작업의 식은 아래와 같다.
// 1. 먼저 url 유효성 검사를 하고, 유효하지 않은 경우 400을 반환하자
// 2. 유효한 url의 경우 가져온 getUrlTitle 메소드를 활용하여 얻은 title을 반환하자

post: async (req, res) =&gt; {
        const url = req.body.url;
        if (!isValidUrl(url)) return res.stauts(400).send(&#39;Not Found&#39;)

                // getUrlTitle 두 번째 인자는 콜백함수였다. 콜백함수에 우리가 작업하고자 하는 식을 넣어 주자.    
        getUrlTitle(url, async (err, title) =&gt; {
            if (err) return res.sendStatus(400);

            // sequelize API 문서를 통해 &quot;조회 또는 생성&quot;하는 문법을 찾았다.
            // title 값을 받아옴
            // title을 url 모델의 title 값으로 =&gt; 레코드 생성
            // 만약에 이미 해당 url이 데이터베이스에 존재한다면 =&gt; 레코드 조회
             model.findOrCreate({
                where: {
                    url 
                },
                defaults: {
                    title
                }
            })
            .then(([result, created]) =&gt; {
                if (created) {
                    return res.status(201).json(result); // created
                } else {
                    return res.status(201).json(result); // find
                }
            })
            .catch((err) =&gt; req.sendStatus(500))
        })
    }</code></pre>
<p>model.<a href="https://sequelize.org/master/manual/model-querying-finders.html#-code-findorcreate--code-">findOrCreate</a>는 특정 옵션에 해당하는 엔트리를 찾지 못한 경우, 옵션 내용 + defaults에 들어간 내용으로 레코드를 생성하게 된다. 그것이 result 객체로 생성이 된다. (다른 곳에서 생성한 getUrlTitle 함수를 가져와서 그 인자에 콜백함수 형식으로 쿼리문을 실행하는 것을 이해하기 굉장히 오래 걸렸다)</p>
<p>3) visits에는 기본값이 있으니 pass</p>
<h3 id="get-컨트롤러-구현">get 컨트롤러 구현</h3>
<p>post보다 어렵지 않았다. <code>findAll()</code> 은 SQL syntax인 <code>select * from</code> 과 동일하게 작동한다.</p>
<pre><code class="language-jsx">get: async (req, res) =&gt; {
        try {
            const result = await model.findAll();
            return res.status(200).json(result)
        } catch (err) {
            return res.sendStatus(500);
        }
    }</code></pre>
<h3 id="getid-컨트롤러-구현-redirect-기능">getId 컨트롤러 구현 (redirect 기능)</h3>
<p>1) model.findByPk를 통해 params로 전달된 id값으로 레코드를 찾는다.</p>
<p>2) 방문 횟수 증가를 위해 model.increment를 사용한다. <code>{ {늘리고자 하는 필드: 늘리는 수의 크기} , {where : 조건}}</code></p>
<p>3) redirect를 구현하기 위해 Express 문법을 사용한다.</p>
<pre><code class="language-jsx">getId: async (req, res) =&gt; {
           // id라는 파라미터
          // 해당 파라미터를 이용해서 url 모델엘 조회를 해야함
        const id = req.params.id;
        const target = await model.findByPk(id);
        await model.update({visits: visits + 1};
        if (!target) {
          res.sendStatus(204);
        } else {
          const url = target.url;
          target.update({
              visits: target.visits + 1
          })
          try {
            res.status(302).redirect(url)
          } catch (err) {
            res.sendStatus(500)
        }
    }</code></pre>
<h2 id="association을-통한-join-테이블-구현해보자"><strong>Association을 통한 JOIN 테이블 구현해보자</strong></h2>
<h3 id="join할-추가-모델을-생성하자">join할 추가 모델을 생성하자</h3>
<pre><code class="language-jsx">npx sequelize-cli model:generate --name user --attributes 
name:string,email:string,age:integer</code></pre>
<h3 id="urls-테이블에-userid-user-테이블의-id를-참조-필드를-생성하자">urls 테이블에 userId (user 테이블의 id를 참조) 필드를 생성하자</h3>
<p><code>migration skeleton</code> 파일을 하나 생성하여 기존 테이블을 업데이트할 수 있다</p>
<pre><code class="language-jsx">npx sequelize-cli migration:generate --name url-skeleton</code></pre>
<h3 id="생성한-skeleton-파일에서-필드를-하나-추가하고-fk-설정을-해주자">생성한 skeleton 파일에서 필드를 하나 추가하고, FK 설정을 해주자</h3>
<pre><code class="language-jsx">&#39;use strict&#39;;

module.exports = {
  up: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.addColumn(&#39;urls&#39;,&#39;userId&#39;,Sequelize.INTEGER);

      {
        type: Sequelize.INTEGER,
        references: {
          model: &#39;users&#39;,
          key: &#39;id&#39;
        },
        onUpdate: &#39;CASCADE&#39;,
        onDelete: &#39;CASCADE&#39;
      }
    )
  },

  down: async (queryInterface, Sequelize) =&gt; {
    return queryInterface.removeColumn(
      &#39;urls&#39;,
      &#39;userId&#39;
    )
  }
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 관계형 데이터베이스 개요]]></title>
            <link>https://velog.io/@jonghwan2_y/1.-%EA%B4%80%EA%B3%84%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@jonghwan2_y/1.-%EA%B4%80%EA%B3%84%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Sun, 09 Jan 2022 07:17:19 GMT</pubDate>
            <description><![CDATA[<h2 id="sql">SQL</h2>
<p><strong>Structured Query Language: 구조화된 쿼리 언어</strong></p>
<p>대표적인 관계형 데이터베이스(RDBMS), MySQL로 Schema를 설계하고,
SQL을 사용하여 데이터를 persistently하게 저장하는 방법을 학습하자</p>
<ul>
<li>3 Tire Architecture: 클라이언트 - 서버 - 데이터베이스</li>
<li>persistently(영속성) 개념</li>
<li>데이터베이스의 종류 (MySQL, NoSQL)</li>
<li>SQL : 주요 문법 (조회, 삽입, 갱신, 삭제), 통계를 위한 쿼리, 스키마 디자인 (앱에 필요한 테이블과 필드, 관계), 1:N, N:N 관계를 이해하고 데이터베이스 테이블 조작 (Foreign Key, Primary Key)</li>
</ul>
<h3 id="sql은">SQL은?</h3>
<p>하나의 데이터베이스 언어이다. 주로 관계형 데이터베이스에서 사용된다(MySQL, Oracle, PostgreSQL 등).</p>
<p>데이터베이스에 쿼리를 보내, 원하는 데이터를 가져오거나 삽입할 수 있다.
구조화된 테이블을 사용한다는 점이 NoSQL과 같이 문서 지향 데이터베이스와 차이점이다.</p>
<h3 id="acid">ACID</h3>
<p>트랜잭션? 여러 개의 작업을 하나로 묶은 실행 유닛.</p>
<p>여러 작업 중 하나라도 실패하면, 모든 작업을 실패한 것으로 판단.</p>
<ul>
<li><strong>Atomicity (원자성)</strong></li>
</ul>
<p>트랜잭션의 모든 작업이 성공하거나 실패하여 결과를 예측할 수 있다.
여러 작업이 모두 성공한 경우에만 데이터를 활용하고, 하나 작업이 실패하면 묶여 있는 모든 작업을 실패한 것으로 만들어 기존 데이터를 보호한다.</p>
<ul>
<li><strong>Consistency (일관성)</strong></li>
</ul>
<p>데이터베이스의 상태가 일관되어야 한다. </p>
<p>예를 들어, 계좌를 만들 때 계좌주의 실명 없이는 계좌를 개설할 수 없다. 모든 계좌에는 계좌주의 실명이 존재하기 떄문이다. </p>
<ul>
<li><strong>Isolaction (격리성, 고립성)</strong></li>
</ul>
<p>모든 트랜잭션은 다른 트랜잭션으로부터 독립되어야 한다. </p>
<p>두 사람에게 동시에 계좌 이체를 할 때, 각 계좌로 이체를 하는 행위가 서로에게 영향을 미쳐서는 안된다. </p>
<ul>
<li><strong>Durability (지속성)</strong></li>
</ul>
<p>만약 하나의 트랜잭션이 성공적으로 수행되면, 해당 작업의 로그가 영구적 남아있어야 한다. </p>
<p>예를 들어, 계좌 이체를 성공한 후 은행 서버에 오류가 발생하더라도 해당 이체는 내역에 남지만, 계좌 이체 중에 은행 서버에 오류가 발생하면 이체 시도는 실패가 되고 원래 데이터의 상태로 복귀한다.</p>
<h2 id="관계형-데이터베이스-vs-비관계형-데이터베이스">관계형 데이터베이스 vs 비관계형 데이터베이스</h2>
<h3 id="관계형-sql">관계형 (SQL)</h3>
<p>테이블의 구조와 데이터 타입을 사전에 정의한다.
원하는 정보를 쿼리할 수 있다 ⇒ 스키마가 뚜렷하다.</p>
<h3 id="비관계형-nosql">비관계형 (NoSQL)</h3>
<p>데이터가 고정되어 있지 않은 데이터베이스.</p>
<blockquote>
<p>관계형 데이터 - 데이터를 입력할 때 스키마에 맞게 입력
비관계형 데이터 - 데이터를 읽어올 때 스키마에 따라 읽어 옮 (schema on read)</p>
</blockquote>
<h3 id="차이점">차이점</h3>
<ul>
<li>데이터 저장(Storage):</li>
</ul>
<p>관계형 - SQL을 이용하여 테이블에 데이터 저장
비관계형 - key-value, document, wide-column, graph 등의 방식</p>
<ul>
<li>스키마(Schema):</li>
</ul>
<p>관계형 - 고정된 형식의 스키마가 필요
비관계형 - 동적으로 스키마의 형태 관리 가능</p>
<ul>
<li>쿼리(Querying):</li>
</ul>
<p>관계형 - SQL과 같이 구조화된 쿼리 언어 사용
비관계형 - 데이트 그룹을 조회하므로, 구조화되지 않은 쿼리 언어로 데이터 요청 가능</p>
<ul>
<li>확장성(Scalability):</li>
</ul>
<p>관계형 - 수직적으로 확장 (하드웨어 성능을 많이 사용 → 비용이 높음)
비관계형 - 수평적으로 확장 (저렴한 서버로 증설하거나 클라우드 서비스 이용 가능 → 비용이 저렴)</p>
<h4 id="schema">Schema?</h4>
<p>데이터베이스에서 데이터가 구성되는 방식과 서로 다른 엔티티 간의 관계에 대한 설명. 즉, “데이터베이스의 청사진”</p>
<h4 id="entities">entities?</h4>
<p>고유한 정보의 단위 (테이블);</p>
<h4 id="entities간의-관계">entities간의 관계?</h4>
<p>예) 교수 - 수업 - 학생
한 교수가 여러 수업을 가르친다 ⇒ 교수 - 수업 (1:N)</p>
<h3 id="관계의-종류">관계의 종류</h3>
<ul>
<li>1:1</li>
</ul>
<p>사용자와 전화번호부의 관계</p>
<ul>
<li>1:N</li>
</ul>
<p>교수와 수업의 관계. 한 교수가 여러 수업을 진행할 수 있으나, 한 수업을 여러 교수가 진행할 수 없다</p>
<ul>
<li>N:N</li>
</ul>
<p>고객과 여행 패키지의 관계. 한 고객이 여러 패키지를 예약할 수 있고, 하나의 패키지를 여러 고객이 예약할 수 있다</p>
<p>이런 관계의 경우 Join table을 만들어 관리한다.</p>
<p><img src="https://images.velog.io/images/jonghwan2_y/post/a5845770-1f6e-440f-85b5-e733f2f32d46/sql%20joins%20guide%20and%20syntax.jpeg" alt=""></p>
<ul>
<li>자기참조 (self referencing)</li>
</ul>
<p>같은 테이블 내에서, 예를 들어 추천인이 누구인지 파악하는 경우.</p>
<p>한 명의 유저는 한 명의 추천인을 가질 수도, 여러 명의  유저가 한명의 추천인을 가질 수도 있어서 1:N 관계와 유사하다고 볼 수 있으나, 1:N 관계는 서로 다른 테이블에서 표현하는 방법</p>
<hr>
<h4 id="primary-key">Primary key</h4>
<p>고유한 값 (id 뿐 아니라 다른 여러 컬럼에도 부여 가능하다)</p>
<h4 id="foreign-key">Foreign key</h4>
<p>다른 키를 참조하는 키</p>
]]></description>
        </item>
    </channel>
</rss>