<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Suzie's Blog</title>
    <link>https://suzie-dev.tistory.com/</link>
    <description>개발  &amp;zwj; 
취미  &amp;zwj;♀️
맛집  
여행 ✈️
</description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 18:23:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Iuna</managingEditor>
    <image>
      <title>Suzie's Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/5838990/attach/6a23ff44dc77442faab87df59787e6ec</url>
      <link>https://suzie-dev.tistory.com</link>
    </image>
    <item>
      <title>스페이스 대신 /u00A0 || 커서 효과 추가 후 글 흔들림 방지 꿀템</title>
      <link>https://suzie-dev.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;커서가 깜빡이는 효과를 구현했는데 커서 (|) 가 생겼다 없어질 때마다 글자가 좌우로 살짝 움직이는 현상이 생김.&lt;br /&gt;이유는 커서가 표시될 때 공간을 차지하고, 사라질때 &quot;&quot; (빈 문자열)이라 해당 공간이 없어지면서 생기는 흔들림이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 &quot;&quot; 대신 /u00A0 (non-breaking space)를 넣으면 문제를 깔끔하게 해결할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1747256409581&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span&amp;gt;{showCursor ? &quot;|&quot; : &quot;\u00A0&quot;}&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;\u00A0 는 눈에는 안보이지만, 공간을 차지하는 특수 공백 문자로 커서가 없어도 자리를 유지하게 해줌&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;before.gif&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxNgPS/btsNXp5MNU4/Oki7QZ37aIm4tELgLVJZD1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxNgPS/btsNXp5MNU4/Oki7QZ37aIm4tELgLVJZD1/img.gif&quot; data-alt=&quot;before&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxNgPS/btsNXp5MNU4/Oki7QZ37aIm4tELgLVJZD1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bxNgPS/btsNXp5MNU4/Oki7QZ37aIm4tELgLVJZD1/img.gif&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;178&quot; height=&quot;98&quot; data-filename=&quot;before.gif&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;before&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;after.gif&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xsz0W/btsNYG6rwpu/nEjfl1UJd3DwBmYh9UDKkk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xsz0W/btsNYG6rwpu/nEjfl1UJd3DwBmYh9UDKkk/img.gif&quot; data-alt=&quot;after&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xsz0W/btsNYG6rwpu/nEjfl1UJd3DwBmYh9UDKkk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Xsz0W/btsNYG6rwpu/nEjfl1UJd3DwBmYh9UDKkk/img.gif&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;178&quot; height=&quot;98&quot; data-filename=&quot;after.gif&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;after&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/65</guid>
      <comments>https://suzie-dev.tistory.com/65#entry65comment</comments>
      <pubDate>Thu, 15 May 2025 06:04:40 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] as const란?</title>
      <link>https://suzie-dev.tistory.com/64</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;as const 란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;as const 는 값을 &lt;i&gt;리터럴 타입(literal type)&lt;/i&gt;으로 고정시키는 TypeScript 문법.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;리터럴 타입 (Literal type) 이란?&lt;br /&gt;값 자체를 타입으로 사용하는것을 말함. 보통 변수의 타입은 string, number, boolean처럼 넓은 타입으로 정해지지만 리터럴 타입을 특정한 하나의 값만 가질 수 있도록 타입을 제함.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 TypeScript 는 변수를 선언할 때, 값을 기반으로 넓은 타입을 추론함.&lt;/p&gt;
