<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>khakha's dev log</title>
    <link>https://khakha.tistory.com/</link>
    <description>박하린의 개발 일지</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 23:11:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>khakhalog</managingEditor>
    <image>
      <title>khakha's dev log</title>
      <url>https://tistory1.daumcdn.net/tistory/5074241/attach/e87de7fe98854a28a10dce2b45ec7ccf</url>
      <link>https://khakha.tistory.com</link>
    </image>
    <item>
      <title>99클럽 코테 스터디 2일차 TIL - 백준 11561. 징검다리</title>
      <link>https://khakha.tistory.com/entry/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-11561-%EC%A7%95%EA%B2%80%EB%8B%A4%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;백준 11561. 징검다리 / python / Silver 3 / 1h+&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 및 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11561&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/11561&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730215364111&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;T = int(input())
tc = []

for _ in range(T):
    tc.append(int(input()))

for n in tc:
    start = 1
    end = n
    answer = 0

    while start &amp;lt;= end:
        mid = (start + end) // 2

        if (mid * (mid+1)//2) &amp;lt;= n:
            start = mid + 1
            answer = mid
        else:
            end = mid - 1

    print(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;징검다리가 n개 일 때, 처음 건넌 거리보다 다음번부터는 최소 1 이상 넘는 거리를 뛰어야 하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1 + 2 + 3 + ... + k 형태의 등차수열 합을 만족하는 건넌 횟수가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등차수열의 합 구하는 공식은 k(k+1)/2 이므로 이분탐색으로 n에 가장 가까운 k를 구해주면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분탐색인지 알고 풀면 어렵지 않은것 같은데 실제 코테 칠 때는 어떤 알고리즘인지 떠오르기가 쉽지 않을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/BinarySearch</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/123</guid>
      <comments>https://khakha.tistory.com/entry/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-11561-%EC%A7%95%EA%B2%80%EB%8B%A4%EB%A6%AC#entry123comment</comments>
      <pubDate>Wed, 30 Oct 2024 00:38:41 +0900</pubDate>
    </item>
    <item>
      <title>99클럽 코테 스터디 1일차 TIL - 백준 1072. 게임</title>
      <link>https://khakha.tistory.com/entry/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-1072-%EA%B2%8C%EC%9E%84</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;백준 1072. 게임 / node.js / Silver 3 / 51m&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 및 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1072&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1072&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730123598235&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const filePath = process.platform === &quot;linux&quot; ? &quot;./dev/stdin&quot; : &quot;input.txt&quot;;
let [x, y] = require(&quot;fs&quot;)
  .readFileSync(filePath)
  .toString()
  .trim()
  .split(&quot; &quot;)
  .map((num) =&amp;gt; +num);

let z = Math.floor((y * 100) / x);

let left = 1;
let right = 1000000000;
let answer = Number.MAX_SAFE_INTEGER;

while (left &amp;lt;= right) {
  let mid = parseInt((left + right) / 2);
  let newZ = Math.floor(((y + mid) * 100) / (x + mid));

  if (z !== newZ) {
    answer = Math.min(answer, mid);
    right = mid - 1;
  } else {
    left = mid + 1;
  }
}

if (answer === Number.MAX_SAFE_INTEGER) console.log(-1);
else console.log(answer);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;이분 탐색 알고리즘&quot; href=&quot;https://khakha.tistory.com/entry/%EC%9D%B4%EB%B6%84-%ED%83%90%EC%83%89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이분 탐색 알고리즘&lt;/a&gt;을 사용해서 풀이하였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;입력 값 변수에 저장 후, 초기 z 값 계산해주기&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= x &amp;lt;= 1,000,000,000 이므로 left, right를 각 최소 최대값으로 초기화 해준다.&amp;nbsp;&lt;br /&gt;answer는 게임을 총 몇 번 더 진행할지 모르므로 가장 큰 정수값으로 초기화 해준다.&lt;/li&gt;
&lt;li&gt;문제에서 z가 최초로 변경되는 시점을 구하라고 했으니 처음 승률 !== 현재 승률 인 경우 mid와 answer 중 최소값을 구해주고 right를 줄인다. 그렇지 않으면 left를 늘려서 게임을 더 진행한다.&lt;/li&gt;
&lt;li&gt;while문을 빠져나왔는데 answer가 처음과 동일하다면 승률이 절대 변할수 없는 경우라고 간주하여 -1을 출력하고 answer를 구했다면 answer를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 오랜만에 푸려니 이분 탐색 알고리즘이 잘 기억이 나지 않아 구글링을 하면서 풀게 된 점이 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음번에 풀 때는 검색 없이 풀 수 있기를..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/BinarySearch</category>
      <category>99클럼</category>
      <category>Til</category>
      <category>개발자취업</category>
      <category>코딩테스트준비</category>
      <category>항해99</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/121</guid>
      <comments>https://khakha.tistory.com/entry/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%B1%EC%A4%80-1072-%EA%B2%8C%EC%9E%84#entry121comment</comments>
      <pubDate>Mon, 28 Oct 2024 23:17:08 +0900</pubDate>
    </item>
    <item>
      <title>이분 탐색</title>
      <link>https://khakha.tistory.com/entry/%EC%9D%B4%EB%B6%84-%ED%83%90%EC%83%89</link>
      <description>&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;이분 탐색이란&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분탐색(Binary Search)은 정렬된 배열에서 원하는 값을 빠르게 찾기 위한 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;span&gt;O(log⁡n)&lt;/span&gt;&lt;/span&gt;이기 때문에, 탐색 속도가 매우 빠르고 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분탐색의 핵심 개념은 탐색 범위를 반씩 줄여가며 원하는 값을 찾아가는 데 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;이분 탐색의 작동 원리&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 설정&lt;/b&gt;: 탐색 범위의 시작점 left와 끝점 right를 설정합니다. 처음에는 left는 배열의 첫 번째 인덱스, right는 마지막 인덱스로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중간값 계산&lt;/b&gt;: left와 right의 중간 인덱스 mid를 계산합니다. mid = (left + right) // 2.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값 비교&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간값 arr[mid]가 찾고자 하는 값 target과 일치하면, mid를 반환하여 탐색을 종료합니다.&lt;/li&gt;
&lt;li&gt;arr[mid]가 target보다 작으면, target은 중간값 오른쪽에 있을 가능성이 있으므로 left = mid + 1로 설정하여 탐색 범위를 오른쪽 절반으로 좁힙니다.&lt;/li&gt;
&lt;li&gt;arr[mid]가 target보다 크면, target은 중간값 왼쪽에 있을 가능성이 있으므로 right = mid - 1로 설정하여 탐색 범위를 왼쪽 절반으로 좁힙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반복&lt;/b&gt;: 위 과정을 left가 right보다 작거나 같을 때까지 반복합니다. 탐색 범위가 더 이상 없을 경우 target은 배열에 없는 것으로 간주하고 실패를 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;예시 코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1730124304483&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function binarySearch(arr, target) {
    let left = 0;
    let right = arr.length - 1;

    while (left &amp;lt;= right) {
        const mid = Math.floor((left + right) / 2);

        if (arr[mid] === target) {
            return mid; // 타겟 값이 배열의 중간 값에 있으면 인덱스를 반환
        } else if (arr[mid] &amp;lt; target) {
            left = mid + 1; // 타겟이 더 크면 왼쪽 포인터를 오른쪽으로 이동
        } else {
            right = mid - 1; // 타겟이 더 작으면 오른쪽 포인터를 왼쪽으로 이동
        }
    }

    return -1; // 찾지 못하면 -1 반환
}

// 예제 사용
const sortedArray = [1, 3, 5, 7, 9, 11];
const target = 7;
const result = binarySearch(sortedArray, target);

console.log(result); // 인덱스 3을 출력 (타겟 값이 위치한 인덱스)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;while문 안의 로직은 요구사항에 맞게 변경해서 원하는 target 값을 찾아내도록 수정할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;주의할 점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간 복잡도가 &lt;span&gt;&lt;span&gt;O(log⁡n)으로 리스트 또는 데이터가 큰 경우 빠르게 탐색가능하지만, 데이터가 순차적 혹은 정렬된 상태여야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/BinarySearch</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/120</guid>
      <comments>https://khakha.tistory.com/entry/%EC%9D%B4%EB%B6%84-%ED%83%90%EC%83%89#entry120comment</comments>
      <pubDate>Mon, 28 Oct 2024 23:06:52 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js 14] Layouts</title>
      <link>https://khakha.tistory.com/entry/Nextjs-14-Layouts</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js app 라우터 버전에서 layout 파일은 다음과 같은 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 route 간에 공유되는 UI&lt;/li&gt;
&lt;li&gt;페이지 이동시 state를 유지하고 interactive 요소를 유지하며 리렌더링이 일어나지 않는다.&lt;/li&gt;
&lt;li&gt;레이아웃은 중첩해서 존재할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃은 기본적으로 layout.js 파일에서 React 컴포넌트를 정의하게 된다.&lt;br /&gt;해당 컴포넌트는 렌더링 중에 자식 레이아웃(존재하는 경우)이나 페이지로 채워질 children prop을 허용해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;  app
├──   layout.tsx
│   └──   dashboard   
│       ├──   layout.tsx
│       ├──   page.tsx
│       └──   settings
└────────────└──  page.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같은 구조로 &lt;code&gt;/dashboard&lt;/code&gt; 페이지와 &lt;code&gt;/dashboard/settings&lt;/code&gt; 페이지 간에 레이아웃이 공유되도록 하려면 props로 &lt;code&gt;children&lt;/code&gt;을 정의해줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/dashboard/layout.tsx

export default function DashboardLayout({
  children, // will be a page or nested layout
}: {
  children: React.ReactNode
}) {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;nav&amp;gt;&amp;lt;/nav&amp;gt;
      {children}
    &amp;lt;/section&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Root Layout (필수)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app 폴더의 최상위에 필수로 존재하는 Root Layout은 모든 라우트에 적용되는 레이아웃이다.&lt;br /&gt;이 루트 레이아웃에는 &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt; 태그를 포함한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &amp;lt;html lang=&quot;ko&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        {/* Layout UI */}
        &amp;lt;main&amp;gt;{children}&amp;lt;/main&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729153057722&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Pages and Layouts | Next.js&quot; data-og-description=&quot;Create your first page and shared layout with the App Router.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/crRNnv/hyXlTVtOnB/lKJQeKE2HM2gSI3iijx3s0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/8sDst/hyXlKK0ajj/1IZ5iQPkq0eIdm93w98nNk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/ATg9S/hyXhMQMRbS/8FLI7Ge0UZgN4kuPx3zGS1/img.png?width=1600&amp;amp;height=606&amp;amp;face=0_0_1600_606&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/crRNnv/hyXlTVtOnB/lKJQeKE2HM2gSI3iijx3s0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/8sDst/hyXlKK0ajj/1IZ5iQPkq0eIdm93w98nNk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/ATg9S/hyXhMQMRbS/8FLI7Ge0UZgN4kuPx3zGS1/img.png?width=1600&amp;amp;height=606&amp;amp;face=0_0_1600_606');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Routing: Pages and Layouts | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Create your first page and shared layout with the App Router.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Next.js</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/118</guid>
      <comments>https://khakha.tistory.com/entry/Nextjs-14-Layouts#entry118comment</comments>
      <pubDate>Wed, 16 Oct 2024 21:46:19 +0900</pubDate>
    </item>
    <item>
      <title>[Tanstack Query] invalidateQueries 무효화 범위</title>
      <link>https://khakha.tistory.com/entry/Tanstack-Query-invalidateQueries-%EB%AC%B4%ED%9A%A8%ED%99%94-%EB%B2%94%EC%9C%84</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;invalidateQueries는 POST, PATCH, DELETE, .. 등의 요청이 완료된 이후 새롭게 데이터를 페칭해야할 때 &lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;캐싱된 쿼리를 무효화&lt;/b&gt;&lt;/span&gt;하는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;기본적으로 &lt;u&gt;queryKey와 일치하는 모든 쿼리를 즉시 유효하지 않은 것으로 표시하고 active한 상태의 쿼리는 refetch&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;무효화시키고 싶은 queryKey를 매칭해주는 방식은 여러가지 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래 방식으로 적절하게 키를 지정해줘야 정확하게 쿼리를 무효화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;키는 hierarchy 구조이기 때문에, 이를 활용해서 화면 영역을 분리하는 용도로도 키를 설정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;1. prefix&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;무효화하고 싶은 접두사로 시작하는 모든 쿼리를 무효화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705043606534&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useQuery, useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()

queryClient.invalidateQueries({ queryKey: ['todos'] })

// 둘 다 'todos'로 시작하기 때문에 모두 무효화
const todoListQuery = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodoList,
})
const todoListQuery = useQuery({
  queryKey: ['todos', { page: 1 }],
  queryFn: fetchTodoList,
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;2. more specific query key&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;구체적인 특정한 쿼리키를 전달하여 그 변수가 포함된 쿼리를 무효화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705043655506&quot; class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;queryClient.invalidateQueries({
  queryKey: ['todos', { type: 'done' }],
})

// 무효화 O
const todoListQuery = useQuery({
  queryKey: ['todos', { type: 'done' }],
  queryFn: fetchTodoList,
})

// 무효화 X
const todoListQuery = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodoList,
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;3. any more variables or subkeys&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;exact 키워드가 의미하는 바처럼 더 이상의 변수나 하위 키가 없고, 특정 변수만 포함하는 쿼리를 무효화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705043713437&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryClient.invalidateQueries({
  queryKey: ['todos'],
  exact: true,
})

// 무효화 O
const todoListQuery = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodoList,
})

