<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sehee-xx.log</title>
        <link>https://velog.io/</link>
        <description>front-end developer</description>
        <lastBuildDate>Mon, 20 Oct 2025 08:17:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sehee-xx.log</title>
            <url>https://velog.velcdn.com/images/sehee-xx/profile/416ff1b1-6bb3-40cd-b487-835b848d92cb/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sehee-xx.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sehee-xx" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React에 TipTap 붙이기]]></title>
            <link>https://velog.io/@sehee-xx/React%EC%97%90-TipTap-%EB%B6%99%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@sehee-xx/React%EC%97%90-TipTap-%EB%B6%99%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Mon, 20 Oct 2025 08:17:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>툴바부터 테마까지 내가 원하는 대로</p>
</blockquote>
<p>TipTap은 디자인 자유도가 높은 리치 텍스트 에디터로
오늘은 아주 유용한 TipTap에 대해서 이야기 해보려고 한다!</p>
<blockquote>
<p>이 글에서 다룰 것</p>
</blockquote>
<ol>
<li><p>TipTap을 쓰면 뭐가 좋은지 한 줄 정리</p>
</li>
<li><p>React에서 설치 → 최소 예제 → 저장/불러오기까지</p>
</li>
<li><p>툴바 버튼 직접 만들어 보기(볼드/이탤릭/헤딩)</p>
</li>
<li><p>스타일링 팁과 Next.js(SSR) 주의점</p>
</li>
<li><p>자주 겪는 이슈 Q&amp;A</p>
</li>
<li><p>왜 TipTap일까?</p>
</li>
</ol>
<ul>
<li><p>헤드리스(Headless): 
“편집기 로직”만 있고, UI는 전부 내가 만든다 
→ 디자인 시스템(Tailwind, shadcn/ui 등)과 찰떡궁합!</p>
</li>
<li><p>확장(Extension) 기반: 필요한 기능만 쏙쏙 무겁지 않게</p>
</li>
<li><p>ProseMirror 위라서 안정적이고 커스터마이징 폭이 큼</p>
</li>
</ul>
<blockquote>
<p>“회사/프로젝트의 룩앤필을 해치지 않고, 에디터를 내 디자인처럼”</p>
</blockquote>
<blockquote>
<p>설치 (3줄 컷)</p>
</blockquote>
<pre><code>npm create vite@latest my-tiptap -- --template react-ts
cd my-tiptap
npm i @tiptap/react @tiptap/pm @tiptap/starter-kit</code></pre><blockquote>
<p>최소 예제</p>
</blockquote>
<pre><code>// src/Tiptap.tsx
import { useEditor, EditorContent } from &#39;@tiptap/react&#39;
import StarterKit from &#39;@tiptap/starter-kit&#39;

export default function Tiptap() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: &#39;&lt;p&gt;Hello TipTap! &lt;strong&gt;Bold&lt;/strong&gt; 테스트&lt;/p&gt;&#39;,
  })

  if (!editor) return null
  return &lt;EditorContent editor={editor} /&gt;
}

// src/App.tsx
import Tiptap from &#39;./Tiptap&#39;