&lt;pre id=&quot;code_1745771322379&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const color = &quot;red&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 작성하면 color의 타입은 stirng으로 추론됨. 그리고 어떤 문자열이든 다시 넣는게 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 as const를 사용하면&lt;/p&gt;
&lt;pre id=&quot;code_1745771404829&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const color = &quot;red&quot; as const;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;color의 타입이 &quot;red&quot;로 고정되고 더 이상 다른 문자열 할당 불가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;as const를 사용하는 이유?&lt;/b&gt;&lt;br /&gt;- 변경 불가능한 값을 만들고 싶을 때&lt;br /&gt;- 리터럴 타입을 기반으로 타입을 더 정확하게 지정하고 싶을 때&lt;br /&gt;- 객체나 배열을 수정 불가능하게 만들고 싶을 때&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, 객체를 만들 때,&lt;/p&gt;
&lt;pre id=&quot;code_1745771562011&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const user = {
	name: &quot;Alice&quot;,
    age: 30
} as const&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 user.name은 'Alice'로, user.age는 30으로 값과 타입이 고정됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;br /&gt;- as const는 값을 리터럴 타입으로 고정시킴&lt;br /&gt;- 값이 변경될 수 없게 만들어 코드의 안정성을 높임&lt;br /&gt;- 배열, 객체, 문자열 등 다양한 값에 사용 가능&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>literaltype</category>
      <category>TypeScript</category>
      <category>리터럴타입</category>
      <category>타입스크립트</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/64</guid>
      <comments>https://suzie-dev.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 28 Apr 2025 01:38:08 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 제네릭 함수(Generic Functions) 이해하기</title>
      <link>https://suzie-dev.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript 제네릭 함수란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript 에서 제네릭 함수는 다양한 타입에 대해 작업할 수 있는 함수.&lt;br /&gt;제네릭을 사용하면 함수의 입력과 반환값의 타입을 유동적으로 정의 가능 -&amp;gt; 코드의 재사용성과 유지 보수성을 높이는데 큰 도움이 됨.&lt;br /&gt;&lt;b&gt;제네릭 함수 특징 : 타입을 미리 알지 못해도, 함수가 어떤 타입을 다룰지 코드가 실행되는 시점에 결정 가능.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;예 : 함수의 매개변수나 반환값의 타입을 동적으로 처리하고 싶을 때 제네릭 사용. 제네릭은 타입 매개변수로 T와 같은 플레이스홀더를 사용하여 타입을 동적으로 정의&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745715464543&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function identity&amp;lt;T&amp;gt;(value: T):T {
	return value;
}