// 무효화 X
const todoListQuery = useQuery({
  queryKey: ['todos', { type: 'done' }],
  queryFn: fetchTodoList,
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;4. predicate function&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;세부적인 조건을 갖는 함수를 지정하여 true, false 값으로 무효화하고 싶은 쿼리키를 선택할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705044029922&quot; class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;queryClient.invalidateQueries({
  predicate: (query) =&amp;gt;
    query.queryKey[0] === 'todos' &amp;amp;&amp;amp; query.queryKey[1]?.version &amp;gt;= 10,
})

// 무효화 O
const todoListQuery = useQuery({
  queryKey: ['todos', { version: 20 }],
  queryFn: fetchTodoList,
})

// 무효화 O
const todoListQuery = useQuery({
  queryKey: ['todos', { version: 10 }],
  queryFn: fetchTodoList,
})

// 무효화 X ( version이 10보다 작다)
const todoListQuery = useQuery({
  queryKey: ['todos', { version: 5 }],
  queryFn: fetchTodoList,
})&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/React</category>
      <category>invalidateQueries</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/117</guid>
      <comments>https://khakha.tistory.com/entry/Tanstack-Query-invalidateQueries-%EB%AC%B4%ED%9A%A8%ED%99%94-%EB%B2%94%EC%9C%84#entry117comment</comments>
      <pubDate>Fri, 12 Jan 2024 16:22:32 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] Generics으로 Axios 깔끔하게 사용해보기</title>
      <link>https://khakha.tistory.com/entry/Typescript-Generics%EC%9C%BC%EB%A1%9C-Axios-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;사이드 프로젝트를 하면서 여느때와 같이 Axios 인스턴스를 만들어서 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;칸반 리팩토링을 하면서 &lt;a href=&quot;https://joshua1988.github.io/ts/guide/generics.html&quot;&gt;Generics&lt;/a&gt;을 사용해 서버에서 만들어주는 응답값 인터페이스를 만들고, 각 api 함수를 선언시 Generics에 들어갈 타입을 지정해주도록 수정해서 pr을 올렸었다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// apis/kanban.ts

interface IResponse&amp;lt;T&amp;gt; {
  success: boolean;
  error: any;
  data: T;
}

export const fetchKanbanList = async (): Promise&amp;lt;IResponse&amp;lt;IKabanData[]&amp;gt;&amp;gt; =&amp;gt; {
  const data: IResponse&amp;lt;IKabanData[]&amp;gt; = await goHigerApi.get('/v1/applications/kanban');
  return data;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;첨엔 나름 괜찮은것같다고 생각했지만... 보다보니 깔끔하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;또, pr 리뷰 중에 응답값 인터페이스를 axios 인스턴스 부분에서 공통적으로 적용하자는 의견이 있어서 검색해보았는데 비슷한 글들이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;여러 개 글을 참고해서 인스턴스를 클래스로 캡슐화하고, http 메서드별로 타입을 지정한 커스텀 메서드를 만들었다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// apis/index.ts
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';

interface ICommonResponse&amp;lt;T&amp;gt; {
  success: boolean;
  error: any;
  data: T;
}

class ApiService {
  private api;

  constructor() {
    this.api = axios.create({
      baseURL: process.env.REACT_APP_BASE_URL,
      withCredentials: true,
    });

    this.api.interceptors.request.use(
      ...
    );

    this.api.interceptors.response.use(
      async (response: AxiosResponse) =&amp;gt; {
        if (!response.data.success) {
          throw new Error(response.data.error);
        }
        return response;
      },
      error =&amp;gt; {
        return Promise.reject(error);
      },
    );
  }

  public async commonRequest&amp;lt;T&amp;gt;(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string,
    data?: any,
    config?: InternalAxiosRequestConfig,
  ): Promise&amp;lt;ICommonResponse&amp;lt;T&amp;gt;&amp;gt; {
    const response = await this.api[method](url, data, config);
    return response.data;
  }

  public Get = async &amp;lt;T&amp;gt;(url: string, config?: InternalAxiosRequestConfig) =&amp;gt;
    this.commonRequest&amp;lt;T&amp;gt;('get', url, undefined, config);

  public Post = async &amp;lt;T&amp;gt;(url: string, data?: any, config?: InternalAxiosRequestConfig) =&amp;gt;
    this.commonRequest&amp;lt;T&amp;gt;('post', url, data, config);

  public Put = async &amp;lt;T&amp;gt;(url: string, data?: any, config?: InternalAxiosRequestConfig) =&amp;gt;
    this.commonRequest&amp;lt;T&amp;gt;('put', url, data, config);

  public Patch = async &amp;lt;T&amp;gt;(url: string, data?: any, config?: InternalAxiosRequestConfig) =&amp;gt;
    this.commonRequest&amp;lt;T&amp;gt;('patch', url, data, config);

  public Delete = async &amp;lt;T&amp;gt;(url: string, config?: InternalAxiosRequestConfig) =&amp;gt;
    this.commonRequest&amp;lt;T&amp;gt;('delete', url, undefined, config);
}

export default new ApiService();&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// apis/kanban.ts
import ApiService from 'apis';

...

export const fetchKanbanList = async () =&amp;gt; {
  const data = await ApiService.Get&amp;lt;IKabanData[]&amp;gt;('/v1/applications/kanban');
  return data;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;commonRequest 함수의 반환값 지정 타입인 &lt;code&gt;Promise&amp;lt;ICommonResponse&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;은&amp;nbsp;&lt;code&gt;AxiosResponse&lt;/code&gt; 타입의 data에 해당하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래는 &lt;code&gt;AxiosResponse&lt;/code&gt; 타입의 내용이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// node_modules/axios/index.d.ts
export interface AxiosResponse&amp;lt;T = any, D = any&amp;gt; {
  data: T;
  status: number;
  statusText: string;
  headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
  config: InternalAxiosRequestConfig&amp;lt;D&amp;gt;;
  request?: any;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그간 Typescript를 소극적으로 사용해왔었는데, 제네릭을 함수 파라미터처럼 사용한다는 것을 알게되었고, 앞으로 더 적절하게 사용하면 편리하지 않을까 생각이 들었다.&lt;/p&gt;</description>
      <category>Language/Typescript</category>
      <category>axios</category>
      <category>제네릭</category>
      <category>타입스크립트</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/116</guid>
      <comments>https://khakha.tistory.com/entry/Typescript-Generics%EC%9C%BC%EB%A1%9C-Axios-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0#entry116comment</comments>
      <pubDate>Thu, 4 Jan 2024 14:40:31 +0900</pubDate>
    </item>
    <item>
      <title>CH4. 액션에서 계산 빼내기</title>
      <link>https://khakha.tistory.com/entry/CH4-%EC%95%A1%EC%85%98%EC%97%90%EC%84%9C-%EA%B3%84%EC%82%B0-%EB%B9%BC%EB%82%B4%EA%B8%B0</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;입력과 출력&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모든 함수는 입력과 출력이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;입력&lt;/b&gt;&lt;/span&gt;은 함수가 계산을 하기 위한 외부정보, &lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;출력&lt;/b&gt;&lt;/span&gt;은 함수 밖으로 나오는 정보나 어떤 동작입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;함수를 호출하는 것은 어떠한 결과(출력)가 필요하기 때문이며 이를 얻으려면 입력이 필요합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;명시적 입/출력과 암묵적 입/출력&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;입력과 출력은 명시적이거나 암묵적일 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703117758519&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var total = 0; // 전역변수
function add_to_total(amount) { // amount : 인자는 명시적 입력입니다.
    console.log(&quot;Old total: &quot; + total); // total : 전역변수를 읽는 것은 암묵적 입력, 콘솔을 찍는 것은 암묵적 출력입니다.
    total += amount; // 전역변수를 수정하는 것은 암묵적 출력입니다.
    return total; // 리턴값은 명시적 출력입니다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적 입력 : 인자&lt;/li&gt;
&lt;li&gt;암묵적 입력 : 인자 외 다른 입력&lt;/li&gt;
&lt;li&gt;명시적 출력 : 리턴값&lt;/li&gt;
&lt;li&gt;암묵적 출력 : 리턴값 외 다른 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;함수에 암묵적 입력과 출력이 있으면 부수효과입니다. (액션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;전역 변수와 같이 공유하는 변수는 일반적으로 암묵적 입력 또는 출력이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;u&gt;암묵적 입력은 함수의 인자로 바꾸고, 암묵적 출력은 리턴값으로 바꾸면 계산&lt;/u&gt;이 됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;액션에서 계산 빼내기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;add_item_to_cart : 장바구니에 아이템을 추가하고, 합계 금액을 계산하는 함수에서 계산을 빼내보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;1. 코드를 선택하고 빼내기&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코드를 추출해 새로운 함수를 만들어(함수 추출하기) 리팩토링합니다. &lt;br /&gt;새 함수에 인자가 필요하다면 추가하고, 원래 코드에서 빼낸 부분에 새 함수를 부르도록 수정합니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;원래코드&lt;/p&gt;
&lt;pre id=&quot;code_1703119252460&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let shopping_cart = [];
let shopping_cart_total = 0;

function add_item_to_cart(name, price) {
  shopping_cart.push({ name: name, price: price });
  calc_cart_total();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;바꾼 코드&lt;/p&gt;
&lt;pre id=&quot;code_1703119416212&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전역변수 생략
function add_item_to_cart(name, price) {
  add_item(name, price);
  calc_cart_total();
}

function add_item(name, price) {
  shopping_cart.push({ name: name, price: price });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;여전히 새로만든 함수 add_item은 전역변수(암묵적 입력)를 수정하고 있기 때문에 액션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;2. 암묵적 입력과 출력을 찾기&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;함수를 호출하는 동안 결과에 영향을 줄 수 있는 것(암묵적 입력), 함수 호출의 결과로 영향을 받는 것(암묵적 출력)을 찾습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;전역 배열 shopping_cart는 암묵적 입력,&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;push() 함수로 전역 배열을 변경하는 부분은 암묵적 출력에 해당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 부분을 명시적인 입력과 출력으로 바꿔보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;3. 입력은 인자로 바꾸고 출력은 리턴값으로 바꾸기&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;입력은 인자로, 출력은 리턴값으로 바꿔줍니다. &lt;br /&gt;새로운 리턴값이 생겼다면 호출하는 코드에서 함수의 결과를 변수에 할당해줍니다.&lt;br /&gt;여기서 &lt;u&gt;인자와 리턴값은 바뀌지 않는 불변값&lt;/u&gt;이라는 것이 중요합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;암묵적 입력, 암묵적 출력을 없앤 코드&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703119739578&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function add_item_to_cart(name, price) {
  shopping_cart = add_item(shopping_cart, name, price);
  calc_cart_total();
}

function add_item(cart, name, price) {
  let new_cart = cart.slice();
  new_cart.push({ name: name, price: price });
  return new_cart;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;암묵적 입력을 인자로 바꿔 명시적 입력이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;shopping_cart 배열의 복사본을 만들고(불변성) 복사본에 제품을 추가해 리턴하여 전역변수에 할당했습니다.&lt;/p&gt;</description>
      <category>개발서적/쏙쏙 들어오는 함수형 코딩</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/115</guid>
      <comments>https://khakha.tistory.com/entry/CH4-%EC%95%A1%EC%85%98%EC%97%90%EC%84%9C-%EA%B3%84%EC%82%B0-%EB%B9%BC%EB%82%B4%EA%B8%B0#entry115comment</comments>
      <pubDate>Thu, 21 Dec 2023 09:59:38 +0900</pubDate>
    </item>
    <item>
      <title>[JS] 모듈(Module)</title>
      <link>https://khakha.tistory.com/entry/JS-%EB%AA%A8%EB%93%88Module</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;게발하는 애플리케이션의 크기가 커지면서 파일을 여러 개로 분리하게 되는데 이때 분리된 파일 각각을 '모듈(module)'이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈은 대개 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된 라이브러리 하나로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스크립트의 크기가 점차 커지고 기능도 복잡해지자 자바스크립트 커뮤니티에서 특별한 라이브러리를 만들어 필요한 모듈을 언제든지 불러올 수 있게 해준다거나 코드를 모듈 단위로 구성해 주는 방법을 만드는 등을 통해 모듈 시스템을 개발하게 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Asynchronous_module_definition&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AMD&lt;/a&gt; - 가장 오래된 모듈 시스템 중 하나로 &lt;a href=&quot;https://requirejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;require.js&lt;/a&gt;라는 라이브러리를 통해 처음 개발&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.commonjs.org/wiki/Modules/1.1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CommonJS&lt;/a&gt; - node.js 서버를 위해 만들어진 모듈 시스템&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/umdjs/umd/blob/master/templates/amdWeb.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UMD&lt;/a&gt; - AMD와 CommonJS와 같은 다양한 모듈 시스템을 함께 사용하기 위해 만들어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위와 같은 모듈 시스템은 현재는 잘 쓰이지 않고, 이후로 관련 문법은 진화를 거듭해 대부분의 주요 브라우저와 Node.js가 모듈 시스템을 지원하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;모듈이란&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스크립트 파일 하나는 모듈 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈에 &lt;b&gt;export, import&lt;/b&gt;를 적용하면 다른 모듈을 불러와 불러온 모듈에 있는 함수를 호출하는 것과 같은 기능 공유가 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;export 지시자를 변수나 함수 앞에 붙이면 외부 모듈에서 해당 변수나 함수에 접근할 수 있습니다. (모듈 내보내기)&lt;/li&gt;
&lt;li&gt;import 지시자를 사용하면 외부 모듈의 기능을 가져올 수 있습니다. (모듈 가져오기)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈은 특수한 키워드나 기능과 함께 사용되므로 &lt;b&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;&lt;/b&gt; 같은 속성을 설정하여 해당 스크립트가 모듈이란 걸 브라우저가 알게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래는 브라우저에서 모듈이 동작하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;  모듈은 로컬 파일에서 동작하지 않고, HTTP/HTTPS 프로토콜을 통해 동작하기 때문에 vscode liver server를 통해 예시를 확인해야하는 점 주의&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;say.js&lt;/p&gt;
&lt;pre id=&quot;code_1702349086233&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function sayHi(user) {
  return `Hello, ${user}!`;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;index.html&lt;/p&gt;
&lt;pre id=&quot;code_1702349099277&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;script type=&quot;module&quot;&amp;gt;
  import { sayHi } from &quot;./say.js&quot;;

  document.body.innerHTML = sayHi(&quot;John&quot;);
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3cBGR/btsBQxUF5m0/OY3m1GivfhUkXeA0njJK51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3cBGR/btsBQxUF5m0/OY3m1GivfhUkXeA0njJK51/img.png&quot; data-alt=&quot;브라우저가 모듈을 가져오고 이를 실행한 것을 확인할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3cBGR/btsBQxUF5m0/OY3m1GivfhUkXeA0njJK51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3cBGR%2FbtsBQxUF5m0%2FOY3m1GivfhUkXeA0njJK51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;254&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저가 모듈을 가져오고 이를 실행한 것을 확인할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;모듈 핵심 기능&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;일반 스크립트와 모듈에는 약간의 차이가 있습니다. 모든 &lt;a href=&quot;https://ko.javascript.info/browser-environment&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;호스트 환경(자바스크립트가 돌아가는 환경)&lt;/a&gt;에 공통으로 적용되는 모듈의 핵심 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;엄격모드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈은 항상 엄격 모드(use Strict)로 실행됩니다. 선언되지 않은 변수에 값을 할당하는 등의 코드는 에러를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;모듈 레벨 스코프&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈 내부에서 정의한 변수나 함수는 다른 스크립트에서 접근할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;외부에 공개하려는 모듈은 &lt;b&gt;export&lt;/b&gt; 해야 하고, 내보내진 모듈을 가져와 사용하려면 &lt;b&gt;import&lt;/b&gt; 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;브라우저 환경에서 &amp;lt;script type=&quot;module&quot;&amp;gt;을 사용해 모듈을 만들면 독립적인 스코프를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;단 한 번만 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출시 단 한 번만 실행됩니다. 실행 후 결과는 이 모듈을 가져가려는 모든 모듈에 내보내집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;정리하면, &lt;b&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;모듈은 단 한 번만 실행되고 실행된 모듈은 필요한 곳에 공유되므로 어느 한 모듈에서 특정 코드를 수정하면 다른 모듈에서도 그 변경사항을 확인&lt;/span&gt;&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이를 이용하면, &lt;u&gt;최상위 레벨 모듈을 불러와서 초기화나 내부에서 쓰이는 데이터 구조를 만들고 이를 내보내 재사용&lt;/u&gt; 하는 등의 &lt;b&gt;모듈 설정&lt;/b&gt;을 쉽게 할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;브라우저에서 모듈&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(type=&quot;module&quot;)&lt;/span&gt;의 특정 기능&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;지연 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈 스크립트는 항상 지연 실행됩니다. 마치 &lt;a href=&quot;https://ko.javascript.info/script-async-defer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;defer&lt;/a&gt; 속성을 붙인 것처럼 동작합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 모듈 스크립트 &amp;lt;script type=&quot;module&quot; src=&quot;...&quot;&amp;gt;를 다운로드할 때 브라우저의 HTML 처리가 멈추지 않습니다. 브라우저는 외부 모듈 스크립트와 기타 리소스를 병렬적으로 불러옵니다.&lt;/li&gt;
&lt;li&gt;모듈 스크립트는 HTML 문서가 완전히 준비될 때까지 대기 상태에 있다가 HTML 문서가 완전히 만들어진 이후에 실행됩니다.&lt;/li&gt;
&lt;li&gt;스크립트의 상대적 순서가 유지됩니다. 문서상 위쪽의 스크립트부터 차례로 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 특징 때문에 모듈 스크립트는 항상 완전한 HTML 문서를 보고 문서 내 DOM 요소 등에도 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;index.html&lt;/p&gt;
&lt;pre id=&quot;code_1702355855444&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;script type=&quot;module&quot;&amp;gt; // 모듈 스크립트는 지연 실행되기 때문에 페이지가 모두 로드되고 난 다음에 실행됩니다.
  alert(typeof button); // Object
&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt; // 일반 스크립트는 페이지가 완전히 구성되기 전이여도 바로 실행됩니다.
  alert(typeof button); // undefined
&amp;lt;/script&amp;gt;
&amp;lt;button id=&quot;button&quot;&amp;gt;Button&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 html을 실행하면 1) undefined 알럿 창 2) Object 알럿 창 + button 이 표시됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1ZHXk/btsBQj940Ks/sKAm80pXvi9u2zMEctbRZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1ZHXk/btsBQj940Ks/sKAm80pXvi9u2zMEctbRZk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;226&quot; data-filename=&quot;스크린샷 2023-12-12 오후 1.40.43.png&quot; style=&quot;width: 43.6662%; margin-right: 10px;&quot; data-widthpercent=&quot;44.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1ZHXk/btsBQj940Ks/sKAm80pXvi9u2zMEctbRZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1ZHXk%2FbtsBQj940Ks%2FsKAm80pXvi9u2zMEctbRZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLk6nY/btsBQu4UzZA/F4RdE640diBADIRH2SowAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLk6nY/btsBQu4UzZA/F4RdE640diBADIRH2SowAK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;437&quot; data-filename=&quot;스크린샷 2023-12-12 오후 1.41.04.png&quot; style=&quot;width: 55.1711%;&quot; data-widthpercent=&quot;55.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLk6nY/btsBQu4UzZA/F4RdE640diBADIRH2SowAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLk6nY%2FbtsBQu4UzZA%2FF4RdE640diBADIRH2SowAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1202&quot; height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;모듈 스크립트 지연 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;페이지 내 특정 기능이 모듈 스크립트에 의존적인 경우, 모듈이 완전히 로딩되기 전에 페이지가 먼저 사용자에게 노출되면 혼란을 느낄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;투명 오버레이나 로딩 인디케이터를 보여주어 혼란을 예방할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;인라인 스크립트의 비동기 처리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;모듈 스크립트에서 &lt;b&gt;async&amp;nbsp;&lt;/b&gt;속성을 적용하면, 필요한 모듈의 로드가 끝난 후 문서나 다른 script가 로드되길 기다리지 않고 바로 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 특징은 어디에도 종속되지 않는 기능을 구현할 때 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702356435590&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 필요한 모듈(analytics.js)의 로드가 끝나면 --&amp;gt;
&amp;lt;!-- 문서나 다른 &amp;lt;script&amp;gt;가 로드되길 기다리지 않고 바로 실행됩니다.--&amp;gt;
&amp;lt;script async type=&quot;module&quot;&amp;gt;
  import {counter} from './analytics.js';

  counter.count();
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;외부 스크립트&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;type=&quot;module&quot; src=&quot;...&quot;&lt;/b&gt; 가 붙은 외부 모듈 스크립트엔 두 가지 특징이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;src&lt;/b&gt; 속성값이 동일한 외부 스크립트는 한 번만 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702356625165&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- my.js는 한 번만 로드 및 실행됩니다. --&amp;gt;
&amp;lt;script type=&quot;module&quot; src=&quot;my.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;module&quot; src=&quot;my.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 사이트같이 다른 오리진에서 모듈 스크립트를 불러오려면 해당 모듈이 저장된 원격 서버가 &lt;br /&gt;&lt;b&gt;Access-Control-Allow-Origin : *&lt;/b&gt; 헤더를 제공해야만 외부 모듈을 불러올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702356773332&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- another-site.com이 Access-Control-Allow-Origin을 지원해야만 외부 모듈을 불러올 수 있습니다.--&amp;gt;
&amp;lt;!-- 그렇지 않으면 스크립트는 실행되지 않습니다.--&amp;gt;
&amp;lt;script type=&quot;module&quot; src=&quot;http://another-site.com/their.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;경로가 없는 모듈은 금지&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;브라우저 환경에서 &lt;b&gt;import&lt;/b&gt;는 반드시 상대 혹은 절대 URL 앞에 와야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Node.js나 번들링 툴은 경로가 없어도 해당 모듈을 찾을 수 있기 때문에 경로가 없는 모듈을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(e.g. import react from 'react')&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;빌드 툴&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;브라우저 환경에서 모듈을 단독으로 사용하는 경우는 거의 없습니다. &lt;br /&gt;대게 Webpack 같은 빌드 툴을 사용해 모듈을 번들링하여 서버에 올리는 방식을 사용합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HTML의 &amp;lt;script type=&quot;module&quot;&amp;gt; 에 넣을 '주요' 모듈(진입접 역할을 하는 모듈)을 선택합니다.&lt;/li&gt;
&lt;li&gt;'주요' 모듈에 의존하고 있는 모듈 분석을 시작으로 모듈 간의 의존관계를 파악합니다.&lt;/li&gt;
&lt;li&gt;모듈 전체를 모아 하나의 큰 파일을 만듭니다.(설정에 따라 여러 파일 만드는 것도 가능) 이 과정에서 import문이 번들러 내 함수로 대체되므로 기존 기능은&amp;nbsp;그대로 유지됩니다.&lt;/li&gt;
&lt;li&gt;이 과정 중에 변형이나 최적화도 함께 수행됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도달 가능하지 않은 코드는 삭제&lt;/li&gt;
&lt;li&gt;내보내진 모듈 중 사용하지 않는 모듈 삭제&lt;/li&gt;
&lt;li&gt;console, debugger 같은 개발 관련 코드 삭제&lt;/li&gt;
&lt;li&gt;최신 자바스크립트 문법이 사용된 경우 바벨(Babel)을 사용해 버전 변환&amp;nbsp;&lt;/li&gt;
&lt;li&gt;공백 제거, 변수 이름 줄이기 등으로 산출물의 크기 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;번들링 툴을 사용해 하나 혹은 여러 개의 파일로 번들링되면 이전에 &lt;b&gt;import, export&lt;/b&gt;문은 특별한 번들러 함수로 대체됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;기존 스크립트에서 import, export가 사라지기 때문에 type=&quot;module&quot;이 필요 없어지므로, 번들링 과정을 거친 스크립트는 일반 스크립트처럼 취급할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702358022463&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 웹팩과 같은 툴로 번들링 과정을 거친 스크립트인 bundle.js --&amp;gt;
&amp;lt;script src=&quot;bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Language/javascript</category>
      <category>모듈</category>
      <category>번들링</category>
      <category>스크립트</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/112</guid>
      <comments>https://khakha.tistory.com/entry/JS-%EB%AA%A8%EB%93%88Module#entry112comment</comments>
      <pubDate>Tue, 12 Dec 2023 10:54:29 +0900</pubDate>
    </item>
    <item>
      <title>CH3. 액션과 계산, 데이터의 차이를 알기</title>
      <link>https://khakha.tistory.com/entry/%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B3%84%EC%82%B0%EC%95%A1%EC%85%98-%EA%B5%AC%EB%B6%84%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;함수형 프로그래머는 &lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;액션과 계산, 데이터&lt;/b&gt;&lt;/span&gt;를 구분하여 개발과정에서 적용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;데이터&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트에 대한 사실. 일어난 일의 결과를 기록한 것입니다.&lt;br /&gt;e.g. 구입하려는 음식 목록, 이름, 전화 번호, 음식 조리법&lt;/li&gt;
&lt;li&gt;데이터는 데이터 자체로 할 수 있는 것이 없기 때문에 데이터 그대로 이해할 수 있는 것이 장점입니다.&lt;/li&gt;
&lt;li&gt;해석하지 않아도 실행할 수 있는 계산에 비해 해석이 반드시 필요하다는 점은 단점입니다.&lt;/li&gt;
&lt;li&gt;데이터를 언제나 쉽게 해석할 수 있도록 표현하는 것이 함수형 프로그래밍에서 중요한 기술입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;계산&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력값으로 출력값을 만드는 것.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;순수 함수, 수학 함수&lt;/b&gt;라고 부르기도 합니다.&lt;br /&gt;e.g. 최댓값 찾기, 이메일 주소가 올바른지 확인하기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;u&gt;실행 시점과 횟수에 관계없이 항상 같은 입력값에 대해 같은 출력값을 돌려줍니다.&lt;/u&gt;&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;함수형 프로그래머는 가능하면 액션보다 계산을 사용하려고 합니다.&lt;/li&gt;
&lt;li&gt;액션보다 좋은점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트가 용이합니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;언제 어디서나 원하는만큼 테스트를 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조합하기 쉽습니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;계산을 조합해 더 큰 계산을 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;계산과 액션은 입력값으로 실행하기 전에는 어떤 결과가 나오는지 알 수 없다는 단점이 있습니다.&lt;/li&gt;
&lt;li&gt;계산은 일반적으로 함수형 프로그래밍 외부에 있는 액션을 통해 수행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;액션&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 세계에 영향을 주거나 받는 것을 말합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부수 효과&lt;/b&gt;라고 부르기도 합니다.&lt;br /&gt;e.g. 이메일 보내기, 계좌에서 인출하기, ajax 요청 보내기&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;u&gt;&lt;b&gt;실행 시점과 횟수에 의존합니다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;액션은 다루기 힘들며 우리가 소프트웨어를 실행하려는 가장 중요한 이유입니다.&lt;/li&gt;
&lt;li&gt;함수형 프로그래머가 액션을 잘 사용하기 위한 방법입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가능한 액션을 적게 사용&lt;/b&gt;합니다. 액션 대신 계산을 사용할 수 있는지 생각해봐야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가능한 작게&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만듭니다. 액션에서 액션과 관련 없는 코드는 모두 제거합니다. (계산으로 빼낼 수 있다면 빼냄.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부세계와 상호작용하는 것을 제한&lt;/b&gt;할 수 있습니다. 내부에 계산과 데이터만 있고 가장 바깥쪽에 액션이 있는 구조가 이상적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호출 시점에 의존하는 것을 제한&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발서적/쏙쏙 들어오는 함수형 코딩</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/110</guid>
      <comments>https://khakha.tistory.com/entry/%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B3%84%EC%82%B0%EC%95%A1%EC%85%98-%EA%B5%AC%EB%B6%84%ED%95%98%EA%B8%B0#entry110comment</comments>
      <pubDate>Wed, 6 Dec 2023 17:08:25 +0900</pubDate>
    </item>
    <item>
      <title>Warning: Cannot update a component (`Kanban`) while rendering a different component (`Kanban`). To locate the bad setState() call inside `Kanban`</title>
      <link>https://khakha.tistory.com/entry/Warning-Cannot-update-a-component-Kanban-while-rendering-a-different-component-Kanban-To-locate-the-bad-setState-call-inside-Kanban</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;컴포넌트 렌더링 중에 상태를 업데이트하려고 시도했기 때문에 발생한 경고이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;워닝 문구 마지막에 &lt;a title=&quot;Github issue&quot; href=&quot;https://reactjs.org/link/setstate-in-render&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github issue&lt;/a&gt;에서처럼 에러가 발생한 부분을 확인하라고 알려주고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bihawu/btsBiod4pka/45iGykA6MvfSEh7kkMenr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bihawu/btsBiod4pka/45iGykA6MvfSEh7kkMenr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bihawu/btsBiod4pka/45iGykA6MvfSEh7kkMenr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbihawu%2FbtsBiod4pka%2F45iGykA6MvfSEh7kkMenr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;693&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanban.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1701663226956&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data, isLoading, isSuccess } = useQuery('fetchKanbanList', fetchKanbanList);

if (!isLoading &amp;amp;&amp;amp; isSuccess) {
  const kanbanList = data.data;
  dispatch(setApplications(kanbanList));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;useQuery훅이 렌더링중에 호출되고, 데이터가 잘 받아와지면 dispatch를 호출하여 스토어 상태를 업데이트하도록하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이는 렌더링 중에 상태를 업데이트하는 것인데, 잘못된 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;useEffect로 감싸주어 isLoading, isSuccess의 변경을 감지하고, effect 내에서 상태를 업데이트하도록 변경하는 것이 React의 규칙을 따르는 패턴이다.&lt;/p&gt;
&lt;pre id=&quot;code_1701663404280&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data, isLoading, isSuccess } = useQuery('fetchKanbanList', fetchKanbanList);

useEffect(() =&amp;gt; {
  if (!isLoading &amp;amp;&amp;amp; isSuccess) {
    const kanbanList = data.data;
    dispatch(setApplications(kanbanList));
  }
}, [isLoading, isSuccess, data, dispatch]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Error</category>
      <author>khakhalog</author>
      <guid isPermaLink="true">https://khakha.tistory.com/108</guid>
      <comments>https://khakha.tistory.com/entry/Warning-Cannot-update-a-component-Kanban-while-rendering-a-different-component-Kanban-To-locate-the-bad-setState-call-inside-Kanban#entry108comment</comments>
      <pubDate>Mon, 4 Dec 2023 13:18:36 +0900</pubDate>
    </item>
  </channel>
</rss>