export default function App() {
  return (
    &lt;main style={{ maxWidth: 720, margin: &#39;40px auto&#39; }}&gt;
      &lt;h1&gt;My TipTap Editor&lt;/h1&gt;
      &lt;Tiptap /&gt;
    &lt;/main&gt;
  )
}</code></pre><p>여기까지가 “보여주기”의 끝!
이제부터 “쓰기 편하게 만들기”를 해보자</p>
<blockquote>
<p>가장 많이 쓰는 툴바 3종 세트 만들기</p>
</blockquote>
<p>TipTap은 버튼을 기본 제공하지 않는다.
대신 editor.chain().focus().toggleBold().run() 같은 명령을 호출하면 끝!</p>
<pre><code>// src/Toolbar.tsx
import { useCurrentEditor } from &#39;@tiptap/react&#39;

export default function Toolbar() {
  const { editor } = useCurrentEditor()
  if (!editor) return null

  const Button = (props: React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;) =&gt; (
    &lt;button
      {...props}
      style={{
        padding: &#39;6px 10px&#39;,
        borderRadius: 8,
        border: &#39;1px solid #ddd&#39;,
        background: &#39;#fff&#39;,
        cursor: &#39;pointer&#39;,
      }}
    /&gt;
  )

  return (
    &lt;div style={{ display: &#39;flex&#39;, gap: 8, margin: &#39;12px 0&#39; }}&gt;
      &lt;Button
        onClick={() =&gt; editor.chain().focus().toggleBold().run()}
        style={{ fontWeight: editor.isActive(&#39;bold&#39;) ? 700 : 400 }}
      &gt;
        B
      &lt;/Button&gt;
      &lt;Button
        onClick={() =&gt; editor.chain().focus().toggleItalic().run()}
        style={{ fontStyle: editor.isActive(&#39;italic&#39;) ? &#39;italic&#39; : &#39;normal&#39; }}
      &gt;
        I
      &lt;/Button&gt;
      &lt;Button onClick={() =&gt; editor.chain().focus().toggleHeading({ level: 2 }).run()}&gt;
        H2
      &lt;/Button&gt;
      &lt;Button onClick={() =&gt; editor.chain().focus().setParagraph().run()}&gt;
        P
      &lt;/Button&gt;
      &lt;Button onClick={() =&gt; editor.chain().focus().undo().run()}&gt;
        Undo
      &lt;/Button&gt;
      &lt;Button onClick={() =&gt; editor.chain().focus().redo().run()}&gt;
        Redo
      &lt;/Button&gt;
    &lt;/div&gt;
  )
}</code></pre><pre><code>// src/Tiptap.tsx (툴바 붙이기)
import { useEditor, EditorContent, EditorContentProps, Editor } from &#39;@tiptap/react&#39;
import StarterKit from &#39;@tiptap/starter-kit&#39;
import Toolbar from &#39;./Toolbar&#39;

export default function Tiptap() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: &#39;&lt;p&gt;여기에 글을 입력해보세요 ✍️&lt;/p&gt;&#39;,
  })

  if (!editor) return null
  return (
    &lt;&gt;
      &lt;Toolbar /&gt;
      &lt;EditorContent editor={editor} className=&quot;tiptap&quot; /&gt;
    &lt;/&gt;
  )
}</code></pre><blockquote>
<p>저장/불러오기(로컬 저장소 기준)</p>
</blockquote>
<p>TipTap은 JSON/HTML 둘 다 지원한다.
보통은 JSON 저장이 구조를 보존하기 좋다!</p>
<pre><code>// src/SaveLoad.tsx
import { useCurrentEditor } from &#39;@tiptap/react&#39;

export default function SaveLoad() {
  const { editor } = useCurrentEditor()
  if (!editor) return null

  const save = () =&gt; {
    const json = editor.getJSON()
    localStorage.setItem(&#39;post&#39;, JSON.stringify(json))
    alert(&#39;저장 완료!&#39;)
  }

  const load = () =&gt; {
    const raw = localStorage.getItem(&#39;post&#39;)
    if (!raw) return alert(&#39;저장된 내용이 없어요&#39;)
    editor.commands.setContent(JSON.parse(raw))
  }

  return (
    &lt;div style={{ display: &#39;flex&#39;, gap: 8, marginBottom: 12 }}&gt;
      &lt;button onClick={save}&gt;저장&lt;/button&gt;
      &lt;button onClick={load}&gt;불러오기&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre><pre><code>// src/Tiptap.tsx (저장/불러오기 UI 포함)
import SaveLoad from &#39;./SaveLoad&#39;
// ...
return (
  &lt;&gt;
    &lt;Toolbar /&gt;
    &lt;SaveLoad /&gt;
    &lt;EditorContent editor={editor} className=&quot;tiptap&quot; /&gt;
  &lt;/&gt;
)</code></pre><blockquote>
</blockquote>
<p>스타일링 팁 (진짜 중요)</p>
<p>TipTap은 스타일을 거의 안 주고 보낸다.
그래서 최소한 아래 정도는 잡아두면 보기 좋아집니다.</p>
<pre><code>/* src/index.css */
.tiptap {
  min-height: 220px;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  line-height: 1.7;
  background: #fff;
}

/* 본문 요소들 */
.tiptap p { margin: 0 0 0.8em; }
.tiptap h1, .tiptap h2, .tiptap h3 { font-weight: 700; line-height: 1.3; margin: 1.2em 0 0.6em; }
.tiptap ul, .tiptap ol { padding-left: 1.4em; margin: 0.8em 0; }
.tiptap code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 2px 6px; border-radius: 6px; background: #f5f5f5; }
.tiptap pre { background: #0b1020; color: #e6edf3; padding: 12px 14px; border-radius: 12px; overflow: auto; }
.tiptap blockquote { border-left: 4px solid #e5e7eb; margin: 1em 0; padding-left: 12px; color: #6b7280; }</code></pre><p>Tailwind를 쓴다면 className=&quot;prose&quot; 같은 걸 바로 붙이지 말고, .tiptap 범위에 필요한 스타일만 얇게 깔아주는 걸 추천한다!
(툴바/테마랑 충돌 적음)</p>
<blockquote>
<p>Next.js(SSR)이라면 이 옵션을 꼭 기억하자!</p>
</blockquote>
<p>SSR에서는 에디터를 클라이언트에서만 그리게 해야 경고가 안 뜬다.</p>
<pre><code>&#39;use client&#39;
import { useEditor, EditorContent } from &#39;@tiptap/react&#39;
import StarterKit from &#39;@tiptap/starter-kit&#39;

export default function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: &#39;&lt;p&gt;SSR 안전 모드&lt;/p&gt;&#39;,
    immediatelyRender: false, // ← 요게 포인트!
  })

  if (!editor) return null
  return &lt;EditorContent editor={editor} className=&quot;tiptap&quot; /&gt;
}</code></pre><blockquote>
</blockquote>
<p>자주 겪는 이슈 Q&amp;A</p>
<p>Q1. 왜 기본 툴바가 없죠?
A. TipTap은 헤드리스라 일부러 안 넣었다.
대신 editor.commands.*나 editor.chain()으로 버튼을 내 입맛대로 만들 수 있다.</p>
<p>Q2. HTML로 저장하면 안 되나요?
A. 가능합니다. 다만 “구조”를 유지해야 하거나 나중에 머지/검증을 하려면 JSON이 더 좋다. (서버에서 파싱/검증도 쉬움)</p>
<p>Q3. 긴 문서에서 렉이 걸려요.
A. 툴바/상태 표시 컴포넌트를 쪼개고, editor.isActive() 호출을 최소화하자! 필요하면 하이라이트/구문강조 같은 무거운 작업은 지연 처리를 고려하자.</p>
<p>Q4. 컴포넌트에서 editor가 자꾸 null이에요.
A. useEditor()는 초기 렌더에서 null일 수 있다.
if (!editor) return null을 기억하자!</p>
<blockquote>
<p>확장(Extension) 선택 가이드 (입문 추천)</p>
</blockquote>
<p>StarterKit: 
문단/헤딩/리스트/코드블럭/수평선/볼드/이탤릭/취소/재실행 등 기본 풀세트</p>
<p>Link / Image / Table / Placeholder / Mention: 필요할 때 하나씩 추가</p>
<p>CodeBlockLowlight: 코드 하이라이트가 필요할 때</p>
<p>팁: “지금 당장 안 쓰는 기능”은 과감히 빼두자
에디터는 가볍고 빠르게 느껴지는 게 가장 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Debouncing & Throttling]]></title>
            <link>https://velog.io/@sehee-xx/Debouncing-Throttling</link>
            <guid>https://velog.io/@sehee-xx/Debouncing-Throttling</guid>
            <pubDate>Wed, 01 Oct 2025 06:34:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>핵심만 먼저 보고 가자!</p>
</blockquote>
<p>➡️ Debounce: 입력/이벤트가 잠시 멈출 때까지 기다렸다가 한 번만 실행</p>
<ul>
<li>검색창 자동완성, 창 크기 변경 후 레이아웃 계산 등 마지막 입력이 중요할 때</li>
</ul>
<p>➡️ Throttle: 정해둔 간격마다 최대 한 번만 실행</p>
<ul>
<li>스크롤 위치 계산, 윈도우 스크롤/휠, 드래그 등 지속 중간값이 중요할 때</li>
</ul>
<p>➡️ 라이브러리 (lodash) 써도 되고, 직접 구현도 간단하다!</p>
<p>➡️ React에서는 커스텀 훅을 만들어 쓰면 재활용이 쉽다
<br /></p>
<blockquote>
<p>왜 필요할까?</p>
</blockquote>
<p>브라우저는 스크롤 중에 scroll 이벤트를 초에 수십에서 수백 번 호출한다..!
매번 무거운 계산 (API 요청, DOM 측정, 차트 업데이트)을 하면 성능 저하가 발생한다...</p>
<p>그래서 이벤트를 
&quot;묶어서&quot; 보내거나 (Throttle),
&quot;마지막 것만&quot; 처리 (Debounce) 해서 부담을 줄인다.</p>
<p>비유로 이해해보자
➡️ Debounce
: 웨이터가 주문을 적다가 손님이 말을 멈추면 그제서야 주문을 확정하는 것</p>
<p>➡️ Throttle
: 수도꼭지에 유량 제한기를 달아, 물이 아무리 많이 와도 일정 간격마다 한 번씩만 내보내는 것
<br /></p>
<blockquote>
<p>표로 한 눈에 비교해보자</p>
</blockquote>
<table>
<thead>
<tr>
<th>구분</th>
<th>Debounce</th>
<th>Throttle</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td><strong>입력이 멈춘 뒤</strong> N ms 후 실행</td>
<td><strong>N ms 간격마다</strong> 최대 한 번 실행</td>
</tr>
<tr>
<td>쓰임새</td>
<td>검색창, 자동저장, 리사이즈 후 정리</td>
<td>스크롤 위치 계산, 드래그 위치 추적</td>
</tr>
<tr>
<td>핵심 옵션</td>
<td>leading / trailing 실행 제어</td>
<td>leading / trailing 실행 제어</td>
</tr>
<tr>
<td>장점</td>
<td>불필요한 호출 거의 제거</td>
<td>실시간성(연속 동작 중 중간 반영) 확보</td>
</tr>
<tr>
<td>주의</td>
<td>너무 길면 반응이 둔해짐</td>
<td>간격이 길면 부자연스러울 수 있음</td>
</tr>
<tr>
<td><br /></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<blockquote>
<p>타임라인으로 이해해보자</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/78671148-1647-4e68-8de3-7072ffa6da42/image.png" alt="">
<br /></p>
<blockquote>
<p>JS로 구현해보자 (직접 만들어보기)</p>
</blockquote>
<p>➡️ Debounce 함수</p>
<pre><code>function debounce(fn, delay, { leading = false, trailing = true } = {}) {
  let timer = null;
  let lastArgs, lastThis, called = false;

  return function debounced(...args) {
    lastArgs = args;
    lastThis = this;

    const callNow = leading &amp;&amp; !timer;
    clearTimeout(timer);

    timer = setTimeout(() =&gt; {
      timer = null;
      if (trailing &amp;&amp; (!leading || called)) {
        fn.apply(lastThis, lastArgs);
      }
      called = false;
    }, delay);

    if (callNow) {
      fn.apply(lastThis, lastArgs);
      called = true;
    }
  };
}</code></pre><br />

<p>➡️ Throttle 함수</p>
<pre><code>function throttle(fn, interval, { leading = true, trailing = true } = {}) {
  let lastTime = 0;
  let timer = null;
  let lastArgs, lastThis;

  const invoke = () =&gt; {
    lastTime = Date.now();
    fn.apply(lastThis, lastArgs);
  };

  return function throttled(...args) {
    const now = Date.now();
    lastArgs = args;
    lastThis = this;

    if (!lastTime &amp;&amp; leading === false) lastTime = now;

    const remaining = interval - (now - lastTime);

    if (remaining &lt;= 0 || remaining &gt; interval) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      invoke();
    } else if (!timer &amp;&amp; trailing !== false) {
      timer = setTimeout(() =&gt; {
        timer = null;
        if (trailing !== false) invoke();
      }, remaining);
    }
  };
}</code></pre><br />

<blockquote>
<p>실전 예제!</p>
</blockquote>
<p>➡️ 검색창 자동완성 → Debounce 300ms</p>
<pre><code>&lt;input id=&quot;q&quot; placeholder=&quot;검색어를 입력하세요&quot; /&gt;
&lt;script&gt;
const input = document.getElementById(&#39;q&#39;);

const fetchSuggest = (q) =&gt; {
  // 실제로는 fetch(`/api/suggest?q=${encodeURIComponent(q)}`)
  console.log(&#39;API 요청:&#39;, q);
};

const onInput = debounce((e) =&gt; {
  const q = e.target.value.trim();
  if (q) fetchSuggest(q);
}, 300);

input.addEventListener(&#39;input&#39;, onInput);
&lt;/script&gt;</code></pre><br />

<p>➡️ 스크롤 위치 기반 헤더 축소 → Throttle 100ms</p>
<pre><code>const onScroll = throttle(() =&gt; {
  const y = window.scrollY;
  document.body.classList.toggle(&#39;shrink-header&#39;, y &gt; 80);
}, 100);

window.addEventListener(&#39;scroll&#39;, onScroll);</code></pre><br />

<blockquote>
<p>React에서 사용하기 (커스텀 훅)</p>
</blockquote>
<p>➡️ 값 디바운싱 훅 (useDebouncedValue)</p>
<pre><code>import { useEffect, useState } from &#39;react&#39;;

export function useDebouncedValue&lt;T&gt;(value: T, delay = 300) {
  const [v, setV] = useState(value);
  useEffect(() =&gt; {
    const id = setTimeout(() =&gt; setV(value), delay);
    return () =&gt; clearTimeout(id);
  }, [value, delay]);
  return v;
}</code></pre><ul>
<li><p>사용:</p>
<pre><code>function SearchBox() {
const [q, setQ] = useState(&#39;&#39;);
const dq = useDebouncedValue(q, 300);

useEffect(() =&gt; {
  if (!dq) return;
  // dq로 API 호출
}, [dq]);

return &lt;input value={q} onChange={e =&gt; setQ(e.target.value)} /&gt;;
}</code></pre><br />

</li>
</ul>
<p>➡️ 콜백 스로틀 훅 (useThrottleFn)</p>
<pre><code>import { useRef, useEffect, useCallback } from &#39;react&#39;;

export function useThrottleFn&lt;F extends (...a: any[]) =&gt; void&gt;(fn: F, interval = 200) {
  const lastTimeRef = useRef(0);
  const timerRef = useRef&lt;number | null&gt;(null);
  const fnRef = useRef(fn);
  useEffect(() =&gt; { fnRef.current = fn; }, [fn]);

  const throttled = useCallback((...args: Parameters&lt;F&gt;) =&gt; {
    const now = Date.now();
    const remaining = interval - (now - lastTimeRef.current);

    if (remaining &lt;= 0) {
      if (timerRef.current) { window.clearTimeout(timerRef.current); timerRef.current = null; }
      lastTimeRef.current = now;
      fnRef.current(...args);
    } else if (!timerRef.current) {
      timerRef.current = window.setTimeout(() =&gt; {
        timerRef.current = null;
        lastTimeRef.current = Date.now();
        fnRef.current(...args);
      }, remaining);
    }
  }, [interval]);

  useEffect(() =&gt; () =&gt; { if (timerRef.current) window.clearTimeout(timerRef.current); }, []);

  return throttled as F;
}</code></pre><ul>
<li><p>사용:</p>
<pre><code>function ScrollAwareHeader() {
const onScroll = useThrottleFn(() =&gt; {
  // 스크롤 처리
}, 100);

useEffect(() =&gt; {
  window.addEventListener(&#39;scroll&#39;, onScroll);
  return () =&gt; window.removeEventListener(&#39;scroll&#39;, onScroll);
}, [onScroll]);

return &lt;header&gt;...&lt;/header&gt;;
}</code></pre><br />

</li>
</ul>
<blockquote>
<p>Lodash로 더 쉽게 사용해보자!</p>
</blockquote>
<pre><code>npm i lodash</code></pre><pre><code>import { debounce, throttle } from &#39;lodash&#39;;

const onChange = debounce((value: string) =&gt; { /* API 요청 */ }, 300, {
  leading: false,
  trailing: true,
});

const onScroll = throttle(() =&gt; { /* 스크롤 로직 */ }, 100, {
  leading: true,
  trailing: true,
});</code></pre><ul>
<li>장점: battle-tested, 옵션이 풍부하다!</li>
<li>단점: 번들 크기 
(필요 시 lodash-es + 트리쉐이킹, 혹은 import debounce from &#39;lodash/debounce&#39;처럼 부분 import)<br />

</li>
</ul>
<blockquote>
<p>언제 무엇을 골라야 할까?</p>
</blockquote>
<ul>
<li><p>검색창/자동저장/리사이즈 후 레이아웃: 마지막 상태만 중요 → Debounce</p>
</li>
<li><p>스크롤 중 애니메이션/프로그레스/무한스크롤: 중간 상태도 중요 → Throttle</p>
</li>
<li><p>API 과금/쿼터가 빡빡한 자동완성: Debounce로 불필요 호출 최소화</p>
</li>
<li><p>부드러운 따라가기 UI(패럴럭스, 드래그 프리뷰): Throttle로 지속 반영</p>
<br />

</li>
</ul>
<blockquote>
<p>자주 하는 실수와 팁</p>
</blockquote>
<ol>
<li><p>이벤트 리스너에 매번 새 함수 바인딩</p>
<ul>
<li>React에서 useCallback/커스텀 훅으로 참조 안정화</li>
</ul>
</li>
<li><p>너무 긴 지연 시간</p>
<ul>
<li>Debounce를 800ms 이상 주면 느리게 느껴짐. 250~400ms부터 테스트</li>
</ul>
</li>
<li><p>서버측도 보호</p>
<ul>
<li>클라이언트 디바운스/스로틀만 믿지 말고 서버 레이트 리미트도 함께</li>
</ul>
</li>
<li><p>leading/trailing 의미 혼동</p>
<ul>
<li><p>leading: true = 첫 이벤트 즉시 실행</p>
</li>
<li><p>trailing: true = 마지막 이벤트 이후 한 번 더 실행</p>
</li>
</ul>
</li>
<li><p>타이머 정리 누락</p>
<ul>
<li>컴포넌트 언마운트 시 clearTimeout/clearInterval 필수<br /> 

</li>
</ul>
</li>
</ol>
<blockquote>
<p>성능 체감 테스트</p>
</blockquote>
<ul>
<li><p>크롬 DevTools → Performance → 스크롤/입력 전/후로 녹화 →
스루풋(Frames), 메인 스레드 바쁜 시간, 이벤트 핸들러 호출 횟수를 비교</p>
</li>
<li><p>Network 탭에서 API 호출 횟수가 디바운스 도입 후 줄었는지 확인</p>
<br />

</li>
</ul>
<blockquote>
<p>TypeScript 시그니처 예시</p>
</blockquote>
<pre><code>export function debounce&lt;F extends (...a: any[]) =&gt; any&gt;(
  fn: F,
  delay: number,
  options?: { leading?: boolean; trailing?: boolean }
): (...args: Parameters&lt;F&gt;) =&gt; void;

export function throttle&lt;F extends (...a: any[]) =&gt; any&gt;(
  fn: F,
  interval: number,
  options?: { leading?: boolean; trailing?: boolean }
): (...args: Parameters&lt;F&gt;) =&gt; void;</code></pre><br />

<blockquote>
<p>미니 FAQ!</p>
</blockquote>
<p>Q. Throttle과 requestAnimationFrame은 뭐가 달라요?
A. requestAnimationFrame은 다음 페인트 직전에 한 번 실행합니다(대개 60fps). Throttle은 임의의 간격을 가집니다. 스크롤 중 위치 계산처럼 프레임 동기화가 중요한 경우 rAF + Throttle을 함께 쓰기도 합니다.</p>
<p>Q. Debounce를 입력창에 쓰면 타이핑이 느려지나요?
A. onChange 등 이벤트 핸들러 내부의 무거운 작업만 디바운스하세요. 입력 자체는 즉시 반영하고, API 호출만 디바운스하는 식으로 분리하면 UX가 좋아집니다.</p>
<p>Q. leading만 켜고 trailing 끄면?
A. 첫 번만 실행되고, 그 뒤로는 간격 내 호출이 무시됩니다(Throttle 기준). UX에 따라 다르게 조합하세요.
<br /></p>
<blockquote>
<p>마지막 정리</p>
</blockquote>
<p>➡️ Debounce = 멈추면 실행, Throttle = 간격마다 실행</p>
<p>➡️ 상황에 맞게 고르면 성능과 UX가 동시에 좋아진다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SEO와 시맨틱 태그, 개념으로 이해하는 연결고리]]></title>
            <link>https://velog.io/@sehee-xx/SEO%EC%99%80-%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8-%EA%B0%9C%EB%85%90%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EC%97%B0%EA%B2%B0%EA%B3%A0%EB%A6%AC</link>
            <guid>https://velog.io/@sehee-xx/SEO%EC%99%80-%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8-%EA%B0%9C%EB%85%90%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EC%97%B0%EA%B2%B0%EA%B3%A0%EB%A6%AC</guid>
            <pubDate>Tue, 16 Sep 2025 06:56:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>먼저 두 용어의 관계부터 알아보자</p>
</blockquote>
<p>SEO는 검색엔진이 내 페이지를 더 잘 찾고, 더 정확하게 이해하고, 더 적절하게 보여주도록 돕는 일이다.</p>
<p>시맨틱 태그는 사람이 읽는 문서에 &quot;기계가 이해할 수 있는 구조와 의미&quot;를 부여하는 방법이다.</p>
<p>프론트엔드에서 이 둘은 분리된 주제가 아니라,
같은 문제를 서로 다른 각도에서 해결하는 도구다.
<br /></p>
<blockquote>
<p>SEO는 정확히 무엇인가</p>
</blockquote>
<p>SEO는 검색엔진의 파이프라인인 크롤링, 렌더링, 인덱싱, 랭킹 전 과정을 고려해 페이지를 준비하는 전략이다.</p>
<p>검색엔진은 링크를 따라 페이지를 수집하고, 필요하면 자바스크립트를 실행해 DOM을 만든 뒤, 문서를 색인에 구조화하고, 사용자의 질의와 의도에 맞춰 결과를 정렬한다.</p>
<p>프론트엔드과 관여하는 지점은 <strong>&quot;초기 HTML의 품질&quot;, &quot;의미 있는 마크업&quot;, &quot;메타데이터와 구조화 정보&quot;, &quot;렌더링 타이밍과 성능&quot;</strong>이다.
<br /></p>
<blockquote>
<p>왜 SEO가 중요할까?</p>
</blockquote>
<p>검색 유입은 광고비 없이도 누적되는 장기 채널이기 때문에 사업적 레버리지와 직접 연결된다.</p>
<p>검색엔진이 이해하기 쉬운 구조는 보조기술과 사람에게도 편하기 때문에 반송률과 체류시간 같은 사용자 행동 지표가 개선된다.</p>
<p>코드를 구조적으로 작성하면 유지보수 비용이 줄고, 페이지 경험이 좋아지면 공유와 재방문이 자연스럽게 늘어나게 된다.
<br /></p>
<blockquote>
<p>시맨틱 태그는 정확히 무엇인가</p>
</blockquote>
<p>시맨틱 태그는 요소의 &quot;모양&quot;이 아니라 <strong>&quot;역할&quot;</strong>을 표현하는 HTML이다.</p>
<pre><code>&lt;main&gt;은 문서의 핵심 콘텐츠
&lt;nav&gt;는 주요 탐색
&lt;article&gt;은 독립적으로 의미가 완결된 콘텐츠 단위
&lt;section&gt;은 주제별 구획
&lt;aside&gt;는 보조 정보
&lt;figure&gt;와 &lt;figcaption&gt;은 미디어와 설명의 결합을 나타낸다.</code></pre><p>시맨틱 마크업의 원칙은 
<strong>&quot;의미는 HTML로, 표현은 CSS로, 상호작용은 JS로 나눈다&quot;</strong>이다.</p>
<blockquote>
<p>왜 시맨틱 태그가 중요할까?</p>
</blockquote>
<p>검색엔진 입장에서는 시맨틱 구조가 문서의 정보 구조를 
명시적으로 드러내기 때문에 인덱싱과 스니펫 생성이 수월해진다.</p>
<p>접근성 관점에서는 스크린 리더가 랜드마크와 헤딩 계층을 청확히 인지해 
빠른 탐색이 가능해진다.</p>
<p>엔지니어링 관점에서는 문서의 의도가 코드에 녹아 유지보수가 쉬워지고, 
테스트와 리팩토링이 일관되게 진행된다.
<br /></p>
<blockquote>
<p>두 개념이 만나는 지점은 어디일까?</p>
</blockquote>
<p>시맨틱 마크업은 &quot;페이지가 무엇에 관한 문서인지&quot;를 
검색엔진과 보조기술 모두에게 선언한다.</p>
<p>메타데이터와 구조화 데이터는 &quot;문서의 외피와 타입&quot;을 검색엔진에게 전달한다.</p>
<p>성능 최적화와 안정적인 렌더링은 &quot;사용자 경험 신호&quot;를 높여간다.
SSR이나 프리렌더링 같은 렌더링 전략은 
&quot;중요한 내용이 초기 HTML에 존재하는가&quot;라는 전제를 보장한다!
<br /></p>
<blockquote>
<p>오해를 풀어야 제대로 설계할 수 있다</p>
</blockquote>
<p>그럼 시맨틱 태그만 잘 사용하면 상위 노출이 보장될까? 그건 아니다..
콘텐츠의 품질과 수요, 내부 링크 구조, 외부 신뢰도 같은 축이 함께 맞아야 한다.</p>
<p>헤딩은 시각 크기를 조절하려고 쓰는 도구가 아니라 정보 구조를 표현하는 도구다.</p>
<p>메타 키워드는 현대 검색엔진에서 핵심 신호가 아니고, 
제목과 설명은 중복 없이 문서 수준에서 고유해야 한다.</p>
<p>CSR만으로도 수집되는 경우가 있지만 중요한 페이지라면?
초기 HTML에 핵심 콘텐츠가 존재하도록 만드는 전략이 안전하다!
<br /></p>
<blockquote>
<p>프론트엔드가 기억해야 할 판단 기준</p>
</blockquote>
<p>➡️ 문서의 주제가 한눈에 드러나는가
➡️ 핵심 내용이 초기 HTML에 포함되는가
➡️ 헤딩 계층과 랜드마크가 의미를 정확히 표현하는가
➡️ 제목과 설명, 정규 URL, 오픈그래프 같은 메타가 문서의 대표성을 보장하는가
➡️ 이미지와 폰트가 레이아웃을 흔들지 않고 빠르게 나타나는가
➡️ 필요한 경우 구조화 데이터로 문서 타입을 선언했는가</p>
<blockquote>
<p>왜 이것들이 결국 SEO를 만든다고 말할 수 있을까?</p>
</blockquote>
<p>검색엔진은 &quot;무엇을 보여줘야 사용자가 만족하는가&quot;를 끊임없이 추적한다.</p>
<p>시맨틱 마크업은 &quot;무엇&quot;을, 메타와 구조화 데이터는 &quot;어떤 문서인지&quot;를, 성능과 렌더링 전략은 &quot;어떻게 보여줄지&quot;를 담당한다.</p>
<p>이 세 축이 맞물리면 봇의 이해와 사람의 경험이 같은 방향을 바라보게 되고,
이는 결국 지속 가능한 노출과 전환으로 연결되는 것이다!
<br /></p>
<blockquote>
<p>개념 정리</p>
</blockquote>
<p>➡️ SEO는 검색엔진이 문서를 더 잘 찾고 이해하도록 만드는 설계 철학이다.
➡️ 시맨틱 태그는 문서의 의미를 코드에 직접 새기는 언어 규칙이다.
➡️ 좋은 SEO는 시맨틱, 메타, 구조화, 성능, 렌더링 전략이 동시에 작동할 때 완성된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 상태관리]]></title>
            <link>https://velog.io/@sehee-xx/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@sehee-xx/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Mon, 15 Sep 2025 13:10:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 데이터, 도대체 어디서 관리해야 하지?</p>
</blockquote>
<p>React를 쓰다 보면 누구나 한 번쯤 이런 고민을 하는 것 같다.
버튼 클릭 횟수, 입력 값 같은 단순한 데이터는 <strong>useState</strong>를 사용해 간단히 처리할 수 있다.</p>
<p>하지만 서비스가 점점 커지고, 컴포넌트가 많아질수록 점점 까다로워 지는 것이 상태관리다.</p>
<p>예를 들어, 로그인 정보, 장바구니, 테마처럼 여러 곳에서 공유해야 하는 상태가 늘어나고,
서버에서 가져온 데이터도 관리해야 하는 상황이 생기면서 점점 머리가 아파진다.</p>
<p>그래서 이번 글에서는 React에서의 상태관리를
개념 → 필요성 → 종류 → 도구별 사용법과 장단점 순서로 정리해보려고 한다.
<br /></p>
<blockquote>
<p>상태(State)란 무엇일까?</p>
</blockquote>
<p>➡️ <strong>상태(State)란 화면(UI)에 영향을 주는 데이터</strong>를 말한다.</p>
<p>예를 들어보자!</p>
<ul>
<li>입력창에 입력된 글자</li>
<li>로그인 여부 (true/false)</li>
<li>장바구니에 담긴 상품 목록</li>
<li>좋아요 버튼을 누른 횟수</li>
</ul>
<p>이 값들이 바뀌면 UI도 자동으로 업데이트 된다.
➡️ <strong>React의 핵심은 바로 상태가 변하면 UI가 다시 렌더링 된다</strong>는 점이다!
<br /></p>
<blockquote>
<p>왜 상태관리가 중요할까?</p>
</blockquote>
<p>작은 서비스에서는 단순히 useState만 써도 충분히 원하는 바를 구현할 수 있다.
하지만 규모가 커지면 다양한 문제가 발생한다.</p>
<p>➡️ Props Drilling
: 부모 → 자식 → 손자 → 증손자 컴포넌트로 props를 줄줄이 전달해야 하는 상황</p>
<p>➡️ 데이터 불일치
: 어떤 컴포넌트는 최신 데이터를, 다른 컴포넌트는 예전의 데이터를 들고 있는 상황</p>
<p>➡️ 유지보수 어려움
: 상태가 어디서 바뀌는지 추적하기가 힘들다.</p>
<p>그래서 상태관리는 단순히 값을 저장하는 것이 아니라,
<strong>서비스를 예측 가능하고 유지보수하기 쉽게 만드는 하나의 전략</strong>이라고 볼 수 있다!
<br /></p>
<blockquote>
<p>상태관리의 세 가지 범주</p>
</blockquote>
<p>React에서 상태는 세 가지 범주로 구분하는데,
<strong>로컬 상태(Local), 전역 상태(Global), 서버 상태(Server)</strong>로 나눌 수 있다.</p>
<h3 id="로컬-상태-local-state">로컬 상태 (Local State)</h3>
<p>컴포넌트 안에서만 쓰는 상태를 말한다.
로컬 상태 관리 시에는 <strong>useState</strong>와 <strong>useReducer</strong>를 사용해 간단하게 처리할 수 있다.</p>
<p>➡️ <strong>useState</strong></p>
<pre><code>import React, { useState } from &quot;react&quot;;

function Counter() {
  const [count, setCount] = useState(0);
  return (
    &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
      클릭 횟수: {count}
    &lt;/button&gt;
  );
}</code></pre><ul>
<li>장점: 가장 직관적이고 빠르게 사용 가능하다.</li>
<li>단점: 컴포넌트 범위를 벗어나면 공유가 불가능하다.<br/>


</li>
</ul>
<p>➡️ <strong>useReducer</strong></p>
<pre><code>import React, { useReducer } from &quot;react&quot;;

function reducer(state, action) {
  switch (action.type) {
    case &quot;increment&quot;: return { count: state.count + 1 };
    case &quot;decrement&quot;: return { count: state.count - 1 };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; dispatch({ type: &quot;decrement&quot; })}&gt;-&lt;/button&gt;
      {state.count}
      &lt;button onClick={() =&gt; dispatch({ type: &quot;increment&quot; })}&gt;+&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre><ul>
<li>장점: 복잡한 상태 로직을 깔끔하게 관리할 수 있다.</li>
<li>단점: 단순 상태에는 오히려 과잉 설계로 여겨질 수 있다.<br />


</li>
</ul>
<h3 id="전역-상태-global-state">전역 상태 (Global State)</h3>
<p>여러 컴포넌트가 공유해야 하는 상태를 말한다.
전역 상태 관리 시에는 <strong>Context API</strong>, <strong>Redux</strong>, <strong>Zustand</strong>, <strong>Jotai</strong>, <strong>Recoil</strong>, <strong>MobX</strong>  같은 툴을 사용할 수 있다.</p>
<p>➡️ <strong>Context API</strong></p>
<pre><code>import React, { createContext, useContext } from &quot;react&quot;;

const ThemeContext = createContext(&quot;light&quot;);

function App() {
  return (
    &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
      &lt;Toolbar /&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return &lt;p&gt;현재 테마: {theme}&lt;/p&gt;;
}</code></pre><ul>
<li>장점: props drilling 문제를 해결할 수 있고, 추가 라이브러리가 필요 없다.</li>
<li>단점: 상태가 많아지면 성능 최적화가 어렵다.<br />

</li>
</ul>
<p>➡️ <strong>Redux</strong></p>
<pre><code>import { configureStore, createSlice } from &quot;@reduxjs/toolkit&quot;;
import { Provider, useDispatch, useSelector } from &quot;react-redux&quot;;

const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState: { value: 0 },
  reducers: {
    increment: state =&gt; { state.value += 1; },
    decrement: state =&gt; { state.value -= 1; }
  }
});

const store = configureStore({ reducer: { counter: counterSlice.reducer } });
export const { increment, decrement } = counterSlice.actions;

function Counter() {
  const dispatch = useDispatch();
  const value = useSelector(state =&gt; state.counter.value);
  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; dispatch(decrement())}&gt;-&lt;/button&gt;
      {value}
      &lt;button onClick={() =&gt; dispatch(increment())}&gt;+&lt;/button&gt;
    &lt;/&gt;
  );
}

export default function App() {
  return (
    &lt;Provider store={store}&gt;
      &lt;Counter /&gt;
    &lt;/Provider&gt;
  );
}</code></pre><ul>
<li>장점: 대규모 서비스에서 예측 가능한 상태 관리, DevTools 지원, 생태계가 풍부하다.</li>
<li>단점: 설정이 복잡하고, 코드가 길어진다.<br />

</li>
</ul>
<p>➡️ <strong>Zustand</strong></p>
<pre><code>import create from &quot;zustand&quot;;

const useStore = create(set =&gt; ({
  count: 0,
  increase: () =&gt; set(state =&gt; ({ count: state.count + 1 }))
}));

function Counter() {
  const { count, increase } = useStore();
  return &lt;button onClick={increase}&gt;클릭: {count}&lt;/button&gt;;
}</code></pre><ul>
<li>장점: 코드가 간단하고, 러닝커브가 낮고, 리렌더링을 최소화 할 수 있다.</li>
<li>단점: 대규모 서비스에서는 구조화 고민이 필요하고, 레퍼런스가 적다.<br />

</li>
</ul>
<p>➡️ <strong>Jotai</strong></p>
<pre><code>import { atom, useAtom } from &quot;jotai&quot;;

const countAtom = atom(0);

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return (
    &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
      클릭: {count}
    &lt;/button&gt;
  );
}</code></pre><ul>
<li>장점: 원자(atom) 단위로 잘게 쪼개서 관리가 가능하고 React 친화적이다.</li>
<li>생태계가 작고, 대규모 서비스에서는 설계 고민이 필요하다.<br />

</li>
</ul>
<p>➡️ <strong>Recoil</strong></p>
<pre><code>import { RecoilRoot, atom, useRecoilState } from &quot;recoil&quot;;

const countState = atom({
  key: &quot;countState&quot;,
  default: 0
});

function Counter() {
  const [count, setCount] = useRecoilState(countState);
  return (
    &lt;button onClick={() =&gt; setCount(count + 1)}&gt;
      클릭: {count}
    &lt;/button&gt;
  );
}

export default function App() {
  return (
    &lt;RecoilRoot&gt;
      &lt;Counter /&gt;
    &lt;/RecoilRoot&gt;
  );
}</code></pre><ul>
<li>장점: Redux보다 훨씬 간단하고 atom 단위 최적화가 가능하다.</li>
<li>단점: 레퍼런스와 사례가 부족하고, 대규모 서비스에서 안정성 검증이 부족하다.</li>
</ul>
<p>참고로 Recoil은 2025년 1월 12일에 공식적으로 개발이 종료 선언되었으며, 
더 이상 지원되지 않는다..
<br /></p>
<p>➡️ <strong>MobX</strong></p>
<pre><code>import { makeAutoObservable } from &quot;mobx&quot;;
import { observer } from &quot;mobx-react-lite&quot;;

class CounterStore {
  count = 0;
  constructor() {
    makeAutoObservable(this);
  }
  increment() { this.count++; }
}

const counterStore = new CounterStore();

const Counter = observer(() =&gt; (
  &lt;button onClick={() =&gt; counterStore.increment()}&gt;
    클릭: {counterStore.count}
  &lt;/button&gt;
));</code></pre><ul>
<li>장점: 상태를 직접 변경하면 UI 자동 반영 (직관적), 코드량이 적고 러닝커브가 낮다.</li>
<li>단점: 상태 흐름이 불투명할 수 있고, Redux에 비해 생태계/디버깅 도구가 부족하다.<br />

</li>
</ul>
<h3 id="서버-상태-server-state">서버 상태 (Server State)</h3>
<p>서버에서 가져온 데이터(API 호출 결과 등)를 관리하는 상태를 말한다.
단순 fetch를 넘어서 캐싱, 동기화, 로딩·에러 처리까지 담당한다.</p>
<p>서버 상태 관리 시에는 <strong>React Query</strong>, <strong>SWR</strong> 과 같은 툴을 사용할 수 있다.</p>
<p>➡️ <strong>React Query</strong></p>
<pre><code>import { useQuery } from &quot;@tanstack/react-query&quot;;

async function fetchUsers() {
  const res = await fetch(&quot;/api/users&quot;);
  return res.json();
}

function Users() {
  const { data, isLoading, error } = useQuery([&quot;users&quot;], fetchUsers);

  if (isLoading) return &lt;p&gt;로딩 중...&lt;/p&gt;;
  if (error) return &lt;p&gt;에러 발생!&lt;/p&gt;;

  return (
    &lt;ul&gt;
      {data.map(user =&gt; &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;)}
    &lt;/ul&gt;
  );
}</code></pre><ul>
<li>장점: API 캐싱, 자동 refetch, 로딩·에러 처리가 내장되어 있다.</li>
<li>단점: 클라이언트 전역 상태와는 별도로 관리해야 한다.<br />

</li>
</ul>
<p>➡️ <strong>SWR</strong></p>
<pre><code>import useSWR from &quot;swr&quot;;

const fetcher = url =&gt; fetch(url).then(res =&gt; res.json());

function Users() {
  const { data, error } = useSWR(&quot;/api/users&quot;, fetcher);

  if (!data) return &lt;p&gt;로딩 중...&lt;/p&gt;;
  if (error) return &lt;p&gt;에러 발생!&lt;/p&gt;;

  return (
    &lt;ul&gt;
      {data.map(user =&gt; &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;)}
    &lt;/ul&gt;
  );
}</code></pre><ul>
<li>장점: 데이터 가져오기 단순화, React Hooks와 자연스럽게 어울린다.</li>
<li>단점: React Query에 비해 기능이 단순하다.<br />

</li>
</ul>
<table>
<thead>
<tr>
<th>분류</th>
<th>도구</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>로컬</td>
<td>useState</td>
<td>간단, 직관적</td>
<td>범위 제한</td>
</tr>
<tr>
<td>로컬</td>
<td>useReducer</td>
<td>복잡한 로직 관리</td>
<td>단순 상태엔 과잉</td>
</tr>
<tr>
<td>전역</td>
<td>Context API</td>
<td>prop drilling 해결</td>
<td>성능 최적화 한계</td>
</tr>
<tr>
<td>전역</td>
<td>Redux</td>
<td>대규모 앱 적합, DevTools</td>
<td>설정 복잡</td>
</tr>
<tr>
<td>전역</td>
<td>Zustand</td>
<td>가볍고 간단</td>
<td>레퍼런스 부족</td>
</tr>
<tr>
<td>전역</td>
<td>Jotai</td>
<td>atom 단위 관리</td>
<td>생태계 작음</td>
</tr>
<tr>
<td>전역</td>
<td>Recoil</td>
<td>단순, 성능 최적화</td>
<td>사례 부족</td>
</tr>
<tr>
<td>전역</td>
<td>MobX</td>
<td>직관적, 코드 간단</td>
<td>상태 흐름 불투명, 생태계 적음</td>
</tr>
<tr>
<td>서버</td>
<td>React Query</td>
<td>강력한 서버 상태 관리</td>
<td>전역 상태와 별도</td>
</tr>
<tr>
<td>서버</td>
<td>SWR</td>
<td>간단, 직관적</td>
<td>기능 단순</td>
</tr>
</tbody></table>
<br />

<blockquote>
<p>그럼 어떤 상태관리 도구를 써야 하는가?</p>
</blockquote>
<p>이건 사람마다 다르겠지만
React 상태관리의 핵심은 <strong>필요에 따라 가장 단순한 도구부터 시작</strong>하는 것이다!</p>
<p>➡️ 작은 서비스 → useState, useReducer
➡️ 전역 공유 조금 필요 → Context API
➡️ 중간 규모의 프로젝트 → Zustand, Jotai, Recoil, MobX
➡️ 대규모 팀 프로젝트 → Redux
➡️ 서버 데이터가 많은 경우 → React Query, SWR</p>
<p><strong>상태관리는 단순히 데이터를 보관하는 게 아니라,
서비스를 예측 가능하고 유지보수하기 쉽게 만드는 핵심 전략이라는 것을 명심하자!</strong></p>
<blockquote>
<p>💡 2025년 React 상태 관리 라이브러리 순위는 명확하게 발표되지 않았으나, <strong>Zustand, Jotai, Recoil</strong>과 같은 경량 라이브러리들이 주목받고 있으며, <strong>React Query</strong>와 같은 데이터 페칭 라이브러리도 중요한 역할을 한다. 이와 함께 <strong>Context API</strong>는 여전히 사용되고 있으며, Redux는 가장 많이 사용되는 라이브러리 중 하나다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Hook Form + Yup]]></title>
            <link>https://velog.io/@sehee-xx/React-Hook-Form-Yup</link>
            <guid>https://velog.io/@sehee-xx/React-Hook-Form-Yup</guid>
            <pubDate>Fri, 12 Sep 2025 12:01:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>React 입력 폼 관리, 왜 이렇게 복잡한걸까</p>
</blockquote>
<p>프로젝트를 하다 보면 생각보다 입력 폼을 만들어야 하는 경우가 많다.
입력 받아야 하는 항목이 적은 경우에는 간단하게 만들 수 있지만
입력 항목이 많은 경우에는 코드가 꽤나 복잡해진다.</p>
<p>실제로 한 스타트업에서 임시보호 지원 신청서를 만든 경험이 있는데
그때는 진짜 입력 받아야 하는 항목이 정말 많아서 힘들었다.
그래서 오늘은 입력 폼을 효율적으로 구현할 수 있는 방법을 소개하고자 한다!
<br /></p>
<blockquote>
<p>입력을 받을 때 고충</p>
</blockquote>
<p>일단 입력을 받을 때 고려해야 할 사항들이 많다.
예를 들어,</p>
<ol>
<li>입력 값이 올바른지 바로 알려줘라 (유효성 검사)</li>
<li>전화번호, 카드번호의 경우 보기 좋게 마스킹 해서 줘라 (포맷팅)</li>
<li>특정 값을 채웠을 때 다른 값이 필수가 되도록 만들어라</li>
<li>항목을 추가·삭제할 수 있는 배열 필드를 넣어 달라</li>
<li>제출 버튼은 오류가 없을 때만 활성화 하도록 만들어라</li>
</ol>
<p>이 외에도 다양한 요구사항들이 존재할 수 있다.
이러한 경우 검증 코드가 불필요하게 중복될 수 있고 성능이 저하될 가능성이 있다.
<br /></p>
<blockquote>
<p>라이브러리 없이 이것을 구현하면 뭐가 불편한가</p>
</blockquote>
<p>우선 이러한 요구사항을 라이브러리 없이 구현을 하면 다양한 문제를 겪는다.</p>
<p>➡️ 코드가 빠르게 비대해진다.</p>
<ul>
<li>입력칸마다 useState와 그에 대한 Set 함수를 만들고, 제출·포커스 이탈 등 여러 지점에서 검증 코드가 반복될 가능성이 높다.</li>
</ul>
<p>➡️ 잦은 리렌더링</p>
<ul>
<li>모든 입력을 제어 방식 입력으로 다루면 글자를 하나 입력할 때마다 화면이 다시 그려져 반응 속도가 급격히 떨어진다.</li>
</ul>
<pre><code>💡제어 방식 입력: 입력 값이 항상 React 상태와 동기화 되는 방식!</code></pre><p>➡️ 화면용 포맷과 실제 값의 충돌이 발생할 수 있다.</p>
<ul>
<li>예를 들어 010-1234-5678을 화면에 보이게 하고, 내부 값은 숫자만 저장한다고 가정했을 때, 포맷팅·치환 코드가 이곳저곳에 흩어져 
버그가 자주 발생할 수 있다.</li>
</ul>
<p>➡️ 동적 필드의 취약성</p>
<ul>
<li>항목 추가·삭제(배열)에서 인덱스와 키 관리가 꼬이기 쉽다.</li>
</ul>
<p>➡️ 일관성 유지의 어려움</p>
<ul>
<li>오류 메시지와 규칙이 파일·컴포넌트마다 달라져
시간이 갈수록 정합성 관리가 힘들 수 있다.<br />

</li>
</ul>
<blockquote>
<p>이러한 불편함을 풀기 위한 기술 두 가지</p>
</blockquote>
<p>이러한 불편함을 효과적으로 해소할 수 있는 두 가지 기술이 있다.</p>
<p>➡️ React Hook Form</p>
<ul>
<li>입력을 기본적으로 비제어 방식으로 다뤄서 불필요한 리렌더링을 줄이고,
폼 상태·제출·에러를 체계적으로 관리하는 라이브러리<pre><code>💡비제어 방식 입력: 브라우저가 입력 값을 보관하고, 
                우리는 참조를 통해 필요한 데이터를 읽는 방식이다!</code></pre></li>
</ul>
<p>➡️ Yup</p>
<ul>
<li>검증 규칙을 스키마(설계도) 형태로 한 곳에 선언적으로 모아 두는 라이브러리<pre><code>💡스키마: 데이터의 형태와 규칙을 코드로 정의한 설계도</code></pre>➡️ 이 둘을 Resolver로 연결해서 사용한다!<pre><code>💡Resolver: React Hook Form이 어떤 검증 엔진을 쓸지 이어주는 어댑터
          (여기서는 Yup 사용)</code></pre><br />

</li>
</ul>
<blockquote>
<p>React Hook Form과 Yup의 원리와 개념</p>
</blockquote>
<p>➡️ React Hook Form의 원리
입력 요소에 register(&quot;필드 이름&quot;)을 호출하면 내부적으로
참조 등록과 이벤트 구독이 이루어진다.</p>
<p>값이 바뀌어도 폼 전체가 아니라 필요한 부분만 다시 그린다. (성능 이점!)</p>
<p>마스킹처럼 값을 직접 통제해야 하는 특수 경우에만
Controller로 그 필드만 제어 방식으로 전환해서 처리한다. </p>
<pre><code>💡리렌더링: 상태 변화로 컴포넌트가 다시 그려지는 것으로, 
           입력마다 리렌더링이 많으면 느려진다.</code></pre><pre><code>💡Controller: 외부 디자인 컴포넌트나 마스킹처럼 
              직접 value-onChange를 다뤄야 하는 필드에 쓰는 어댑터 컴포넌트</code></pre><br />

<p>➡️ Yup의 원리
&quot;문자열 → 필수 → 이메일 형식&quot; 같은 규칙을 사슬로 연결하듯 선언한다.</p>
<p>transform(전처리), when(조건부), test(맞춤) 이 세 가지로
복작한 규칙도 짧게 기술할 수 있다.</p>
<p>규칙과 메시지가 한 곳에 모여 중복·불일치가 줄어드는 효과가 있다!
<br /></p>
<p>➡️ 함께 쓸 때의 흐름</p>
<ol>
<li>사용자 입력 </li>
<li>(선택) setValueAs로 입력 단계 전처리 </li>
<li>Yup 스키마 검증</li>
<li>실패 혹은 통과</li>
</ol>
<ul>
<li>실패: formState.errors에 기록되어 화면에 즉시 표시</li>
<li>통과: 정상화된 값이 제출 처리로 이동<br />

</li>
</ul>
<blockquote>
<p>그럼 어떻게 사용하는가</p>
</blockquote>
<p>➡️ 설치
우선 &#39;라이브러리&#39;라서 설치를 해야 한다!</p>
<pre><code>npm i react-hook-form yup @hookform/resolvers
# 또는
yarn add react-hook-form yup @hookform/resolvers</code></pre><br />
➡️ 작은 예제(필수값 + 형식)

<ul>
<li>스키마 정의<pre><code>import * as yup from &quot;yup&quot;;
</code></pre></li>
</ul>
<p>const schema = yup.object({
  name: yup.string().required(&quot;이름을 입력하세요&quot;),
  email: yup.string().email(&quot;이메일 형식이 아닙니다&quot;).required(&quot;이메일을 입력하세요&quot;),
});</p>
<pre><code>* 폼과 연결</code></pre><p>import { useForm } from &quot;react-hook-form&quot;;
import { yupResolver } from &quot;@hookform/resolvers/yup&quot;;</p>
<p>type FormValues = { name: string; email: string };</p>
<p>export default function SimpleForm() {
  const { register, handleSubmit, formState: { errors, isValid } } = useForm<FormValues>({
    resolver: yupResolver(schema),   // Yup과 연결
    mode: &quot;onChange&quot;,                // 입력 중 검증 (필요에 따라 onBlur, onSubmit로 변경 가능)
    defaultValues: { name: &quot;&quot;, email: &quot;&quot; },
  });</p>
<p>  const onSubmit = (data: FormValues) =&gt; alert(JSON.stringify(data, null, 2));</p>
<p>  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <label>
        이름
        &lt;input {...register(&quot;name&quot;)} aria-invalid={!!errors.name} aria-describedby=&quot;name-err&quot; /&gt;
      </label>
      {errors.name &amp;&amp; <small id="name-err" role="alert">{errors.name.message}</small>}</p>
<pre><code>  &lt;label&gt;
    이메일
    &lt;input {...register(&quot;email&quot;)} aria-invalid={!!errors.email} aria-describedby=&quot;email-err&quot; /&gt;
  &lt;/label&gt;
  {errors.email &amp;&amp; &lt;small id=&quot;email-err&quot; role=&quot;alert&quot;&gt;{errors.email.message}&lt;/small&gt;}

  &lt;button type=&quot;submit&quot; disabled={!isValid}&gt;제출&lt;/button&gt;
&lt;/form&gt;</code></pre><p>  );
}</p>
<pre><code>&lt;br /&gt;
➡️ 확장

Ex. 숫자만, 최소·최대, 빈 값 안전 처리
* 입력 단계 전처리 (빈 문자열을 undefined로, 숫자만 남기기)
</code></pre><p>&lt;input
  inputMode=&quot;numeric&quot;
  placeholder=&quot;나이(숫자만)&quot;
  {...register(&quot;age&quot;, {
    setValueAs: (v) =&gt; {
      const digits = String(v ?? &quot;&quot;).replace(/\D/g, &quot;&quot;);
      return digits === &quot;&quot; ? undefined : Number(digits);
    },
  })}
/&gt;</p>
<pre><code>* 스키마</code></pre><p>age: yup
  .number()
  .typeError(&quot;숫자만 입력하세요&quot;)
  .integer(&quot;정수만 입력하세요&quot;)
  .min(14, &quot;만 14세 이상만 가능합니다&quot;)
  .max(120, &quot;값이 너무 큽니다&quot;)
  .required(&quot;나이를 입력하세요&quot;),</p>
<pre><code>
Ex. 문자열 다듬기(앞뒤 공백 제거, 길이 제한)</code></pre><p>username: yup
  .string()
  .transform((v) =&gt; (v ?? &quot;&quot;).trim())
  .min(2, &quot;최소 2자&quot;)
  .max(20, &quot;최대 20자&quot;)
  .required(&quot;아이디를 입력하세요&quot;),</p>
<pre><code>
Ex. 조건부 검증 (값 A가 있으면 값 B는 필수)</code></pre><p>phone: yup.string().optional(),
code: yup.string().when(&quot;phone&quot;, (phone, s) =&gt;
  phone ? s.required(&quot;인증 코드를 입력하세요&quot;) : s.notRequired()
),</p>
<pre><code>
Ex. 마스킹이 필요한 입력 (화면 포맷과 실제 값 분리)</code></pre><p>💡마스킹: 사용자에게는 1234-1234-...처럼 보기 좋게 보여 주고, 
          내부 값은 숫자만 저장하는 것</p>
<pre><code></code></pre><p>import { Controller, useForm } from &quot;react-hook-form&quot;;</p>
<p>const formatCard = (s: string) =&gt;
  (s || &quot;&quot;).replace(/\D/g, &quot;&quot;).slice(0, 16).replace(/(\d{4})(?=\d)/g, &quot;$1-&quot;);</p>
<p>export default function MaskedCardField() {
  const { control } = useForm&lt;{ cardNumber: string }&gt;({ defaultValues: { cardNumber: &quot;&quot; } });</p>
<p>  return (
    &lt;Controller
      name=&quot;cardNumber&quot;
      control={control}
      render={({ field }) =&gt; (
        &lt;input
          inputMode=&quot;numeric&quot;
          placeholder=&quot;XXXX-XXXX-XXXX-XXXX&quot;
          value={formatCard(field.value)}
          onChange={(e) =&gt; field.onChange(e.target.value.replace(/\D/g, &quot;&quot;))}
        /&gt;
      )}
    /&gt;
  );
}</p>
<pre><code></code></pre><p>cardNumber: yup
  .string()
  .transform((v) =&gt; (v ? v.replace(/\D/g, &quot;&quot;) : &quot;&quot;))
  .length(16, &quot;카드번호는 16자리입니다&quot;)
  .required(&quot;카드번호를 입력하세요&quot;),</p>
<pre><code>
Ex. 동적 필드 (추가·삭제 가능한 배열)</code></pre><p>import { useForm, useFieldArray } from &quot;react-hook-form&quot;;</p>
<p>type Link = { url: string };
type Values = { links: Link[] };</p>
<p>export default function DynamicLinks() {
  const { control, register, handleSubmit } = useForm<Values>({ defaultValues: { links: [{ url: &quot;&quot; }] } });
  const { fields, append, remove } = useFieldArray({ control, name: &quot;links&quot; });</p>
<p>  return (
    <form onSubmit={handleSubmit(console.log)}>
      {fields.map((f, i) =&gt; (
        <div key={f.id}>
          &lt;input {...register(<code>links.${i}.url</code>)} placeholder={<code>링크 #${i + 1}</code>} /&gt;
          &lt;button type=&quot;button&quot; onClick={() =&gt; remove(i)}&gt;삭제</button>
        </div>
      ))}
      &lt;button type=&quot;button&quot; onClick={() =&gt; append({ url: &quot;&quot; })}&gt;추가</button>
      <button type="submit">저장</button>
    </form>
  );
}</p>
<pre><code></code></pre><p>links: yup.array().of(
  yup.object({
    url: yup.string().url(&quot;주소 형식이 아닙니다&quot;).required(&quot;주소를 입력하세요&quot;),
  })
),</p>
<pre><code>
Ex. 검증 시점(모드) 선택 가이드</code></pre><p>// 제출 시에만 검사
useForm({ resolver: yupResolver(schema), mode: &quot;onSubmit&quot; }); 
// 포커스가 나갈 때 검사
useForm({ resolver: yupResolver(schema), mode: &quot;onBlur&quot; });<br>// 타이핑 중 즉시 검사
useForm({ resolver: yupResolver(schema), mode: &quot;onChange&quot; }); </p>
<pre><code>* 즉시 피드백이 중요한 가입·결제: onChange
* 입력칸이 많은 설정 화면: onBlur 또는 onSubmit
&lt;br /&gt;

&gt; 장단점 정리

➡️ 장점
* 큰 폼에서도 빠른 반응성 (불필요한 리렌더링 최소화)
* 규칙과 메시지 중앙화로 가독성·유지보수성 향상
* 입력 전처리 → 스키마 검증 → 오류 표시의 명확한 구조
* 동적 필드·조건부 검증·마스킹 같은 실전 요구에 강함

➡️ 단점
* 비제어 입력과 Controller 개념의 초기 학습 필요
* 외부 UI 컴포넌트 연동 시 접착 코드가 약간 필요
* 의존성(React Hook Form, Yup, 리졸버) 추가

➡️ 선택 기준
필드 수가 많거나, 검증 규칙이 존재하거나, 향후 확장이 예상된다면
이 조합이 개발 속도와 품질 모두 이득이라고 생각한다!

작은 화면 하나에 먼저 적용해서 효과를 확인해보고,
점차 범위를 넓히는 것을 추천한다.
&lt;br /&gt;

&gt;용어 정리

💡**제어 방식 입력(Controlled)**
입력 값이 항상 React 상태와 동기화되는 방식
단순하지만 리렌더링이 잦음

💡**비제어 방식 입력(Uncontrolled) **
브라우저가 값 변화를 관리하고 우리는 참조만 등록함
리렌더링이 적어 속도가 빠름

💡**스키마(schema)**
데이터 형태와 검증 규칙의 설계도

💡**리졸버(resolver)**
React Hook Form과 검증 엔진(Yup)을 이어 주는 어댑터

💡**마스킹(masking)**
화면에서 보기 좋게 포맷만 입히는 것
(내부 값은 가공되지 않은 상태로 유지)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JS 동기, 비동기]]></title>
            <link>https://velog.io/@sehee-xx/JS-%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@sehee-xx/JS-%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Mon, 01 Sep 2025 07:33:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>동기 vs. 비동기</p>
</blockquote>
<p>** 동기 (Synchronous)**
한 줄의 코드가 실행을 마쳐야 그 다음 줄이 실행되는 방식이다.
<code>순차적</code>, <code>직렬 처리</code> 라고 생각하면 된다.</p>
<p>** 비동기 (Asynchronous)**
특정 작업이 끝날 때까지 기다리지 않고, 
나머지 코드들을 먼저 실행시키는 방식이다.
<code>병렬 처리</code>, <code>이벤트 기반</code> 실행이라고 생각하면 된다.
<br></p>
<blockquote>
<p>예시 코드</p>
</blockquote>
<p><strong>동기 예시 코드</strong></p>
<pre><code>console.log(&quot;1. 시작&quot;);
console.log(&quot;2. 처리 중...&quot;);
console.log(&quot;3. 끝&quot;);
</code></pre><pre><code>1. 시작
2. 처리 중...
3. 끝</code></pre><p>➡️ 위에서부터 순차적으로 실행되는 것을 확인할 수 있다.</p>
<p><strong>비동기 예시 코드</strong></p>
<pre><code>console.log(&quot;1. 시작&quot;);

setTimeout(() =&gt; {
  console.log(&quot;2. 처리 중...(2초 후)&quot;);
}, 2000);

console.log(&quot;3. 끝&quot;);</code></pre><pre><code>1. 시작
3. 끝
2. 처리 중...(2초 후)</code></pre><p>➡️ setTimeout은 비동기 처리라서, 2초가 지나야 실행된다.
➡️ 하지만 그동안 코드 실행은 멈추지 않고 바로 3. 끝이 출력된다.
<br></p>
<blockquote>
<p>왜 비동기가 필요할까?</p>
</blockquote>
<p>JS는 <code>싱글 스레드 언어</code>라서 동기만 사용하면 긴 작업(예: 서버 요청, 파일 읽기) 때문에 전체 프로그램이 멈추게 된다.</p>
<p>비동기를 사용하면:</p>
<p>➡️ 서버 응답을 기다리면서도 다른 UI 동작이 가능하고
➡️ 이로 인해 사용자 경험(UX) 개선이 가능하다!
<br></p>
<blockquote>
<p>싱글 스레드 언어</p>
</blockquote>
<p><strong>스레드(Thread)란?</strong></p>
<ul>
<li><p>스레드는 CPU가 일을 처리하는 실행 단위</p>
</li>
<li><p>쉽게 말하면 한 명의 일꾼이라고 생각하면 된다!</p>
</li>
<li><p>여러 스레드가 있으면 여러 명의 일꾼이 동시에 일을 나눠서 할 수 있고, 스레드가 하나라면 일꾼은 한 명뿐이다...!</p>
<br>

</li>
</ul>
<blockquote>
<p>자바스크립트는 왜 싱글 스레드일까?</p>
</blockquote>
<p>자바스크립트는 <strong>하나의 스레드(=한 명의 일꾼)</strong>만 가지고 있다.</p>
<p>즉, 한 번에 하나의 작업만 실행할 수 있다.</p>
<p>이 때문에 자바스크립트에서는 긴 작업이 있으면 
프로그램 전체가 멈춰버릴 위험이 있다! (그래서 비동기 처리가 중요)
<br></p>
<blockquote>
<p>그런데 왜 비동기 처리가 가능할까?</p>
</blockquote>
<p>자바스크립트가 싱글 스레드임에도 불구하고 비동기 동작이 가능한 이유는     <code>브라우저(또는 Node.js)</code> 환경 덕분이다.</p>
<p>브라우저/Node.js는 <code>Web API / 백그라운드 스레드</code>를 따로 가지고 있다.</p>
<p>자바스크립트 엔진은 <code>한 명의 일꾼</code>이지만, 
무거운 일(예: setTimeout, fetch 요청)은 브라우저에게 맡기고, 
일이 끝나면 <code>이벤트 루프(Event Loop)</code>가 “이제 처리해!” 하고 
다시 자바스크립트에게 알려준다.</p>
<ul>
<li><p>싱글 스레드 = 카페 직원이 한 명뿐인 상황</p>
</li>
<li><p>주문받기, 커피 만들기, 계산하기… 전부 그 한 명이 처리해야 하는 상황</p>
</li>
<li><p>그런데 사장이 <strong>커피 기계(비동기 API)</strong>를 따로 줌 
→ 직원은 주문만 넣어두고 다른 손님 응대 가능
→ 커피가 다 되면 기계가 알려줌 
→ 직원은 다시 서빙</p>
<br>

</li>
</ul>
<blockquote>
<p>이벤트 루프(Event Loop)</p>
</blockquote>
<p>자바스크립트가 비동기 동작을 할 수 있는 핵심은 바로 
이벤트 루프(Event Loop) 구조 덕분이다.</p>
<p>이벤트 루프는 크게 <strong>네 가지 요소</strong>로 이루어진다!</p>
<p><code>Call Stack (콜 스택)</code> → 실행 중인 코드가 쌓이는 곳</p>
<p><code>Web API</code> → 브라우저/Node.js가 제공하는 비동기 작업 공간 
(setTimeout, fetch, 이벤트 등)</p>
<p><code>Task Queue (태스크 큐)</code> → 완료된 콜백들이 대기하는 줄</p>
<p><code>Event Loop (이벤트 루프)</code> → 콜 스택이 비면, 태스크 큐에서 대기 중인 작업을 가져와 실행</p>
<pre><code>console.log(&quot;1. 시작&quot;);

setTimeout(() =&gt; {
  console.log(&quot;2. 타이머 끝&quot;);
}, 0);

console.log(&quot;3. 끝&quot;);


출력 결과

1. 시작
3. 끝
2. 타이머 끝</code></pre><p>➡️ setTimeout의 콜백은 바로 실행되지 않고, 
Web API → Task Queue → Event Loop → Call Stack 순서로 이동한 뒤 실행된다.</p>
<br>


<p>카페로 비유한다면?</p>
<p><strong>Call Stack</strong> = 직원이 지금 처리 중인 주문</p>
<p><strong>Web API</strong> = 커피 기계 (타이머, 서버 요청 등)</p>
<p><strong>Task Queue</strong> = 다 된 커피가 대기하는 선반</p>
<p><strong>Event Loop</strong> = 직원이 선반을 확인하고, 손이 비면 커피를 손님에게 내주는 과정
<br></p>
<blockquote>
<p>비동기 처리 방식</p>
</blockquote>
<p><strong>1. 콜백 (Callback)</strong></p>
<p>옛날 방식으로, <strong>함수의 결과를 다른 함수에 전달</strong>하는 구조
여러 개가 중첩되면 콜백 지옥(Callback Hell) 문제가 생긴다.</p>
<pre><code>console.log(&quot;데이터 요청 시작&quot;);

setTimeout(() =&gt; {
  console.log(&quot;1차 응답 처리&quot;);
  setTimeout(() =&gt; {
    console.log(&quot;2차 응답 처리&quot;);
    setTimeout(() =&gt; {
      console.log(&quot;3차 응답 처리 완료&quot;);
    }, 1000);
  }, 1000);
}, 1000);


출력:

데이터 요청 시작
1초 후 → 1차 응답 처리
2초 후 → 2차 응답 처리
3초 후 → 3차 응답 처리 완료</code></pre><p>➡️ 가독성이 떨어지고, 유지보수가 어렵다.
<br></p>
<p><strong>2. Promise</strong></p>
<p>콜백 대신 <strong>체이닝(.then, .catch)</strong>을 통해 순서를 제어할 수 있다.</p>
<pre><code>console.log(&quot;데이터 요청 시작&quot;);

new Promise((resolve) =&gt; {
  setTimeout(() =&gt; {
    console.log(&quot;1차 응답 처리&quot;);
    resolve();
  }, 1000);
})
.then(() =&gt; new Promise((resolve) =&gt; {
  setTimeout(() =&gt; {
    console.log(&quot;2차 응답 처리&quot;);
    resolve();
  }, 1000);
}))
.then(() =&gt; {
  setTimeout(() =&gt; {
    console.log(&quot;3차 응답 처리 완료&quot;);
  }, 1000);
});</code></pre><p>➡️ 중첩은 줄었지만 .then 체인이 길어지면 여전히 복잡할 수 있다.
<br></p>
<p><strong>3. async / await</strong></p>
<p>최신 방식으로, 마치 동기 코드처럼 읽히지만 내부는 비동기이다!</p>
<pre><code>function delay(ms) {
  return new Promise((resolve) =&gt; setTimeout(resolve, ms));
}

async function process() {
  console.log(&quot;데이터 요청 시작&quot;);

  await delay(1000);
  console.log(&quot;1차 응답 처리&quot;);

  await delay(1000);
  console.log(&quot;2차 응답 처리&quot;);

  await delay(1000);
  console.log(&quot;3차 응답 처리 완료&quot;);
}

process();</code></pre><p>➡️ 코드가 깔끔하고, 가독성이 높아져서 실무에서 가장 많이 사용된다.
<br></p>
<blockquote>
<p>정리</p>
</blockquote>
<ul>
<li><p>동기: 직렬, 순차 실행 → 이해하기 쉽지만 효율이 낮다.</p>
</li>
<li><p>비동기: 병렬처럼 동작 → 효율적, UX 개선이 가능하다.</p>
<br>

</li>
</ul>
<p><code>카페에서 커피 주문</code>을 한다고 가정해보자!</p>
<ul>
<li><p>동기: 내가 커피 받을 때까지 줄 서서 기다림</p>
</li>
<li><p>비동기: 주문하고 자리에 앉아 다른 일 하다가, 
커피 나오면 알림 받고 가지러 감</p>
<br>

</li>
</ul>
<p><strong>JS에서는 비동기 프로그래밍이 필수적 → 주로 async/await을 사용</strong></p>
<ul>
<li><p>콜백 : 단순하지만 중첩되면 가독성↓</p>
</li>
<li><p>Promise : 체이닝으로 개선했지만 여전히 복잡 가능</p>
</li>
<li><p>async/await : 동기처럼 작성 가능, 가장 깔끔</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD]]></title>
            <link>https://velog.io/@sehee-xx/CICD</link>
            <guid>https://velog.io/@sehee-xx/CICD</guid>
            <pubDate>Thu, 30 Jun 2022 13:29:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/dab8f9e3-891d-4728-98e9-1b463b3fbe05/image.png" alt="">
CI/CD는 애플리케이션 개발 단계를 자동화하여 애플리케이션을 보다 짧은 주기로 고객에게 제공하는 방법이다. </p>
<p>CI/CD의 기본 개념은 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포다. </p>
<p>CI/CD는 새로운 코드 통합으로 인해 개발 및 운영팀에 발생하는 문제(일명 &quot;인테그레이션 헬(integration hell)&quot;)을 해결하기 위한 솔루션이다.</p>
<blockquote>
<p>CI와 CD의 차이점</p>
</blockquote>
<p>CI/CD는 약어로, 몇 가지의 다른 의미를 가지고 있다. </p>
<p>CI/CD의 &quot;CI&quot;는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을 의미한다. CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트 돼서 공유 레포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있다.</p>
<p>CI/CD의 &quot;CD&quot;는 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용된다. 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지를 설명하기 위해 별도로 사용되기도 한다.</p>
<p>지속적인 제공이란 개발자들이 애플리케이션에 적용한 변경 사항이 버그 테스트를 거쳐 레포지토리(예: GitHub 또는 컨테이너 레지스트리)에 자동으로 업로드되는 것을 뜻하며, 운영팀은 이 레포지토리에서 애플리케이션을 실시간 프로덕션 환경으로 배포할 수 있다. </p>
<p>이는 개발팀과 비즈니스팀 간의 가시성과 커뮤니케이션 부족 문제를 해결해준다. 
지속적인 제공은 최소한의 노력으로 새로운 코드를 배포하는 것을 목표로 한다.</p>
<p>지속적인 배포(또 다른 의미의 &quot;CD&quot;: Continuous Deployment)란 개발자의 변경 사항을 레포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미한다. 이는 애플리케이션 제공 속도를 저해하는 수동 프로세스로 인한 운영팀의 프로세스 과부하 문제를 해결해준다. 지속적인 배포는 파이프라인의 다음 단계를 자동화함으로써 지속적인 제공이 가진 장점을 활용한다.</p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/97e493df-bcc4-47ee-90bc-f02781067d45/image.png" alt=""></p>
<p>CI/CD는 지속적 통합 및 지속적 제공(CD, Continuous Delivery)의 구축 사례만을 
지칭할 때도 있고, 지속적 통합, 지속적 제공, 지속적 배포라는 3가지 구축 사례 모두를 의미하는 것일 수도 있다. 좀 더 복잡하게 설명하면 &quot;지속적인 서비스 제공&quot;은 때로 
지속적인 배포의 과정까지 포함하는 방식으로 사용되기도 한다.</p>
<p>결과적으로 CI/CD는 파이프라인으로 표현되는 실제 프로세스를 의미하고, 
애플리케이션 개발에 지속적인 자동화 및 지속적인 모니터링을 추가하는 것을 의미한다. </p>
<p>이 용어는 사례별로 CI/CD 파이프라인에 구현된 자동화 수준 정도에 따라 그 의미가 달라진다. 대부분의 기업에서는 CI를 먼저 추가한 다음 클라우드 네이티브 애플리케이션의 일부로서 배포 및 개발 자동화를 구현해 나간다.</p>
<blockquote>
<p>지속적 통합</p>
</blockquote>
<p>현대적인 애플리케이션 개발에서는 여러 개발자들이 동일한 애플리케이션의 각기 다른 기능을 동시에 작업할 수 있도록 하는 것을 목표로 한다. </p>
<p>그러나 조직에서 특정한 날(&quot;병합(머지)하는 날(merge day)&quot;)을 정해 모든 분기 소스 코드를 병합하는 경우, 결과적으로 반복적인 수작업에 많은 시간을 소모하게 된다. 이렇게 반복적인 수작업을 하는 이유는 독립적으로 작업하는 개발자가 애플리케이션에 변경 사항을 적용할 때 다른 개발자가 동시에 적용하는 변경 사항과 충돌할 가능성이 있기 때문이다. </p>
<p>이는 팀이 하나의 클라우드 기반 통합 개발 환경(Integrated Development Environment, IDE) 사용에 동의하는 대신 각 개발자가 각자의 로컬 IDE를 커스터마이징하는 경우 더욱 복합적인 문제가 될 수 있다.</p>
<p>CI(지속적 통합)를 통해 개발자들은 코드 변경 사항을 공유 브랜치 또는 &quot;트렁크&quot;로 다시 병합하는 작업을 더욱 수월하게 자주 수행할 수 있다. 개발자가 애플리케이션에 적용한 변경 사항이 병합되면 이러한 변경 사항이 애플리케이션을 손상시키지 않도록 자동으로 애플리케이션을 구축하고 각기 다른 레벨의 자동화 테스트(일반적으로 단위 테스트 및 통합 테스트) 실행을 통해 변경 사항이 애플리케이션에 제대로 적용되었는지를 확인한다. </p>
<p>다시 말해, 클래스와 기능에서부터 전체 애플리케이션을 구성하는 서로 다른 모듈에 이르기까지 모든 것에 대한 테스트를 수행한다. 자동화된 테스트에서 기존 코드와 신규 코드 간의 충돌이 발견되면 CI를 통해 이러한 버그를 더욱 빠르게 자주 수정할 수 있다.</p>
<blockquote>
<p>지속적 제공</p>
</blockquote>
<p>CI의 빌드 자동화, 유닛 및 통합 테스트 수행 후, 이어지는 지속적 제공(CD, Continuous Delivery) 프로세스에서는 유효한 코드를 레포지토리에 자동으로 릴리스한다.</p>
<p>그러므로 효과적인 지속적 제공 프로세스를 실현하기 위해서는 개발 파이프라인에 
CI가 먼저 구축되어 있어야 한다. 지속적 제공의 목표는 프로덕션 환경으로 배포할 준비가 
되어 있는 코드베이스를 확보하는 것이다.</p>
<p>지속적 제공의 경우, 코드 변경 사항 병합부터 프로덕션에 적합한 빌드 제공에 이르는 모든 단계에는 테스트 자동화와 코드 릴리스 자동화가 포함된다. 이 프로세스를 완료하면 운영팀이 보다 빠르고 손쉽게 애플리케이션을 프로덕션으로 배포할 수 있게 된다.</p>
<blockquote>
<p>지속적 배포</p>
</blockquote>
<p>CI/CD 파이프라인의 마지막 단계는 지속적 배포다. 
프로덕션 준비가 완료된 빌드를 코드 리포지토리에 자동으로 릴리스하는 지속적 제공의 
확장된 형태인 지속적 배포는 애플리케이션을 프로덕션으로 릴리스하는 작업을 자동화한다. </p>
<p>프로덕션 이전의 파이프라인 단계에는 수동 작업 과정이 없으므로, 
지속적 배포가 제대로 이루어지려면 테스트 자동화가 제대로 설계되어 있어야 한다.</p>
<p>실제 사례에서 지속적 배포란 개발자가 애플리케이션에 변경 사항을 작성한 후 몇 분 이내에 애플리케이션을 자동으로 실행할 수 있는 것을 의미한다. (자동화된 테스트를 통과한 것으로 간주) 이를 통해 사용자 피드백을 지속적으로 수신하고 통합하는 일이 훨씬 수월해진다. </p>
<p>이러한 모든 CI/CD 적용 사례는 애플리케이션 배포의 위험성을 줄여주므로 애플리케이션 변경 사항을 한 번에 모두 릴리스하지 않고 작은 조각으로 세분화하여 더욱 손쉽게 릴리스할 수 있다. </p>
<p>그러나 자동화된 테스트는 CI/CD 파이프라인의 여러 테스트 및 릴리스 단계를 
수행할 수 있어야 하기 때문에 많은 선행 투자가 필요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@media , Responsive Design]]></title>
            <link>https://velog.io/@sehee-xx/media-Responsive-Design</link>
            <guid>https://velog.io/@sehee-xx/media-Responsive-Design</guid>
            <pubDate>Thu, 30 Jun 2022 13:14:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/bf08e17e-9222-46dd-9a9e-a71ee4b52644/image.png" alt=""></p>
<blockquote>
<p>Responsive Design</p>
</blockquote>
<p>반응형 웹 디자인(Responsive Design)이란 웹 디자인 기법 중 하나로, 
하나의 웹사이트에서 PC, 스마트폰, 태블릿 등 접속하는 디스플레이 종류에 따라 화면의 크기가 자동으로 변하도록 만든 웹페이지 접근 기법을 의미한다.</p>
<p>이를 사용할 때, 미디어 쿼리를 이용해 사용할 수 있다.</p>
<blockquote>
<p>@media</p>
</blockquote>
<p>@media CSS 규칙은 스타일 시트의 일부를 
하나 이상의 미디어 쿼리 결과에 따라 적용할 때 사용할 수 있다.</p>
<p>@media를 사용해 미디어 쿼리를 지정하면 
해당 쿼리를 만족하는 장치에서만 CSS 블록을 적용할 수 있다.</p>
<p>@media @규칭은 최상위 코드나, 아무 조건부 그룹 @규칙 안에 중첩해 작성할 수 있다.</p>
<blockquote>
<p>미디어 쿼리</p>
</blockquote>
<ul>
<li><p>미디어 쿼리는 단말기 유형과 어떤 특성이나 수치에 따라 
웹 사이트나 앱의 스타일을 수정할 때 유용하다.</p>
</li>
<li><p>미디어 쿼리는 선택 사항인 미디어 유형과, 
자유로운 수의 미디어 특성 표현식으로 이루어진다.</p>
</li>
</ul>
<pre><code class="language-js">/* 최상위 코드 레벨 */
@media screen and (min-width: 900px) {
  article {
    padding: 1rem 3rem;
  }
}

/* 다른 조건부 @규칙 내에 중첩 */
@supports (display: flex) {
  @media screen and (min-width: 900px) {
    article {
      display: flex;
    }
  }
}</code></pre>
<blockquote>
<p>조건부 @규칙이란?</p>
</blockquote>
<p>속성값과 마찬가지로, 각각의 @규칙은 다른 구문이 있다.
그럼에도 그 중 몇몇은 그룹 규칙으로 불리는 특별한 범주로 분류될 수 있다.</p>
<p>이들 문은 공통 구문을 공유하고 그 각각은 중첩 문 또는 중첩 @규칙을 포함할 수 있다.</p>
<p>모두 어떤 유형의 조건을 링크한다. 언제라도 참 또는 거짓 중 하나로 평가하는 조건을 
참으로 평가하면 그룹 내 모든 문이 적용된다.</p>
<pre><code class="language-js">@media print {
  body { font-size: 10pt; }
}

@media screen {
  body { font-size: 13px; }
}

@media screen, print {
  body { line-height: 1.2; }
}

@media only screen
  and (min-width: 320px)
  and (max-width: 480px)
  and (resolution: 150dpi) {
    body { line-height: 1.4; }
}</code></pre>
<blockquote>
<p>미디어 유형</p>
</blockquote>
<ul>
<li>all : 모든 장치에 적합</li>
<li>print : 인쇄 결과물 및 출력 미리보기 화면에 표시중인 문서</li>
<li>screen : 주로 화면이 대상</li>
<li>speech : 음성 합성장치 대상</li>
<li>Media Queries Level 4부터는 새로운 범위 표현 구문을 사용해 
더 간결한 미디어 쿼리를 작성할 수 있다.</li>
</ul>
<pre><code class="language-js">@media (height &gt; 600px) {
    body { line-height: 1.4; }
}

@media (400px &lt;= width &lt;= 700px) {
    body { line-height: 1.4; }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker]]></title>
            <link>https://velog.io/@sehee-xx/Docker</link>
            <guid>https://velog.io/@sehee-xx/Docker</guid>
            <pubDate>Thu, 30 Jun 2022 12:25:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/76344a18-6ac3-489b-9fcf-b34fab2e96aa/image.png" alt=""></p>
<blockquote>
<p>Docker란?</p>
</blockquote>
<p>Docker는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼이다. </p>
<p>Docker는 소프트웨어를 컨테이너라는 표준화된 유닛으로 패키징하며, 이 컨테이너에는 라이브러리, 시스템 도구, 코드, 런타임 등 소프트웨어를 실행하는 데 필요한 모든 것이 포함되어 있다. </p>
<p>Docker를 사용하면 환경에 구애받지 않고 애플리케이션을 신속하게 배포 및 확장할 수 있으며 코드가 문제없이 실행될 것임을 확신할 수 있다.</p>
<blockquote>
<p>Docker 작동 방식</p>
</blockquote>
<p>Docker는 코드를 실행하는 표준 방식을 제공한다. </p>
<p>Docker는 컨테이너를 위한 운영 체제이다. 가상 머신이 서버 하드웨어를 가상화하는 방식과 비슷하게(직접 관리해야 하는 필요성 제거) 컨테이너는 서버 운영 체제를 가상화한다. </p>
<p>Docker는 각 서버에 설치되며 컨테이너를 구축, 시작 또는 중단하는 데 사용할 수 있는 
간단한 명령을 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/a08004db-22da-4540-924e-6e99ea028e77/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[https vs http]]></title>
            <link>https://velog.io/@sehee-xx/https-vs-http</link>
            <guid>https://velog.io/@sehee-xx/https-vs-http</guid>
            <pubDate>Thu, 30 Jun 2022 12:22:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/3d472c4e-f14f-4ac7-a82f-a9cec13fe3af/image.png" alt=""></p>
<blockquote>
<p>HTTP</p>
</blockquote>
<p>HTTP는 Hyper Text Transfer Protocol의 줄임말으로써 서버와 클라이언트간에 데이터를 주고 받는 프로토콜이다. </p>
<p>HTTP는 텍스트, 이미지,영상, JSON 등등 거의 모든 형태의 데이터를 전송할 수 있다.</p>
<p>세상에 등장한지 벌써 30년이나 된 HTTP는 1997년 만들어진 HTTP/1.1가 가장 보편화 되어있으며, 현재는 HTTP/2를 거쳐 HTTP/3까지 개발된 상태이다. </p>
<p>TCP를 개선해서 만들어진** UDP가 HTTP/3 기술에 사용된다.</p>
<blockquote>
<p>HTTP는 보안적으로 안전할까?</p>
</blockquote>
<p>HTTP 통신은 클라이언트와 서버간의 통신에 있어서 별다른 보안 조치가 없기 때문에 
만약 누군가 네트워크 신호를 가로챈다면 HTTP의 내용은 그대로 외부에 노출된다.</p>
<p>중요 정보가 없는 소규모의 프로젝트라면 문제가 되지 않겠지만 고객의 개인정보나 비밀을 취급하는 대규모 서비스라면 큰 보안적 허점이 될 것이다.</p>
<p>이런 문제를 해결하기 위해 등장한 것이 <code>HTTPS</code>이다.</p>
<blockquote>
<p>HTTPS</p>
</blockquote>
<p>요즘은 거의 모든 사이트의 주소창에서 자물쇠 표시를 볼 수 있다.</p>
<p>HTTPS가 적용되었다는걸 알려주는게 바로 저 자물쇠다.</p>
<p>HTTPS가 옛날부터 보편화 되어 있지는 않았다. 
처음에는 전자상거래 등 고객의 중요 정보를 다루는 사이트 위주로 사용되었다.</p>
<p>그러다가 2014년 구글에서는 HTTP를 HTTPS로 변환하라고 권고하기 시작하고, 구글은 HTTPS를 적용하는 사이트들에게 SEO(검색 엔진 최적화)에 있어서 가산점을 주겠다고 했다. </p>
<p>사용자 정보의 안전성도 보장받고, 사용자들의 웹사이트 유입도 늘릴수 있으니 
이때부터 사이트들이 HTTPS로 변환하기 시작했다.</p>
<blockquote>
<p>HTTPS는 어떤 방식으로 보안 이슈를 해결할까?</p>
</blockquote>
<p>기존의 HTTP 프로토콜은 전송계층의 TCP위에서 동작한다.
여기서 SSL(Secure Sockets Layer)이라는 보안계층이 전송계층 위에 올라간다. </p>
<p>HTTPS는 SSL 위에 HTTP를 얹어서 보안이 보장된 통신을 하는 프로토콜이다.
이 통신 방식을 SSL 암호화 통신이라고도 한다. 
SSL 암호화 통신은 공개키 암호화 방식이라는 알고리즘을 통해 구현된다.</p>
<blockquote>
<p>공개키 암호화 방식이란?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/3a807617-7b4c-48b2-bb68-c715fc57e1c2/image.png" alt=""></p>
<p>공개키 암호화 방식에는 공개키와 개인키 두 종류의 키가 존재한다.
한쪽 키로 데이터를 암호화 했다면 오직 다른쪽 키로만 복호화를 할 수 있다.</p>
<blockquote>
<p>클라이언트와 서버간의 요청과 응답 과정</p>
</blockquote>
<p>CA는 서버 운영 기업이 넘겨준 공개키를 인증서 발급자, CA의 이름 등과 함께 묶어서 
CA가 가지고 있는 개인키로 암호화해서 SSL인증서로 발급해준다.</p>
<p>차후에 클라이언트에서 요청을 하면 서버는 클라이언트에게 SSL 인증서를 보낸다.
브라우저(클라이언트)는 대표적인 CA들의 리스트와 그들의 공개키를 보유하고 있다. </p>
<p>만약 인증서에 적힌 CA의 이름과 브라우저가 소유하고 있는 CA 이름이 같다면 
CA의 공개키로 SSL 인증서를 복호화 한다.</p>
<p>이제 SSL내부에 들어있던 서버의 공개키를 가지고 요청을 암호화해서 서버에게 보낸다. 서버측은 가지고 있는 개인키로 요청을 복호화하여 해석하고 응답은 다시 암호화 해서 보낸다. 이 과정을 통해 보안성이 강한 통신을 할 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SPA vs MPA]]></title>
            <link>https://velog.io/@sehee-xx/SPA-vs-MPA</link>
            <guid>https://velog.io/@sehee-xx/SPA-vs-MPA</guid>
            <pubDate>Thu, 30 Jun 2022 12:05:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/b732f2ab-651d-4146-b189-94a0487b0a0b/image.png" alt=""></p>
<blockquote>
<p>SPA, MPA</p>
</blockquote>
<p>SPA(Single Page Application)는 한 개(Single)의 Page로 구성된 Application이다.
MPA(Multiple Page Application)는 여러 개(Single)의 Page로 구성된 Application이다.</p>
<p>MPA는 새로운 페이지를 요청할 때마다 정적 리소스가 다운로드된다. 
매번 전체 페이지가 다시 렌더링 된다.
반면, SPA는 웹 에플리케이션에 필요한 모든 정적 리소스를 최초 한 번에 다운로드한다.</p>
<p>그래서 SPA를 CSR(Client Side Rendering) 방식으로 렌더링 한다고 말하고,
MPA를 SSR(Server Side Rendering) 방식으로 렌더링 한다고 말한다.</p>
<blockquote>
<p>SPA(Single Page Application)</p>
</blockquote>
<ul>
<li><p>한 개(Single)의 Page로 구성된 Application이다.</p>
</li>
<li><p>SPA는 CSR(Client Side Rendering) 방식으로 렌더링한다.</p>
</li>
<li><p>단 한 번만 리소스(HTML, CSS, JavaScript)를 로딩한다.
그 후에는 데이터를 받아올 때만 서버와 통신한다.</p>
</li>
<li><p>즉, 첫 요청시 딱 한 페이지만 불러오고 페이지 이동 시 
기존 페이지의 내부를 수정해서 보여주는 방식이다.</p>
</li>
<li><p>이를 클라이언트 관점에서 말하자면 최초 페이지를 로딩한 시점부터는 
페이지 리로딩 없이 필요한 부분만 서버로 부터 받아서 화면을 갱신하는 것이다.</p>
</li>
<li><p>필요한 부분만 갱신하기 때문에 네이티브 앱에 가까운 자연스러운 페이지 이동과 
사용자 경험(UX)을 제공할 수 있다.</p>
</li>
<li><p>Angular, React, Vue 등 프론트엔드 기술들이 나오면서 크게 유행하고 있다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/d1a185fb-4c2d-42c4-b8d9-ade22f85694b/image.png" alt=""></p>
<h3 id="spa-장점">SPA 장점</h3>
<ol>
<li><p>자연스러운 사용자 경험 (UX)
전체 페이지를 업데이트 할 필요가 없기 때문에 빠르고 ‘깜빡’ 거림이 없다.</p>
</li>
<li><p>필요한 리소스만 부분적으로 로딩 (성능)
SPA의 Application은 서버에게 정적리소스를 한 번만 요청한다.
그리고 받은 데이터는 전부 저장해놓는다. (캐시=Cache)</p>
</li>
<li><p>서버의 템플릿 연산을 클라이언트로 분산 (성능)</p>
</li>
<li><p>컴포넌트별 개발 용이 (생산성)</p>
</li>
<li><p>모바일 앱 개발을 염두에 둔다면 동일한 API를 사용하도록 설계 가능 (생산성)</p>
</li>
</ol>
<h3 id="spa-단점">SPA 단점</h3>
<ol>
<li><p>JavaScript 파일을 번들링해서 한 번에 받기 때문에 초기 구동 속도가 느리다. (Webpack의 code splitting으로 해결 가능)</p>
</li>
<li><p>검색엔진최적화(SEO)가 어려움 (SSR로 해결 가능)</p>
</li>
<li><p>보안 이슈 (프론트엔드에 비즈니스 로직 최소화)</p>
</li>
</ol>
<ul>
<li>SSR에서는 사용자에 대한 정보를 서버측에서 세션으로 관리를 하지만 CSR 방식에서는 클라이언트측의 쿠키말고는 사용자에 대한 정보를 저장할 공간이 마땅치 않다.</li>
</ul>
<blockquote>
<p>MPA(Multiple Page Application)</p>
</blockquote>
<ul>
<li><p>여러 개(Single)의 Page로 구성된 Application이다.</p>
</li>
<li><p>MPA는 SSR(Server Side Application) 방식으로 렌더링한다.</p>
</li>
<li><p>새로운 페이지를 요청할 때마다 서버에서 렌더링된 정적 리소스
(HTML, CSS, JavaScript)가 다운로드된다.</p>
</li>
<li><p>페이지 이동하거나 새로고침하면 전체 페이지를 다시 렌더링한다.
<img src="https://velog.velcdn.com/images/sehee-xx/post/93754186-55b8-47f4-ac92-e6f98d5f7436/image.png" alt=""></p>
</li>
</ul>
<h3 id="mpa-장점">MPA 장점</h3>
<ol>
<li><p>SEO 관점에서 유리하다.
MPA는 완성된 형태의 HTML 파일을 서버로부터 전달받는다.
따라서 검색엔진이 페이지를 크롤링하기에 적합하다.</p>
</li>
<li><p>첫 로딩 매우 짧다.
서버에서 이미 렌더링해 가져오기 때문이다.
그러나 클라이언트가 JS 파일을 모두 다운로드하고 적용하기 전까지는 
각각의 기능은 동작하지 않는다.</p>
</li>
</ol>
<h3 id="mpa-단점">MPA 단점</h3>
<ol>
<li><p>새로운 페이지를 이동하면 ‘깜빡’인다. (UX)
매 페이지 요청마다 리로딩(새로고침) 발생.
새로운 페이지를 요청할 때마다 전체 페이지를 다시 렌더링하기 때문이다.</p>
</li>
<li><p>페이지 이동시 불필요한 템플릿도 중복해서 로딩 (성능)</p>
</li>
<li><p>서버 렌더링에 따른 부하</p>
</li>
<li><p>모바일 앱 개발시 추가적인 백엔드 작업 필요 (생산성)
개발이 복잡해질 수 있다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Domain, DNS, Hosting]]></title>
            <link>https://velog.io/@sehee-xx/Domain-DNS-Hosting</link>
            <guid>https://velog.io/@sehee-xx/Domain-DNS-Hosting</guid>
            <pubDate>Thu, 30 Jun 2022 11:51:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/f27e45d5-2b8c-4e48-91f3-dfd740b05fda/image.png" alt=""></p>