let result = identity(5); // result는 'number' 타입
let result2 = identity(&quot;hello&quot;); // result2 는 'string' 타입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제네릭 함수의 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- 재사용성 증가 : 제네릭을 사용하면 같은 함수를 여러 타입에 대해 재사용 가능. 타입을 다르게 해서 여러 상황에 동일한 로직 하용 가능&lt;/i&gt;&lt;br /&gt;&lt;i&gt;- 타입 안전성 : 제네릭을 사용하면 함수가 처리할 수 있는 타입을 명확히 정의 가능. 잘못된 타입을 전달했을 때 오류 사전 방지 가능&lt;/i&gt;&lt;br /&gt;&lt;i&gt;- 유지보수 용이 : 코드가 더 유연해짐. 나중에 타입 변경 되도 코드 수정 용이&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론 : 예전에 TypeScript가 JavaScript에 비해 불편하다고 느꼈던 이유가 바로 이 제네릭함수의 존재 여부를 몰랐을 때였던거 같다. 제네릭 함수 덕분에 타입을 동적으로 처리 가능하고, 이 방식이 코드 유연성, 안전성 높일 수 있다는거 알게되니 TypeScript가 더 효율적이고 강력한 언어로 느껴짐!&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/63</guid>
      <comments>https://suzie-dev.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 27 Apr 2025 10:02:18 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 15에서 &amp;quot;URL.canParse()&amp;quot; 오류 해결하기 (feat: i18next)</title>
      <link>https://suzie-dev.tistory.com/62</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제상황&lt;/b&gt; : Next.js 15에서 i18next를 설치하자 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;TypeError:&amp;nbsp;URL.canParse&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;function&lt;/b&gt;&lt;/span&gt;&quot;오류 발생.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러는 &quot;URL.canParse()&quot;라는 함수가 브라우저나 Node.js 환경에서 아직 정식으로 지원되지 않아서 발생.&lt;br /&gt;특히 &lt;b&gt;&quot;i18next&quot;&lt;/b&gt;, &lt;b&gt;&quot;resourcesToBackend&quot;&lt;/b&gt; 같은 라이브러리 내부에서 이 함수를 사용하는 경우, 별도로 처리해주지 않으면 앱이 크래시가 남.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  해결 방법 : 간단한 폴리필(Polyfill) 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 이 문제는 &quot;폴리필(Polyfill) 을 통해 쉽게 해결 가능.&lt;br /&gt;폴리필이란?&lt;br /&gt;지원되지 않는 기능을 흉내 내는 코드&lt;br /&gt;다음 코드를 앱 진입점 (예: app/layout.tsx 또는 app/page.tsx)상단에 추가하면 에러메세지가 더 이상 뜨지 않는걸 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1744831191929&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (typeof URL.canParse !== &quot;function&quot;) {
  URL.canParse = function (url: string) {
    try {
      new URL(url);
      return true;
    } catch {
      return false;
    }
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 &quot;canParse()&quot; 함수가 없을 경우에만 동작하며, 정상적인 URL이면 true, 문제가 있는 URL 이면 false를 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ Tips&lt;/b&gt;&lt;br /&gt;- 이 폴리필(Polyfill)은 Node.js 18 또는 20 환경에서도 안전하게 동작.&lt;br /&gt;- Next.js 15에서 &quot;URL.canParse()&quot;를 직접 쓰지 않더라도, 내부 의존성에서 사용할 수 있으므로 위처럼 미리 막아두는게 안전.&lt;br /&gt;- 특히 i18n 설정 중 &quot;i18next-resources-to-backend&quot; 사용 시 발생 가능성 높음&lt;/p&gt;</description>
      <category>개발/Next.js</category>
      <category>I18N</category>
      <category>i18next</category>
      <category>isnotafunction</category>
      <category>Nextjs</category>
      <category>nextjs15</category>
      <category>nextjserror</category>
      <category>polyfill</category>
      <category>url.canparse()</category>
      <category>에러해결</category>
      <category>폴리필</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/62</guid>
      <comments>https://suzie-dev.tistory.com/62#entry62comment</comments>
      <pubDate>Thu, 17 Apr 2025 04:24:41 +0900</pubDate>
    </item>
    <item>
      <title>[서울 &amp;rarr; 파리] 비행기 놓칠뻔.. 했지만 잘 간 썰 | 대한항공 공동운항 항공편 주의할 점 | AF267 | 에어프랑스 | Air France</title>
      <link>https://suzie-dev.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;17 March 2025 (Mon) , Seoul&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일 년 전부터 준비하던 순간이 드디어 오고야 말았다.&lt;br /&gt;나는 캐나다 짐 + 전세계약 정리를 동시에 진행해야 해서 서울에 살고 있는 집을 아예 정리해버려야 했다.&lt;br /&gt;당근 거래 및 나눔으로 정리하고 택배 3개는 본가로 보냈지만 쉽지 않았다. 다행히 친동생이 같은 빌딩에 살아서 많은 도움을 주었다  &lt;br /&gt;가기 직전이라 매일 약속이 있었고 집에있는 시간이 많이 없다 보니 더 힘들었던 것 같기도..  &lt;br /&gt;&lt;br /&gt;전세계약이 4월 1일까지여서 전세금을 돌려받지 못하면 어쩌지 하며 불안해했지만 일단.. 그냥 가는 걸로.. ㅋㅋㅋ&lt;br /&gt;고맙게도 제주에서 올라온 친구와 인천공항에서 마지막 고등어세트 한식을 먹고 작별 인사 후 입국장(?) 으로 들어갔다.&lt;br /&gt;&lt;br /&gt;무슨 사람들이 월요일에 이렇게 여행을 많이가는지.. 줄이 엄청 길었지만 아직 비행시간이 남아있어서 괜찮았다.&lt;br /&gt;나는 대한항공 앱으로 티켓을 예약했는데 공동운항으로 Air France를 타고 파리로 가는 여정이었다.&lt;br /&gt;앱으로 체크인을 했는데 Gate가 나와있지 않았다. 그래서 공항에 설치되어 있는 전광판을 보고 찾아갔다.&lt;br /&gt;&lt;br /&gt;게이트가 은근히 멀었다. 화장품 판매가 널려있는 곳을 지나자 게이트가 나왔고 혹시 몰라 승무원과 한번 더 체크했다.&lt;br /&gt;승무원이 여기 게이트가 맞다고 컨펌해 주셨다. 여유 있게 비행기 출발시간에 맞춰 줄을 섰는데 그때 방송이 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;Air France 게이트가 곧 닫힐 예정이니 탑승하지 않은 승객께서는 서둘러 탑승하시길 바랍니다&quot;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;이때 뭔가 싸한 느낌이 들긴 했다 ㅋㅋ&lt;br /&gt;내 차례가 와서 큐알코드를 찍었는데 &quot;삐빅&quot; 하더니 이 게이트가 아니라고 승무원이 말씀하셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  : &quot;엇...??! 여기가 맞다고 했는데요???&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;✈️ : &quot;여기가 아닙니다. 잠시만요&quot;&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;span style=&quot;color: #333333; text-align: start;&quot;&gt; &amp;zwj;✈️ :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;게이트가 나오지 않는데 전광판 찾으셔서 알아서 찾아가셔야 할 것 같아요&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;?!?!!??!!?!?!!?!??!?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈앞이 하얘졌다. 아까 그 방송은 나를 찾는 방송이었구나.. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ&lt;br /&gt;&lt;br /&gt;서둘러 뛰어다녔지만 전광판은 어디에도 보이지 않았고 비행기 출발하기 몇 분이 남지 않은 상황이었다.&lt;br /&gt;심지어 워킹홀리데이를 가는 짐이어서 자켓도 3개나 껴입고 가방도 무거운 상태였다 (지금 생각해도 아찔쓰  )&lt;br /&gt;화장품가게 직원에게 도와달라고 했더니 중간쯤 가면 인포메이션 센터가 있다고 하셨다.&amp;nbsp;&lt;br /&gt;인포메이션 센터로 무작정 달려갔다. 가서 헉헉 거리면서 비행기 게이트 알려달라 했더니 아까 내가 타려고 했던 곳 바로 옆이었다  &lt;br /&gt;힘들게 돌아온 길을 다시 달렸다. 그때 모르는 번호로 전화가 왔다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;zwj;✈️ :&amp;nbsp;&quot;승객분 이제 게이트 닫힙니다&quot;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;  :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;제발 잠시만요!! 근처에요!!&quot;&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;br /&gt;그리고 얼마 지나지 않아 비행기가 출발했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운 좋게 나는 비행기를 놓치지 않았다 ㅋㅋㅋ 지금 생각해도 살 떨리는 이 기분 ㅋㅋ&lt;br /&gt;비행기 타자마자 모든 자켓을 벗고 반팔만 입었는데도 너무 많이 뛰어서 땀이 비 오듯 났다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7YSyL/btsNbyOvXwD/lcbwHujrp4UKt7DK5aKw3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7YSyL/btsNbyOvXwD/lcbwHujrp4UKt7DK5aKw3K/img.png&quot; data-alt=&quot;공동운항 탑승권&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7YSyL/btsNbyOvXwD/lcbwHujrp4UKt7DK5aKw3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7YSyL%2FbtsNbyOvXwD%2FlcbwHujrp4UKt7DK5aKw3K%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;255&quot; height=&quot;542&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;829&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공동운항 탑승권&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️ 공동운항 항공편 주의할 점!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이 탑승권이 내가 받은 건데 탑승구와 터미널이 누락되어 있다. &lt;br /&gt;심지어 나는 KE5901만 보고 게이트를 찾아가서 이 낭패를 봤던 것.. ㅠ_ㅠ&lt;br /&gt;&lt;b&gt;밑에 작게 쓰여있는 실제 운항 편 AF267을 찾던가 아니면 인포메이션 센터에 미리 물어보는 걸 추천한다.&lt;/b&gt;&lt;br /&gt;심지어 승무원들도 잘 몰라서 나한테 이 게이트가 맞다고 설명해 주셨으니..  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;1604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLb7EJ/btsM97roQmV/H2ICNwDtlva9ClF9jWmRP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLb7EJ/btsM97roQmV/H2ICNwDtlva9ClF9jWmRP0/img.png&quot; data-alt=&quot;에어프랑스 AF267 기내식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLb7EJ/btsM97roQmV/H2ICNwDtlva9ClF9jWmRP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLb7EJ%2FbtsM97roQmV%2FH2ICNwDtlva9ClF9jWmRP0%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;551&quot; height=&quot;520&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;1604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에어프랑스 AF267 기내식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 기내식은 비행기 뜨고 얼마 지나지 않아 바로 주신다. 치킨과 파스타 중에 하나 고르라고 하는데&lt;br /&gt;난 당연히 고기.. ㅋㅋㅋ&lt;br /&gt;기내식 맛있었고 특히 샴페인이 진짜 맛있었음! 치킨이랑 같이 샴페인 먹으니 진짜 잘 어울렸다!&lt;br /&gt;&lt;br /&gt;그리고 좌석에 붙어있는 화면도 엄청 크고 볼 것도 많이 있었다. 게임도 좀 있어서 게임도 몇 개 했다. 테트리스 했는데 랭킹 1 찍음 ㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XPWV6/btsNa83N9tY/SexledmsBYTxPKM28jnvgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XPWV6/btsNa83N9tY/SexledmsBYTxPKM28jnvgk/img.png&quot; data-alt=&quot;에어프랑스 AF267 두번째 기내식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XPWV6/btsNa83N9tY/SexledmsBYTxPKM28jnvgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXPWV6%2FbtsNa83N9tY%2FSexledmsBYTxPKM28jnvgk%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;474&quot; height=&quot;516&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에어프랑스 AF267 두번째 기내식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도착시간 1시간 전에 준 따뜻한 샌드위치와 빵들.. ㅋㅋ 빵이 너무 많다 역시 빵의 나라인가..&lt;br /&gt;따뜻한 샌드위치 안에는 치즈와 햄이 들어가 있었고 맛있었다!! 블루베리 머핀과 빵은 먹지 못하고 들고 내려서 다다음날 섭취함 ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울에서 파리로 날아갈 때 뷰가 진짜 장난 아니었다.&lt;br /&gt;아무것도 없이 눈으로만 덮여있는 땅을 보면서 지구에 사람이 살지 않는 황무지가 진짜 많구나 하고 느꼈다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/olvmd/btsM98Kx1dg/6bNGKkrmlYrMK7VVKyLoCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/olvmd/btsM98Kx1dg/6bNGKkrmlYrMK7VVKyLoCK/img.png&quot; data-alt=&quot;하늘에서 바라본 땅! 아마 카자흐스탄이였던거 같음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/olvmd/btsM98Kx1dg/6bNGKkrmlYrMK7VVKyLoCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Folvmd%2FbtsM98Kx1dg%2F6bNGKkrmlYrMK7VVKyLoCK%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;565&quot; height=&quot;1204&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하늘에서 바라본 땅! 아마 카자흐스탄이였던거 같음&lt;/figcaption&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;-끝-&lt;/p&gt;</description>
      <category>일기/Montr&amp;eacute;al  </category>
      <category>af267</category>
      <category>ke5901</category>
      <category>공동운항</category>
      <category>기내식</category>
      <category>대한항공</category>
      <category>대한항공앱</category>
      <category>에어프랑스</category>
      <category>파리행</category>
      <category>항공편</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/61</guid>
      <comments>https://suzie-dev.tistory.com/61#entry61comment</comments>
      <pubDate>Sun, 6 Apr 2025 01:33:12 +0900</pubDate>
    </item>
    <item>
      <title>[Quiz] 이벤트 루프 (Event Loop) 문제</title>
      <link>https://suzie-dev.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프(Event Loop)의 개념을 설명하고, 아래 코드가 실행될 때 콘솔에 출력되는 순서를 예측해보세요.&lt;/p&gt;
&lt;pre id=&quot;code_1741048847619&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(&quot;A&quot;);
setTimeout(() =&amp;gt; console.log(&quot;B&quot;), 0);
Promise.resolve().then(() =&amp;gt; console.log(&quot;C&quot;));
console.log(&quot;D&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0px; caret-color: auto; background-color: #fafafa; padding: 20px 20px 22px; border: 1px dashed #c5c5c5; color: #333333;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 정답 : &lt;b&gt;&quot;A&quot;, &quot;D&quot;, &quot;C&quot;, &quot;B&quot;&lt;/b&gt;&lt;br /&gt;- 코드가 실행이 되고 &quot;&lt;b&gt;A&lt;/b&gt;&quot; 가 처음 실행되어 콘솔에 찍힘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WebAPI에서 &quot;B&quot; 가 실행되고 task queue로 이동&lt;br /&gt;- 자체적 비동기인 Promise는 microtask queue로 이동&lt;br /&gt;- 마지막 코드가 실행되고 &quot;&lt;b&gt;D&lt;/b&gt;&quot; 가 콘솔에 찍힘.&lt;br /&gt;- 우선순위가 microtask queue가 우선이므로 &quot;&lt;b&gt;C&lt;/b&gt;&quot; 가 콘솔에 찍히고&lt;br /&gt;- 그 다음 task queue 인 &quot;&lt;b&gt;B&lt;/b&gt;&quot; 가 콘솔에 찍히게 됨.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;이벤트 루프의 개념&lt;/b&gt; : 자바스크립트의 비동기 처리 방식을 담당하는 핵심 개념&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작방식&lt;/b&gt;&lt;br /&gt;&lt;i&gt;1. 콜 스택 (Call Stack)&lt;/i&gt;&lt;br /&gt;- LIFO (Last In, First Out) 구조&lt;br /&gt;&lt;i&gt;2. 웹 API (또는 백그라운드 작업)&lt;/i&gt;&lt;br /&gt;- setTimeout(), fetch(), addEventListener() 같은 비동기 함수는 Web API나 Node.js 백그라운드에서 실행&lt;br /&gt;&lt;i&gt;3. Task Queue &amp;amp; Microtask Queue&lt;/i&gt;&lt;br /&gt;- 비동기 작업이 완료되면 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Task Queue 또는 Microtask Queue 에 콜백 함수가 추가됨&lt;br /&gt;- &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Microtask Queue(우선순위 높음) -&amp;gt; &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Task Queue(우선순위 낮음)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;i&gt;4. 이벤트 루프 (Event Loop)&lt;/i&gt;&lt;br /&gt;- 콜 스택이 비어 있을 때, 태스크 큐에서 작업을 꺼내 실행&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>eventloop</category>
      <category>Javascript</category>
      <category>Queue</category>
      <category>Task</category>
      <category>스택</category>
      <category>이벤트루프</category>
      <category>자바스크립트</category>
      <category>큐</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/60</guid>
      <comments>https://suzie-dev.tistory.com/60#entry60comment</comments>
      <pubDate>Tue, 4 Mar 2025 10:47:05 +0900</pubDate>
    </item>
    <item>
      <title>[Quiz] 비동기 처리 문제</title>
      <link>https://suzie-dev.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 실행하면 어떤 출력이 나올까?&lt;/p&gt;
&lt;pre id=&quot;code_1740702122199&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function fetchData() {
  return &quot;Data fetched!&quot;;
}

fetchData().then(data =&amp;gt; console.log(data));
console.log(&quot;Fetching data...&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 : 콘솔에 출력되는 결과는?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div style=&quot;margin: 20px 0px; caret-color: auto; background-color: #fafafa; padding: 20px 20px 22px; border: 1px dashed #c5c5c5; color: #333333;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 정답&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fetching &lt;span&gt;data&lt;/span&gt;... &lt;br /&gt;Data fetched!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  설명&lt;/b&gt;&lt;br /&gt;- `fetchData` 함수는 비동기 함수로 &quot;Data fetched!&quot; 라는 값을 가진 Promise를 return&lt;br /&gt;- `fetchData().then(data =&amp;gt; console.log(data));` 는 Promise가 이행되면 &quot;Data fetched!&quot;를 출력하게 되지만, 이는 비동기적으로 실행됨&lt;br /&gt;- 반면 `console.log(&quot;Fetching data...&quot;)는 즉시 실행되서 이 문장이 먼저 출력됨&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  나를 위한 심층 분석 ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetchData().then(data =&amp;gt; console.log(data));&lt;/b&gt;&lt;br /&gt;- &lt;b&gt;fetchData()&lt;/b&gt; : 이 함수는 비동기 작업을 수행하고 Promsie를 반환&lt;br /&gt;- &lt;b&gt;.then()&lt;/b&gt; : Promise가 이행되면, then() 메서드 안의 콜백함수가 실행되고 이 콜백함수는 이행된 Promise의 결과를 인수로 받음&lt;br /&gt;- &lt;b&gt;결과 출력&lt;/b&gt;: 콜백함수에서 console.log(data)를 호출하여 Promise가 이행된 결과를 콘솔에 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>개발/JavaScript</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/59</guid>
      <comments>https://suzie-dev.tistory.com/59#entry59comment</comments>
      <pubDate>Fri, 28 Feb 2025 10:06:49 +0900</pubDate>
    </item>
    <item>
      <title>[Quiz] 스코프(Scope) 관련 문제</title>
      <link>https://suzie-dev.tistory.com/58</link>
      <description>&lt;pre id=&quot;code_1740615754569&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function test() {
  var a = 10;
}
console.log(a);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위 코드를 실행하면 어떤 결과가 나올까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;387&quot; data-start=&quot;327&quot; data-ke-size=&quot;size16&quot;&gt;A) 10&lt;br /&gt;B) undefined&lt;br /&gt;C) ReferenceError&lt;br /&gt;D) null&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0px; caret-color: auto; background-color: #fafafa; padding: 20px 20px 22px; border: 1px dashed #c5c5c5; color: #333333;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;387&quot; data-start=&quot;327&quot; data-ke-size=&quot;size16&quot;&gt;✅ 정답: &lt;b&gt;C (ReferenceError)&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;231&quot; data-start=&quot;218&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;399&quot; data-start=&quot;232&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;274&quot; data-start=&quot;232&quot;&gt;var는 &lt;b&gt;함수 스코프&lt;/b&gt;(Function Scope)를 가짐.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;274&quot; data-start=&quot;232&quot;&gt;var로 선언한 변수는 &lt;b&gt;함수 내부에서만 유효함&lt;/b&gt; (즉, 함수 바깥에서는 접근 불가능!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;322&quot; data-start=&quot;275&quot;&gt;즉, a는 test() 함수 내부에서만 존재하고 함수가 끝나면 사라짐.&lt;/li&gt;
&lt;li data-end=&quot;399&quot; data-start=&quot;323&quot;&gt;함수 바깥에서 console.log(a);를 실행하면 &lt;b&gt;a가 정의되지 않았기 때문에 ReferenceError가 발생&lt;/b&gt;함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오답노트&lt;/b&gt; : 나는 B undefined 라고 생각했는데 생각해보면 정의 되어있지 않는 상태이기때문에 &lt;b&gt;ReferenceError &lt;/b&gt;로 봐야한다&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/58</guid>
      <comments>https://suzie-dev.tistory.com/58#entry58comment</comments>
      <pubDate>Thu, 27 Feb 2025 09:29:55 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js / TypeScript] custom hook : useEventListener</title>
      <link>https://suzie-dev.tistory.com/57</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;개발할때 유용하게 사용한 &lt;u&gt;&lt;b&gt;useEventListener hook&lt;/b&gt;&lt;/u&gt; 이다. (Next.js, TypeScript)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;i&gt;src &amp;gt; utils &amp;gt; hooks&lt;/i&gt;&lt;/span&gt; 에 저장 후 import하여 사용 하면 코드도 간편해지고 편함.&lt;/p&gt;
