<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyolog</title>
        <link>https://velog.io/</link>
        <description>개발을 게임처럼!</description>
        <lastBuildDate>Wed, 26 Jun 2024 14:17:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyolog</title>
            <url>https://images.velog.io/images/kimhyo_0218/profile/d570555a-b9ef-47d0-a8ad-778180f58307/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyolog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kimhyo_0218" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[CSS] CSS-in-JS 라이브러리 간단 분석(runtime, zero-runtime)]]></title>
            <link>https://velog.io/@kimhyo_0218/CSS-in-JS-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EA%B0%84%EB%8B%A8-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@kimhyo_0218/CSS-in-JS-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EA%B0%84%EB%8B%A8-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Wed, 26 Jun 2024 14:17:07 GMT</pubDate>
            <description><![CDATA[<p>CSS-in-JS 라이브러리는 JS와 상태를 공유할 수 있어서 동적으로 스타일링을 할 수 있는 장점이 있습니다. 컴포넌트 기반 개발과 잘 맞아떨어지고 스타일 충돌 문제를 방지할 수 있어 개발자들에게 많은 사랑을 받고 있습니다(저 포함)✨
CSS-in-JS 는 동작 방식이 런타임에 스타일을 생성하는 방식과 빌드 타임에 스타일을 생성하는 방식으로 나누어집니다.</p>
<h1 id="스타일-생성-방식-런타임-vs-빌드-타임">스타일 생성 방식: 런타임 vs 빌드 타임</h1>
<h2 id="런타임-스타일-생성">런타임 스타일 생성</h2>
<p>자바스크립트가 실행되는 시점(컴포넌트가 렌더링될 때) 스타일을 생성합니다. 동적으로 <code>&lt;style&gt;</code> 태그를 생성하여 <code>&lt;head&gt;</code> 태그에 추가됩니다.
대표적인 라이브러리로는 styled-components, emotion, JSS 등이 있습니다.</p>
<h3 id="styled-components-및-emotion의-런타임-스타일-생성">styled-components 및 emotion의 런타임 스타일 생성</h3>
<pre><code class="language-jsx">const StyledButton = styled.button`
  background-color: ${props =&gt; props.primary ? &#39;blue&#39; : &#39;gray&#39;};
  color: white;
`;

// 렌더링 시점에:
&lt;StyledButton primary&gt;Click Me&lt;/StyledButton&gt;</code></pre>
<p>브라우저의 <code>&lt;head&gt;</code> 섹션이나 특정 <code>&lt;style&gt;</code> 태그 안에 동적으로 생성된 스타일이 삽입됩니다.</p>
<pre><code class="language-html">&lt;style&gt;
  .sc-AxjAm { background-color: blue; color: white; }
&lt;/style&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/74540686-9c74-43ef-b818-d449e3ea58f2/image.png" alt=""></p>
<p>개발 모드에서 주로 사용되며 <code>&lt;style&gt;</code> 태그를 생성하여 스타일을 삽입합니다.</p>
<p>단점 - 런타임 오버헤드가 발생할 수 있습니다. 스타일을 계산하고 적용하는 데 시간이 걸리므로 초기 로딩 속도가 느려질 수 있습니다. SSR 설정이 별도로 필요할 수 있습니다.</p>
<h2 id="빌드-타임-스타일-생성-zero-runtime에-관하여">빌드 타임 스타일 생성 (zero-runtime에 관하여)</h2>
<blockquote>
<p>zero-runtime이란 런타임에 스타일을 생성하거나 적용하는 작업이 없는 것을 의미</p>
</blockquote>
<p>스타일이 빌드 과정에서 정적으로 생성되어 CSS 파일로 출력됩니다.
초기 로딩 성능이 개선되며, 스타일 적용이 빠르고 브라우저의 스타일 계산 부담이 줄어듭니다. 
대표적으로 styled-components / emotion, linaria, stitches, @vanilla-extract 등이 있습니다.</p>
<h3 id="styled-components--emotion-완전히-zero-runtime은-아님">styled-components &amp; emotion (완전히 zero-runtime은 아님)</h3>
<p>styled-components &amp; emotion 의 경우 개발 모드와 프로덕션 모드에 차이가 있는데, 프로덕션 모드에선 빌드 타임에 스타일을 미리 생성해서 최적화합니다.
(개발 모드에서의 <code>&lt;style&gt;</code> 태그와 프로덕션 모드에서 <code>&lt;style&gt;</code> 태그를 비교해 보면 차이가 납니다)</p>
<ul>
<li>프로덕션 모드에서 styled-components는 내부 Babel 플러그인을 통해 템플릿 리터럴을 해석하여 최종적으로 CSS 스타일 시트를 생성합니다.
이렇게 생성된 CSS는 하나의 스타일 태그에 모두 적용되어, 불필요한 DOM 조작을 줄이고 초기 렌더링 속도를 향상시킵니다.</li>
<li>프로덕션 모드에서 emotion은 빌드 타임에서 스타일을 생성하여 내부 Babel 플러그인을 통해 컴파일되고(이 과정에서 템플릿 리터럴을 CSS로 변환), 최적화된 스타일 시트를 생성해서 CSSOM에 직접 주입합니다.</li>
</ul>
<blockquote>
<p>내부 Babel 플러그인들(babel-plugin-styled-components, @emotion/babel-plugin)이 제공하는 최적화는 런타임 오버헤드를 완전히 제거하지는 않습니다! 이 플러그인들은 스타일의 생성과 적용 과정을 최적화하고 일부 작업을 빌드 타임으로 이전하지만, 몇 가지 런타임 작업은 여전히 필요하기 때문에 완전한 zero-runtime 으로 볼 수는 없습니다. <strong>컴포넌트가 렌더링될 때 스타일을 적용하는 런타임 작업이 여전히 필요</strong>합니다..!</p>
</blockquote>
<h3 id="stitches-완전히-zero-runtime은-아님">Stitches (완전히 zero-runtime은 아님)</h3>
<p>빌드 타임에 CSS를 생성하여 성능을 최적화하는 CSS-in-JS 라이브러리입니다.
스타일을 빌드 타임에 추출하지만, 실제로 런타임에 <code>&lt;style&gt;</code> 태그를 동적으로 삽입하므로 완전한 zero-runtime이라고 할 수 없습니다. 런타임에서 스타일을 적용하는 과정이 포함됩니다.</p>
<pre><code class="language-tsx">import { styled } from &#39;@stitches/react&#39;;

const Button = styled(&#39;button&#39;, {
  backgroundColor: &#39;gray&#39;,
  borderRadius: &#39;4px&#39;,
  border: &#39;none&#39;,
  color: &#39;white&#39;,
  padding: &#39;10px 20px&#39;,
  cursor: &#39;pointer&#39;,
  &#39;&amp;:hover&#39;: {
    backgroundColor: &#39;darkgray&#39;,
  },
});

//...
&lt;Button&gt;버튼&lt;/Button&gt;</code></pre>
<p>빌드 타임에 Stitches가 생성하는 CSS 객체</p>
<pre><code class="language-tsx">const styles = {
  h1: {
    fontSize: &#39;24px&#39;,
    color: &#39;#333&#39;,
  },
  button: {
    backgroundColor: &#39;gray&#39;,
    borderRadius: &#39;4px&#39;,
    border: &#39;none&#39;,
    color: &#39;white&#39;,
    padding: &#39;10px 20px&#39;,
    cursor: &#39;pointer&#39;,
    &#39;&amp;:hover&#39;: {
      backgroundColor: &#39;darkgray&#39;,
    },
  },
};
</code></pre>
<p>HTML에 삽입된 <code>&lt;style&gt;</code> 태그</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;title&gt;Stitches Example&lt;/title&gt;
  &lt;style&gt;
    /* Injected by Stitches at runtime */
    .title_hash {
      font-size: 24px;
      color: #333;
    }

    .button_hash {
      background-color: gray;
      border-radius: 4px;
      border: none;
      color: white;
      padding: 10px 20px;
      cursor: pointer;
    }
    .button_hash:hover {
      background-color: darkgray;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id=&quot;root&quot;&gt;
    &lt;button class=&quot;button_hash&quot;&gt;버튼&lt;/button&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3 id="linaria">Linaria</h3>
<p>빌드 타임에 CSS를 생성하여 런타임 오버헤드가 없는 zero-runtime CSS-in-JS 라이브러리입니다. 
Linaria의 문법은 Styled Components와 유사합니다!</p>
<pre><code class="language-tsx">import { styled } from &#39;@linaria/react&#39;;

const Button = styled.button`
  background: gray;
  border-radius: 4px;
  border: none;
  color: white;
  padding: 10px 20px;
  cursor: pointer;
  &amp;:hover {
    background: darkgray;
  }
`;

//...
&lt;Button&gt;버튼&lt;/Button&gt;</code></pre>
<p>빌드 타임에 Linaria가 생성하는 CSS 파일 (예: styles.css)</p>
<pre><code class="language-css">.button_classname {
  background: gray;
  border-radius: 4px;
  border: none;
  color: white;
  padding: 10px 20px;
  cursor: pointer;
}
.button_classname:hover {
  background: darkgray;
}</code></pre>
<p>HTML에 삽입된 <code>&lt;style&gt;</code> 태그
Linaria는 빌드된 HTML 파일에 <code>&lt;style&gt;</code> 태그를 포함시켜 스타일을 적용</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;title&gt;Linaria Example&lt;/title&gt;
  &lt;style&gt;
    .button_classname {
      background: gray;
      border-radius: 4px;
      border: none;
      color: white;
      padding: 10px 20px;
      cursor: pointer;
    }
    .button_classname:hover {
      background: darkgray;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id=&quot;root&quot;&gt;
    &lt;button class=&quot;button_classname&quot;&gt;버튼&lt;/button&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3 id="vanilla-extract">Vanilla Extract</h3>
<p>개발 모드와 프로덕션 모드에서 빌드 타임 스타일 생성을 지향하는 zero-runtime  CSS-in-JS 라이브러리입니다.</p>
<ul>
<li>TypeScript와 함께 정적 타입과 최적화된 스타일 생성을 제공합니다.</li>
<li>개발 모드와 프로덕션 모드 모두에서 일관된 방식으로 스타일을 처리하며, 빌드 타임 스타일 생성을 통해 런타임 오버헤드를 최소화합니다✨</li>
</ul>
<p>Vanilla Extract의 컴파일 타임 CSS 파일 생성</p>
<pre><code class="language-tsx">// styles.css.ts
import { style } from &#39;@vanilla-extract/css&#39;;

export const button = style({
  backgroundColor: &#39;blue&#39;,
  color: &#39;white&#39;,
});

// Component.tsx
import { button } from &#39;./styles.css&#39;;

function Component() {
  return &lt;button className={button}&gt;버튼&lt;/button&gt;;
}</code></pre>
<p>컴파일 타임에 <code>styles.css</code> 파일이 생성되고, HTML 파일에 포함됩니다.</p>
<pre><code class="language-html">&lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&gt;</code></pre>
<p>styles.css 내용</p>
<pre><code class="language-css">.button { background-color: blue; color: white; }</code></pre>
<blockquote>
<p>프로젝트 규모와 복잡도, SSR인지 CSR인지, 스타일링 방식 등 기준을 고려하여 요구사항에 맞는 CSS-in-JS 라이브러리를 선택하는 것이 중요하다고 봅니다✨</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Bundler] Webpack vs Vite ! (Webpack 과 Vite  비교하기)]]></title>
            <link>https://velog.io/@kimhyo_0218/webpack-vs-vite</link>
            <guid>https://velog.io/@kimhyo_0218/webpack-vs-vite</guid>
            <pubDate>Sun, 16 Jun 2024 08:36:24 GMT</pubDate>
            <description><![CDATA[<h1 id="번들러의-배경">번들러의 배경</h1>
<p>과거의 웹 애플리케이션은 지금에 비해 비교적 크기와 복잡성이 덜했기 때문에 하나의 거대한 스크립트 파일로 관리되거나 적은 수의 스크립트 파일로도 유지보수가 가능했습니다. 하지만 현대의 웹 애플리케이션은 점점 복잡도가 증가하면서 수많은 자바스크립트 파일과 모듈을 포함하게 되었는데요. 이로 인해 다양한 문제점이 발생하게 됩니다.</p>
<ol>
<li>HTTP 요청의 증가</li>
</ol>
<p>많은 파일을 개별적으로 요청하면 HTTP 요청이 많아집니다. HTTP 요청 수가 많아지면 네트워크 병목 현상이 발생할 수 있고, 웹 페이지 로딩 속도가 느려집니다.
각 파일에 대해 별도의 요청을 처리하는 데 시간이 걸리고, 이는 사용자 경험에 부정적인 영향을 미칠 수 있습니다.</p>
<ol start="2">
<li>로드 타임 증가</li>
</ol>
<p>많은 수의 파일을 개별적으로 불러오면 전체 로드 타임이 증가합니다. 특히, 작은 파일이 많을 경우 파일 하나하나를 로드하는 시간이 누적되어 페이지 로드 속도가 현저히 느려질 수 있습니다.</p>
<ol start="3">
<li>의존성 관리의 어려움</li>
</ol>
<p>각 파일이 서로 의존 관계에 있을 경우, 파일의 로드 순서와 관계를 제대로 관리하지 않으면 문제가 발생할 수 있습니다.
어떤 파일이 다른 파일을 필요로 하는데 해당 파일이 아직 로드되지 않은 상황에서는 에러가 발생할 수 있습니다.</p>
<p>이외에도 의존성 관리가 제대로 되지 않으면 코드 중복으로 인한 불필요한 파일 크기 증가, 변수 이름 충돌, 코드 가독성 문제와 유지보수가 어려워짐 등 다양한 문제점이 있습니다.</p>
<p>이러한 복잡한 구조를 효율적으로 관리하고 재사용성을 높이기 위해, 여러 파일을 하나의 파일로 묶는 방법이 필요해졌습니다. <code>Webpack</code>, <code>Rollup</code> 등 번들러는 이러한 요구를 충족시키기 위해 등장한 도구입니다.</p>
<h1 id="webpack">Webpack</h1>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/2e3dba14-d9d2-4fe9-8dcd-628422363828/image.png" alt=""></p>
<p><code>Webpack</code> 은 모듈 번들러(module bundler)로, 주로 JavaScript 애플리케이션을 개발할 때 사용됩니다. 애플리케이션을 구성하는 <strong>다양한 파일과 모듈을 하나의 번들 파일로 묶어</strong>서 효율적으로 관리하고 배포할 수 있게 해줍니다. </p>
<p>앞서 말한 문제점들도 개선해 주는데요.
여러 JavaScript 파일을 하나로 번들링함으로써 HTTP 요청 수를 줄이고, 로딩 성능도 개선할 수 있습니다!✨</p>
<p>게다가, 현대의 웹 애플리케이션은 JavaScript 외에도 CSS, 이미지, 폰트 등 다양한 파일 형식을 사용하는데
<code>로더(loader)</code>라는 기능을 통해 이러한 다양한 파일들을 모두 하나의 번들 파일로 묶을 수 있게 해줍니다!✨</p>
<p>또한</p>
<ul>
<li>다양한 플러그인을 통해 기능을 확장할 수 있습니다.
예를 들어, <code>Babel</code> 을 사용하여 최신 JavaScript 문법을 구 버전 브라우저에서도 사용할 수 있게 트랜스파일링(transpiling) 할 수 있습니다.</li>
<li>개발 중에 코드 변경을 즉시 반영할 수 있는 Hot Module Replacement(HMR)와 같은 기능을 통해 개발 생산성을 크게 향상시킬 수 있습니다.</li>
<li>미사용 코드를 제거하는 Tree Shaking 을 해서 번들 크기를 줄일 수 있습니다.</li>
<li>Code Splitting 을 하여 번들 파일을 여러개로 쪼개어 초기 로드 시간을 단축시킵니다.</li>
<li>Lazy Loading 기능이 있으며, 사용자가 실제로 필요로 할 때만 리소스를 로드하여 성능을 최적화합니다.</li>
</ul>
<blockquote>
</blockquote>
<p><code>Webpack</code> 은 현대 웹 애플리케이션의 복잡성을 해결하고, 개발자들이 더 효율적으로 작업할 수 있도록 돕는 필수 도구입니다. 코드의 모듈화와 의존성 관리를 자동화하고, 성능을 최적화하며, 다양한 파일 형식을 처리할 수 있게 해줍니다. 이를 통해 개발자는 더욱 빠르고 쉽게 애플리케이션을 개발하고 유지보수할 수 있습니다.</p>
<h2 id="hot-module-replacementhmr-">Hot Module Replacement(HMR) ?</h2>
<p><code>HMR(Hot Module Replacement)</code> 은 개발 중인 애플리케이션의 특정 모듈을 새로 고침하지 않고도(브라우저를 새로 고침 하지 않고도) 교체할 수 있게 하는 기능입니다. 이로 인해 개발자가 코드 변경 사항을 즉시 확인할 수 있습니다.</p>
<h2 id="하지만">하지만..</h2>
<p><code>Webpack</code> 은 개발 중 변경 사항이 있을 때마다 <strong>전체 번들을 재생성</strong>해야 하므로, 변경 반영 속도가 느려질 수 있습니다. 
코드 변경 시 전체 번들을 재생성해야 하므로, 개발 중 빠른 피드백을 받기 어렵고, HMR(Hot Module Replacement) 속도도 느려질 수 있습니다.</p>
<p>이 번들 파일을 브라우저에 로드할 때 애플리케이션이 동작하는 방식은 다음과 같은 단계를 포함하는데요.</p>
<ol>
<li>모든 모듈 분석: 프로젝트의 모든 모듈을 분석하고, 그 의존성을 계산합니다.</li>
<li>모듈 병합: 의존성을 바탕으로 모든 모듈을 하나의 큰 파일(또는 여러 파일)로 병합합니다.</li>
<li>번들 제공: 이 병합된 파일을 개발 서버에서 제공하거나, 프로덕션 환경에 배포합니다.</li>
</ol>
<p>이 방식은 한 번에 모든 모듈을 처리해야 하므로 프로젝트 크기가 커질수록 빌드 시간이 길어질 수 있어요.</p>
<h1 id="webpack-vs-vite">Webpack vs Vite</h1>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/e5bccd06-6419-4b26-800f-b1fbef170ab3/image.jpeg" alt=""></p>
<h2 id="vite-의-탄생">Vite 의 탄생</h2>
<p><code>Vite</code> 는 2020년에 처음 공개되었습니다. 프랑스어로 &quot;빠르다&quot;를 의미하며, 이름에서 알 수 있듯이 빠른 빌드와 개발 속도를 지향합니다. Vue.js의 창시자 Evan You 가 개발했는데요, Vue 3의 개발 중 전통적인 번들링 도구의 한계를 체감하고 더 나은 개발 경험을 제공하기 위해 Vite를 개발했습니다. </p>
<h2 id="✨-esm-기반의-vite-✨">✨ ESM 기반의 Vite ✨</h2>
<h3 id="esm">ESM</h3>
<blockquote>
<p>브라우저가 JavaScript 모듈 시스템을 네이티브로 지원하기 전에는, JavaScript 모듈화를 위해 CommonJS, AMD(Asynchronous Module Definition), UMD(Universal Module Definition) 등의 라이브러리나 패턴을 사용해야 했습니다. 이러한 모듈 시스템은 브라우저에서 네이티브로 지원되지 않기 때문에, Webpack, Browserify 같은 번들러가 필요했습니다.</p>
</blockquote>
<p>ESM이 표준으로 채택되면서, 브라우저가 네이티브로 JavaScript 모듈을 지원하게 되었습니다. 이를 통해 <code>&lt;script type=&quot;module&quot;&gt;</code> 태그를 사용하여 모듈을 로드할 수 있게 되었습니다.</p>
<pre><code class="language-html">&lt;script type=&quot;module&quot; src=&quot;main.js&quot;&gt;&lt;/script&gt;</code></pre>
<p>ESM을 사용하면, <code>import</code> , <code>export</code> 키워드로 모듈간의 의존성을 정의할 수 있게 됩니다.
브라우저는 ESM을 사용하여 필요한 모듈을 개별 파일 단위로 네트워크를 통해 요청하고 로드할 수 있습니다. 이는 개발 중 빠른 피드백과 모듈 간의 독립적인 관리를 가능하게 합니다.</p>
<h3 id="vite">Vite</h3>
<p><code>Vite</code> 는 앞서 설명한 전통적인 번들링 도구의 문제들을 <code>ESM</code> 을 사용하여 해결합니다.
<strong>필요한 모듈만 로드한다</strong> 는 <code>Vite</code> 의 작동 방식은 다음과 같습니다.</p>
<ol>
<li>모듈별 로드: 애플리케이션이 시작되면 index.html 에 있는 스크립트 태그가 브라우저에서 ESM을 사용하여 필요한 모듈을 로드합니다.</li>
<li>온디맨드(on-demand) 로딩: 브라우저가 main.js 파일을 로드할 때, 이 파일이 다른 모듈을 import하면 브라우저는 해당 모듈을 즉시 요청하고 로드합니다.</li>
<li>변경된 모듈만 갱신: 개발 중에 파일이 변경되면, <code>Vite</code>는 변경된 파일만 브라우저에 다시 요청하도록 하여 빠르게 반영됩니다. 전체 애플리케이션을 다시 번들링할 필요가 없습니다.</li>
</ol>
<p>즉, <code>Vite</code> 는 로컬 개발 시에는 번들링을 하지 않고 프로덕션 빌드 시에만 번들링을 하는데, 각 모듈을 브라우저가 직접 요청하도록 합니다. 이는 HMR 성능을 극대화합니다!✨
<code>ES Module</code> 을 사용하여 자바스크립트 파일을 각각의 모듈로 취급하여 필요한 모듈만 개별적으로 로드하기 때문에 개발 시 속도가 매우 빠른 편입니다.✨
또한</p>
<ul>
<li>Vue, React, Preact, Svelte 등 다양한 프레임워크와 호환됩니다.</li>
<li><code>Rollup</code> 을 사용하여 프로덕션 빌드를 최적화합니다.
  -&gt; <code>Rollup</code> 은 <code>Webpack</code> 에 비해 단순하고 이해하기 쉬운 설정을 제공합니다.
  -&gt; Tree Shaking 에 더욱 엄격해요!<pre><code>  ESM(ES Modules) 기반으로 번들링을 수행하기 때문에 모듈 간의 의존성을 정적으로 파악하여 사용되지 않는 코드를 효과적으로 제거할 수 있습니다.</code></pre></li>
</ul>
<h2 id="무엇을-선택해야할까">무엇을 선택해야할까?</h2>
<ul>
<li>npmtrends</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/f2c6f068-0781-4b48-ba9b-3b6f64482ab9/image.png" alt=""></p>
<p><code>Webpack</code> 은 역사가 오래된 만큼 다운로드 수에서 높은 존재감을 보여주고 있습니다.</p>
<ul>
<li>GitHub Star History</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/15a26cb6-bad5-4b1d-ba3d-9037d682fae5/image.png" alt="">
<img src="https://velog.velcdn.com/images/kimhyo_0218/post/b2d00f82-c621-4b5a-847d-c69ac18aa447/image.png" alt=""></p>
<p>상대적으로 새로운 프로젝트인 <code>Vite</code>  의 star 수치가 현재 더 높게 나타나고 있습니다.</p>
<h3 id="webpack-1">Webpack</h3>
<p><code>Webpack</code> 은 매우 큰 커뮤니티와 풍부한 생태계를 가지고 있어, 다양한 플러그인, 로더, 도구 등을 통해 지원받을 수 있습니다.
오래된 프로젝트로, 지속적인 업데이트와 풍부한 문서, 예제가 많아 문제 해결에 유리합니다.</p>
<p>또한 매우 유연한 설정을 제공하며, 다양한 플러그인과 로더를 통해 프로젝트에 맞는 커스터마이징이 가능합니다. 특히, 복잡하고 큰 프로젝트에서 강력한 의존성 관리와 코드 최적화가 필요한 경우 <code>Webpack</code> 이 적합할 수 있습니다.</p>
<h3 id="vite-1">Vite</h3>
<p><code>Vite</code> 는 상대적으로 새로운 프로젝트지만 빠르게 성장 중이며, 공식 문서도 잘 갖추어져 있고 사용이 쉽도록 설계되어 있습니다.
초기 설정이나 기본적인 사용이 쉬우며, 작은 프로젝트나 빠른 프로토타이핑과 새로운 프로젝트 시작에 적합합니다.</p>
<p>특히 모듈 번들링에 있어서 ESM 기반 최적화가 잘 되어 있어, 작은 번들 크기와 빠른 로딩 속도를 제공합니다.
하지만 기존 프로젝트의 마이그레이션 과정에서는 일부 어려움이 있을 수 있고, 안정성 문제도 고려해야 합니다. </p>
<p>결론적으로, 프로젝트의 특정 요구사항과 개발 팀의 기술적 성향을 고려하여 <code>Webpack</code> 과 <code>Vite</code> 중 적절한 도구를 선택하는 것이 좋을 것 같습니다!🌟</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Quill] 리액트 퀼 에디터 Ios 한글 줄바꿈 버그 고치기 (조합 문자 문제)]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Quill-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%80%BC-%EC%97%90%EB%94%94%ED%84%B0-Ios-%ED%95%9C%EA%B8%80-%EC%A4%84%EB%B0%94%EA%BF%88-%EB%B2%84%EA%B7%B8-%EA%B3%A0%EC%B9%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimhyo_0218/React-Quill-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%80%BC-%EC%97%90%EB%94%94%ED%84%B0-Ios-%ED%95%9C%EA%B8%80-%EC%A4%84%EB%B0%94%EA%BF%88-%EB%B2%84%EA%B7%B8-%EA%B3%A0%EC%B9%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Mar 2024 09:52:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/97b1a7ae-27e5-4144-aea0-a7b10dcaab26/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/fe0f1fd0-ed16-490a-bdf4-c2fc83ce1949/image.gif" alt=""></p>
<p>회사에서 1월 초에 인아웃 앱 대나무숲 게시판 에디터를 react quill 라이브러리로 교체했는데 어느 날부터 한글 입력 줄바꿈이 제대로 적용이 되지 않는 문제가 생겼습니다. 안드로이드와 ios 둘다 테스트를 진행했는데 ios에서만 해당 버그가 발견됐습니다.</p>
<p>엔터를 쳐서 줄바꿈을 하면 줄바꿈이 되지 않고 원래 줄에서 마지막 글자가 복사가 되고 그 다음 입력하려던 글자가 입력 됩니다. 꼭 받침 없는 글자에서만 버그가 발생해서 찾아보니 한글이 조합 문자이기 때문에 생기는 문제 같았습니다.
(배포 당시 QA에서는 문제가 없었는데 🥹)</p>
<p>일단 리액트 퀼에 적용하는 모듈의 코드 입니다.</p>
<pre><code>  const modules = useMemo(
    () =&gt; ({
      toolbar: {
        container: &#39;#toolbar&#39;,
      },
    }),
    [],
  );</code></pre><p>커스텀 헤더를 사용 중이라 container에 class 값을 적용해준 것 밖에 없었습니다.