<blockquote>
<p>Domain</p>
</blockquote>
<h3 id="도메인이란">도메인이란?</h3>
<p>도메인은 인터넷에 연결된 컴퓨터를 사람이 쉽게 기억하고 입력할 수 있도록 
문자(영문, 한글 등)로 만든 인터넷 주소다.</p>
<p>법률상으로는 인터넷주소자원에 관한 법률 제2조에 따라 도메인은 인터넷에서 
인터넷 프로토콜 주소를 사람이 기억하기 쉽도록 하기 위해서 만들어진 것이다. </p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/1d5c0580-ce3b-41d0-a62c-cb4177352d44/image.png" alt=""></p>
<h3 id="도메인-체계">도메인 체계</h3>
<p>도메인은 &quot;.&quot; 또는 루트(root)라 불리는 도메인 이하에 아래 그림과 같이 역트리(Inverted tree)구조로 구성되어 있다. 루트 도메인 바로 아래의 단계를 1단계 도메인 또는 최상위 도메인(TLD, Top Level Domain)이라고 부르며, 그 다음 단계를 2단계 도메인(SLD, Second Level Domain)이라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/ea5bb367-aec6-45f4-b790-b28aac5859a5/image.png" alt=""></p>
<h3 id="도메인-종류">도메인 종류</h3>
<p>도메인에는 국가도메인(ccTLD, country code Top Level Domain)과 일반도메인(gTLD, generic Top Level Domain)이 있다.</p>
<p>국가도메인은 인터넷 상에서 국가를 나타내는 도메인으로 ‘.kr(대한민국) .jp(일본), .cn(중국), .us(미국) 등 영문으로 구성된 영문 국가도메인이 있다. 또한 ‘.한국(대한민국)’, ‘중국(중국), .러시아(러시아), .이집트(이집트)처럼 자국어 국가도메인이 있다.</p>
<p>일반도메인은 ‘.com(회사)’, ‘.net(네트워크 관련기관)’, ‘org(비영리기관)’, ‘.biz(사업)’ 등 등록인의 특성에 따라 사용할 수 있는 도메인이다.</p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/1524401e-de16-4ec1-bb5e-0d5a66256a49/image.png" alt=""></p>
<blockquote>
<p>DNS</p>
</blockquote>
<h3 id="dns란">DNS란?</h3>
<p>도메인 이름 시스템(DNS)은 사람이 읽을 수 있는 도메인 이름(예: <a href="http://www.amazon.com)%EC%9D%84">www.amazon.com)을</a> 머신이 읽을 수 있는 IP 주소(예: 192.0.2.44)로 변환한다.</p>
<h3 id="dns-기본-사항">DNS 기본 사항</h3>
<p>스마트폰이나 노트북부터 대규모 소매 웹 사이트의 콘텐츠를 서비스하는 서버에 이르기까지 인터넷상의 모든 컴퓨터는 숫자를 사용하여 서로를 찾고 통신한다. 이러한 숫자를 IP 주소라고 한다. 웹 브라우저를 열고 웹 사이트로 이동할 때는 긴 숫자를 기억해 입력할 필요가 없다. 그 대신 example.com과 같은 도메인 이름을 입력해도 원하는 웹 사이트로 갈 수 있다.</p>
<p>Amazon Route 53과 같은 DNS 서비스는 전 세계에 배포된 서비스로서, <a href="http://www.example.com%EA%B3%BC">www.example.com과</a> 같이 사람이 읽을 수 있는 이름을 192.0.2.1과 같은 숫자 IP 주소로 변환하여 컴퓨터가 서로 통신할 수 있다. 인터넷의 DNS 시스템은 이름과 숫자 간의 매핑을 관리하여 전화번호부 같은 기능을 한다. DNS 서버는 이름을 IP 주소로 변환하여 도메인 이름을 웹 브라우저에 입력할 때 최종 사용자를 어떤 서버에 연결할 것인지를 제어한다. 이 요청을 쿼리라고 부른다.</p>
<h3 id="dns-서비스-유형">DNS 서비스 유형</h3>
<p><code>신뢰할 수 있는 DNS</code> : 신뢰할 수 있는 DNS 서비스는 개발자가 퍼블릭 DNS 이름을 관리하는 데 사용하는 업데이트 메커니즘을 제공한다. 이 메커니즘을 통해 DNS 시스템은 DNS 쿼리에 응답하고 도메인 이름을 IP 주소로 변환한다. 그러면 컴퓨터가 서로 통신할 수 있게 된다. 신뢰할 수 있는 DNS는 도메인에 대해 최종 권한이 있으며 재귀적 DNS 서버에 IP 주소 정보가 담긴 답을 제공할 책임이 있다.</p>
<p><code>재귀적 DNS</code> : 대개 클라이언트는 신뢰할 수 있는 DNS 서비스에 직접 쿼리를 수행하지 않는다. 대신 해석기 또는 재귀적 DNS 서비스라고 알려진 다른 유형의 DNS 서비스에 연결하는 경우가 일반적이다. 재귀적 DNS 서비스는 호텔 컨시어지와 같은 역할을 합니다. DNS 레코드를 소유하고 있지 않지만 사용자를 대신해서 DNS 정보를 가져올 수 있는 중간자의 역할을 한다. 재귀적 DNS가 일정 기간 캐시된 또는 저장된 DNS 참조를 가지고 있는 경우, 소스 또는 IP 정보를 제공하여 DNS 쿼리에 답을 준다. 그렇지 않다면, 해당 정보를 찾기 위해 쿼리를 하나 이상의 신뢰할 수 있는 DNS 서버에 전달한다.</p>
<h3 id="dns는-트래픽을-웹-애플리케이션에-어떻게-라우팅할까">DNS는 트래픽을 웹 애플리케이션에 어떻게 라우팅할까?</h3>
<p>다음 그림은 재귀적 DNS 서비스와 신뢰할 수 있는 DNS 서비스가 서로 연계하여 최종 사용자를 웹 사이트 또는 애플리케이션으로 라우팅하는 방법에 대한 개요를 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/sehee-xx/post/4efeeeb8-7c6d-4c8f-9433-8e74afbca541/image.png" alt=""></p>
<blockquote>
<p>Hosting</p>
</blockquote>
<h3 id="호스팅이란">호스팅이란?</h3>
<p>제공자 등의 사업자가 개인용 홈페이지의 서버 기능을 대행하는 것이다.</p>
<p>또 기업의 대용량 메모리 공간을 이용해서 사용자의 홈페이지나 웹 서버 기능을 대행하는 서비스라고 볼 수 있다. </p>
<p>이를 통해 사용자는 웹서버의 운영 관리와 전용회선 사용료의 부담이 줄어든다. </p>
<p>사용자가 가진 도메인에서 홈페이지 개설부터 서버 관리까지 대행하기 때문에 
독자 도메인 서비스라고 부르기도 하고, 서버를 가지고 있지 않은 사용자들에게 
웹 사이트나 서버를 대여해주므로 렌탈 서버라고 부르기도 한다.</p>
<h3 id="호스팅의-종류">호스팅의 종류</h3>
<h4 id="웹-호스팅">웹 호스팅</h4>
<p>서버 하나를 다수가 공유하여 사용하는 시스템으로 블로그 운영자에게 적합하다.</p>
<p>장점 : 임대비용이 저렴하고, 관리가 편리하다.
단점 : 서버의 용량에 트래픽 양이 증가할 경우 다운될 염려가 있어서 서버 사용에 제한을 둔다.</p>
<h4 id="서버-호스팅">서버 호스팅</h4>
<p>서버 컴퓨터의 전부 또는 일부를 임대하는 시스템으로 가격은 비싸지만 트래픽 양이 많은 큰 사이트에 적합하다.</p>
<p>단독으로 서버를 사용하므로 설치, 삭제, 개발이 자유롭지만 보안과 관리에 전문성이 필요하다.   </p>
<h4 id="쇼핑몰-호스팅">쇼핑몰 호스팅</h4>
<p>국내의 유명한 쇼핑몰이 여기에 해당되고, 구매와 결제 시스템까지 연결되도록 되어 있다.</p>
<h4 id="클라우드-호스팅">클라우드 호스팅</h4>
<p>클라우드 상의 서버를 임대하여 사용하는 시스템으로, 자유롭게 사용하고 이용한 만큼만 비용을 지불하면 된다.</p>
<p>트래픽의 변동이 잦은 사용자가 이용하기에 적당하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[reduce]]></title>
            <link>https://velog.io/@sehee-xx/reduce</link>
            <guid>https://velog.io/@sehee-xx/reduce</guid>
            <pubDate>Wed, 22 Jun 2022 08:30:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/9d2be704-f788-48c0-b9a9-a26174984779/image.png" alt=""></p>