&lt;pre id=&quot;code_1740374384162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useRef } from &quot;react&quot;;

function useEventListener&amp;lt;K extends keyof WindowEventMap&amp;gt;(
  eventType: K,
  handler: (event: WindowEventMap[K]) =&amp;gt; void,
  element: HTMLElement | Document | Window | null = typeof window !==
  &quot;undefined&quot;
    ? window
    : null
) {
    const savedHandler = useRef&amp;lt;(event: WindowEventMap[K]) =&amp;gt; void | null&amp;gt;(null);

  useEffect(() =&amp;gt; {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(() =&amp;gt; {
    if (!element) return;

    const eventListener = (event: Event) =&amp;gt; {
      if (savedHandler.current) {
        savedHandler.current(event as WindowEventMap[K]);
      }
    };

    element.addEventListener(eventType, eventListener);
    return () =&amp;gt; element.removeEventListener(eventType, eventListener);
  }, [eventType, element]);
}

export default useEventListener;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- `useEventListener` Hook은 언제 사용할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Hook은 컴포넌트에서 이벤트 리스너를 쉽게 추가/제거하기 위해 사용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 전역 이벤트 (window, document) 감지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- scroll, resize, keydown, mousemove 같은 전역 이벤트를 감지할 때 유용&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;&lt;b&gt;2. 키보드 입력 감지 (keydown, keyup)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 특정 키(Esc, Enter 등)를 누르면 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 마우스 이벤트 감지 (mousemove, click, mouseover)&lt;/b&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;&lt;b&gt;4. 특정 요소의 이벤트 감지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 div, button, input 등 특정 요소의 이벤트를 감지하고 싶을 때 사용.&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;u&gt;&lt;b&gt;useEventListener Hook을 사용하면 좋은 점&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 이벤트 추가/제거를 자동화 -&amp;gt; useEffect로 수동관리할 필요가 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 메모리 누수 방지 -&amp;gt; 컴포넌트 언마운트 시 자동으로 이벤트 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 전역 및 특정 요소 이벤트를 쉽게 감지 가능&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;b&gt;- &amp;lt;K extends keyof WindowEventMap&amp;gt; 이란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 TypeScript의 제네릭(Generic) 문법을 사용하여 K가 WindowEventMap의 키 값 중 하나가 되도록 제한하는 코드.&lt;br /&gt;&lt;b&gt;WindowEventMap&lt;/b&gt;은 TypeScript에서 제공하는 내장 타입 : 브라우저에서 발생할 수 있는 모든 이벤트를 포함하는 객체&lt;br /&gt;&lt;b&gt;keyof WindowEventMap&lt;/b&gt; 은 모든 키를 가지고오는 유니온 타입&lt;br /&gt;&lt;b&gt;K extends keyof WindowEventMap&lt;/b&gt;에서 K는 &quot;click&quot;, &quot;keydown&quot; 등 문자열만 가능하도록 제한&lt;br /&gt;&lt;i&gt;다른 변수명 사용해도 문제는 없지만 일반적으로 &quot;K&quot;를 사용 함. 제네릭에서 &quot;K&quot;는 &quot;key&quot;를 의미&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740375516332&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const savedHandler = useRef&amp;lt;(event: WindowEventMap[K]) =&amp;gt; void&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 useRef를 사용한 이유는 이벤트 리스너가 최신 핸들러를 참조하도록 유지하면서 리렌더링 방지 가능&lt;/p&gt;</description>
      <category>개발/Next.js</category>
      <category>customHook</category>
      <category>Javascript</category>
      <category>Nextjs</category>
      <category>react</category>
      <category>reacthook</category>
      <category>useeventlistener</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/57</guid>
      <comments>https://suzie-dev.tistory.com/57#entry57comment</comments>
      <pubDate>Mon, 24 Feb 2025 14:18:50 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 에서  이미지 관리 : public vs src/assets</title>
      <link>https://suzie-dev.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Next.js로 프로젝트를 진행하면서 이미지를 저장하는 두 가지 방식이 있는 걸 알게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWAvj/btsLWCLqIz2/2IUJ4dhn1HKr9Ofa8NTCc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWAvj/btsLWCLqIz2/2IUJ4dhn1HKr9Ofa8NTCc1/img.png&quot; data-alt=&quot;폴더구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWAvj/btsLWCLqIz2/2IUJ4dhn1HKr9Ofa8NTCc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWAvj%2FbtsLWCLqIz2%2F2IUJ4dhn1HKr9Ofa8NTCc1%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;151&quot; height=&quot;162&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;폴더구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;1. public 폴더에 저장하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;2. src/assets 에 저장하기&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;둘 중 하나만 쓰면 되는 거 아니야?&lt;/span&gt;&lt;/blockquote&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;일단 특징을 표로 정리해 보았다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 99.0698%; height: 152px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;public&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;src/assets&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;사용 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;URL 접근 가능 (import 도 가능)&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;Import 로 불러옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 38px;&quot;&gt;&lt;b&gt;캐싱 문제&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 38px;&quot;&gt;업데이트 된 이미지가 이전 이미지와 파일명이 같다면 옛날 파일을 보여줄 수도 있음&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 38px;&quot;&gt;해시된 파일명으로 최신 파일만 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;번들링&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;번들링 되지 않음&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;번들링과 최적화 자동으로 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;번들 크기에 영향을 안줌&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;자주 사용하면 번들 크기가 커질 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;최적화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;Next/image 와 함께 사용시 최적화 가능&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;빌드 과정에서 자동 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.1473%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;적합한 경우&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%; text-align: center; height: 19px;&quot;&gt;로고, 아이콘처럼 변경이 드문 정적 자산&lt;/td&gt;
&lt;td style=&quot;width: 51.0077%; text-align: center; height: 19px;&quot;&gt;자주 바뀌거나 컴포넌트와 관련된 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&lt;b&gt;해시(Hash) 값을 파일명에 붙이는 이유&lt;/b&gt; : 고유한 값을 파일에 붙여줘서 업데이트된 내용이 제대로 반영되게 해 줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자주 사용하면 번들 크기가 커질 수 있음의 의미 : &lt;/b&gt;최적화되지 않은 파일을 추가하면 번들크기에 영향을 줄 수 있다. &amp;amp;&amp;amp; 번들에 포함된 자산 크기와 수에 따라 초기 로딩 속도에 영향을 미칠 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;캐싱 문제란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 이전에 불러왔던 파일을 저장해서 빠르게 다시 보여준다. 문제는 파일 내용을 바꿔도 파일명이 같으면 브라우저가 옛날 파일을 계속 보여준다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;예) public/images/logo.png를 새로운 이미지로 교체했는데, 브라우저는 캐시 돼서 여전히 예전 이미지를 보여주는 경우&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;위의 문제를 해결하는 방법 : src/assets&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- src/assets에 이미지를 저장하면 Next.js가 파일명을 해싱 (예: logo.abc123.png)한다. 파일이 바뀔 때마다 새로운 이름이 만들어져 브라우저가 무조건 최신 파일을 가지고 옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;예)&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- 원본파일 : src/assets/images/logo.png&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- 빌드 수: _next/static/media/logo.abc123.png&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 방식의 활용법&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Public 폴더&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 거의 바뀌지 않는 이미지 (예: 로고, favicon)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. JSON 데이터나 API 응답에서 이미지 URL을 참조해야 할 때.&lt;/p&gt;
&lt;pre id=&quot;code_1737504413532&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//예
[
  {
    &quot;id&quot;: 1,
    &quot;title&quot;: &quot;멋진 카드&quot;,
    &quot;img&quot;: &quot;/images/cards/card1.png&quot;
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 번들 크기를 최소화하고 싶을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;src/assets 폴더&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 특정 컴포넌트와 밀접하게 연결된 이미지 (모듈화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 자주 바뀌는 비주얼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 타입 안전한 Import로 경로 오류를 방지하고 싶을 때&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;** 부록 : Public 폴더의 캐싱 문제 해결법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 파일명 변경 : 새로운 업데이트마다 고유한 이름 붙이기 (예:logo_v2.png)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이게 귀찮으니까 자주 바뀐다면 그냥 src/assets에 저장하는 걸 추천함.&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;b&gt;결론 : 적절히 섞어 쓰자!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 내린 결론은 일단 이미지들은 assets에 저장하고 url으로 접근해야 하는 경우에는 public에 옮겨서 쓰는 게 낫겠다고 판별함.&lt;/p&gt;</description>
      <category>개발/Next.js</category>
      <category>assets폴더</category>
      <category>Next.js</category>
      <category>next.js이미지저장</category>
      <category>Nextjs</category>
      <category>public폴더</category>
      <category>이미지</category>
      <author>Iuna</author>
      <guid isPermaLink="true">https://suzie-dev.tistory.com/56</guid>
      <comments>https://suzie-dev.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 22 Jan 2025 09:14:08 +0900</pubDate>
    </item>
  </channel>
</rss>