찾아보니 키보드 입력 함수를 새로 바인딩 할 수 있어서 Enter 키 입력 시에 해당 문제를 고쳐야겠다고 생각했습니다.</p>
<h2 id="해결-코드">해결 코드</h2>
<pre><code>  const modules = useMemo(
    () =&gt; ({
      toolbar: {
        container: &#39;#toolbar&#39;,
      },
      keyboard: {
        bindings: {
          enter: {
            key: 13,
            handler: (range: any) =&gt; {
              const selection = document.getSelection();
              selection?.removeAllRanges();
              editorRef.current?.editor?.setSelection(range.index, 0);
              selection?.addRange(range);
              editorRef.current?.editor?.insertText(range.index, &#39;\n&#39;);
            },
          },
        },
      },
    }),
    [],
  );</code></pre><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection">getSelection</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Selection/removeAllRanges">removeAllRanges</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Selection/addRange">addRange</a></p>
<p>처음에 데이터 확인을 하려고 <code>editorRef.current?.editor?.insertText(range.index, &#39;\n&#39;);</code> 코드와 <code>alert()</code> 메소드만 handler 내부에 넣었었는데 엔터를 입력하고 얼럿창이 실행되고 나서는 해당 버그가 일어나지 않는 것을 발견했습니다.
그렇다면 커서를 제어하면 되지 않을까 생각했고
여러 시도 끝에 엔터 키를 누르면 커서를 삭제한다 -&gt; 해당 글자 index 값에 다시 커서를 set 해준다 로 가니 아래처럼 버그가 수정되었습니다 :) 짜잔</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/2cb4a834-d2b1-4ccf-ba18-cde8c04696e2/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] 리액트 쿼리 조건부 렌더링 주의할 점 그리고 렌더링 최적화]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Fri, 22 Jul 2022 13:17:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/e66decf1-d8be-4bbc-b7cb-7b0f9df55615/image.png" alt=""></p>
<p> 지난 포스팅에서는 리액트 쿼리 기본 옵션 설정이나 커스텀 훅, 쿼리 키 관리 등에 대해 소개를 해드렸는데요! 이번 포스팅에서는 리액트 쿼리를 사용하면서 실제로 제가 마주쳤던 잦은 api 요청과 데이터가 변한 것이 없는데도 발생하는 리렌더링, 도대체 왜 이런건지 이유와 개선하는 방법에 대해 알아보았던 내용을 소개하겠습니다.</p>
<h1 id="usequery">useQuery</h1>
<pre><code class="language-tsx">// 부모 컴포넌트
const { data: userList } = useQuery([&#39;user&#39;], queryFn, queryOptions);</code></pre>
<pre><code class="language-tsx">// 자식 컴포넌트
const { data: userList } = useQuery([&#39;user&#39;], queryFn, queryOptions);</code></pre>
<p>위 코드에서<code>[&#39;user&#39;]</code> 라는 키 값을 설정했는데 이 키 값을 가진 동일한 <code>useQuery</code> 가 부모 컴포넌트, 자식 컴포넌트에서 같이 호출하고 있는 상태이면 리액트 쿼리는 한 번의 api 요청 후 지정해둔 키를 식별자로 데이터를 캐싱합니다.</p>
<p>그런데,
조건부 렌더링이 들어가있다면 의도와 다르게 api 요청이 여러번 일어나는 것을 볼 수 있는데요..! 조건부 렌더링이 무엇인지부터 코드를 살펴볼게요!</p>
<h1 id="조건부-렌더링">조건부 렌더링</h1>
<pre><code class="language-tsx">const { data, isLoading, isError, error } = useQuery(queryKey, queryFn, queryOptions);

if (isLoading) return &lt;span&gt;isLoading...&lt;/span&gt;;
if (isError) return &lt;span&gt;Error: {error.message}&lt;/span&gt;;
if (!data) return null;

return (
  // ...
)</code></pre>
<p>리액트 쿼리를 사용하다보면 위와 같은 조건부 렌더링 로직을 많이 접하게 됩니다. isLoading 중일 때 보여줄 화면, data가 없을 때 보여줄 화면 등 상태에 따라 다른 화면을 보여줄 때 많이 사용되고 있는 로직이죠.
부모 컴포넌트에서 이렇게 조건부 렌더링이 작성되어있고 자식 컴포넌트에서 같은 쿼리를 호출하는 상황에서 네트워크 탭을 보면 api 요청이 2번 된 것을 볼 수 있습니다. 왜 그럴까요? 재밌는 포인트는 <code>staleTime</code> 과 <code>cacheTime</code> 그리고 <code>refetchOnMount</code> 입니다.</p>
<h2 id="메인-예제-코드">메인 예제 코드</h2>
<p> <code>useQuery</code> 를 커스텀 훅으로 만들고 부모와 자식 컴포넌트에서 호출하는 로직을 예제 코드로 만나볼게요.</p>
<pre><code class="language-ts">// user 정보를 가져오는 쿼리 커스텀 훅
import { AxiosError } from &#39;axios&#39;;
import { useQuery, UseQueryOptions, UseQueryResult } from &#39;react-query&#39;;
import { getUserInfoById } from &#39;src/api/user&#39;;
import { UserInfoType } from &#39;src/types/userType&#39;;
import { queryKeys } from &#39;src/types/commonType&#39;;

export default function useGetUserInfoByIdQuery(
  id: number,
  options?: UseQueryOptions&lt;UserInfoType, AxiosError&gt;
): UseQueryResult&lt;UserInfoType, AxiosError&gt; {
  return useQuery&lt;UserInfoType, AxiosError&gt;(queryKeys.userInfo(id), () =&gt; getUserInfoById(id),
  {
    enabled: !!id,
    ...options
  });
}</code></pre>
<pre><code class="language-tsx">// 부모 컴포넌트인 Modal.tsx
export default function Modal({ id }: Props) {
 const { data, isLoading, isError, error } = useGetUserInfoByIdQuery(id);
  console.log(&#39;(1) Modal Component&#39;);

  if (isLoading) { 
    console.log(&#39;isLoading&#39;, isLoading);
    return &lt;span&gt;Loading...&lt;/span&gt;;
  }

  if (isError) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;;
  }

  if (!data) {
    return &lt;span&gt;No data found&lt;/span&gt;;
  }

  return (
    &lt;Container&gt;
      &lt;UserInfoSection id={id} /&gt;
    &lt;/Container&gt;
  );
}</code></pre>
<p>부모 컴포넌트인 Modal.tsx 에서 조건부 렌더링을 하고 있습니다.
자식 컴포넌트인 <code>&lt;UserInfoSection /&gt;</code> 코드도 보겠습니다.</p>
<pre><code class="language-tsx">// UserInfoSection.tsx
export default function UserInfoSection({ id }: Props) {
  const { data, isLoading } = useGetUserInfoByIdQuery(id);
  console.log(&#39;(2) UserInfoSection Component&#39;);

    if (isLoading) { 
    console.log(&#39;isLoading2&#39;);
    return &lt;span&gt;Loading...&lt;/span&gt;;
  }

  return (
    // data 렌더링...
  )    
}</code></pre>
<p>코드를 실행하고 네트워크 탭을 보면 api를 2번 요청하는 것을 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/3baba36b-2f46-4ef8-a2e0-df092387fa88/image.png" alt=""></p>
<h2 id="리액트-쿼리-라이프-사이클">리액트 쿼리 라이프 사이클</h2>
<p>요청이 2번 일어나는 이유를 알기 전에 리액트 쿼리의 라이프 사이클을 짚고 넘어가면 이해하기 더 쉽습니다!</p>
<p>console.log 를 확인해볼게요.</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/53d222b7-9669-4c7d-928c-0c15f0934b97/image.png" alt=""></p>
<h3 id="queryoptions-staletime">[queryOptions] staleTime</h3>
<p>기본 값이 <code>0초</code>인 <code>staleTime</code> 은 fetch 이후 데이터가 바로 <code>fresh</code> 상태에서 오래된 상태인 <code>stale</code> 상태로 변경됩니다.</p>
<h3 id="queryoptions-refetchonmount-개념">[queryOptions] refetchOnMount 개념</h3>
<p><code>refetchOnMount</code> 는 기본 값이 <code>true</code> 로 설정되어있는데요! 데이터가 <code>stale</code> 상태일 때 마운트 시 refetch 를 하는 옵션입니다.</p>
<p>흐름은 이렇습니다!</p>
<blockquote>
<ol>
<li>부모 컴포넌트를 렌더링한다.</li>
<li><code>useGetUserInfoByIdQuery(id)</code> 이 호출된다. (isFetching, isLoading : true)</li>
<li>요청이 완료되면<ul>
<li>isFetching 과 isLoading 은 false가 된다.</li>
<li>key 값을 식별자로 데이터를 캐싱한다.</li>
<li>데이터는 <code>fresh</code> 상태이며 <code>staleTime</code> 이 지나면 <code>stale</code> 상태가 된다.</li>
</ul>
</li>
<li>부모 컴포넌트가 다시 리렌더링 되면서 자식 컴포넌트의 <code>useGetUserInfoByIdQuery(id)</code> 가 호출된다.<ul>
<li>데이터가 <code>stale</code> 상태이고 <code>refetchOnMount</code> 가 true 이므로  background refetch 가 진행된다. </li>
</ul>
</li>
</ol>
</blockquote>
<p>첫 fetching 이후 <code>staleTime</code>과 <code>refetchOnMount</code>로 인해 refetching 을 하면서 총 2번 요청하게 되는 것 입니다.</p>
<p>제가 원했던 건 자식 컴포넌트까지 api를 요청할 게 아니라, 처음 한 번의 요청과 추가적으로 mount 시 캐싱된 데이터를 사용하는 것 입니다.</p>
<h3 id="staletime-을-적용해보자">staleTime 을 적용해보자</h3>
<p>자식 컴포넌트에서 커스텀 훅에 옵션을 파라미터로 전달해서 적용해도 되고,</p>
<pre><code class="language-tsx"> const { data, isLoading } = useGetUserInfoByIdQuery(id, {
   staleTime: 60 * 1000 // 1분
 });</code></pre>
<p>커스텀 훅 파일에서 직접 적용해도 됩니다.</p>
<pre><code class="language-tsx">const useGetUserInfoByIdQuery = (id) =&gt; {
  return useQuery([&#39;userInfo&#39;, id], () =&gt; getUserInfoById(id), {
    enabled: !!id,
    staleTime: 60 * 1000,
    ...options
  });
};</code></pre>
<p><code>staleTime</code> 을 1분으로 설정해봤는데요! 이렇게 되면 위의 과정에서
데이터가 <code>staleTime</code> 1분이 지나기 전에 자식 컴포넌트의 쿼리가 mount 될 때 데이터가 <code>fresh</code> 상태이기 때문에 <code>refetchOnMount</code> 가 일어나지 않습니다.
 다시 네트워크 탭을 확인해보면 api 요청이 1번만 일어나는 것을 확인할 수 있습니다!</p>
<p>저는 여기서 좀 이상함을 느꼈던 부분이 있는데요,</p>
<blockquote>
<ol>
<li>자식 컴포넌트의 console.log(isLoading2) 는 왜 안찍히는 걸까?</li>
<li>console.log 는 왜 이렇게 많이 찍힐까?</li>
</ol>
</blockquote>
<h3 id="cachetime">cacheTime</h3>
<p>리액트 쿼리에서 <code>cacheTime</code> 은 기본 5분으로 설정되어있습니다. 처음 fetch 를 하고나서 query key 값을 식별자로 5분동안 데이터가 캐싱되어있는데요, 쿼리가 unmount 되면 <code>inactive</code> 상태가 되고 그 상태로 5분이 지나면 가비지 콜렉터로 수집 됩니다.
 5분이 지나기 전에 같은 key 값의 쿼리가 마운트되어 재실행되면 isFetching 은 true 가 되지만 isLoading 은 그대로 false 입니다.</p>
<p>즉, 캐싱된 데이터가 없는 상태에서의 fetch가 실행될 때 isLoading 이 true 로 되는거에요!</p>
<h1 id="렌더링-최적화">렌더링 최적화</h1>
<p> 두 번째 의문을 살펴보겠습니다. 위에서 console.log 가 많이 찍혀있는 것을 확인했는데요! 데이터가 변한게 없는데 왜 컴포넌트가 리렌더링 될까요?</p>
<pre><code class="language-tsx">{ status: &#39;success&#39;, data: [...], isFetching: true }
{ status: &#39;success&#39;, data: [...], isFetching: false }</code></pre>
<p>리액트 쿼리가 제공하는 많은 메타 정보 중에 isFetching 이 있는데 이 상태가 변경되면서 해당 쿼리를 갖고 있는 컴포넌트들이 리렌더링 되는 것입니다.</p>
<p>음, 그러면 <code>staleTime</code> 을 설정해서 refetch 가 진행되지 않게 하면 리렌더링이 안 일어날까요?</p>
<p>테스트를 위해 <code>staleTime</code> 을 <strong>3초</strong>로 지정해보았습니다.</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/0eac3417-ca94-455f-bf4e-75e202cbba21/image.png" alt=""></p>
<p>3초가 지나자 추가 리렌더링이 발생하였습니다 isFetching 은 계속 false 인데 말이죠!</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/9d6e8d25-828a-4f67-adaf-f8a69bad5fd6/image.png" alt=""></p>
<p>isStale 을 찍어보니, 3초가 지난 뒤 isStale 이 true 가 되면서 리렌더링이 발생하는 것을 확인할 수 있습니다. </p>
<blockquote>
<ol>
<li>부모 컴포넌트의 첫 fetch 후 isStale 이 false 가 되고 리렌더링</li>
<li>다시 부모 컴포넌트의 콘솔이 찍히고 자식 컴포넌트가 렌더링 되면서 자식 컴포넌트의 콘솔이 찍힘</li>
<li>3초 후 isStale이 true 가 되면서 다시 해당 쿼리를 품고 있는 부모 컴포넌트와 자식 컴포넌트가 리렌더링 된다.</li>
</ol>
</blockquote>
<p>이렇게 isFetching 이나 isStale 이 아니어도 리액트 쿼리에서 제공하는 여러 플래그 값들로 인해서 리렌더링이 발생할 수 있습니다.
이것을 최적화 하는 방법이 있는데요! </p>
<h2 id="notifyonchangeprops">notifyOnChangeProps</h2>
<pre><code class="language-tsx">const useGetUserInfoByIdQuery = (id) =&gt; {
  return useQuery([&#39;userInfo&#39;, id], () =&gt; getUserInfoById(id), {
    enabled: !!id,
    staleTime: 3 * 1000,
    notifyOnChangeProps: &#39;tracked&#39;,
  });
};</code></pre>
<p><strong>notifyOnChangeProps</strong> 라는 옵션을 설정해주면 됩니다.
이 옵션은 나열된 속성들 중 하나만이라도 변경되면 다시 리렌더링을 해주는 옵션인데요</p>
<pre><code class="language-tsx">notifyOnChangeProps: [&#39;data&#39;],</code></pre>
<p>이렇게 적용하면 <code>const { data, isLoading } = useQuery(...);</code> 의 <code>data</code> 만 변경되었을 때 리렌더링이 됩니다. 그런데 <code>isLoading</code> 을 이용해서 조건부 렌더링을 해주고 있는 경우라면 문제가 생깁니다. <code>isLoading</code> 이 변경되었는지 감지하지 못하기 때문입니다.</p>
<h2 id="tracked">tracked</h2>
<pre><code class="language-tsx">notifyOnChangeProps: &#39;tracked&#39;,</code></pre>
<p><code>tracked</code> 로 설정해주면 리액트 쿼리는 렌더 중에 사용 중인 속성을 알아서 추적하고 사용 중인 속성들이 변화가 있을 때에만 리렌더링을 해줍니다.
console.log 로 테스트할 때 작성했던 isFetching 과 isStale 을 지우고 다시 확인해보면,</p>
<p><img src="https://velog.velcdn.com/images/kimhyo_0218/post/9a09f800-fde0-4381-9710-a59eacdf9131/image.png" alt=""></p>
<p>요렇게 <code>staleTime</code> 이 지나도 리렌더링이 발생하지 않는 것을 확인할 수 있습니다!</p>
<p>그럼 또 하나의 의문이 생깁니다.
왜 리액트 쿼리는 이것을 default 로 설정하지 않은 걸까요?
선택 옵션으로 둔 이유가 있는데 몇 가지 살펴보겠습니다.</p>
<pre><code class="language-tsx">const { data, ...queryInfo } = useQuery(...);</code></pre>
<p>object rest destructuring 로 사용하면 모든 필드를 추적하기 때문에 주의해야합니다.</p>
<pre><code class="language-tsx">const queryInfo = useQuery(...);

React.useEffect(() =&gt; {
    console.log(queryInfo.data)
})
</code></pre>
<p>위에서 언급했듯이 &quot;렌더 중&quot;에 사용 중인 쿼리를 추적하는데 이렇게 의존성 배열에 사용 중인 속성을 담지 않으면 정확한 추적이 되지 않습니다.</p>
<p>그리고, tracked 를 설정해서 사용 중인 속성들을 추적하는 것도 약간의 오버헤드가 있으니 상황에 따라 사용해야한다고 합니다!</p>
<p>여기까지 리액트 쿼리의 조건부 렌더링과 렌더링 최적화에 관련된 내용과 옵션들을 살펴보았습니다. 이렇게 다양한 옵션을 설정하고 공부하는 재미가 있는 리액트 쿼리를 저는 정말정말 좋아합니다!</p>
<p>잘못된 내용이나 피드백은 댓글 남겨주세요 :)</p>
<p><a href="https://tkdodo.eu/blog/react-query-render-optimizations">리액트 쿼리 렌더링 최적화 참고 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] 리액트 쿼리 useMutation 실용 편(custom hook 으로 사용해보자)]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-useMutation-%EC%8B%A4%EC%9A%A9-%ED%8E%B8custom-hook-%EC%9C%BC%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-useMutation-%EC%8B%A4%EC%9A%A9-%ED%8E%B8custom-hook-%EC%9C%BC%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 03 Apr 2022 13:47:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://media.vlpt.us/images/kimhyo_0218/post/cd787b86-2b48-43c5-a6de-0b3b1a8752db/reactquery_logo.png" alt=""></p>
<p>지난 번에는 <code>useMutation</code> 의  기본 적인 사용 방법에 대해서 소개했다. 이번에는 전 편에서 잠깐 언급했던 custom hook을 사용하면서 queryClient랑 함께 사용하기와 <code>optimistic updates</code> 에 대한 내용을 적으려한다.</p>
<h1 id="🌟-usemutation-커스텀-훅-with-typescript">🌟 useMutation 커스텀 훅 with TypeScript</h1>
<p><code>useMutation</code> 도 <code>useQuery</code> 와 마찬가지로 커스텀 훅으로 만들어서 사용하면 많이 유용하다.</p>
<h2 id="기본-형태의-커스텀-훅1">기본 형태의 커스텀 훅(1)</h2>
<ul>
<li>/hooks 폴더에서 생성한 useAddTodoMutation 훅에서 <code>onSuccess</code> 와 <code>onError</code> 를 사용했을 때의 경우이다.<pre><code class="language-tsx">// src/hooks/
import { AxiosError } from &#39;axios&#39;;
import { useMutation, UseMutationResult } from &#39;react-query&#39;;
import { addTodo } from &#39;src/api/todos&#39;;
import { TodoType } from &#39;src/types/todoType&#39;;
</code></pre>
</li>
</ul>
<p>export default function useAddTodoMutation(): UseMutationResult&lt;TodoType, AxiosError, TodoType&gt; {
  return useMutation(addTodo, {
    onSuccess: (data) =&gt; {
      console.log(data); // mutation 이 성공하면 response를 받을 수 있다.
    },<br>    onError: (error) =&gt; { // mutation 이 에러가 났을 경우 error를 받을 수 있다.
      console.error(error);
    },
  });
}</p>
<pre><code>## 기본 형태의 커스텀 훅(2)
- (1)번의 훅에는 단점이 있다.
- 공통으로 사용되는 훅인데, mutation 이 실행되고 난 후의 행동이 각각 다르다면?? onSuccess 등의 추가 콜백을 달기엔 애매한 상황이 온다.

- 그럴 땐 useAddTodoMutation 에 달았던 콜백 함수를 지우고, 아래 예시처럼 mutate 옵션 자리에 작성해주면 된다.
(전 편에서 언급했다시피 mutate 의 추가 콜백은 useMutation 추가 콜백 다음에 실행되며, unmount가 되면 실행되지 않을 수 있다.)
```tsx
// src/components/
// ...
const { mutate: addTodoMutate } = useAddTodoMutation();

const handleAddTodo = useCallback(() =&gt; {
  addTodoMutate({
    id: 1, todo: &#39;mutate에서의 추가 콜백&#39;
  },
  {
    onSuccess: (data) =&gt; {
      alert(&#39;Todo added!&#39;);
    },
  });
}, [addTodoMutate]);

return (
  // ...
  &lt;button onClick={handleAddTodo}&gt;작성 완료&lt;/button&gt;
  // ...
)</code></pre><hr>
<h2 id="🌟-queryclient-의-invalidatequeries-">🌟 queryClient 의 invalidateQueries !</h2>
<blockquote>
<p> mutation 과 최고의 효율을 자랑하는 invalidateQueries 메소드는 정말 정말 유용하다.</p>
</blockquote>
<p>어떨 때 사용할 수 있을까 ? 🤔</p>
<p>바로, 기존 쿼리를 <strong>강제로 오래된 데이터로 취급하는 무효화 처리를 하고 싶을 때</strong> 쓰면 된다!</p>
<p>&lt;&lt; useQuery 로 return 받은 data 를 그대로 UI에 렌더링 하는 경우의 이야기 입니다. &gt;&gt;
🍃 <strong>리액트 쿼리 사용 전</strong></p>
<ol>
<li>todo 를 새로 작성하고 create api 를 요청한다.</li>
<li>api 가 성공하고 나면 클라이언트 상태(todo list)에 response(추가한 todo)를 update 해주는 dispatch 같은 액션 함수를 사용해야한다.</li>
</ol>
<p>🌼 <strong>리액트 쿼리 사용 후</strong></p>
<ol>
<li>todo 를 새로 작성하고 create mutation을 실행한다.</li>
<li>onSettled 나 onSuccess 콜백 함수에 todo list 쿼리를 invalidateQueries() 작성해주면 알아서 최신 값으로 refetch 된다.</li>
</ol>
<p>바아로 (1)에 작성했던 코드를 가져와 변형시켜보겠다.</p>
<pre><code class="language-tsx">// src/hooks
import { AxiosError } from &#39;axios&#39;;
import { useMutation, UseMutationResult, useQueryClient } from &#39;react-query&#39;;
import { addTodo } from &#39;src/api/todos&#39;;
import { TodoType } from &#39;src/types/todoType&#39;;
import { queryKeys } from &#39;src/types/commonType&#39;; // useQuery 실용 편 참조

export default function useAddTodoMutation(): UseMutationResult&lt;TodoType, AxiosError, TodoType&gt; {
  const queryClient = useQueryClient();
  return useMutation(addTodo, {
    onSuccess: () =&gt; {
      queryClient.invalidateQueries(queryKeys.todos); // mutation을 성공하면 todo list를 불러오는 useQuery를 무효화 시킨다.
    },  
    onError: (error) =&gt; {
      console.error(error);
    },
  });
}</code></pre>
<ul>
<li><code>invalidateQueries</code> 가 실행되어 쿼리가 무효화되면 해당 쿼리는 오래된 것으로 취급 된다.</li>
<li>쿼리를 다시 refetching 한다!</li>
</ul>
<p>todo list 를 렌더링 하고 있던 컴포넌트는 새로운 todo 가 추가됨과 동시에 바로 해당 키값의 쿼리를 refetch 시키기 때문에
새로고침이나, 클라이언트 상태에 해당 데이터를 업데이트 시켜주지 않아도 된다. (별도로 클라이언트 상태를 건들 작업이 없을 경우에 해당)</p>
<ul>
<li>쿼리를 다시 refetch 하는 것을 원하지 않고 단순히 무효화만 시키고 싶을 경우에는 <code>refetchActive: false</code> 를 붙여주면 된다.<pre><code class="language-tsx">queryClient.invalidateQueries(queryKeys.todos, {
refetchActive: false, 
});</code></pre>
<a href="https://react-query.tanstack.com/reference/QueryClient#queryclientinvalidatequeries">queryClient.invalidateQueries 문서</a>
<a href="https://react-query.tanstack.com/guides/query-invalidation#query-matching-with-invalidatequeries">queryClient.invalidateQueries 문서2</a></li>
</ul>
<hr>
<h2 id="🌟-optimistic-updates-">🌟 optimistic updates !</h2>
<blockquote>
<p>💡 mutation 의 <code>optimistic update</code> 는 사용자가 어떠한 액션을 발생시켰을 때 요청이 성공했는지 실패했는지 아직 알 수 없는 상태에서 성공할 것이라고 낙관적으로 가정하고 사용자가 보고 있는 UI 를 먼저 변화시켜주는 것을 말한다. 사용자에게 빠른 경험을 제공할 수 있다는 큰 장점이 있다!</p>
</blockquote>
<pre><code class="language-tsx">import { AxiosError } from &#39;axios&#39;;
import { useMutation, UseMutationResult, useQueryClient } from &#39;react-query&#39;;
import { addTodo } from &#39;src/api/todos&#39;;
import { TodoType } from &#39;src/types/todoType&#39;;
import { queryKeys } from &#39;src/types/commonType&#39;;  // useQuery 실용 편 참조

export default function useAddTodoMutation(): UseMutationResult&lt;
  TodoType,
  AxiosError,
  TodoType,
  {
    previousTodos: TodoType[] | undefined;
  }
&gt; {
  const queryClient = useQueryClient();
  return useMutation(addTodo, {
    onMutate: async (newTodo: TodoType) =&gt; { // mutate가 호출될 때
      // 쿼리를 확실하게 취소하고
      await queryClient.cancelQueries(queryKeys.todos);
      // 쿼리 상태를 가져온다(이전 값 스냅샷)
      const previousTodos = queryClient.getQueryData&lt;TodoType[]&gt;(queryKeys.todos);

      if (previousTodos) {
        // previousTodos 가 있으면 setQueryData 를 이용하여 즉시 새 데이터로 업데이트 해준다.
        queryClient.setQueryData&lt;TodoType[]&gt;(queryKeys.todos, (old) =&gt; [
          ...(old as TodoType[]),
          newTodo,
        ]);
      }
      return { previousTodos }; // 이전 값을 리턴한다
    },
    onError: (
      err: AxiosError,
      variables: TodoType,
      context?: { previousTodos: TodoType[] | undefined }
    ) =&gt; {
      if (context?.previousTodos) { // error 를 만났을 경우 onMutate에서 반환된 값으로 다시 롤백시켜준다.
        queryClient.setQueryData&lt;TodoType[]&gt;(queryKeys.todos, context.previousTodos);
      }
    },
    onSettled: () =&gt; { // mutation이 끝나면 (성공유무 상관없이) 쿼리를 무효화 처리하고 새로 가져온다.
      queryClient.invalidateQueries(queryKeys.todos);
    },
  });
}</code></pre>
<ul>
<li><code>queryClient.cancelQuerie</code> : 발신 쿼리를 취소하는 데 사용할 수 있다. (optimistic update를 덮어쓰지 않도록)</li>
<li><code>queryClient.getQueryData</code> : 기존 쿼리의 상태를 가져오는 동기 함수. 쿼리가 존재하지 않으면 <code>undefined</code> 를 반환.</li>
<li><code>queryClient.setQueryData</code> : 쿼리의 캐시된 데이터를 즉시 업데이트할 수 있는 동기 함수. 쿼리가 존재하지 않으면 생성된다.</li>
</ul>
<p>optimistic updates 를 이용하면 사용자가 api 요청 성공 유무를 기다리지 않고 바로 UI 단에서 변화를 확인할 수 있어 유용하다.
ex) 페이스북 좋아요를 누른다고 가정할 때 좋아요를 누르거나 취소할 때마다 api 결과 기다려야 한다면 매우 답답할 것이다. 사용자가 좋아요를 마구 눌렀다 취소할 수 도 있는데 이럴 때 특히 유용하다.</p>
<p><a href="https://react-query.tanstack.com/guides/optimistic-updates">optimistic updates 문서</a></p>
<p>모두 리액트 쿼리 쓰세요오오😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] 리액트 쿼리 useMutation 기본 편]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-useMutation-%EA%B8%B0%EB%B3%B8-%ED%8E%B8</link>
            <guid>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-useMutation-%EA%B8%B0%EB%B3%B8-%ED%8E%B8</guid>
            <pubDate>Wed, 30 Mar 2022 15:46:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/kimhyo_0218/post/4f30b454-866f-4638-9fcf-fad49222bd0a/reactquery_logo.png" alt=""></p>
<p>너무나 오랜만에 쓰는 블로그....ㅠ.ㅠ</p>
<h2 id="usemutation">useMutation</h2>
<ul>
<li>useQuery 와 다르게 mutation은 데이터를 생성 / 업데이트 / 삭제 할 때 사용 된다.</li>
<li><strong>내가 리액트 쿼리를 좋아하는 이유 중 큰 비중을 차지하는</strong> queryClient 의 <code>invalidateQueries</code> 메소드 및 <code>setQueryData</code> 메소드랑 같이 사용하면 최고의 효율을 낼 수 있다 너어무 좋다</li>
<li>mutation 의 성공을 바라며 <strong>미리 UI부터 변화시켜주는</strong> <code>optimistic update</code> 기능도 사용자에게 정말 좋은 경험을 제공할 수 있다.</li>
<li>전에 useQuery 편 처럼 useMutation 을 커스텀 훅으로 만드는 내용까지 시리즈로 모두 기록할 예정 !</li>
</ul>
<pre><code class="language-tsx">import { useMutation } from &quot;react-query&quot;;
// 더 많은 return 값들이 있다. 
const { data, isLoading, mutate, mutateAsync } = useMutation(mutationFn, options);

mutate(variables, {
  onError,
  onSettled,
  onSuccess,
});</code></pre>
<h2 id="options">Options</h2>
<h4 id="mutationfn-variables-tvariables--promisetdata"><code>mutationFn (variables: TVariables) =&gt; Promise&lt;TData&gt;</code></h4>
<ul>
<li>Required </li>
<li>비동기 작업을 수행하고 프로미스를 반환하는 함수이다. (쉽게 말해 api 요청하는 함수)</li>
<li>variables 는 mutate가 전달하는 객체이다.</li>
</ul>
<h4 id="onmutate-variables-tvariables--promisetcontext--void--tcontext--void"><code>onMutate: (variables: TVariables) =&gt; Promise&lt;TContext | void&gt; | TContext | void</code></h4>
<ul>
<li>onMutate 는 mutation 함수가 실행되기 전에 실행되고 mutation 함수가 받을 동일한 변수가 전달된다.</li>
<li><code>optimistic update</code> 사용 시 유용한 함수이다.</li>
</ul>
<h4 id="onsuccess-data-tdata-variables-tvariables-context-tcontext--promiseunknown--voi"><code>onSuccess: (data: TData, variables: TVariables, context?: TContext) =&gt; Promise&lt;unknown&gt; | voi</code></h4>
<ul>
<li>onSuccess 는 mutation 이 성공하고 결과를 전달할 때 실행된다.</li>
</ul>
<h4 id="onerror-err-terror-variables-tvariables-context-tcontext--promiseunknown--void"><code>onError: (err: TError, variables: TVariables, context?: TContext) =&gt; Promise&lt;unknown&gt; | void</code></h4>
<ul>
<li>onError 는 mutation 이 error 를 만났을 때 실행된다.</li>
</ul>
<h4 id="onsettled-data-tdata-error-terror-variables-tvariables-context-tcontext--promiseunknown--void"><code>onSettled: (data: TData, error: TError, variables: TVariables, context?: TContext) =&gt; Promise&lt;unknown&gt; | void</code></h4>
<ul>
<li>onSettled 는 mutation 이 성공해서 성공한 데이터 또는 error가 전달될 때 실행된다. (성공하든 실패하든 아무튼 결과가 전달된다)</li>
</ul>
<h2 id="returns">Returns</h2>
<h4 id="mutate-variables-tvariables--onsuccess-onsettled-onerror---void"><code>mutate: (variables: TVariables, { onSuccess, onSettled, onError }) =&gt; void</code></h4>
<ul>
<li>mutate 를 호출해서 mutation 을 실행시킬 수 있다. </li>
<li>variables 는 mutationFn 에 전달하는 객체이다.</li>
<li>onSuccess, onSettled, onError 는 useMutation option에 설명한 것과 동일하다.<blockquote>
<p>❗️ 다만, 두 곳에서 추가 콜백(onSuccess, onSettled, onError)을 실행할 경우 <strong>useMutation 의 추가 콜백 -&gt;  mutate 의 추가 콜백 순서로 실행</strong>된다. 
컴포넌트가 unmount 되면 추가 콜백이 실행되지 않는다 주의!!!</p>
</blockquote>
</li>
</ul>
<pre><code class="language-tsx">useMutation(addTodo, {
   onSuccess: (data, variables, context) =&gt; {
     // I will fire first
   },
   onError: (error, variables, context) =&gt; {
     // I will fire first
   },
   onSettled: (data, error, variables, context) =&gt; {
     // I will fire first
   },
  });

mutate(todo, {
   onSuccess: (data, variables, context) =&gt; {
     // I will fire second!
   },
   onError: (error, variables, context) =&gt; {
     // I will fire second!
   },
   onSettled: (data, error, variables, context) =&gt; {
     // I will fire second!
   },
});</code></pre>
<h4 id="mutateasync-variables-tvariables--onsuccess-onsettled-onerror---promisetdata"><code>mutateAsync: (variables: TVariables, { onSuccess, onSettled, onError }) =&gt; Promise&lt;TData&gt;</code></h4>
<ul>
<li>mutate 와 같으나 promise 를 반환한다!</li>
</ul>
<p><a href="https://react-query.tanstack.com/reference/useMutation">&gt;&gt; 더 많은 option과 return 값들은 이 링크에서 &lt;&lt;</a></p>
<hr>
<h2 id="example">Example</h2>
<pre><code class="language-tsx">import { useMutation } from &quot;react-query&quot;;
import axios from &#39;axios&#39;;

interface TodoType {
  id: number;
  todo: string;
}

const addTodo = async (newTodo: TodoType): Promise&lt;TodoType&gt; =&gt; {
  const { data } = await axios.post&lt;TodoType&gt;(`/todos`, newTodo);
  return data;
};

// api 요청하는 함수(addTodo) 를 작성하지 않았을 경우
const { mutate, isLoading, isError, error, isSuccess } = useMutation(newTodo =&gt; {
  return axios.post&lt;TodoType&gt;(&#39;/todos&#39;, newTodo);
});
// api 요청하는 함수(addTodo) 를 작성했을 경우
const { mutate, isLoading, isError, error, isSuccess } = useMutation(addTodo);

export default function App() {
  return (
    &lt;div&gt;
      {
        isLoading ? (
          &#39;Adding todo...&#39;
        ) : (
        &lt;&gt;
          {isError &amp;&amp; &lt;p&gt;error: {error.message}&lt;/p&gt;}

          {isSuccess &amp;&amp; &lt;p&gt;Todo added!&lt;/p&gt;}

          &lt;button
            onClick={() =&gt; {
              mutate({ id: 1, todo: &#39;useMutation 블로그 작성하기&#39; })
            }}
          &gt;
               작성 완료
          &lt;/button&gt;
        &lt;/&gt;
        )
      }
    &lt;/div&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Storybook] emotion + typescript로 버튼 컴포넌트 만들기]]></title>
            <link>https://velog.io/@kimhyo_0218/Storybook-emotion-typescript%EB%A1%9C-%EB%B2%84%ED%8A%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@kimhyo_0218/Storybook-emotion-typescript%EB%A1%9C-%EB%B2%84%ED%8A%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 19 Dec 2021 11:09:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/kimhyo_0218/post/ddd3ea14-3f39-440b-b160-80944e6188ff/storybook.webp" alt=""></p>
<p>스토리북으로 가장 먼저 만들어보았던 컴포넌트는 버튼 컴포넌트!
버튼이라고 만만하게 보면 안된다.
disabled, fullWidth, hover...등 경우의 수가 많기 때문에 작성해야하는 스타일이 많다 :)</p>
<h1 id="버튼-컴포넌트-작성">버튼 컴포넌트 작성</h1>
<h2 id="디자인">디자인</h2>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/3de91e11-6051-4753-8a94-436a06b6030e/image.png" alt=""></p>
<p>피그마로 직접 만들어본 버튼 디자인이다.
피그마 처음 써봤는데 기존에 포토샵, 일러스트만 쓰던 사람으로써.. 
바리에이션 기능이나 오토 레이아웃 등 너무 좋은 기능이 많아서 대박 이라는 감탄을 외치며 만졌던 기억이 난다.</p>
<p>버튼 사이즈는 height를 기준으로 삼는다.</p>
<h2 id="코드-작성">코드 작성</h2>
<pre><code class="language-tsx">import { ButtonHTMLAttributes, forwardRef } from &#39;react&#39;;
import styled from &#39;@emotion/styled&#39;;

export interface IButtonProps extends ButtonHTMLAttributes&lt;HTMLButtonElement&gt; {
  variant?: &#39;default&#39;;
  size?: &#39;xs&#39; | &#39;sm&#39; | &#39;md&#39; | &#39;lg&#39; | &#39;xl&#39;;
  color?: &#39;blue&#39; | &#39;black&#39;;
  label?: string;
  fullWidth?: boolean;
}

const Button = forwardRef&lt;HTMLButtonElement, IButtonProps&gt;((props, ref) =&gt; {
  const {
    variant = &#39;default&#39;,
    size = &#39;md&#39;,
    color = &#39;blue&#39;,
    label,
    disabled,
    fullWidth,
    ...restProps
  } = props;
  return (
    &lt;ButtonElement
      variant={variant}
      size={size}
      color={color}
      ref={ref}
      {...restProps}
    &gt;
      {label}
    &lt;/ButtonElement&gt;
  );
});


const ButtonElement = styled.button&lt;IButtonProps&gt;`
  ${(props) =&gt;
    props.size === &#39;md&#39; &amp;&amp;
    `
      height: 36px;
      padding: 0 14px;
      font-size: 1.4rem;
      border-radius: 6px;
    `};

  ${(props) =&gt;
    props.variant === &#39;default&#39; &amp;&amp;
    props.color === &#39;blue&#39; &amp;&amp;
    !props.disabled &amp;&amp;
    `
      color: #fff;
      background-color: #478dff;
      &amp;:hover {
        background: #2B7CFE;
      }
      &amp;:active {
        background: #1B64DA;
      }
    `};

  font-weight: 500;
  transition: 100ms ease-out;
`;
export default Button;
</code></pre>
<p>ref를 전달 받는 상황을 위해 forwardRef로 감싸준다.
디자인에 맞게 사이즈, 컬러 등 interface로 타입을 정의한다.
interface에 없는 disabled를 props에서 꺼낼 수 있는 이유는 <code>ButtonHTMLAttributes&lt;HTMLButtonElement&gt;</code> 을 확장했기 때문이다.</p>
<p>md 사이즈의 blue 색상 버튼을 만들어보았다.
이 상태에서 <code>yarn storybook</code>을 터미널에 입력하면,</p>
<h2 id="yarn-storybook">yarn storybook</h2>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/37fcbfe1-b168-4ff5-a542-177900f440bc/image.png" alt=""></p>
<p>성공적으로 버튼이 나온다 :)</p>
<p>size, color, fullWidth, disabled 등등 디자인 가이드에 맞게 추가하면 버튼 컴포넌트 작성 끝 !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Storybook] emotion으로 storybook 글로벌 스타일 설정하기 (reset css)]]></title>
            <link>https://velog.io/@kimhyo_0218/Storybook-emotion%EC%9C%BC%EB%A1%9C-storybook-%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-reset-css</link>
            <guid>https://velog.io/@kimhyo_0218/Storybook-emotion%EC%9C%BC%EB%A1%9C-storybook-%EA%B8%80%EB%A1%9C%EB%B2%8C-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-reset-css</guid>
            <pubDate>Mon, 13 Dec 2021 14:09:55 GMT</pubDate>
            <description><![CDATA[<p>컴포넌트를 만들기 전, css를 reset하는 작업이 필요하다.
공통으로 사용되는 font-family도 이때 설정해주면 편하다.</p>
<h1 id="globalstyletsx">GlobalStyle.tsx</h1>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/62fe9cce-1e7d-4e4f-8606-53958b0d9a61/image.png" alt=""></p>
<p>src 폴더 하위에 <code>GlobalStyle.tsx</code> 를 작성했다.
(font는 Noto Sans KR 기준)</p>
<pre><code class="language-tsx">import { Global, css } from &#39;@emotion/react&#39;;

const GlobalStyle = () =&gt; (
  &lt;Global
    styles={css`
      @import url(//fonts.googleapis.com/earlyaccess/notosanskr.css);
      html,
      body,
      div,
      span,
      applet,
      object,
      iframe,
      h1,
      h2,
      h3,
      h4,
      h5,
      h6,
      p,
      blockquote,
      pre,
      a,
      abbr,
      acronym,
      address,
      big,
      cite,
      code,
      del,
      dfn,
      em,
      img,
      ins,
      kbd,
      q,
      s,
      samp,
      small,
      strike,
      strong,
      sub,
      sup,
      tt,
      var,
      b,
      u,
      i,
      center,
      dl,
      dt,
      dd,
      ol,
      ul,
      li,
      fieldset,
      form,
      label,
      legend,
      table,
      caption,
      tbody,
      tfoot,
      thead,
      tr,
      th,
      td,
      article,
      aside,
      canvas,
      details,
      embed,
      figure,
      figcaption,
      footer,
      header,
      hgroup,
      menu,
      nav,
      output,
      ruby,
      section,
      summary,
      time,
      mark,
      audio,
      input,
      textarea,
      video {
        margin: 0;
        padding: 0;
        border: 0;
        font-size: 100%;
        font: inherit;
        vertical-align: baseline;
        box-sizing: border-box;
      }
      html {
        font-size: 62.5%;
        font-family: &#39;Noto Sans KR&#39;, &#39;sans-serif&#39;;
        body {
          font-size: 1.6rem;
        }
      }
      ol,
      ul {
        list-style: none;
      }
      a {
        background-color: transparent;
        text-decoration: none;
        outline: none;
        color: inherit;
        &amp;:active,
        &amp;:hover {
          text-decoration: none;
          color: inherit;
          outline: 0;
        }
      }
      button {
        display: flex;
        align-items: center;
        justify-content: center;
        outline: none;
        border: none;
        background: none;
        padding: 0;
        user-select: none;
        cursor: pointer;
        white-space: nowrap;
        letter-spacing: inherit;
        font: inherit;
        color: inherit;
      }
      input {
        outline: none;
      }
    `}
  /&gt;
);
export default GlobalStyle;</code></pre>
<p>reset css는 구글에 서치하면 나오는 내용과 개인적으로 추가하고 싶은 부분들을 추가해서 작성했다.</p>
<h1 id="previewjs">preview.js</h1>
<p><code>preview.js</code> 에서 파일을 import 할 수가 있다.
스토리북에 보이는 컴포넌트들에 공통 스타일을 주고싶을 때 여기서 설정하면 된다.</p>
<pre><code class="language-tsx">import GlobalStyle from &#39;../src/GlobalStyle&#39;;

export const decorators = [
  (Story) =&gt; (
    &lt;&gt;
      &lt;GlobalStyle /&gt;
      &lt;Story /&gt;
    &lt;/&gt;
  ),
];

export const parameters = {
  actions: { argTypesRegex: &#39;^on[A-Z].*&#39; },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};
</code></pre>
<p><a href="https://storybook.js.org/docs/react/writing-stories/decorators">Decorators에 대한 스토리북 문서 보기</a></p>
<blockquote>
<p>하지만 이곳에서 주는 스타일은 스토리북에서 볼 때만 적용되는 스타일이지, 실제 라이브러리로 배포 후 프로젝트에서 컴포넌트를 사용할 때에는 적용되어 있지 않는 스타일인 점을 유의해야한다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/aa0b2928-eed6-4eac-b945-ec171c6fd6a6/image.png" alt="">
이렇게 만든 GlobalStyle.tsx 컴포넌트를 추후에 같이 라이브러리로 배포하면 사진 예시처럼
별도의 reset css 파일 필요 없이 편리하게 import해서 적용하기 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Storybook] Storybook 시작하기 Emotion 과 Storybook + TypeScript 로 디자인 시스템 구축하기]]></title>
            <link>https://velog.io/@kimhyo_0218/Storybook-Emotion-%EA%B3%BC-Storybook-TypeScript-%EB%A1%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-%EC%B4%88%EA%B8%B0-%EC%85%8B%ED%8C%85%ED%8E%B8-</link>
            <guid>https://velog.io/@kimhyo_0218/Storybook-Emotion-%EA%B3%BC-Storybook-TypeScript-%EB%A1%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-%EC%B4%88%EA%B8%B0-%EC%85%8B%ED%8C%85%ED%8E%B8-</guid>
            <pubDate>Fri, 10 Dec 2021 16:27:33 GMT</pubDate>
            <description><![CDATA[<p>회사에서 부족한 일정으로 미처 도입하지 못했던 디자인 시스템 구축을 개인적으로라도 꼭 해보고 싶어서 저녁 시간마다 열심히 디자인하고 개발했다.
emotion + storybook + typescript 로 진행했으며 rollup.js 로 번들 작업 후 npm 라이브러리로 배포해보았다.</p>
<p>내껄 하나 만들어두면 앞으로 사이드 프로젝트를 진행할 때 마다 마크업 시간이 줄어들고 그만큼 아주 편리하게 쓸 수 있을 것 같아서 꼭 만들어보고 싶었다😊</p>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/bc153ebf-89f2-4ed3-b8a8-51131b7d09f3/image.png" alt="">
피그마로 열심히 디자인 한 것..!</p>
<h1 id="시작하기">시작하기</h1>
<pre><code class="language-shell">mkdir hyosun-design-system
cd hyosun-design-system
yarn init -y
npx -p @storybook/cli sb init --type react</code></pre>
<p>이름이 효선이라서 그냥 hyosun-design-system 으로 지었다🤭</p>
<h3 id="디렉토리">디렉토리</h3>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/f70b77a3-6259-4d24-899d-c708d89cd076/image.png" alt=""></p>
<p>src 폴더를 만들고 하위에 stories 폴더를 옮기고 components 폴더를 생성하였다.
디렉토리 구조는 정답이 없어서 편하다고 생각되는대로 작업하면 될 것 같다.</p>
<h3 id="storybookmainjs-수정-1">.storybook/main.js 수정 (1)</h3>
<p>stories 폴더가 src 하위로 옮겨졌기 때문에 <code>.storybook/main.js</code> 의 stories 배열 경로를 수정해주어야한다.</p>
<pre><code class="language-js">module.exports = {
  stories: [&#39;../src/**/*.stories.mdx&#39;, &#39;../src/**/*.stories.@(js|jsx|ts|tsx)&#39;],
  addons: [&#39;@storybook/addon-links&#39;, &#39;@storybook/addon-essentials&#39;],
  framework: &#39;@storybook/react&#39;,
};</code></pre>
<h3 id="추가-install">추가 install</h3>
<h4 id="react-react-dom-설치">react react-dom 설치</h4>
<pre><code class="language-shell">yarn add -D react react-dom</code></pre>
<p>devDependencies 에 react 와 react-dom을 설치한다.
설치안하고 yarn storybook 으로 실행해버리면 아래와 같이 react 가 존재하지 않는다고 에러가 난다!</p>
<pre><code class="language-shell">ERR! Error: Cannot find module &#39;react/package.json&#39;
ERR! Require stack:
...</code></pre>
<h4 id="typescript-와-babel-preset-react-app-설치">typescript 와 babel-preset-react-app 설치</h4>
<p>타입스크립트를 사용하기 위해서 꼭 설치해주어야하는 <code>typescript</code>
creact-react-app 에서 사용하는 바벨 사전 설정이 포함되어있는 패키지 <code>babel-preset-react-app</code> 도 필요합니다.</p>
<pre><code class="language-shell">yarn add -D typescript babel-preset-react-app</code></pre>
<h4 id="emotion-설치">emotion 설치</h4>
<p>그리고 emotion도 설치해준다.</p>
<pre><code class="language-shell">yarn add @emotion/babel-preset-css-prop @emotion/react @emotion/styled</code></pre>
<p><code>@emotion/styled</code> 는 styled-components 처럼 사용하기 위함이고
<code>@emotion/babel-preset-css-prop</code> 는 css props 으로 스타일을 줄 때 상단에</p>
<pre><code class="language-jsx">/** @jsx jsx */
import { jsx } from &#39;@emotion/react&#39;</code></pre>
<p>매번 저것을 달아야하는 번거로움이 있다.
jsx 를 달아주는 이유는
React.createElement 가 아닌 jsx 함수를 호출하여 컴포넌트를 렌더링하기 위함인데, (<a href="https://emotion.sh/docs/@emotion/babel-preset-css-prop">추가문서</a>)
@emotion/babel-preset-css-prop 를 설치하고 바벨 설정을 해주면 컴파일시 자동으로 jsx를 호출해주어 편리해진다.</p>
<h3 id="storybookmainjs-수정-2">.storybook/main.js 수정 (2)</h3>
<p>설치한 emotion 의 버전은 11버전인데
storybook addon 에서 <code>@storybook/addon-docs</code> 가 emotion 10버전을 사용함으로써 버전 충돌 이슈가 있다.
main.js 에서 에러 해결을 위해 webpackFinal 설정을 해주어야한다.</p>
<p><code>@emotion/babel-preset-css-prop</code> 설정도 이때 함께 해주면 된다.</p>
<pre><code class="language-js">const path = require(&#39;path&#39;);
const toPath = (_path) =&gt; path.join(process.cwd(), _path);

module.exports = {
  stories: [&#39;../src/**/*.stories.mdx&#39;, &#39;../src/**/*.stories.@(js|jsx|ts|tsx)&#39;],
  addons: [&#39;@storybook/addon-links&#39;, &#39;@storybook/addon-essentials&#39;],
  webpackFinal: async (config) =&gt; {
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      loader: require.resolve(&#39;babel-loader&#39;),
      options: {
        presets: [
          [&#39;react-app&#39;, { flow: false, typescript: true }],
          require.resolve(&#39;@emotion/babel-preset-css-prop&#39;),
        ],
      },
    });
    config.resolve.extensions.push(&#39;.ts&#39;, &#39;.tsx&#39;);
    config.resolve.alias = {
      ...config.resolve.alias,
      &#39;@emotion/core&#39;: toPath(&#39;node_modules/@emotion/react&#39;),
      &#39;@emotion/styled&#39;: toPath(&#39;node_modules/@emotion/styled&#39;),
      &#39;emotion-theming&#39;: toPath(&#39;node_modules/@emotion/react&#39;),
    };
    return config;
  },
};
</code></pre>
<p>기본 셋팅은 끝났으니 이제 컴포넌트를 만든 다음</p>
<pre><code class="language-shell">yarn storybook</code></pre>
<p>으로 실행하면 된다!</p>
<p>컴포넌트 만들기는 바로 다음에 글을 작성할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] 리액트 쿼리 useQuery 실용 편]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Query-useQuery-%EC%8B%A4%EC%9A%A9%ED%8E%B8</link>
            <guid>https://velog.io/@kimhyo_0218/React-Query-useQuery-%EC%8B%A4%EC%9A%A9%ED%8E%B8</guid>
            <pubDate>Thu, 25 Nov 2021 14:12:40 GMT</pubDate>
            <description><![CDATA[<p>이전 시리즈에서 더 나아가 useQuery를 실무에서 사용했던 경험을 적어보려한다.</p>
<h2 id="🌟-queryclient-의-defaultoptions-설정하기">🌟 QueryClient 의 defaultOptions 설정하기</h2>
<ul>
<li><code>QueryClient</code> 에서 추가적으로 <code>defaultOptions</code> 를 적용할 수 있다.</li>
<li><code>refetchOnMount</code>, <code>refetchOnReconnect</code>, <code>refetchOnWindowFocus</code> 는 기본적으로 true로 설정되어있는데 이렇게 defaultOptions 에서 false로 셋팅해두면 모든 쿼리들이 이 옵션에 영향을 받는다.
(무분별한 refetch를 막기 위해 모두 false로 설정해두었고 상황에 따라 필요한 쿼리에만 true로 별도의 옵션을 적용해서 사용하고 있다.)</li>
<li><code>staleTime</code> 이나 <code>cacheTime</code> 등 다양한 옵션을 더 넣을 수 있다.<pre><code class="language-tsx">const queryClient = new QueryClient({
defaultOptions: {
  queries: {
    refetchOnMount: false,
    refetchOnReconnect: false,
    refetchOnWindowFocus: false,
  }
}
});</code></pre>
</li>
</ul>
<h2 id="🌟-querykey-관리하기">🌟 QueryKey 관리하기</h2>
<ul>
<li>회사에선 컨벤션을 맞추기 위해 문자열 키를 사용하지 않고 배열 키를 사용하고 있다!<pre><code class="language-tsx">export const queryKeys = {
todos: [&#39;todos&#39;] as const,
todoById: (todoId: string) =&gt; [&#39;todos&#39;, todoId] as const,
};
</code></pre>
</li>
</ul>
<p>// 이런식으로 사용하면 된다.
useQuery(queryKeys.todos, fetchTodos);
useQuery(queryKeys.todoById(todoId), () =&gt; fetchTodoById(todoId));</p>
<pre><code>
## 🌟 useQuery 커스텀 훅 with TypeScript
- 여러 컴포넌트에서 같은 요청을 해줘야하는 경우엔 커스텀 훅으로 만들어서 공통으로 사용하고 있다.
```tsx
import { AxiosError } from &#39;axios&#39;;
import { useQuery, UseQueryOptions, UseQueryResult } from &#39;react-query&#39;;
import { fetchTodos } from &#39;src/api/todos&#39;;
import { TodoType } from &#39;src/types/todoType&#39;;
import { queryKeys } from &#39;src/types/commonType&#39;;

export default function useTodosQuery(
  options?: UseQueryOptions&lt;TodoType[], AxiosError&gt;
): UseQueryResult&lt;TodoType[], AxiosError&gt; {
  return useQuery(queryKeys.todos, fetchTodos, options);
}</code></pre><ul>
<li>사용 예제 코드</li>
</ul>
<pre><code class="language-tsx">import useTodosQuery from &#39;src/hooks/useTodosQuery&#39;;

//...
// option을 추가하지 않았을 경우
const { data, isLoading } = useTodosQuery(); 
// option을 추가할 경우
const { data, isLoading } = useTodosQuery({ staleTime: 60 * 1000 }); </code></pre>
<blockquote>
<p>옵션을 주고 싶을 때 파라미터로 옵션을 넘기면 된다!</p>
</blockquote>
<p>다음 편은 useMutation에 대해서 정리할 예정이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] 리액트 쿼리 시작하기 (useQuery)]]></title>
            <link>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-useQuery</link>
            <guid>https://velog.io/@kimhyo_0218/React-Query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-useQuery</guid>
            <pubDate>Mon, 22 Nov 2021 15:45:29 GMT</pubDate>
            <description><![CDATA[<h1 id="react-query">React Query</h1>
<p>리액트 쿼리!
리액트에서 비동기 로직을 리액트스럽게 다룰 수 있게 해주는 라이브러리이다.
server state를 아주 효율적으로 관리할 수 있다.
기존에 isLoading, isError, refetch, 데이터 캐싱 등 개발자가 직접 구현하려면 꽤 귀찮거나 까다로웠던 기능을 제공해준다.
이번 편에서는 간단하게 기본적인 사용 방법에 대해 작성하려한다.</p>
<h2 id="queryclientprovider">QueryClientProvider</h2>
<ul>
<li>리액트 쿼리 사용을 위해 <code>QueryClientProvider</code> 를 최상단에서 감싸주어야한다.</li>
<li>쿼리 인스턴스를 생성 후 <code>client={queryClient}</code> 작성해준다.</li>
</ul>
<h3 id="기본-셋팅">기본 셋팅</h3>
<pre><code class="language-tsx">import { QueryClient, QueryClientProvider } from &quot;react-query&quot;;

const queryClient = new QueryClient();

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Home /&gt;
    &lt;/QueryClientProvider&gt;
  );
}</code></pre>
<h2 id="usequery">useQuery</h2>
<pre><code class="language-tsx">import { useQuery } from &quot;react-query&quot;;
// 주로 사용되는 3가지 return 값 외에도 더 많은 return 값들이 있다. 
const { data, isLoading, error } = useQuery(queryKey, queryFn, options)</code></pre>
<h3 id="querykey">QueryKey</h3>
<ul>
<li><p><code>QueryKey</code> 를 기반으로 데이터 캐싱을 관리한다.</p>
</li>
<li><p>문자열 또는 배열로 지정할 수 있다.</p>
<pre><code class="language-tsx">// 문자열
useQuery(&#39;todos&#39;, ...)
// 배열
useQuery([&#39;todos&#39;], ...)</code></pre>
</li>
<li><p>쿼리가 변수에 의존하는 경우에는 QueryKey 에도 해당 변수를 추가해주어야한다.</p>
<pre><code class="language-tsx">const { data, isLoading, error } = useQuery([&#39;todos&#39;, id], () =&gt; axios.get(`http://.../${id}`));</code></pre>
</li>
</ul>
<h3 id="query-functions">Query Functions</h3>
<ul>
<li>useQuery 의 두번째 인자에는 promise 를 반환하는 함수를 넣어주어야한다.</li>
<li>거의 첫번째나 두번째 방식으로 코드를 작성하고 있다.<pre><code class="language-tsx">useQuery(&#39;todos&#39;, fetchTodos);
useQuery([&#39;todos&#39;, todoId], () =&gt; fetchTodoById(todoId));
useQuery([&#39;todos&#39;, todoId], async () =&gt; {
 const data = await fetchTodoById(todoId);
 return data
});
useQuery([&#39;todos&#39;, todoId], ({ queryKey }) =&gt; fetchTodoById(queryKey[1]));</code></pre>
</li>
</ul>
<h3 id="query-options">Query Options</h3>
<ul>
<li>리액트 쿼리 공식 문서에 많은 쿼리 옵션에 대한 소개가 있지만 개인적으로 자주 쓰이는 것만 다뤄보겠다.</li>
</ul>
<h4 id="enabled-boolean">enabled (boolean)</h4>
<ul>
<li><code>enabled</code> 는 쿼리가 자동으로 실행되지 않게 설정하는 옵션이다.</li>
<li>아래의 코드는 id가 존재할 때만 쿼리 요청을 한다는 의미의 코드이다.<pre><code class="language-tsx">const { data } = useQuery(
[&#39;todos&#39;, id],
() =&gt; fetchTodoById(id),
{
  enabled: !!id,
}
);</code></pre>
<h4 id="retry-boolean--number--failurecount-number-error-terror--boolean">retry (boolean | number | (failureCount: number, error: TError) =&gt; boolean)</h4>
</li>
<li><code>retry</code> 는 실패한 쿼리를 재시도하는 옵션이다.</li>
<li>기본적으로 쿼리 실패시 3번 재시도 한다.</li>
<li>true 로 설정하면 쿼리 실패시 무한 재시도하고 false로 설정하면 재시도를 하지 않는다.</li>
</ul>
<h4 id="staletime-number--infinity">staleTime (number | Infinity)</h4>
<ul>
<li><code>staleTime</code> 은 데이터가 <code>fresh</code> 상태로 유지되는 시간이다. 해당 시간이 지나면 <code>stale</code> 상태가 된다.</li>
<li>default staleTime은 0 이다.</li>
<li>fresh 상태에서는 쿼리가 다시 mount 되어도 fetch가 실행되지 않는다.</li>
</ul>
<h4 id="cachetime-number--infinity">cacheTime (number | Infinity)</h4>
<ul>
<li><code>cacheTime</code> 은 <code>inactive</code> 상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 가비지 컬렉터에 의해 메모리에서 제거된다.</li>
<li>default cacheTime 은 5분이다.</li>
</ul>
<h4 id="refetchonmount-boolean--always">refetchOnMount (boolean | &quot;always&quot;)</h4>
<ul>
<li><code>refetchOnMount</code> 는 데이터가 <strong>stale 상태일 경우</strong> 마운트 시 마다 refetch를 실행하는 옵션이다.</li>
<li>default true</li>
<li>always 로 설정하면 마운트 시 마다 매번 refetch 를 실행한다.</li>
</ul>
<h4 id="refetchonwindowfocus-boolean--always">refetchOnWindowFocus (boolean | &quot;always&quot;)</h4>
<ul>
<li><code>refetchOnWindowFocus</code> 는 데이터가 <strong>stale 상태일 경우</strong> 윈도우 포커싱 될 때 마다 refetch를 실행하는 옵션이다.</li>
<li>예를 들어, 크롬에서 다른 탭을 눌렀다가 다시 원래 보던 중인 탭을 눌렀을 때도 이 경우에 해당한다. 심지어 F12로 개발자 도구 창을 켜서 네트워크 탭이든, 콘솔 탭이든 개발자 도구 창에서 놀다가 페이지 내부를 다시 클릭했을 때도 이 경우에 해당한다.</li>
<li>default true</li>
<li>always 로 설정하면 항상 윈도우 포커싱 될 때 마다 refetch를 실행한다는 의미이다.</li>
</ul>
<p>예제 코드!</p>
<pre><code class="language-tsx">// 유저 정보를 가져오는 쿼리이다.
const { data: userInfo } = useQuery(
  [&#39;user&#39;],
  getUser,
  {
    refetchOnWindowFocus: true,
    staleTime: 60 * 1000, // 1분
  }
)</code></pre>
<ul>
<li>QueryClient defaultOptions 설정으로 refetch 기능들을 다 false로 꺼버렸을 경우에는 refetch 기능이 실행되지 않는다. 그럴 경우엔 refetchOnWindowFocus 옵션이 실행되게끔 true로 설정하면 된다.</li>
<li>fresh 상태인 1분 동안은 아무리 다른 탭을 왔다갔다해도 fetch 요청을 하지 않는다.</li>
</ul>
<h4 id="refetchonreconnect-boolean--always">refetchOnReconnect (boolean | &quot;always&quot;)</h4>
<ul>
<li><code>refetchOnReconnect</code> 는 데이터가 <strong>stale 상태일 경우</strong> 재 연결 될 때 refetch를 실행하는 옵션이다.</li>
<li>default true</li>
<li>always 도 위에 두 옵션 처럼 쿼리가 매번 재 연결될 때 마다 refetch를 실행한다.</li>
</ul>
<h4 id="onsuccess-data-tddata--void">onSuccess ((data: TDdata) =&gt; void)</h4>
<ul>
<li><code>onSuccess</code> 는 쿼리 성공 시 실행되는 함수이다.</li>
<li>매개변수 data는 성공 시 서버에서 넘어오는 response 값이다.</li>
</ul>
<h4 id="onerror-error-terror--void">onError ((error: TError) =&gt; void)</h4>
<ul>
<li><code>onError</code> 는 쿼리 실패 시 실행되는 함수이다.</li>
<li>매개변수로 에러 값을 받을 수 있다.</li>
</ul>
<p>예제 코드!</p>
<pre><code class="language-tsx">const { data: userInfo } = useQuery(
  [&#39;user&#39;],
  getUser,
  {
    refetchOnWindowFocus: true,
    staleTime: 60 * 1000, // 1분
    onError: (error) =&gt; {
      if (error.response?.data.code === 401) {
        //...
      }
    },
  }
)</code></pre>
<h4 id="onsettled-data-tdata-error-terror--void">onSettled ((data?: TData, error?: TError) =&gt; void)</h4>
<ul>
<li><code>onSettled</code> 는 쿼리가 성공해서 성공한 데이터가 전달되거나, 실패해서 에러가 전달 될 때 실행되는 함수이다.</li>
<li>매개변수로 성공 시엔 성공 데이터, 실패 시에는 에러가 전달된다.</li>
</ul>
<h4 id="initialdata-tdata----tdata">initialData (TData | () =&gt; TData)</h4>
<ul>
<li><code>initialData</code> 를 설정하면 쿼리 캐시의 초기 데이터로 사용된다. (쿼리가 아직 생성되지 않았거나 캐시되지 않았을 때)</li>
<li>staleTime 이 설정되지 않은 경우 초기 데이터는 기본적으로 stale 상태로 간주한다.</li>
</ul>
<p><a href="https://react-query.tanstack.com/reference/useQuery">React Query 공식문서 - useQuery</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 변수에 대해 (let, const, var)]]></title>
            <link>https://velog.io/@kimhyo_0218/JavaScript-%EB%B3%80%EC%88%98%EC%97%90-%EB%8C%80%ED%95%B4-let-const-var</link>
            <guid>https://velog.io/@kimhyo_0218/JavaScript-%EB%B3%80%EC%88%98%EC%97%90-%EB%8C%80%ED%95%B4-let-const-var</guid>
            <pubDate>Sun, 11 Jul 2021 11:43:11 GMT</pubDate>
            <description><![CDATA[<h1 id="자바스크립트-변수variable">자바스크립트 변수(variable)</h1>
<h2 id="📔-변수-구분">📔 변수 구분</h2>
<p>변수는 크게 선언되는 위치에 따라 <code>로컬(지역)변수</code>와 <code>글로벌(전역)변수</code>로 나뉜다.</p>
<h3 id="전역-변수global-variable">전역 변수(Global variable)</h3>
<ul>
<li>공통적으로 사용하는 변수로 프로그램 자체에서 접근이 가능한 변수.</li>
</ul>
<pre><code class="language-javascript">let a = 1;
function myFunction() {
  console.log(a);
}
myFunction(); // 1</code></pre>
<p><code>변수 a</code>는 블록 밖에 선언된 전역 변수로 함수 블록 안에서도 접근이 가능하다.</p>
<h3 id="지역-변수local-variable">지역 변수(Local variable)</h3>
<ul>
<li>가까운 스코프 내에 존재하는 변수.<pre><code class="language-javascript">let a = 1;
function myFunction() {
let a = &#39;하나&#39;;
console.log(a);
}
myFunction(); // &#39;하나&#39;
console.log(a); // 1</code></pre>
블록 안에서는 밖에 있는 변수와 동일한 이름의 새 변수를 선언할 수 있다.</li>
</ul>
<blockquote>
<p>즉, 블록 내부에 선언된 지역 변수가 있는지 확인하고 있으면 해당 지역 변수를 사용, 없으면 블록 밖에 있는 전역 변수를 사용하게 된다.</p>
</blockquote>
<hr>
<h2 id="📔-변수-선언">📔 변수 선언</h2>
<p>자바스크립트에는 3가지의 변수 선언 방법이 있다.</p>
<ol>
<li><code>let</code></li>
<li><code>const</code></li>
<li><code>var</code></li>
</ol>
<h3 id="let-변수">let 변수</h3>
<blockquote>
<p>블록 스코프를 가진 변수</p>
</blockquote>
<pre><code class="language-javascript">let a;
let name = &#39;효선&#39;;
let age = 27, gender = &#39;여성&#39;;</code></pre>
<ul>
<li>값을 할당하지 않아도 된다.</li>
<li>콤마로 구분하여 여러 개의 변수를 선언할 수 있다. 대신 let 키워드는 맨 앞에 한번만 작성해야한다.</li>
</ul>
<h3 id="var-변수">var 변수</h3>
<blockquote>
<p>블록 스코프가 없는 변수
-&gt; <code>var</code> 로 선언한 변수의 스코프는 <strong>함수 스코프</strong>이거나 <strong>전역 스코프</strong>이다.</p>
</blockquote>
<pre><code class="language-javascript">반복문 예시)
for (var i = 0; i &lt; 10; i++) {
  // ...
}
console.log(i); // 10 반복문이 종료되었지만 전역 스코프이므로 접근이 가능하다.</code></pre>
<pre><code class="language-javascript">함수 스코프 예시)
function myVar() {
  if(true) {
    var name = &#39;효선&#39;;
  }
  console.log(name);
}
myVar(); // 효선
console.log(name); // 출력되지 않는다.</code></pre>
<h3 id="const-변수">const 변수</h3>
<blockquote>
<p>값을 바꿀 수 없는 변수 선언. (상수)</p>
</blockquote>
<pre><code class="language-javascript">const a; // SyntaxError: Missing initializer in const declaration

</code></pre>
<ul>
<li>const 변수는 반드시 선언과 동시에 할당이 이루어져야한다</li>
</ul>
<pre><code class="language-javascript">const BIRTHDAY = 950218;
BIRTHDAY = 931226; // TypeError: Assignment to constant variable.</code></pre>
<ul>
<li>재할당도 할 수 없다.</li>
</ul>
<h2 id="📔-window-객체global-object">📔 window 객체(Global object)</h2>
<p>window 객체는 브라우저의 요소들과 자바스크립트 엔진, 그리고 모든 변수를 담고 있는 객체이다. 글로벌 객체로 부르기도 한다.</p>
<pre><code class="language-javascript">var food = &#39;음식&#39;;
let animal = &#39;동물&#39;;

console.log(this.food, this.animal);// 음식, undefined
// window객체에 들어가므로 window.food 로 사용할 수도 있다.</code></pre>
<ul>
<li>글로벌 스코프에 선언된 var 변수는 this로 참조할 수 있지만 let은 그렇지 않다.</li>
<li>글로벌 스코프에 선언된 let 변수는 엔진이 블록을 만들고 이를 스코프로 사용한다.</li>
</ul>
<h2 id="📔-호이스팅hoisting">📔 호이스팅(Hoisting)</h2>
<blockquote>
<p>💡 변수나 함수의 선언이 최상위로 끌어올려지는 현상, 변수가 함수 내에서 선언된 지역 변수이면 함수 내의 최상위, 글로벌 스코프에 선언된 전역 변수이면 전역 컨텍스트 최상위로 끌어올려진다.</p>
</blockquote>
<p>함수 선언문 호이스팅은 따로 다룰 예정이다.</p>
<h3 id="var-변수의-호이스팅">var 변수의 호이스팅</h3>
<ul>
<li>모든 var 변수는 할당하기 전까진 값이 <code>undefined</code> 이다.</li>
<li>선언은 호이스팅 되지만 할당은 호이스팅 되지 않는다.</li>
</ul>
<pre><code class="language-javascript">console.log(food); // undefined
var food = &#39;음식&#39;;</code></pre>
<p>var 변수는 아래에 선언되어있지만 <strong>선언이 위로 끌어올려지면서</strong> food의 값을 undefined로 출력할 수 있다.</p>
<p>위 코드를 풀어보면, 아래와 같이 실행된다고 볼 수 있다.</p>
<pre><code class="language-javascript">var food;
console.log(food); // undefined
food = &#39;음식&#39;;
console.log(food); // 음식</code></pre>
<h3 id="letconst-변수의-호이스팅">let/const 변수의 호이스팅</h3>
<pre><code class="language-javascript">console.log(animal); // ReferenceError
let animal = &#39;동물&#39;;</code></pre>
<p>let과 const에선 <code>ReferenceError</code>가 발생한다. 이는 <code>TDZ(Temporal Dead Zone)</code>에 영향을 받기 때문에 var 변수와는 다르게 작동한다.</p>
<blockquote>
<p>❗ 흔히들 let과 const는 호이스팅이 되지 않는다고 생각할 수 있지만.. <strong>let과 const도 변수도 호이스팅이 된다!!!!</strong></p>
</blockquote>
<h3 id="⚠-tdztemporal-dead-zone">⚠ TDZ(Temporal Dead Zone)?</h3>
<p>직역하면 일시적인 사각지대 라는 뜻인데 이 일시적인 사각지대는 <strong>스코프의 시작 지점부터 초기화 시작 지점까지의 구간</strong>을 말한다.</p>
<p>변수 생성의 3단계</p>
<h4 id="💖-선언-단계declaration-phase">💖 선언 단계(Declaration phase)</h4>
<p>-&gt; 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계!
이 변수 객체는 스코프가 참조하는 대상이 된다.</p>
<h4 id="🧡-초기화-단계initialization-phase">🧡 초기화 단계(Initialization phase)</h4>
<p>-&gt; 실행 컨텍스트에 존재하는 변수 객체에 등록된 변수를 위한 메모리를 만드는 단계!
이 단계에서 변수는 <code>undefined</code>로 초기화된다.</p>
<h4 id="💛-할당-단계assignment-phase">💛 할당 단계(Assignment phase)</h4>
<p>-&gt; <code>undefined</code>로 초기화된 변수에 다른 값을 할당하는 단계</p>
<blockquote>
<p>📃 var 변수의 경우 <strong>선언과 초기화를 동시</strong>에 진행한다. 메모리를 할당해주고 undefined로 초기화되기 때문에 호이스팅될 때 에러가 나지 않는다.</p>
</blockquote>
<blockquote>
<p>📃 let 변수의 경우 <strong>선언과 초기화를 따로</strong> 진행한다. 선언 단계에서 변수가 등록되지만 초기화 단계는 변수 선언문에 도달했을 때 이루어지기 때문에 그 전에 변수에 접근하려하면 <code>ReferenceError</code> 가 발생한다. *변수를 위한 메모리 공간이 확보되지 않았기 때문.</p>
</blockquote>
<pre><code class="language-javascript">console.log(animal); // ReferenceError

let animal; // 변수 선언문에서 초기화 단계
console.log(animal); // undefined

animal = &#39;동물&#39;; // 할당문에서 할당 단계
console.log(animal); // 동물</code></pre>
<blockquote>
<p>📃 const 변수의 경우 var 변수처럼 <strong>선언과 초기화를 동시</strong> 진행하지만 선언 이전에 <code>TDZ</code>가 생성되어 접근하면 <code>ReferenceError</code> 가 발생한다. <strong>반드시 선언과 동시에 할당도 해줘야한다</strong></p>
</blockquote>
<hr>
<p>취업을 하기 전 한 회사에서 면접을 보았는데 let과 const가 호이스팅이 되느냐란 질문에 안된다고 대답했었다. var변수만 된다고 대답했었다.. TDZ에 대해서도 물어보셨는데 대답을 하지 못했던 기억이 있다.... 변수는 그냥 일상 생활에서 사용하듯이(?) 써서 TDZ에 대해 깊게 다뤄보지 못했었는데 이제와 생각해보면 참 리액트 공부하는데에만 급급했구나 싶다.. 그 후에 TDZ가 뭔지 알아보았는데 이번 회사에서 동료들이랑 자바스크립트 스터디를 시작한 김에 공부한 것을 블로그에 정리하면 좋을 것 같아서 적어보았다.</p>
<p>그리고 결론 .... 처음 자바스크립트 공부 시작할 때 es6부터 바로 해서 var 변수는 사용한 적도 없었는데 더더욱 쓰지 말아야겠다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Dynamic Routes 동적 라우팅]]></title>
            <link>https://velog.io/@kimhyo_0218/Next.js-Dynamic-Routes-%EB%8F%99%EC%A0%81-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
            <guid>https://velog.io/@kimhyo_0218/Next.js-Dynamic-Routes-%EB%8F%99%EC%A0%81-%EB%9D%BC%EC%9A%B0%ED%8C%85</guid>
            <pubDate>Sat, 10 Jul 2021 13:27:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💡 외부 데이터에 의존하는 경로가 있을 경우 동적 라우팅을 이용할 수 있다.</p>
</blockquote>
<ul>
<li><code>[</code> 로 시작하고 <code>]</code> 로 끝나는 페이지는 Next.js의 <code>dynamic routes</code>이다.</li>
</ul>
<pre><code class="language-jsx">ex) pages/posts/[id].js</code></pre>
<hr>
<p>pages/post/[pid].js</p>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

export default function Post() {
    const router = useRouter();
    const { pid } = router.query;

    return &lt;p&gt;Post: {pid}&lt;/p&gt;
}</code></pre>
<p>경로 /post/1, /post/abc 등이 일치된다. 경로 매개변수는 페이지에 쿼리 매개변수로 전송되고 다른 쿼리 매개변수와 병합된다. </p>
<p>예를 들어 /post/abc 에는 query 객체가 있다.</p>
<pre><code class="language-jsx">{ &quot;pid&quot;: &quot;abc&quot; }</code></pre>
<p>마찬가지로 경로 /post/abc?foo=bar 에는 다음 query 객체가 있다.</p>
<pre><code class="language-jsx">{ &quot;foo&quot;: &quot;bar&quot;, &quot;pid&quot;: &quot;abc&quot; }</code></pre>
<p>경로 매개 변수는 동일한 이름의 쿼리 매개변수를 재정의한다.
예를 들어 경로 /post/abc?pid=123 에는 다음 query 객체가 있다.</p>
<pre><code class="language-jsx">{ &quot;pid&quot;: &quot;abc&quot; }</code></pre>
<p>pages/post/[pid]/[comment].js
/post/abc/a-comment 의 query 객체는</p>
<pre><code class="language-jsx">{ &quot;pid&quot;: &quot;abc&quot;, &quot;comment&quot;: &quot;a-comment&quot; }</code></pre>
<hr>
<h2 id="catch-all-routes">Catch all routes</h2>
<blockquote>
<p>💡 괄호 안에 세 개의 점 (...)을 추가하여 모든 경로를 포착하도록 동적 경로를 확장 할 수 있다.</p>
</blockquote>
<ul>
<li>pages/post/[...slug].js 는 /post/a, /post/a/b, /post/a/b/c 과 일치한다.</li>
</ul>
<p>query 객체</p>
<pre><code class="language-jsx">/post/a
{ &quot;slug&quot;: [&quot;a&quot;] }

/post/a/b
{ &quot;slug&quot;: [&quot;a&quot;, &quot;b&quot;] }</code></pre>
<hr>
<h2 id="optional-catch-all-routes">Optional catch all routes</h2>
<blockquote>
<p>💡 이중 괄호 안에 매개변수를 포함하여 모든 경로를 선택적으로 잡을 수 있다. </p>
</blockquote>
<ul>
<li>pages/post/[[...slug]].js 는 /post, /post/a, /post/a/b 와 모두 일치한다.</li>
</ul>
<p>위의 [...slug] 와의 주요 차이점은 optional을 사용하면 매개변수가 없는 경로도(/post) 일치한다는 것.</p>
<p>query 객체</p>
<pre><code class="language-jsx">{ } // GET `/post` (empty object)
{ &quot;slug&quot;: [&quot;a&quot;] } // GET /post/a (single-element array)
{ &quot;slug&quot;: [&quot;a&quot;, &quot;b&quot;] } // GET /post/a/b (multi-element array)</code></pre>
<hr>
<h2 id="주의-사항">주의 사항</h2>
<blockquote>
<p>💡 사전 정의 된 경로는 동적 경로보다 우선적이고, 동적 경로는 모든 경로를 포착하는 것 보다 우선한다.</p>
</blockquote>
<ul>
<li>pages/post/create.js === /post/create</li>
<li>pages/post/[pid].js === /post/1, /post/abc 등과 일치하지만 /post/create 와는 일치하지 않는다.</li>
<li>pages/post/[...slug].js  === /post/1/2, /post/a/b/c 등과 일치하지만 /post/create, /post/abc 와는 일치하지 않는다.</li>
</ul>
<p>정적으로 최적화 된 페이지는 경로 매개변수를 제공하지 않는다. 즉, 쿼리는 빈 객체 ({ }) 가 된다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Routes basic ! 라우팅 살펴보기]]></title>
            <link>https://velog.io/@kimhyo_0218/Next.js-Routes-basic-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
            <guid>https://velog.io/@kimhyo_0218/Next.js-Routes-basic-%EB%9D%BC%EC%9A%B0%ED%8C%85</guid>
            <pubDate>Sat, 03 Jul 2021 13:08:39 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-의-라우팅">Next.js 의 라우팅</h1>
<p>처음 Next.js를 시작할 때 CRA와 눈에 띄게 달랐던 점은 바로 라우팅 구조 였다.</p>
<h2 id="pages-디렉토리">pages 디렉토리</h2>
<blockquote>
<p>💡 pages 에서 만든 폴더와 파일은 곧 Router 경로가 된다.</p>
</blockquote>
<pre><code class="language-jsx">pages/about.js

export default function About() {
  return &lt;div&gt;About&lt;/div&gt;
}

------------------------------------
http://localhost:3000/about 경로에 About이라는 문구가 보인다.</code></pre>
<ul>
<li>pages/index.js ⇒ /</li>
<li>pages/blog/index.js ⇒ /blog</li>
</ul>
<h2 id="nextlink">next/link</h2>
<blockquote>
<p>💡 <code>Link</code> 라는 React 구성 요소를 사용하면 SPA처럼, 클라이언트 사이드 라우팅으로 페이지 이동이 가능하다.</p>
</blockquote>
<pre><code class="language-jsx">import Link from &#39;next/link&#39;;

export default function Home() {
  return (
    &lt;ul&gt;
      &lt;li&gt;
        &lt;Link href=&quot;/&quot;&gt;
          &lt;a&gt;Home&lt;/a&gt;
        &lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link href=&quot;/about&quot;&gt;
          &lt;a&gt;About Us&lt;/a&gt;
        &lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link href=&quot;/blog/hello-world&quot;&gt;
          &lt;a&gt;Blog Post&lt;/a&gt;
        &lt;/Link&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  )
}</code></pre>
<ul>
<li>/  ⇒ pages/index.js</li>
<li>/about ⇒ pages/about.js</li>
<li>/blog/hello-world ⇒ pages/blog/[slug].js</li>
</ul>
<p>정적 생성을 사용하는 모든 뷰 <Link />  페이지에 대해 기본적으로 (데이터 포함) 프리 페치 된다. 서버 사이드 렌더링 경로에 해당하는 데이터는 프리 페치 되지 않는다. </p>
<h3 id="link-props-">Link props ~</h3>
<ul>
<li><code>href</code> - 이동할 경로 또는 URL 이다. 유일한 필수 props 이다.</li>
<li><code>as</code> - 브라우저 URL 표시 줄에 표시 될 경로. Next.js 9.5.3 버전 이전에는 동적 경로에서 사용되었다.</li>
<li><code>passHref</code> - 기본 값은 false. Link가 href 속성을 child에게 보낸다.</li>
<li><code>prefetch</code> - 기본 값은 true. 백그라운드에서 페이지를 미리 가져온다. <Link /> 뷰포트에 있는 모든 항목이 미리 로드 된다. prefetch={false} 을 전달하여 프리 페치를 비활성화 할 수 있다. false 여도 마우스를 hover 하면 프리페치가 계속 발생한다. 정적 생성을 사용하는 페이지는 더 빠른 페이지 전환을 위해 데이터와 함께 JSON 파일을 미리 로드한다. 프리 페치는 프로덕션에서만 활성화된다.</li>
<li><code>replace</code> - 기본 값은 false. history 스택에 새 URL 추가하는 대신 현재 상태를 바꾼다.</li>
<li><code>scroll</code> - 기본 값은 true. 이동 후 페이지 상단으로 스크롤 한다.</li>
<li><code>shallow</code> - 기본 값은 false. getStaticProps, getServerSideProps 또는 getInitialProps 를 다시 실행하지 않고 현재 페이지의 경로를 업데이트.</li>
<li><code>locale</code> - 활성 locale 이 자동으로 앞에 추가된다. locale을 사용하면 다른 locale을 제공 할 수 있다. false href 가  locale을 포함해야하는 경우 기본 동작이 비활성화된다.</li>
</ul>
<h3 id="with-url-object">With URL Object</h3>
<blockquote>
<p>💡  <code>Link</code> 는 URL 객체도 받을 수 있다. 자동으로 형식을 지정하여 URL 문자열을 만든다. </p>
</blockquote>
<pre><code class="language-jsx">import Link from &#39;next/link&#39;;

export default function Home() {
  return (
    &lt;ul&gt;
      &lt;li&gt;
        &lt;Link href={{ pathname: &#39;/about&#39;, query: { name: &#39;test&#39; } }}&gt;
          &lt;a&gt;About us&lt;/a&gt;
        &lt;/Link&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;Link href={{ pathname: &#39;/blog/[slug]&#39;, query: { slug: &#39;my-post&#39; } }}&gt;
          &lt;a&gt;Blog Post&lt;/a&gt;
        &lt;/Link&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  )
}</code></pre>
<ul>
<li>사전 정의된 경로: /about?name=test</li>
<li>동적 경로: /blog/my-post</li>
</ul>
<h2 id="nextrouter">next/router</h2>
<h3 id="router-object">router object</h3>
<blockquote>
<p>💡 router 객체에 접근하려면 useRouter 훅을 사용하면 된다.</p>
</blockquote>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

export default function Home() {
  const router = useRouter();
  console.log(router);
  // ...
}</code></pre>
<p>router 를 콘솔로 찍어보면 아래와 같은 객체가 나타난다.</p>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/27c0cd36-d201-4418-89e5-07d2e690325b/useRouter.png" alt=""></p>
<ul>
<li><p><code>asPath</code> - string - basePath 또는 locale 없이 브라우저에 표시되는 경로 (쿼리포함)</p>
</li>
<li><p><code>basePath</code> - string - 활성 basePath (활성화 된 경우)</p>
</li>
<li><p><code>defaultLocale</code> - string - 현재 기본 locale (활성화 된 경우)</p>
</li>
<li><p><code>isFallback</code> - boolean - 현재 페이지가 fallback 모드인지 여부</p>
</li>
<li><p><code>isPreview</code> - boolean - 앱이 현재 미리보기 모드인지 여부</p>
</li>
<li><p><code>isReady</code> - boolean - 라우터 필드가 클라이언트 측에서 업데이트되고 사용할 준비가 되었는지 여부. useEffect 메소드 내에서만 사용해야하며 서버에서 조건부로 렌더링 하는 데에 사용해야한다.</p>
</li>
<li><p><code>locale</code> - string - 활성 로케일 (활성화 된 경우)</p>
</li>
<li><p><code>locales</code> - string[] - 지원되는 모든 로케일 (활성화 된 경우)</p>
</li>
<li><p><code>pathname</code> - string - 현재 경로. 이는 /pages 의 페이지 경로이며(파일명) 구성된 basePath 또는 locale 은 포함되지 않는다.</p>
</li>
<li><p><code>query</code> - object -  객체로 구문 분석 된 쿼리 문자열. 페이지에 데이터 가져오기 요구사항이 없는 경우 사전 렌더링 중에 빈 객체가 된다. 기본값은 {}</p>
<p>  ⇒ 동적 라우팅 사용시 쿼리값이 객체로 들어감.</p>
</li>
</ul>
<h3 id="routerpush">router.push</h3>
<blockquote>
<p>💡  클라이언트 사이드 라우팅을 처리한다. next/link 로 충분하지 않은 경우에 유용하다.</p>
</blockquote>
<pre><code class="language-jsx">router.push(url, as, options)</code></pre>
<ul>
<li><code>url</code> - 이동할 경로</li>
<li><code>as</code> - 브라우저에 표시될 URL에 대한 선택적 데코레이터.</li>
<li><code>options</code> - 다음 구성 옵션이 있는 선택적 객체<ul>
<li><code>scroll</code> - 기본 값은 true. 선택 boolean. 이동 후 페이지를 상단으로 스크롤 제어.</li>
<li><code>shallow</code> - 기본 값은 false. getStaticProps, getServerSideProps 또는 getInitialProps 를 다시 실행하지 않고 현재 페이지의 경로를 업데이트.</li>
<li><code>locale</code> - 선택적 문자열. 새 페이지의 로케일을 나타낸다.</li>
</ul>
</li>
</ul>
<p>외부 URL에 router.push 를 사용할 필요가 없다. window.location이 이러한 경우 더 적합하다.</p>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

export default function Page() {
  const router = useRouter();
  return (
    &lt;button type=&quot;button&quot; onClick={() =&gt; router.push(&#39;/about&#39;)}&gt;
      Click me
    &lt;/button&gt;
  )
}</code></pre>
<h3 id="with-url-object-1">With URL object</h3>
<blockquote>
<p>💡  next/link 에서 사용한 것처럼 URL 객체를 사용할 수 있다.</p>
</blockquote>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;

export default function ReadMore({ post }) {
  const router = useRouter()

  return (
    &lt;button
      type=&quot;button&quot;
      onClick={() =&gt; {
        router.push({
          pathname: &#39;/post/[pid]&#39;,
          query: { pid: post.id },
        })
      }}
    &gt;
      Click here to read more
    &lt;/button&gt;
  )
}</code></pre>
<p>그 외 메소드 참고링크 <a href="https://nextjs.org/docs/api-reference/next/router#router-object">https://nextjs.org/docs/api-reference/next/router#router-object</a></p>
<p>뚜둥,
어려운 Next.js.. 이렇게 라우팅 기능만 보면 CRA보다 참 쉬운 것 같았는데.. 절대 아니었다... </p>
<p>다음은 Dynamic Routes,  Data Fetching에 대해 글을 쓸 예정이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 시작하기]]></title>
            <link>https://velog.io/@kimhyo_0218/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimhyo_0218/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 21 Jun 2021 14:21:04 GMT</pubDate>
            <description><![CDATA[<p>이번 달에 새로 시작하게된 프로젝트에 도입한 Next.js.
이미 일은 진행 중이지만 개념을 돌아보고자 퇴근 후 작성합니다!!</p>
<h1 id="nextjs-">Next.js ?</h1>
<blockquote>
<p>서버사이드 렌더링이 가능한 React 프레임워크</p>
</blockquote>
<p><strong>서버사이드 렌더링은 무엇일까?</strong>
쉽게 말해서 서버에서 html을 내려주는 것이다.
클라이언트 사이드 렌더링은 페이지의 내용을 브라우저에서 그리는 반면, 서버사이드 렌더링은 서버에서 페이지를 그리고 브라우저에 내려준다.
서버에서 사전 렌더링 된 html을 내려주기 때문에 검색엔진 최적화(SEO)에 좋다는 장점이 있다.
자바스크립트를 차단하고 페이지를 보면 html이 잘 로드된 것을 알 수 있다.
자바스크립트를 실행하면 이 html을 꾸며주고 Link 컴포넌트 등이 작동을 시작한다.</p>
<h1 id="nextjs의-기능들">Next.js의 기능들</h1>
<ul>
<li><p>직관적 인 페이지 기반 라우팅 시스템 ( 동적 라우팅 지원 )</p>
</li>
<li><p>사전 렌더링 , 정적 생성 (SSG) 및 서버 측 렌더링 (SSR) 모두 페이지별로 지원된다.</p>
<p>  ⇒ 기본적으로 Next.js는 모든 페이지를 <strong>사전 렌더링</strong> 한다. </p>
</li>
</ul>
<p>사전 렌더링에는 두 가지가 있는데, 차이점은 &#39;언제&#39; html을 만드는지이다.
    - <strong>정적 생성(권장):</strong> html은 빌드시 생성되며 각 요청에 <strong>재사용</strong>된다.
    - <strong>서버 사이드 렌더링:</strong> <strong>각 요청에 대해 HTML이 생성</strong>된다.</p>
<blockquote>
<p>   각 페이지에 사용할 렌더링 양식을 선택할 수 있어 하이브리드 앱을 만들 수 있다.
   또는 클라이언트 사이드 렌더링도 사용할 수 있다.</p>
</blockquote>
<ul>
<li>더 빠른 페이지 로드를 위한 자동 코드 분할</li>
<li>최적화 된 프리 페치를 사용한 클라이언트 측 라우팅</li>
<li>내장 CSS 및 Sass 지원 및 모든 CSS-in-JS 라이브러리 지원</li>
<li>Fast Refresh를 지원하는 개발 환경</li>
<li>서버리스 함수로 API 엔드 포인트를 빌드하기위한 API 경로</li>
<li>완전히 확장 가능</li>
</ul>
<h2 id="setup">Setup</h2>
<h3 id="기본-setup">기본 Setup</h3>
<pre><code class="language-javascript">npx create-next-app sampleNext</code></pre>
<h3 id="타입스크립트-setup">타입스크립트 Setup</h3>
<pre><code class="language-javascript">npx create-next-app --typescript sampleNext</code></pre>
<p>이어서 쭉 쭉 공식 문서를 보며 공부한 내용을 기록할 예정입니다!!</p>
<p><a href="https://nextjs.org/docs/getting-started">Next.js 공식 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘/자료구조] JavaScript 트리(Tree)]]></title>
            <link>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-JavaScript-%ED%8A%B8%EB%A6%ACTree</link>
            <guid>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-JavaScript-%ED%8A%B8%EB%A6%ACTree</guid>
            <pubDate>Mon, 22 Feb 2021 01:24:14 GMT</pubDate>
            <description><![CDATA[<h2 id="트리tree">트리(Tree)</h2>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/3c395186-b02d-4997-9be5-5f9a3e40a2d5/tree.jpg" alt="">
이미지 출처: <a href="https://monsieursongsong.tistory.com/6">https://monsieursongsong.tistory.com/6</a></p>
<ul>
<li>스택/큐와는 다른 비선형 구조이다.</li>
<li>여러 데이터가 계층 구조 안에서 서로 연결된 형태를 나타낼 때 사용된다.</li>
<li>정점을 <strong>노드(Node)</strong> 라고 하고 노드와 노드를 연결하는 선을 <strong>가지(Edge)</strong> 라고 한다.</li>
</ul>
<h3 id="관련-용어">관련 용어</h3>
<ul>
<li><p><strong>Root node (뿌리 노드)</strong>
: 트리 구조에서 최상위에 있는 노드</p>
</li>
<li><p><strong>Parent node (부모 노드)</strong>
: 어떤 노드에서 자신과 인접한 노드들 중 뿌리 노드로 향하는 노드</p>
</li>
<li><p><strong>Child node (자식 노드)</strong>
: 어떤 노드에서 자신과 인접한 노드들 중 뿌리 노드의 반대 방향으로 향하는 노드</p>
</li>
<li><p><strong>Leaf node (단말 노드)</strong>
: 자식 노드가 없는 노드</p>
</li>
<li><p><strong>Sibling node (형제 노드)</strong>
: 부모 노드 가 같은 다른 노드</p>
</li>
<li><p><strong>Sub tree (부트리)</strong>
: 큰 트리(전체)에 속하는 작은 트리</p>
</li>
<li><p><strong>Degree (차수)</strong>
: 자식 노드의 개수</p>
</li>
<li><p><strong>Length (길이)</strong>
: 임의의 두 노드를 시작 노드, 도착 노드로 하는 경로에서 거치게 되는 노드의 수</p>
</li>
<li><p><strong>Depth (깊이)</strong>
: 뿌리 노드에서 해당 노드까지의 길이</p>
</li>
<li><p><strong>Level (레벨)</strong>
: 깊이가 같은 노드의 집합 (각 층별로 숫자를 매김)</p>
</li>
<li><p><strong>Height (높이)</strong>
: 단말 노드까지의 길이 (최고 레벨)</p>
</li>
</ul>
<h3 id="트리의-종류">트리의 종류</h3>
<ul>
<li><p><strong>이진 트리</strong>
: 모든 노드의 차수가 2 이하인 트리</p>
</li>
<li><p><strong>전 이진 트리</strong>
: 모든 노드의 차수가 0 또는 2인 트리</p>
</li>
<li><p><strong>포화 이진 트리</strong>
: 모든 단말 노드의 깊이가 같은 이진 트리</p>
</li>
<li><p><strong>완전 이진 트리</strong>
: 포화 이진 트리에서 오른쪽부터 제거된 트리
= 빈 곳이 없어야 하고 마지막 레벨 노드가 왼쪽에 몰려있어야 한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘/자료구조] JavaScript 연결리스트(Linked List)]]></title>
            <link>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-JavaScript-%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8Linked-List</link>
            <guid>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-JavaScript-%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8Linked-List</guid>
            <pubDate>Sun, 21 Feb 2021 14:47:15 GMT</pubDate>
            <description><![CDATA[<h2 id="연결리스트linked-list">연결리스트(Linked List)</h2>
<ul>
<li>연결리스트는 여러 개의 노드로 이루어져 있다.</li>
<li>각각의 노드는 데이터와 다음 노드의 주소를 가지고 있다. 맨 뒤 노드가 맨 앞 노드를 다음 노드로 가지게 할 수도 있다.</li>
<li>새로운 데이터를 추가하거나 위치를 찾거나 제거할 수가 있다.</li>
<li>데이터의 추가/삭제가 용이하나 순차적으로 탐색하지 않으면 특정 위치 요소에 접근할 수 없어 탐색 속도가 떨어진다. 탐색/정렬을 자주 하면 배열, 추가/삭제가 많으면 연결리스트를 사용하는 것이 유리하다.</li>
</ul>
<h3 id="단순-연결-리스트singly-linked-list">단순 연결 리스트(Singly Linked List)</h3>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/73e54750-cbd1-42b6-a9ca-db7bf471ebb4/%EB%8B%A8%EC%88%9C%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8.png" alt=""></p>
<p>이미지 출처: <a href="https://namu.wiki/w/%EC%97%B0%EA%B2%B0%20%EB%A6%AC%EC%8A%A4%ED%8A%B8">나무위키</a></p>
<ul>
<li>다음 노드에 대한 정보만을 가진 단순한 형태의 연결리스트이다.</li>
<li>가장 마지막 원소를 찾으려면 리스트 끝까지 가야해서 O(n).</li>
<li>추가는 O(1).</li>
<li>head노드 유실 시 전체 자료를 잃어버리게 된다.</li>
</ul>
<h3 id="이중-연결-리스트doubly-linked-list">이중 연결 리스트(Doubly Linked List)</h3>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/26aa5d5d-90c3-45be-b4e1-001bf19c00f0/%EC%9D%B4%EC%A4%91%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8.png" alt=""></p>
<p>이미지 출처: <a href="https://namu.wiki/w/%EC%97%B0%EA%B2%B0%20%EB%A6%AC%EC%8A%A4%ED%8A%B8">나무위키</a></p>
<ul>
<li>다음 노드의 주소뿐만 아니라 이전 노드의 주소도 같이 가르키는 연결리스트 이다.</li>
<li>단순 연결 리스트에 비해 노드 삭제가 간단하다.</li>
<li>관리해야할 주소가 두 개여서 삽입이나 정렬의 경우 작업량이 더 많고 크기가 약간 더 커진다.</li>
<li>head나 tail 노드 둘 중 하나만 있어도 전체 리스트를 순회할 수 있어서 끊어진 체인을 복구하는게 가능.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘/프로그래머스] JavaScript 스택/큐 기능개발]]></title>
            <link>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-JavaScript-%EC%8A%A4%ED%83%9D%ED%81%90-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-JavaScript-%EC%8A%A4%ED%83%9D%ED%81%90-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Sun, 21 Feb 2021 13:57:18 GMT</pubDate>
            <description><![CDATA[<p>프로그래머스의 스택/큐 Level2의 기능개발 문제이다.</p>
<p>처음 풀었던 코드는</p>
<pre><code class="language-javascript">function solution(progresses, speeds) {
  let answer = [];
  answer = [0];
  let day = [];

  for (let i = 0; i &lt; progresses.length; i++) {
    day[i] = Math.ceil((100 - progresses[i]) / speeds[i]);
  }

  for (let i = 0, j = 0; i &lt; day.length; i++) {
    if (day[i] &lt;= day[0] || day[i] &lt;= day[i - 1]) {
      answer[j] += 1;
    } else {
      answer[++j] = 1;
    }
  }
  return answer;
}</code></pre>
<p>이것이었는데 제출 후 채점하기를 하면 2,3,4,7,9,10 번 테스트는 자꾸 실패로 떠서 다시 풀어야했다.ㅜㅠ
어떤 테스트 케이스를 추가해봐도 오류를 찾을 수가 없어서...</p>
<pre><code class="language-javascript">function solution(progresses, speeds) {
  let answer = [];
  let days = [];
  let count = 1;
  let compareIndex = 0;

  for (let i = 0; i &lt; progresses.length; i++) {
    days[i] = Math.ceil((100 - progresses[i]) / speeds[i]);
  }
  for (let i = 1; i &lt; days.length; i++) {
    if (days[compareIndex] &gt;= days[i]) {
      count++;
    } else {
      answer.push(count);
      count = 1;
      compareIndex = i;
    }
    if (i === days.length - 1) {
      answer.push(count);
    }
  }
  return answer;
}</code></pre>
<pre><code class="language-javascript">function solution(progresses, speeds) {
  let answer = [];

  while (progresses.length) {
    for (let i = 0; i &lt; progresses.length; i++) {
      if (progresses[i] &lt; 100) {
        progresses[i] += speeds[i];
      }
    }
    if (progresses[0] &gt;= 100) {
      let count = 0;
      while (progresses[0] &gt;= 100) {
        progresses.shift();
        speeds.shift();
        count++;
      }
      answer.push(count);
    }
  }
  return answer;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 공부 시작하기]]></title>
            <link>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%B5%EB%B6%80-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimhyo_0218/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%B5%EB%B6%80-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 21 Feb 2021 12:49:53 GMT</pubDate>
            <description><![CDATA[<p> 2020년 9월, 개발자에 관심을 갖고 처음 자바스크립트 공부를 시작하면서 눈에 보이는 무언가를 만드는 데에만 급급했다. 현실적인 문제로 취업이 급해서 리액트와 리액트 네이티브를 건드리면서 포트폴리오를 만들었고 취업을 했지만 알고리즘에 대한 지식과 문제풀이 실력이 너무 형편없었다 ㅜ.ㅜ 같은 기능을 하는 코드여도 더 깔끔한 코드로 만드는 것에 대해서도 부족함을 느꼈고.. 알고리즘 공부와 자바스크립트 기초를 다시 다져봐야겠단 결심을 했다. </p>
<p>이 게시글은 알고리즘의 개념들을 전체적으로 훑어보는 용으로 작성하였으며 코드 예시나 자세한 내용은 따로 게시글을 올릴 예정이다.</p>
<p><a href="https://visualgo.net/ko">알고리즘을 시각화한 사이트</a></p>
<h2 id="시간복잡도">시간복잡도</h2>
<p>문제를 해결하기 위한 시간(연산)의 횟수를 <code>시간복잡도</code> 라고 한다.
시간복잡도가 큰 알고리즘들은 더 큰, 혹은 많은 데이터를 처리할 때 훨씬 오랜 시간이 걸린다.
이 계산 횟수의 정확한 측정이 어렵기 때문에 보통 <code>Big-O 표기법</code>을 이용한다고 한다.</p>
<p>시간복잡도에서 중요하게 보는 것은 가장 큰 영향을 미치는 n의 단위이다.</p>
<ul>
<li>*<em>O(1) *</em>
: 상수 형태. n의 값에 상관없이 일정한 양의 계산만 한다.</li>
<li>*<em>O(logn) *</em>
: 로그 형태.</li>
<li>*<em>O(n) *</em>
: 선형.</li>
<li><strong>O(nlogn)</strong> 
: 선형로그 형태.</li>
<li><strong>O(n²),O(n³),⋯</strong>
: 다차 형태.</li>
<li>*<em>O(2ⁿ) *</em>
: 지수 형태.</li>
<li><strong>O(n!)</strong>
: 팩토리얼 형태.</li>
</ul>
<p><img src="https://images.velog.io/images/kimhyo_0218/post/0bc4f3e6-708d-4baa-923c-41657d70e536/bigO.png" alt=""><img src="https://images.velog.io/images/kimhyo_0218/post/0f91edc7-31f7-48fb-a905-f9a949020bfb/bigO%EC%88%9C%EC%84%9C.png" alt=""></p>
<h2 id="자료구조">자료구조</h2>
<p>자료구조란 데이터 사이의 관계를 반영한 저장구조 및 그 조작방법을 뜻한다.
데이터에 맞는 특성의 자료구조를 사용하는 것이 중요하다.</p>
<h4 id="선형-구조--한-종류의-데이터가-선처럼-길게-나열된-자료구조">선형 구조 : 한 종류의 데이터가 선처럼 길게 나열된 자료구조.</h4>
<ul>
<li>배열</li>
<li>해시</li>
<li>스택</li>
<li>큐</li>
<li>덱</li>
<li>연결리스트</li>
</ul>
<h4 id="비선형-구조--선형-구조가-아닌-모든-자료구조-i번째-값을-탐색한-뒤의-i1이-정해지지-않는-구조">비선형 구조 : 선형 구조가 아닌 모든 자료구조. i번째 값을 탐색한 뒤의 i+1이 정해지지 않는 구조</h4>
<ul>
<li>그래프</li>
<li>트리</li>
</ul>
<p><a href="https://librewiki.net/wiki/%EC%8B%9C%EB%A6%AC%EC%A6%88:%EC%88%98%ED%95%99%EC%9D%B8%EB%93%AF_%EA%B3%BC%ED%95%99%EC%95%84%EB%8B%8C_%EA%B3%B5%ED%95%99%EA%B0%99%EC%9D%80_%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B3%BC%ED%95%99/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98_%EA%B8%B0%EC%B4%88">Libreswiki 참조</a></p>
<h2 id="정렬">정렬</h2>
<h4 id="시간복잡도-on²-정렬-알고리즘">시간복잡도 O(n²) 정렬 알고리즘</h4>
<p>O(n²) : 계산 시간이 정렬할 자료의 수의 제곱에 비례해서 늘어난다. 즉, 1만 개를 1초에 정렬하면 10만 개를 정렬하는 데에는 100초 정도가 필요하다.</p>
<ul>
<li><p><strong>버블(bubble) 정렬</strong>
: 가장 쉬운 정렬 알고리즘이지만 비효율적이고 잘 사용하지 않는다.
바로 뒤의 원소와 비교하여 정렬한다.</p>
</li>
<li><p><strong>선택(Selection) 정렬</strong>
: 버블 정렬과 유사하다. 처음부터 끝까지 훑어서 가장 작은게 첫번째, 그 다음 2번째부터 끝까지 훑어서 가장 작은게 두번째.. 이런식으로 가장 큰 수가 배열의 마지막에 위치하게 되는 정렬이다.</p>
</li>
<li><p><strong>삽입(Insertion) 정렬</strong>
: 하나씩 삽입해 나가는 형태의 정렬방식.
특정 값에 대해서 그 값이 맞는 위치를 찾고, 나머지는 한칸씩 밀어낸다.
인덱스를 설정하여 현재 위치의 값을 아래쪽으로 순회하며 알맞은 곳에 넣어준다.</p>
</li>
</ul>
<h4 id="시간복잡도-onlogn-정렬-알고리즘">시간복잡도 O(nlogn) 정렬 알고리즘</h4>
<ul>
<li><p><strong>병합(Merge) 정렬</strong>
: 원소 갯수가 1 또는 0이 될 때까지 쪼개나가며 좌측과 우측 리스트를 계속 분할해 나간 후 각 리스트 내에서 정렬 후 병합한다.</p>
</li>
<li><p><strong>힙(Heap) 정렬</strong>
: 힙이라는 자료구조를 통해 내림차순으로 숫자를 넣은후, 역순으로 꺼내어 정렬한다.</p>
</li>
<li><p><strong>퀵(Quick) 정렬</strong>
: 시간복잡도 O(nlogn) 인 정렬 알고리즘들 중 가장 빠르다. 
원소 하나를 기준(pivot)으로 삼아 그보다 작은 것을 왼쪽으로 빼내고 큰 것을 오른쪽으로 보내는 걸 반복해 정렬한다.</p>
</li>
</ul>
<p><a href="https://namu.wiki/w/%EC%A0%95%EB%A0%AC%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98#s-2.2">정렬알고리즘 나무위키 참조</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘/자료구조] JavaScript 덱(Deque) 구현하기]]></title>
            <link>https://velog.io/@kimhyo_0218/JavaScript-%EB%8D%B1Deque-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimhyo_0218/JavaScript-%EB%8D%B1Deque-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 20 Feb 2021 10:10:08 GMT</pubDate>
            <description><![CDATA[<h2 id="덱deque">덱(Deque)</h2>
<ul>
<li>덱은 Double-ended Queue 의 약자이다</li>
<li>스택과 큐를 합친 자료구조여서 양 끝에서 데이터를 넣거나 추출할 수 있다.</li>
</ul>
<pre><code class="language-javascript">class Deque {
  constructor() {
    this.arr = [];
    this.head = 0;
    this.tail = 0;
  }
  push_front(item) {
    if (this.arr[0]) {
      for (let i = this.arr.length; i &gt; 0; i--) {
        this.arr[i] = this.arr[i - 1];
      }
    }
    this.arr[this.head] = item;
    this.tail++;
  }
  push_back(item) {
    this.arr[this.tail++] = item;
  }
  pop_front() {
    if (this.head &gt;= this.tail) {
      return null;
    } else {
      const result = this.arr[this.head++];
      return result;
    }
  }
  pop_back() {
    if (this.head &gt;= this.tail) {
      return null;
    } else {
      const result = this.arr[--this.tail];
      return result;
    }
  }
}

let deque = new Deque();
deque.push_front(1); // arr: [1] head: 0 tail: 1
deque.push_front(2); // arr: [2, 1] head: 0 tail: 2
console.log(deque.pop_front()); // 2, head: 1 tail: 2
deque.push_front(3); // arr: [2, 3, 1] head: 1 tail: 3
console.log(deque.pop_front()); // 3, head: 2 tail: 3
console.log(deque.pop_front()); // 1, head: 3 tail: 3
console.log(deque.pop_front()); // null
deque.push_back(5); // arr: [5] head: 3 tail: 4
// 실제 배열 출력은 arr: [2, 3, 1, 5] 이지만 배열 요소 2, 3, 1은 pop_front()를 하였기에 shift()가 된 요소로 생각할 수 있다.
console.log(deque.pop_back()); // 5, head: 3 tail: 3
console.log(deque.pop_back()); // null
deque.push_back(6); // arr: [6] head: 3 tail: 4
// 실제 배열 출력은 arr: [2, 3, 1, 6] 이지만 배열 요소 2, 3, 1 은 pop_front()를 하였기에 shift()가 된 요소로, 배열 요소 5는 pop_back()을 실행해서 pop()가 된 요소로 생각할 수 있다.
deque.push_front(9); // arr: [9, 6] head: 3 tail: 5</code></pre>
]]></description>
        </item>
    </channel>
</rss>