<blockquote>
<p>reduce()</p>
</blockquote>
<p><code>reduce()</code> 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환한다. </p>
<pre><code class="language-js">const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (previousValue, currentValue) =&gt; previousValue + currentValue,
  initialValue
);

console.log(sumWithInitial);
// expected output: 10</code></pre>
<p>리듀서 함수는 네 개의 인자를 가진다.</p>
<ol>
<li>누산기 (acc)</li>
<li>현재 값 (cur)</li>
<li>현재 인덱스 (idx)</li>
<li>원본 배열 (src)</li>
</ol>
<p>리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 
결국 최종 결과는 하나의 값이 된다.</p>
<h2 id="구문">구문</h2>
<pre><code class="language-js">arr.reduce(callback[, initialValue])</code></pre>
<h3 id="매개변수">매개변수</h3>
<p><code>callback</code>
배열의 각 요소에 대해 실행할 함수로 아래의 네 가지 인수를 받는다.</p>
<ul>
<li><p><code>accumulator</code>
누산기는 콜백의 반환값을 누적한다. 콜백의 이전 반환값 또는, 콜백의 첫 번째 호출이면서
<code>initialValue</code>를 제공한 경우에는 <code>initialValue</code>의 값이다.</p>
</li>
<li><p><code>currentValue</code>
처리할 현재 요소</p>
</li>
<li><p><code>currentIndex</code>
처리할 현재 요소의 인덱스. <code>initialValue</code>를 제공한 경우 0, 아니면 1부터 시작한다.</p>
</li>
<li><p><code>array</code>
<code>reduce()</code>를 호출한 배열</p>
</li>
</ul>
<p><code>initialValue</code>
<code>callback</code>의 최초 호출에서 첫 번째 인수에 제공하는 값.
초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용한다.
빈 배열에서 초기값 없이 <code>reduce()</code>를 호출하면 오류가 발생한다.</p>
<h3 id="반환-값">반환 값</h3>
<p>누적 계산의 결과 값</p>
<h2 id="설명">설명</h2>
<p><code>reduce()</code>는 빈 요소를 제외하고 배열 내에 존재하는 각 요소에 대해 <code>callback</code> 함수를
한 번씩 실행하는데, 콜백 함수는 아래의 네 개의 인수를 받는다.</p>
<ul>
<li><code>accumulator</code></li>
<li><code>currentValue</code></li>
<li><code>currentIndex</code></li>
<li><code>array</code></li>
</ul>
<p>배열이 비어있는데 <code>initialValue</code>도 제공하지 않으면 <code>TypeError</code>가 발생한다. 배열의 요소가 (위치와 관계없이) 하나 뿐이면서 <code>initialValue</code>를 제공하지 않은 경우, 또는 <code>initialValue</code>는 주어졌으나 배열이 빈 경우엔 그 단독 값을 <code>callback</code> 호출 없이 반환합니다.</p>
<p>아래 예제처럼 <code>initialValue</code>을 제공하지 않으면 출력 가능한 형식이 세 가지이므로, 
보통 초기값을 주는 것이 더 안전하다.</p>
<pre><code class="language-js">var maxCallback = ( acc, cur ) =&gt; Math.max( acc.x, cur.x );
var maxCallback2 = ( max, cur ) =&gt; Math.max( max, cur );

// initialValue 없이 reduce()
[ { x: 22 }, { x: 42 } ].reduce( maxCallback ); // 42
[ { x: 22 }            ].reduce( maxCallback ); // { x: 22 }
[                      ].reduce( maxCallback ); // TypeError

// map/reduce로 개선 - 비었거나 더 큰 배열에서도 동작함
[ { x: 22 }, { x: 42 } ].map( el =&gt; el.x )
                        .reduce( maxCallback2, -Infinity );</code></pre>
<h2 id="예제">예제</h2>
<p>배열의 모든 값 합산</p>
<pre><code class="language-js">var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
  return accumulator + currentValue;
}, 0);
// sum is 6</code></pre>
<p>객체 배열에서의 값 합산</p>
<pre><code class="language-js">var initialValue = 0;
var sum = [{x: 1}, {x:2}, {x:3}].reduce(function (accumulator, currentValue) {
    return accumulator + currentValue.x;
},initialValue)

console.log(sum) // logs 6</code></pre>
<p>중첩 배열 펼치기</p>
<pre><code class="language-js">var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(accumulator, currentValue) {
    return accumulator.concat(currentValue);
  },
  []
);
// 펼친 결과: [0, 1, 2, 3, 4, 5]![](https://velog.velcdn.com/images/sehee-xx/post/eea67a31-14bd-4de6-a6fb-7c189096bc39/image.png)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[memoization]]></title>
            <link>https://velog.io/@sehee-xx/memoization</link>
            <guid>https://velog.io/@sehee-xx/memoization</guid>
            <pubDate>Wed, 22 Jun 2022 00:59:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/2ac3472c-bf08-4c3c-9530-dd3677ee8865/image.png" alt=""></p>
<blockquote>
<p>Dynamic Programming이 필요한 이유</p>
</blockquote>
<p>프로그래밍을 하다 보면, 같은 값을 내는 함수를 여러번 호출하게 되는 경우가 생긴다. 
대표적인 예로 재귀함수인 피보나치 함수가 있다. 동적 프로그래밍이란 함수의 결과를 저장해서 같은 함수가 불릴 때 다시 실행시키지 않고 저장된 결과값을 return 하는 것을 말한다.</p>
<h4 id="피보나치">피보나치</h4>
<pre><code class="language-js">let fibonacci = function(number) {
  if (number === 0) return 0;
  if (number === 1 || number === 2) return 1;

  return fibonacci(number - 1) + fibonacci(number - 2);
}</code></pre>
<p>피보나치 수는 첫째 및 둘째 항이 1이며, 그 뒤에 모든 항은 바로 앞 두항의 합인 수열이다.</p>
<pre><code class="language-js">fibonaccis = [0, 1, 1, 2, 3, 5, 8, ...]
Fibonacci(0) = 0;
Fibonacci(1) = 1;
Fibonacci(2) = 1;
Fibonacci(3) = 2;
Fibonacci(4) = 3;
Fibonacci(5) = 5;
Fibonacci(6) = 8;
.
.
.</code></pre>
<p><code>F(n) = F(n-1) + F(n-2)</code> 즉, 해당 값은 그 전의 두 값의 합이 된다.
이 피보나치 수열은 바로 앞의 숫자를 뒤의 숫자로 나누면 값이 1.618에 수렴한다고 한다.</p>
<p>해당번째의 피보나치의 수열의 값을 구하기 위해서는 이전의 모든 값들을 다 구해야 하기 때문에 피보나치 수열을 컴퓨팅에는 어려움이 있다.</p>
<pre><code class="language-js">Fibonacci(5) =&gt; Fibonacci(4) + Fibonnaci(3) =&gt; Fibonacci(3) + Fibonacci(2) + Fibonacci(2) + Fibonacci(1) ...
Fibonacci(4) =&gt; Fibonacci(3) + Fibonacci(2) =&gt; Fibonacci(2) + Fibonacci(1) + Fibonacci(2) ... 
Fibonacci(3) =&gt; Fibonacci(2) + Fibonacci(1) ... 
Fibonacci(2) =&gt; return 1;
Fibonacci(1) =&gt; return 1;
Fibonacci(0) =&gt; return 0;</code></pre>
<p>위와 같이 계속해서 재귀함수가 호출된다. 
같은 함수가 수없이 많이 반복돼서 쓸데없는 자원만 들고 성능 저하를 겪게 된다.</p>
<blockquote>
<p>Memoization (=Dynamic Programming)</p>
</blockquote>
<p>이러한 경우가 바로 dynamic programming이 필요한 시점이다. 위에서 동적 프로그래밍은 함수의 결과를 저장(caching)하는 것이라고 했다.</p>
<p>특별히 함수에 대해 같은 인자가 들어올 때, 함수의 결과를 저장해 놓는 것을 memoization이라고 부른다. memoization은 &#39;기억된다&#39;라는 라틴어 동사 memorandum에서 유래되었다고 한다.</p>
<p>memoization은 hash table 같은 자료구조에 이전에 불린 함수들의 결과를 저장해 놓는 기술을 의미한다. 그래서 똑같은 함수가 불린다면 이 hash table의 value 값을 리턴해 버리면 함수를 실행시킬 필요가 없다. memoization은 히보나치와 같은 재귀함수에서 큰 힘을 발휘한다.</p>
<p>위에서 피보나치 함수의 인자로 5가 들어간다고 해도, 계속 쪼개져서 수 많은 함수의 호출을 야기시키는 것을 확인했다. 이제 memoization 함수를 사용해서 피보나치 함수를 업그레이드 시켜보자.</p>
<p>아이디어는 간단하다. 함수의 상태를 caching 하면 된다.
JavaScript에서 caching은 Object 자료구조를 사용하면 된다.</p>
<p>즉, memoization 기법을 사용해서 함수를 캐싱하면, 동적 프로그래밍을 구현하는 것이다.</p>
<h3 id="구현">구현</h3>
<p>JavaScript가 제공하는 클로저 기능을 사용하자.</p>
<h4 id="outer-function--memoization">outer Function = memoization</h4>
<ul>
<li><p>memoization 함수는 함수를 인자로 받는다.</p>
</li>
<li><p>JavaScript의 클로저 기능 때문에 memoization 함수가 불려지면, 
cache(object)를 공유하게 된다.</p>
</li>
<li><p>memoization은 함수를 받아서 함수 자체를 return 한다.</p>
</li>
</ul>
<h4 id="inner-function--arrow-function">inner Function = arrow function</h4>
<ul>
<li><p>arrow function으로 return 되는 함수를 구현한다. 이때 memoization이 범용적으로 사용될 수 있도록 어떤 인자든 받을 수 있게 spread syntaz로 인자를 받는다. 
이때 인자로 들어온 args는 배열의 형태이다.</p>
</li>
<li><p>cache(object)에 <code>cache[args]</code>로 접근해서 있다면, 
이미 이 인자로 함수가 불린 것이므로 결과를 리턴한다.</p>
</li>
<li><p>없다면 인자로 들어온 함수를 인자와 함께 호출하고 <code>func(...args)</code> 결과값을 cache(oject)에 캐싱하고 리턴한다. 다시 spread syntax로 함수를 호출하는 이유는, args가 배열의 상태이기 때문이다. spread syntax를 사용해서 함수를 호출하면, 함수의 인자에 배열의 순서대로 들어가게 된다.</p>
</li>
</ul>
<pre><code class="language-js">let fibonacci = function(number) {
  if (number === 0) return 0;
  if (number === 1 || number === 2) return 1;

  return fibonacci(number - 1) + fibonacci(number - 2);
}

const memoization = (func) =&gt; { // input parameter: function
  const cache = {}; // empty hash map
  return (...args) =&gt; { // inner function, spread syntax for generic use
    if (cache[args]) {
      console.log(`${args} already called, return from cache`);
      return cache[args];
    }

    console.log(`${args} never called, execute and cache`);
    cache[args] = func(...args); // put result into cache

    return cache[args];
  }
}


fibonacci = memoization(fibonacci); // wrap fibonacci function with memoization
console.log(fibonacci(15));

// 실행결과 
// 15 never called, execute and cache
// 14 never called, execute and cache
// 13 never called, execute and cache
// 12 never called, execute and cache
// 11 never called, execute and cache
// 10 never called, execute and cache
// 9 never called, execute and cache
// 8 never called, execute and cache
// 7 never called, execute and cache
// 6 never called, execute and cache
// 5 never called, execute and cache
// 4 never called, execute and cache
// 3 never called, execute and cache
// 2 never called, execute and cache
// 1 never called, execute and cache
// 2 already called, return from cache
// 3 already called, return from cache
// 4 already called, return from cache
// 5 already called, return from cache
// 6 already called, return from cache
// 7 already called, return from cache
// 8 already called, return from cache
// 9 already called, return from cache
// 10 already called, return from cache
// 11 already called, return from cache
// 12 already called, return from cache
// 13 already called, return from cache
// 610</code></pre>
<blockquote>
<p>memoization 필수 사항</p>
</blockquote>
<p>memoization을 구현할 때 필수적인 사항들이 있다.</p>
<ol>
<li><p>위에서 피보나치와 같이, memoization 함수로 감싸질 함수는 <code>순수(pure)</code>함수여야 한다. 캐싱을 하기 때문이다. 같은 Input이 들어오면 항상 같은 결과값이 나와야 한다.</p>
</li>
<li><p>memoization 기법은 성능을 얻는 대신에 공간(메모리)를 더 많이 사용하게 된다.
캐싱해야 하는 함수의 인자의 크기와 다양성 그리고 결과에 따라 캐싱해야 하는 데이터가 언제나 변하기 때문이다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[optimistic-ui]]></title>
            <link>https://velog.io/@sehee-xx/optimistic-ui</link>
            <guid>https://velog.io/@sehee-xx/optimistic-ui</guid>
            <pubDate>Wed, 22 Jun 2022 00:30:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/f7b704d1-a70f-4804-bb27-a705c58c4af7/image.png" alt="">
freeboard에서 구현했던 좋아요 기능을 생각해보자.</p>
<ol>
<li><p>사용자가 좋아요 버튼을 누르다.</p>
</li>
<li><p>onClick 함수가 실행되고 서버에 mutation 요청을 보낸다.</p>
</li>
<li><p>서버에 보낸 요청이 완료될 때까지 await으로 기다린다. </p>
</li>
<li><p>완료가 되었으면 refetch로 좋아요 갯수 데이터를 다시 가져온다.</p>
</li>
<li><p>가져온 데이터를 화면에 보여준다.</p>
</li>
</ol>
<p>단순히 좋아요를 하나 늘려주는 기능이지만 그 속에는 이렇게 많은 과정이 있다.
이런 부분을 개선하기 위해 사용할 수 있는 것이 Optimistic UI이다.</p>
<blockquote>
<p>Optimistic UI</p>
</blockquote>
<p>Optimistic UI는 단어 그대로 낙관적으로 생각하는 UI이다.</p>
<p>즉 좋아요 버튼을 클릭했을 때 성공했다고 간주하고 바로 갯수를 하나 늘려준다. </p>
<p>혹시라도 좋아요 기능이 실패한다면 조용히 원래 상태로 되돌린다.</p>
<p>Optimistic UI는 기본적으로 성공했다고 간주하는 것이기 때문에 
중요한 기능에서는 사용하지 못하고 좋아요 기능처럼 단순한 기능에 사용할 수 있다.</p>
<blockquote>
<p>Optimistic UI 적용 예제코드</p>
</blockquote>
<pre><code class="language-js">import { gql, useMutation, useQuery } from &quot;@apollo/client&quot;;
import { update } from &quot;lodash&quot;;

const FETCH_BOARD = gql`
  query fetchBoard($boardId: ID!) {
    fetchBoard(boardId: $boardId) {
      likeCount
    }
  }
`;

const LIKE_BOARD = gql`
  mutation likeBoard($boardId: ID!) {
    likeBoard(boardId: $boardId)
  }
`;

export default function OptimisticUIPage() {
  const [likeBoard] = useMutation(LIKE_BOARD);

  const { data } = useQuery(FETCH_BOARD, {
    variables: { boardId: &quot;612f4257abd89b00293adda7&quot; },
  });

  const onClickLike = () =&gt; {
    likeBoard({
      variables: { boardId: &quot;612f4257abd89b00293adda7&quot; },
      //   refetchQueries: [
      //     {
      //       query: FETCH_BOARD,
      //       variables: { boardId: &quot;612f4257abd89b00293adda7&quot; },
      //     },
      //   ],

      // 리패치 될때까지 기다려야함
      optimisticResponse: {
        likeBoard: data?.fetchBoard.likeCount + 1,
      },
      update(cache, { data }) {
        cache.writeQuery({
          query: FETCH_BOARD,
          variables: { boardId: &quot;612f4257abd89b00293adda7&quot; },
          data: {
            fetchBoard: {
              likeCount: data.likeBoard,
            },
          },
        });
      },
    });
  };

  return (
    &lt;&gt;
      &lt;div&gt;좋아요 갯수: {data?.fetchBoard.likeCount}&lt;/div&gt;
      &lt;button onClick={onClickLike}&gt;좋아요 올리기!&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>기존에는 refetchQueries를 통해 완료시 다시 화면을 그려줬지만 위 예제에서는 
optimisticResponse를 이용한다.</p>
<p>optimisticResponse에 적은 내용이 apollo client cache에 optimistic 버전으로 저장되면 apollo client는 이를 알아차려 저장된 데이터를 가지고 연관된 컴포넌트를 다시 랜더링 한다. </p>
<p>서버의 응답으로 진짜 데이터를 받으면, apollo client는 기존의 optimistic    버전을 지우고 서버에서 받은 데이터를 저장한다.</p>
<p>apollo client는 다시 이를 알아차리고 연관된 컴포넌트를 re-rendering한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LazyLoad vs PreLoad]]></title>
            <link>https://velog.io/@sehee-xx/LazyLoad-vs-PreLoad</link>
            <guid>https://velog.io/@sehee-xx/LazyLoad-vs-PreLoad</guid>
            <pubDate>Tue, 21 Jun 2022 10:46:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/d11791e5-e50d-4fb1-8a87-50877b26e08e/image.png" alt=""></p>
<blockquote>
<p>LazyLoad</p>
</blockquote>
<p>LazyLoad는 웹페이지를 불러올 때, 바로 필요하지 않은, 
즉 눈에 보여지지 않는 이미지들의 로딩 시점을 뒤로 미루는 것이다. 
사용자가 스크롤 등을 통해 이미지가 보여지는 시점이 되면 그때 로딩을 한다.</p>
<p>LazyLoad를 사용해야 하는 이유는 당장 불필요한 이미지들가지 웹페이지를 불러올 때, 
한번에 가지고 오면 로딩 시간과 성능 면에서 떨어지기 때문이다. </p>
<p>또한 이미지를 모두 받아오면 데이터를 모두 서버에 저장해야 하기 때문에 
비용적인 측면에서도 문제가 된다.</p>
<h3 id="react-lazy-load-라이브러리">react-lazy-load 라이브러리</h3>
<p>npm에서 react-lazy-load를 설치할 수 있다. </p>
<pre><code class="language-js">import React from &quot;react&quot;;
import LazyLoad from &quot;react-lazy-load&quot;;

export default function MyComponent() {
  return (
    &lt;div&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2017/09/25/13/12/cocker-spaniel-2785074_1280.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2019/08/19/07/45/dog-4415649_1280.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2016/10/31/14/55/rottweiler-1785760_1280.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2016/01/05/17/51/maltese-1123016_1280.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313_1280.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2014/12/10/05/50/english-bulldog-562723__480.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2016/11/21/00/47/view-1844110__480.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2018/05/17/06/22/dog-3407906__480.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2016/11/22/20/10/dog-1850465__480.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
      &lt;div className=&quot;filler&quot; /&gt;
      &lt;LazyLoad height={600} offsetVertical={300}&gt;
        &lt;img
          src=&quot;https://cdn.pixabay.com/photo/2019/11/07/08/40/dog-4608266__480.jpg&quot;
          width={500}
          height={500}
        /&gt;
      &lt;/LazyLoad&gt;
    &lt;/div&gt;</code></pre>
<p>10개의 이미지의 src를 직접 넣어주었고, 이 코드를 실행하면 드래그를 할 때마다 
네트워크 탭에서 각 화면에 필요한 이미지를 불러오는 것을 확인할 수 있다. </p>
<blockquote>
<p>PreLoad</p>
</blockquote>
<p>PreLoad는 브라우저에게 현제 페이지에서 필요한 리소스를 빠르게 가져오게 한다.
이미지를 미리 로드하는 경우에 편리하게 사용할 수 있다.</p>
<h3 id="preload의-문법">PreLoad의 문법</h3>
<pre><code class="language-js">&lt;link rel=&quot;preload&quot; as=&quot;...&quot;\&gt;</code></pre>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-js">let images = [];

function preload() {
  for(let i = 0; i &lt; preload.arguments.lenght; i++) {
    images[i] = new Image();
    images[i].src = preload.arguments.src;
  }
}

preload(
  &quot;./images1.jpg&quot;,
  &quot;./images2.jpg&quot;,
  &quot;./images3.jpg&quot;,
  &quot;./images4.jpg&quot;
)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Promise all]]></title>
            <link>https://velog.io/@sehee-xx/Promise-all</link>
            <guid>https://velog.io/@sehee-xx/Promise-all</guid>
            <pubDate>Tue, 21 Jun 2022 10:43:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sehee-xx/post/049bc539-5ff6-4854-9c06-931356e0062a/image.png" alt=""></p>
<blockquote>
<p>Promise.all()</p>
</blockquote>
<p><code>Promise.all()</code> 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 <code>Promise</code>를 반환한다.</p>
<p>주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부한다.</p>
<pre><code class="language-js">const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) =&gt; {
  setTimeout(resolve, 100, &#39;foo&#39;);
});

Promise.all([promise1, promise2, promise3]).then((values) =&gt; {
  console.log(values);
});
// expected output: Array [3, 42, &quot;foo&quot;]</code></pre>
<h2 id="구문">구문</h2>
<pre><code>Promise.all(iterable);</code></pre><h3 id="매개변수">매개변수</h3>
<p><code>iterable</code> : <code>Array</code>와 같이 순회(iterable) 가능한 객체</p>
<h3 id="반환-값">반환 값</h3>
<ul>
<li><p>매개변수로 주어진 순회 가능한 객체가 비어 있으면 이미 이행한 <code>Promise</code></p>
</li>
<li><p>객체에 프로미스가 없으면, 비동적으로 이행하는 Promise</p>
</li>
<li><p>단, Google Chrome 58은 이미 이행한 프로미스를 반환한다.</p>
</li>
<li><p>그렇지 않은 경우, 대기 중인 <code>Promise</code>, 결과로 반환하는 프로미스는 인자의 모든 프로미스가 이행하거나 어떤 프로미스가 거부할 때 (호출 스택이 비는 즉시) 비동기적으로 이행/거부한다.</p>
</li>
<li><p>&quot;<code>Promise.all()</code>의 동기성/비동기성&quot; 예제를 참고하면, 반환하는 프로미스의 이행 값은 매개변수로 주어진 프로미스의 순서와 일치하며, 완료 순서에 영향을 받지 않는다. </p>
<h2 id="설명">설명</h2>
<p>이 메소드는 여러 프로미스의 결과를 집계할 때 유용하게 사용할 수 있다.
일반적으로 다음 코드를 계속 실행하기 전에 서로 연관된 비동기 작업 여러 개가 모두 이행되어야 하는 경우에 사용된다. </p>
</li>
</ul>
<p>입력 값으로 들어온 프로미스 중 하나라도 거부 당하면 <code>Promise.all()</code>은 즉시 거부한다.
이에 비해, <code>Promise.allSettled()</code>가 반환하는 프로미스는 이행/거부 여부에 관계없이 주어진 프로미스가 모두 완료될 때까지 기다린다. 결과적으로, 주어진 <code>iterable</code>의 모든 프로미스와 함수의 결과 값을 최종적으로 반환한다.</p>
<h3 id="이행">이행</h3>
<p>반환한 프로미스의 이행 결과값은 (프로미스가 아닌 값을 포함하여) 매개변수로 주어진 
순회 가능한 객체에 포함된 모든 값을 담은 배열이다.</p>
<ul>
<li>빈 객체를 전달한 경우, (동기적으로) 이미 이행한 프로미스를 반환한다.</li>
<li>전달받은 모든 프로미스가 이미 이행되어 있거나 프로미스가 없는 경우,
비동기적으로 이행하는 프로미스를 반환한다.</li>
</ul>
<h3 id="거부">거부</h3>
<p>주어진 프로미스 중 하나라도 거부하면, 다른 프로미스의 이행 여부에 상관없이
첫 번째 거부 이유를 사용해 거부한다. </p>
<h2 id="예제">예제</h2>
<h3 id="promiseall-사용하기"><code>Promise.all()</code> 사용하기</h3>
<p><code>Promise.all</code>은 배열 내 모든 값의 이행(또는 첫 번째 거부)을 기다린다.</p>
<pre><code class="language-js">var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
    resolve(&quot;foo&quot;);
  }, 100);
});

Promise.all([p1, p2, p3]).then(values =&gt; {
  console.log(values); // [3, 1337, &quot;foo&quot;]
});</code></pre>
<p>순회 가능한 객체에 프로미스가 아닌 값이 들어있다면 무시하지만, 
이행 시 결과 배열에는 포함한다.</p>
<pre><code class="language-js">// 매개변수 배열이 빈 것과 동일하게 취급하므로 이행함
var p = Promise.all([1,2,3]);
// 444로 이행하는 프로미스 하나만 제공한 것과 동일하게 취급하므로 이행함
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// 555로 거부하는 프로미스 하나만 제공한 것과 동일하게 취급하므로 거부함
var p3 = Promise.all([1,2,3, Promise.reject(555)]);

// setTimeout()을 사용해 스택이 빈 후에 출력할 수 있음
setTimeout(function() {
    console.log(p);
    console.log(p2);
    console.log(p3);
});

// 출력
// Promise { &lt;state&gt;: &quot;fulfilled&quot;, &lt;value&gt;: Array[3] }
// Promise { &lt;state&gt;: &quot;fulfilled&quot;, &lt;value&gt;: Array[4] }
// Promise { &lt;state&gt;: &quot;rejected&quot;, &lt;reason&gt;: 555 }</code></pre>
<br>

<h3 id="promiseall-의-동기성비동기성"><code>Promise.all()</code> 의 동기성/비동기성</h3>
<p>아래 예제는 <code>Promise.all</code>의 비동기성(주어진 인자가 빈 경우엔 동기성)을 보인다.</p>
<pre><code class="language-js">// Promise.all을 최대한 빨리 완료시키기 위해
// 이미 이행된 프로미스로 배열을 만들어 인자로 전달
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

var p = Promise.all(resolvedPromisesArray);
// 실행 즉시 p의 값을 기록
console.log(p);

// 호출 스택을 비운 다음 실행하기 위해 setTimeout을 사용
setTimeout(function() {
    console.log(&#39;the stack is now empty&#39;);
    console.log(p);
});

// 로그 출력 결과 (순서대로):
// Promise { &lt;state&gt;: &quot;pending&quot; }
// the stack is now empty
// Promise { &lt;state&gt;: &quot;fulfilled&quot;, &lt;value&gt;: Array[2] }</code></pre>
<p><code>Promise.all()</code>이 거부하는 경우에도 동일한 일이 발생한다.</p>
<pre><code class="language-js">var mixedPromisesArray = [Promise.resolve(33), Promise.reject(44)];
var p = Promise.all(mixedPromisesArray);
console.log(p);
setTimeout(function() {
    console.log(&#39;the stack is now empty&#39;);
    console.log(p);
});

// 출력
// Promise { &lt;state&gt;: &quot;pending&quot; }
// the stack is now empty
// Promise { &lt;state&gt;: &quot;rejected&quot;, &lt;reason&gt;: 44 }</code></pre>
<p>그러나, <code>Promise.all</code>은 주어진 순회 가능한 객체가 비어있는 경우에만 
동기적으로 이행된다.</p>
<pre><code class="language-js">// 즉시 이행함
var p = Promise.all([]); 
// 프로미스가 아닌 값은 무시하지만 비동기적으로 실행됨
var p2 = Promise.all([1337, &quot;hi&quot;]); 
console.log(p);
console.log(p2);
setTimeout(function() {
    console.log(&#39;the stack is now empty&#39;);
    console.log(p2);
});

// 출력
// Promise { &lt;state&gt;: &quot;fulfilled&quot;, &lt;value&gt;: Array[0] }
// Promise { &lt;state&gt;: &quot;pending&quot; }
// the stack is now empty
// Promise { &lt;state&gt;: &quot;fulfilled&quot;, &lt;value&gt;: Array[2] }</code></pre>
<h3 id="promiseall-의-실패-우선성"><code>Promise.all()</code> 의 실패 우선성</h3>
<p><code>Promise.all()</code>은 배열 내 요소 중 어느 하나라도 거부하면 즉시 거부한다. 예를 들어, 일정 시간이 지난 이후 이행하는 네 개의 프로미스와, 즉시 거부하느 ㄴ하나의 프로미스를 전달한다면 <code>Promise.all()</code>도 즉시 거부한다.</p>
<pre><code class="language-js">var p1 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&#39;하나&#39;), 1000);
});
var p2 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&#39;둘&#39;), 2000);
});
var p3 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&#39;셋&#39;), 3000);
});
var p4 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&#39;넷&#39;), 4000);
});
var p5 = new Promise((resolve, reject) =&gt; {
  reject(new Error(&#39;거부&#39;));
});


// .catch 사용:
Promise.all([p1, p2, p3, p4, p5])
.then(values =&gt; {
  console.log(values);
})
.catch(error =&gt; {
  console.log(error.message)
});

// 콘솔 출력값:
// &quot;거부&quot;</code></pre>
<p>발생할 수 있는 거부를 사전에 처리해 동작 방식을 바꿀 수 있다.</p>
<pre><code class="language-js">var p1 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; resolve(&#39;p1_지연_이행&#39;), 1000);
});

var p2 = new Promise((resolve, reject) =&gt; {
  reject(new Error(&#39;p2_즉시_거부&#39;));
});

Promise.all([
  p1.catch(error =&gt; { return error }),
  p2.catch(error =&gt; { return error }),
]).then(values =&gt; {
  console.log(values[0]) // &quot;p1_지연_이행&quot;
  console.log(values[1]) // &quot;Error: p2_즉시_거부&quot;
})
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Observable]]></title>
            <link>https://velog.io/@sehee-xx/Observable</link>
            <guid>https://velog.io/@sehee-xx/Observable</guid>
            <pubDate>Mon, 20 Jun 2022 09:39:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>RxJS</p>
</blockquote>
<p>RxJS는 ReactiveX의 JavaScript를 위한 라이브러리이다.</p>
<p>ReactiveX는 Observer 패턴, 함수형 프로그래밍을 조합하여 제공한다.</p>
<p>ReactiveX는 이벤트를 Observable로 추상화하여 시간에 따른 스트림으로 간주할 수 있게 한다. Observable이 Observer에게 전달되기 전, operator를 이용해 재가공이 가능하다. </p>
<p>RxJS를 사용해 비동기 코드를 직관적이고 가독성 좋게 작성할 수 있다.</p>
<blockquote>
<p>Reactive Programing (반응형 프로그래밍)</p>
</blockquote>
<p>반응형 프로그래밍은 이벤트나 배열과 같은 데이터 스트림을 비동기로 처리해 변화에 유연하게 반응하는 프로그래밍 패러다임이다</p>
<p>반응형 프로그래밍은 외부에서 명령하지 않고, 응답이 오면 그때그때 반응해서 처리하는 Push 시나리오를 채택하고 있으며, 데이터를 가져오기 위해서는 <code>subscribe</code> 해야 한다.</p>
<p><code>Vanilar JS</code>에서 이벤트를 처리하는 방식인 <code>addEventListener</code>가 이와 같은 방식이다.</p>
<p><code>targetElement</code>가 이벤트를 <code>subscribe</code>하고 이벤트가 발생하면 
<code>event function</code>이 작동한다.</p>
<p>여기서 Observer 패턴을 볼 수 있다. </p>
<pre><code>Observer pattern은 객체의 상태 변화를 관찰하는 옵저버들의 목록을 객체에 등록하여 
상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 
목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.</code></pre><p><code>addEventListener</code>로 이벤트를 구독하는 옵저버를 등록하고 이벤트 발행 시 
<code>event function</code>을 호출하여 옵저버에게 알리는 방식이다.</p>
<p>하지만, 이 event function이 외부 변수를 참조하고 있다면 순수성을 잃고 
<code>side-effect</code>가 발생할 수도 있다.</p>
<p>이를 RxJS로 처리하면 다음과 같이 순수함수로 작성할 수 있다.</p>
<pre><code class="language-js">import {fromEvent} from &#39;rxjs&#39;;
import {scan} from &#39;rxjs/operators&#39;;

fromEvent(document, &#39;click&#39;)
 .pipe(scan(count =&gt; count + 1, 0))
 .subscribe(console.log(`${count}번 클릭!`));</code></pre>
<blockquote>
<p>Observable</p>
</blockquote>
<p><code>Observable</code>은 시간의 흐름에 따라 발생하는 이벤트들의 스트림이라고 볼 수 있다.</p>
<p>pipeline을 설치해서 여러 이벤트나 데이터를 Observer에게 보낸다.</p>
<p>스트림은 관념적으로 뒤에 <code>$</code>를 붙인다.</p>
<p><code>Observable.create()</code>나 <code>new Observable()</code>로 생성할 수 있다.</p>
<p>Observable의 <code>subscribe()</code>를 통해 Observer 객체를 생성하고 
만들어진 Observer 객체로 함수를 호출해 값을 발행한다.</p>
<p>Observable을 subscribe하고 반환받은 객체로 구독을 해제할 수 있다.</p>
<pre><code class="language-js">const subscription = observable.subscribe(x =&gt; console.log(x));
subscription.unsubscribe();</code></pre>
<p>Observable의 <code>unsubscribe()</code>로 구독을 해제하면 Observable을 실행하지 않고 멈춘다.</p>
<p>Observer는 <code>next</code>, <code>error</code>, <code>complete</code> 3가지 함수로 구성된 객체다.</p>
<ul>
<li>next : Observable subscriber에게 데이터를 전달</li>
<li>complete : Observable subscriber에게 완료를 알리면 next가 데이터 전달을 멈춤</li>
<li>error : Observable subscriber에게 에러를 전달하면 next, complete 발생 X</li>
</ul>
<p>Observable은 <code>생성</code>, <code>구독</code>, <code>실행</code>, <code>구독해제</code>의 Life-Cycle을 가진다.</p>
<ul>
<li>생성 : Observable.create()</li>
<li>구독 : Observable.subscribe()</li>
<li>실행 : observer.next()</li>
<li>구독해제 : observer.complete() / Observable.unsubscribe()</li>
</ul>
<blockquote>
<p>Subject</p>
</blockquote>
<p>Subject는 멀티캐스팅을 지원하는 객체다.</p>
<p>일반적으로 하나의 Observable에 하나의 Observer를 등록하지만,</p>
<p>Subject는 여러 Observer를 생성하고 데이터를 전달받을 수 있다.</p>
<p>Subject는 Observable이면서 Observer이기도 하다.</p>
<p>따라서 여러 Observable을 구독할 수 있고
<code>next</code>,<code>error</code>,<code>complete</code> 함수를 호출할 수 있다.</p>
<pre><code class="language-js">const subject = new Subject();
subject.subscribe({
    next: (x) =&gt; console.log(`observer A : ${x}`)
});
subject.subscribe({
    next: (x) =&gt; console.log(`observer B : ${x}`)
});
subject.next(1);
subject.next(2);

/*
observer A : 1
observer B : 1

observer A : 2
observer B : 2
*/</code></pre>
<blockquote>
<p>Scheduler</p>
</blockquote>
<p>JavaScript는 싱글 스레드, 이벤트 루프로 동작하지만 
RxJS의 Scheduler를 사용하여 이벤트 루프에 스케줄링이 가능하다</p>
<ul>
<li><code>asyncScheduler</code> : setTimeout과 비슷하다.</li>
<li><code>asapScheduler</code> : 다음 이벤트 루프 실행한다.</li>
<li><code>queueScheduler</code> : 스케쥴러에 전달된 state를 처리한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[token, XSS, CSRF]]></title>
            <link>https://velog.io/@sehee-xx/token-XSS-CSRF</link>
            <guid>https://velog.io/@sehee-xx/token-XSS-CSRF</guid>
            <pubDate>Thu, 16 Jun 2022 08:39:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>token</p>
</blockquote>
<h3 id="토큰의-종류">토큰의 종류</h3>
<p>JWT에는 <code>Access Token</code>, <code>Refresh Token</code> 두 가지 종류의 토큰이 있다.</p>
<p>Access Token을 통해서 민감한 정보에 접근할 수 있으며
두 가지 토큰 중에서 실제 권한에 접근하는 토큰이다.</p>
<p>짧은 유효기간을 가지며 Refresh Token을 통해서 만료된 Access Token을 발급받을 수 있다.</p>
<p>Access Token보다 긴 유효기간을 가지며 이때, 유저는 다시 로그인하지 않아도 된다.</p>
<p>Refresh Token이 탈취당하면 위험하기 때문에 정보에 민감한 사이트는 Refresh Token을 사용하지 않을 수도 있다.</p>
<h3 id="토큰-기반-인증의-절차">토큰 기반 인증의 절차</h3>
<p>클라이언트가 서버에 아이디와 비밀번호를 담아 로그인 요청을 보낸다.</p>
<p>아이디와 비밀번호가 일치하는지 확인한 뒤에 
클라이언트에 보낼 access와 refresh 두 가지 토큰을 만든다. 
이 두 토큰에 담길 정보(payload)는 같을 필요는 없다.</p>
<p>토큰을 클라이언트에 보내고, 클라이언트는 토큰을 저장한다.
저장위치: 쿠키, state, local Storage 등</p>
<p>클라이언트가 민감한 정보에 대한 요청을 보낼 때, HTTP의 
req.headers.authorization에 토큰을 담아 보낸다. </p>
<p>서버가 토큰을 해독하여 유효한 토큰이라면 클라이언트의 요청을 처리한 뒤에 응답을 보낸다.
유효하지 않은 토큰이라면 에러가 발생한다.</p>
<blockquote>
<p>Statelessness &amp; Scalability (무상태성 &amp; 확장성)</p>
</blockquote>
<p>서버는 클라이언트의 정보를 저장하지 않아도 된다.
클라이언트는 새로운 요청을 보낼 때마다 헤더에 토큰을 담아 보내면 된다.</p>
<p>만약, 여러 개의 서버를 가진 서비스라면 클라이언트가 가진 하나의 토큰으로 여러 개의 서버에 인증을 할 수 있기 때문에 편리하다. 또한 암호화한 토큰을 사용하고, 암호화 키를 노출할 필요가 없어서 안전하다. </p>
<p>토큰을 확인하는 서버가 토큰을 만들 필요는 없기에 어디에서나 토큰을 만들 수 있다.</p>
<p>토큰용 서버를 만들 수 있고, 다른 회사에 토큰과 관련된 작업을 맡길 수도 있다. </p>
<p>권한을 부여하는데 용이하다.</p>
<p>토큰의 payload 안에 어떤 정보에 접근할 수 있는지 정할 수 있다.
node.js의 환경에서 jsonwebtoken 라이브러리를 사용하여 토큰을 생성할 수 있다.</p>
<blockquote>
<p>XSS</p>
</blockquote>
<p>크로스 사이트 스크립팅은 웹 어플리케이션에서 어플리케이션 관리자가 아닌 악성 유저가 일부러 심어놓은 악성 코드를 실행하게 함으로써 발생하는 취약점이다. 악의적인 사용자는 공격하려는 사이트에 다른 유저로 하여금 악의적인 스크립트를 실행하도록 유도하여 정보를 편취하거나 의도치 않은 행동을 유발하게 한다.</p>
<h3 id="reflected-xss">Reflected XSS</h3>
<p>해커가 사용자로 하여금 악성 링크를 클릭하도록 유도하여 정보를 탈취하는 방법이다.
일반적으로 메일 또는 문자 등에 악성 URL을 전송한 후, 사용자가 해당 링크를 클릭하면 
악성 스크립트가 실행된다.</p>
<p>일반적으로 사용자가 해당 URL을 읽고 잘못된 URL이라고 판단할 수 있기 때문에 
해당 URL을 난독화하여 알아보지 못하도록 만든 후, 접근하도록 유도한다. </p>
<h3 id="stored-xss">Stored XSS</h3>
<p>게시판, 쪽지함과 같이 저장된 형태의 거시글에 많이 사용되며, 게시글을 작성할 때 
악성 스크립트를 심어서 올려서 해당 스크립트 링크를 클릭하거나 혹은 이미지 태그를 클랙하도록 유도해서 악성 스크립트를 실행시킨다.</p>
<p>이를 통해 지속적인 (Persistent) 공격을 할 수 있는 방식으로,
일반적으로 stored XSS가 막혀있는 페이지의 경우, 스크립트 코드가 포함된 게시글은 
단순히 텍스트로 인식해서 출력한다.</p>
<h3 id="dom-based-xss">DOM based XSS</h3>
<p>사용자 브라우저에서 서버로 요청하는 과정이 아니라, 브라우저 상에서 DOM을 구축하는 과정에서 해커가 해당 DOM 환경을 악의적으로 수정해서 이를 통해 예상치 못한 코드를 실행하도록 만드는 공격 방식이다.</p>
<blockquote>
<p>CSRF</p>
</blockquote>
<p><code>Cross-site request forgery</code>, 사이트 간 요청 위조</p>
<p>사이트 간 요청 위조는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[callback]]></title>
            <link>https://velog.io/@sehee-xx/callback</link>
            <guid>https://velog.io/@sehee-xx/callback</guid>
            <pubDate>Wed, 15 Jun 2022 08:42:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Callback Function이란?</p>
</blockquote>
<ul>
<li>파라미터로 함수를 전달하는 함수</li>
<li>콜백함수란 파라미터로 함수를 전달받아, 함수의 내부에서 실행하는 함수이다.<pre><code class="language-js">let number = [1, 2, 3, 4, 5];
</code></pre>
</li>
</ul>
<p>number.forEach(x =&gt; {
  console.log(x * 2);
}); // output: 2 4 6 8 10</p>
<pre><code>콜백함수는 자주 사용된다.
예로, `forEach` 함수의 경우 함수 안에 익명의 함수를 넣어서 `forEach`문을 동작시킨다.

&gt; 콜백함수의 사용 원칙

### 익명의 함수 사용
```js
let number = [1, 2, 3, 4, 5];

number.forEach(function(x) {
  console.log(x * 2);
});</code></pre><p>위의 예제를 화살표 함수에서 일반 함수로 바꿔보았다.
콜백함수는 이름이 없는 <code>익명의 함수</code>를 사용한다. 
함수의 내부에서 실행되기 때문에 이름을 붙이지 않아도 된다. </p>
<h3 id="함수의-이름만-넘기기">함수의 이름(만) 넘기기</h3>
<pre><code class="language-js">function whatYourName(name, callback) {
  console.log(&#39;name: &#39;, name);
  callback();
}

function finishFunc() {
  console.log(&#39;finish function&#39;);
}

whatYourName(&#39;sehee&#39;, finishFunc);

// output
// name: sehee
// finish function      </code></pre>
<p>자바스크립트는 <code>null</code>과 <code>undefined</code> 타입을 제외하고 모든 것을 객체로 다룬다.</p>
<p>함수를 변수 또는 다른 함수의 변수처럼 사용할 수 있다.</p>
<p>함수를 콜백함수로 사용할 경우, 함수의 이름만 넘겨주면 된다.</p>
<p>즉, 위의 예제에서 함수를 인자로 사용할 때, 
<code>callback</code>, <code>finishFunc</code>처럼 <code>()</code>를 붙일 필요가 없다.</p>
<h3 id="전역변수-지역변수-콜백함수의-파라미터로-전달-가능">전역변수, 지역변수 콜백함수의 파라미터로 전달 가능</h3>
<ul>
<li>전역변수(Global variable) : 함수 외부에서 선언한 변수</li>
<li>지역변수(Local variable) : 함수 내부에서 선언한 변수<pre><code class="language-js">let fruit = &#39;apple&#39;; // Global variable
</code></pre>
</li>
</ul>
<p>function callbackFunc(callback) {
  let vegetable = &#39;carrot&#39;; // Local variable
  callback(vegetable);
}</p>
<p>function eat(vegetable) {
  console.log(<code>fruit: ${fruit} / vegetable: ${vegetable}</code>);
}</p>
<p>callbackFunc(eat);</p>
<p>// output
// fruit: apple / vegetable: carrot</p>
<pre><code>
&gt; 콜백함수 주의할 점

### this를 사용한 콜백함수
```js
let userData = {
    signUp: &#39;2020-10-06 15:00:00&#39;,
    id: &#39;minidoo&#39;,
    name: &#39;Not Set&#39;,
    setName: function(firstName, lastName) {
        this.name = firstName + &#39; &#39; + lastName;
    }
}

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName);
}

getUserName(&#39;PARK&#39;, &#39;MINIDDO&#39;, userData.setName);

console.log(&#39;1: &#39;, userData.name);
console.log(&#39;2: &#39;, window.name);

&lt;output&gt;
1: Not Set
2: PARK MINIDDO</code></pre><p>첫 번째 콘솔의 값이 <code>PAKR MINIDDO</code> 이기를 기대했지만, <code>Not Set</code>이 출력된다.</p>
<p><code>setName()</code> 함수가 실행되기 전의 <code>name</code> 값이 나오는 것인데, 
이는 <code>getUserName()</code> 이 전역 함수이기 때문이다.</p>
<p>즉, <code>setName()</code>에서 사용된 <code>this</code> 객체가 <code>window</code>라는 글로벌 객체를 가리킨다.
따라서 this를 보호할 수 있도록 콜백함수를 만들어야 한다.</p>
<blockquote>
<p>해결 방안: <code>call()</code>과 <code>apply()</code>를 사용하여 <code>this</code>를 보호할 수 있다.</p>
</blockquote>
<ul>
<li><code>call()</code> : 첫 번째 인자로 <code>this</code> 객체 사용, 나머지 인자들은 <code>,</code> 로 구분</li>
<li><code>apply()</code> : 첫 번째 인자로 <code>this</code> 객체 사용, 나머지 인자들은 배열 형태로 전달<pre><code class="language-js">// call
</code></pre>
</li>
</ul>
<p>...</p>
<p>function getUserName(firstName, lastName, callback, obj) {
    callback.call(obj, firstName, lastName);    - (1)
}</p>
<p>getUserName(&#39;PARK&#39;, &#39;MINIDDO&#39;, userData.setName, userData);    - (2)</p>
<p>console.log(userData.name);</p>
<output>
PARK MINIDDO
```
( 2 ) 에서 마지막 인자에 담긴 `userData` 는 ( 1 )에서 call 함수의 첫번째 인자로 전달된다.
즉, `call()` 에 의해서 `userData`에 `this` 객체가 매핑된다.

<p><code>apply()</code> 도 인자를 배열로 전달한다는 점만 다르고 동일하게 작동한다.</p>
<pre><code class="language-js">// apply

...

function getUserName(firstName, lastName, callback, obj) {
    callback.apply(obj, [firstName, lastName]);
}

getUserName(&#39;PARK&#39;, &#39;MINIDDO&#39;, userData.setName, userData);

console.log(userData.name);

&lt;output&gt;
PARK MINIDDO</code></pre>
<h3 id="콜백지옥-callback-hell">콜백지옥 (Callback Hell)</h3>
<p>비동기 호출이 자주 일어나는 프로그램의 경우 &#39;콜백 지옥&#39;이 발생한다.</p>
<p>함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 
감당하기 힘들어질 정도로 깊어지는 현상이다.</p>
<pre><code class="language-js">function add(x, callback) {
    let sum = x + x;
    console.log(sum);
    callback(sum);
}

add(2, function(result) {
    add(result, function(result) {
        add(result, function(result) {
            console.log(&#39;finish!!&#39;);
        })
    })
})

&lt;output&gt;
4
8
16
finish!!</code></pre>
<blockquote>
<p>해결 방안 : <code>Promise</code>를 사용하여 콜백지옥을 탈출할 수 있다.</p>
</blockquote>
<pre><code class="language-js">function add(x) {
    return new Promise((resolve, reject) =&gt; {
        let sum = x + x;
        console.log(sum);
        resolve(sum);
    })
}

add(2).then(result =&gt; {
    add(result).then(result =&gt; {
        add(result).then(result =&gt; {
            console.log(&#39;finish!!&#39;);
        })
    })
})

&lt;output&gt;
4
8
16
finish!!</code></pre>
<p><code>Promise</code> 는 정상 수행 후 <code>resolve</code>, 실패 후 <code>reject</code> 가 실행된다.
<code>callback</code>을 사용했던 것과 마찬가지로 <code>resolve</code>에 값을 담아 전달한다.</p>
<p>하지만, 이 패턴도 그리 좋은 방법은 아니다. 
결국 콜백지옥처럼 들여쓰기 수준을 감당하기 힘들어진다.</p>
<blockquote>
<p>해결 방안 : <code>Promise</code>의 <code>return</code>을 사용하여 <code>Promise Hell</code>을 탈출할 수 있다.</p>
</blockquote>
<p>&quot;프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하지는 않고, 대신 프로미스를 반환해서 미래의 어떤 시점에 결과를 제공합니다.&quot;</p>
<p>MDN 에서 정의하고 있는 <code>Promise</code>에 대한 설명이다.</p>
<p>프로미스는 비동기 호출 시, 마치 동기 호출 처럼 값을 반환할 수 있다는 문구에 집중해보자.
즉, <code>resolve</code>를 통해 전달 받은 값을 반환하여 사용해야 한다.</p>
<pre><code class="language-js![](https://velog.velcdn.com/images/sehee-xx/post/69c80907-94e9-44a8-9595-a5cee3817007/image.png)">
function add(x) {
    return new Promise((resolve, reject) =&gt; {
        let sum = x + x;
        console.log(sum);
        resolve(sum);
    })
}

add(2).then(result =&gt; {
    return add(result);
}).then(result =&gt; {
    return add(result);
}).then(result =&gt; {
    console.log(&#39;finish!!&#39;);
})

&lt;output&gt;
4
8
16
finish!!</code></pre>
]]></description>
        </item>
    </channel>
</rss>