<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>이챙의 개발 log</title>
    <link>https://chaeyoung2.tistory.com/</link>
    <description>프론트엔드 개발도 하고 뛰기도 하고</description>
    <language>ko</language>
    <pubDate>Sun, 16 Aug 2026 21:11:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>이챙(leechaeng)</managingEditor>
    <image>
      <title>이챙의 개발 log</title>
      <url>https://tistory1.daumcdn.net/tistory/3689438/attach/14ab3a6c50bb4a55ac2b6c599800eaae</url>
      <link>https://chaeyoung2.tistory.com</link>
    </image>
    <item>
      <title>클로드 코드와 피그마 MCP로 디자인 시스템 만들어보기</title>
      <link>https://chaeyoung2.tistory.com/158</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인 시스템 프롬프트 한줄로 구축이 된다?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; 디자인 시스템&lt;br /&gt;색상,폰트,컴포넌트 등 제품의 UI규칙을 하나로 정리한것&lt;br /&gt;디자인토큰, 컴포넌트, 가이드라인 등으로 구성되어 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 디자인 시스템을 만들려면 피그마에서 토큰을 정의하고 JSON으로 추출해서 개발자가 CSS/JS로 변환하여 컴포넌트화 한다.&lt;br /&gt;근데 Figma MCP + 클로드코드 조합으로 이 과정을 프롬프트 한줄로 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클로드코드에 Figma MCP 연동&lt;/h2&gt;
&lt;pre id=&quot;code_1774764362981&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;claude mcp add --transport http figma-remote-mcp https://mcp.figma.com/mcp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드코드 접속후 명령어를 입력후 auth인증 해주면 MCP 연동은 끝난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2J6tw/dJMcac3AVQq/tS8NXqJvU69CkueTbFyV71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2J6tw/dJMcac3AVQq/tS8NXqJvU69CkueTbFyV71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2J6tw/dJMcac3AVQq/tS8NXqJvU69CkueTbFyV71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2J6tw%2FdJMcac3AVQq%2FtS8NXqJvU69CkueTbFyV71%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;657&quot; height=&quot;153&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connected처리가 되어있으면 연동된것임.&lt;br /&gt;&lt;b&gt;사실 피그마가 유료 버전에 dev mode를 사용하면 디자인 설계기준으로 코드를 뽑아주기 때문에 시스템을 구축하기에 더 좋다.&lt;/b&gt; 하지만 나는 무료이기 때문에,,  무료로 테스트를 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;AI가 잘 읽어오려면 디자인 구조를 잘짜야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 피그마내 디자인을 잘 읽어오려면 디자인 구조가 잘 짜여있어야 한다. 구조가 곧 프롬프트 품질이며 AI가 디자인상 컴포넌트가 어떻게 동작하는지 제대로 이해하고 뽑아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 피그마 토큰 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvorQe/dJMcaaY1Fa1/LFK2Gt3aOIBpLh8Jq6Udsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvorQe/dJMcaaY1Fa1/LFK2Gt3aOIBpLh8Jq6Udsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvorQe/dJMcaaY1Fa1/LFK2Gt3aOIBpLh8Jq6Udsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvorQe%2FdJMcaaY1Fa1%2FLFK2Gt3aOIBpLh8Jq6Udsk%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;635&quot; height=&quot;328&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상, 폰트, 간격을 반드시 변수(Variables)로 등록해놓는다.&amp;nbsp;&lt;br /&gt;그냥 하드코딩으로 적용하는 것이 아니라 색상을 하나 쓰더라도 color/primary 같은 변수로 등록해서 레이아웃에 적용시켜야함.&lt;br /&gt;AI가 디자인을 뽑아올때 변수 등록을 안하고 단순히 하드코딩 처리하면 primary인지 secondary인지 맥락을 모른다. 정확히 변수로 지정을 해줘야 토큰 생성시 AI가 피그마에서 정의한 기준대로 추출한다.&lt;br /&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. AutoLayout 사용&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKNGoB/dJMcabp8il0/DGkE2RKvbN8iXWAasFOBHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKNGoB/dJMcabp8il0/DGkE2RKvbN8iXWAasFOBHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKNGoB/dJMcabp8il0/DGkE2RKvbN8iXWAasFOBHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKNGoB%2FdJMcabp8il0%2FDGkE2RKvbN8iXWAasFOBHK%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;234&quot; height=&quot;336&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토레이아웃을 사용하지 않으면 AI는 프레임이 어떻게 배치되어 있는지 모른다. 간격이나 정렬이 어떻게 되어있는지 모르기 때문에 이상하게 생성해줄 확률이 높다. 오토레이아웃을 쓰면 피그마에서 정의가 이미 되어있기 때문에 AI가 이건 flex의 column구조구나 하고 이해후 코드를 작성해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1774765726002&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 오토레이아웃 미사용
position: absolute;
top: 23px;
left: 117px;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1774765745470&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 오토레이아웃 사용
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;&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;&lt;b&gt;3. 레이어이름은 명확하게 작성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpjGSy/dJMcai3Ml3G/E2lKlnPmzWAyuY75vfAtXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpjGSy/dJMcai3Ml3G/E2lKlnPmzWAyuY75vfAtXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpjGSy/dJMcai3Ml3G/E2lKlnPmzWAyuY75vfAtXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpjGSy%2FdJMcai3Ml3G%2FE2lKlnPmzWAyuY75vfAtXK%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;226&quot; height=&quot;143&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어이름을 명확하게 작성해야 AI가 컴포넌트를 디자인한 의도대로 추출할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1774766158762&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❌
Frame 1
Rectangle 23
Group 5
Text 12&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1774766175180&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;⭕️
Card
CardHeader
CardDescription&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI입장에서 중구난방으로 레이어 이름이 작성되어있으면 어느것이 헤더이고 버튼인지 헷갈린다. 명확한 레이어 이름을 지정해주면 아 이건 Card컴포넌트구나 라고 이해하고 바로 컴포넌트 구조로 변환이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 피그마 구조가 잘 짜여져 있으면 AI 결과 품질이 향상된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클로드 코드에게 코드 추출 부탁하기&lt;/h2&gt;
&lt;pre id=&quot;code_1774766569004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이 피그마 파일 분석해서                                                                                                                                                                                          
1. 디자인 토큰 (색상, 폰트, 간격) 추출                                                                                                                                                                             
2. 컴포넌트별 React 코드 생성                                                                                                                                                                                      
3. 각 컴포넌트 Storybook story 파일 생성                                                                                                                                                                           
해줘                                                                                                                                                                                                               
[피그마 URL]&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1774766797739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// global.css

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);

  /* Card Design Tokens from Figma */
  --color-card-bg: #ffffff;
  --color-skeleton: #f4f4f4;
  --color-cta: #c50e0e;
  --color-cta-text: #ffffff;

  --spacing-card-padding: 5px;
  --spacing-card-gap: 5px;
  --spacing-cta-py: 5px;

  --font-size-cta: 16px;
  --font-weight-cta: 400;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 기존에 피그마 파일에 Card 컴포넌트만 작성을 해놔서 인지 Card 기반으로 토큰을 뽑아줬다.&lt;br /&gt;피그마 파일에서 정의한 간격과 색상기준으로 토큰을 추출해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774767123510&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Card.tsx

&quot;use client&quot;;

interface CardProps {
	title?: string;
	description?: string;
	ctaLabel?: string;
	onCtaClick?: () =&amp;gt; void;
}

export default function Card({
	title,
	description,
	ctaLabel = &quot;보러가기&quot;,
	onCtaClick,
}: CardProps) {
	return (
		&amp;lt;div className=&quot;w-[245px] flex flex-col gap-[5px] p-[5px] bg-card-bg&quot;&amp;gt;
			{/* Card Title */}
			{title ? (
				&amp;lt;div className=&quot;text-sm font-semibold text-foreground&quot;&amp;gt;{title}&amp;lt;/div&amp;gt;
			) : (
				&amp;lt;div className=&quot;h-[11px] bg-skeleton rounded&quot; /&amp;gt;
			)}

			{/* Card Description */}
			{description ? (
				&amp;lt;div className=&quot;text-sm text-foreground line-clamp-2&quot;&amp;gt;{description}&amp;lt;/div&amp;gt;
			) : (
				&amp;lt;div className=&quot;h-[40px] bg-skeleton rounded&quot; /&amp;gt;
			)}

			{/* Card CTA Button */}
			&amp;lt;button
				onClick={onCtaClick}
				className=&quot;h-[25px] py-[5px] bg-cta hover:bg-cta/90 text-cta-text font-medium text-base transition-colors flex items-center justify-center cursor-pointer&quot;
			&amp;gt;
				{ctaLabel}
			&amp;lt;/button&amp;gt;
		&amp;lt;/div&amp;gt;
	);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev mode로 사용할경우 get_code기반으로 실제 피그마 설계 기반을 그대로 가져오기 때문에 AI가 컴포넌트 구조를 더 정확하게 추출할수있다.&lt;br /&gt;나는 무료 피그마를 사용하고 있어서 어느정도 AI가 추측하여 컴포넌트를 생성해준것같다. 실제로 결과물을 보면 gap 같은 스타일 값이 변수가 아닌 하드코딩으로 처리된 것을 확인할 수 있었다. 다만 레이어 이름을 규칙에 맞게 잘 지정해놓은 덕분인지, Props 네이밍은 피그마 기준대로 잘 작성해줬다.&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-filename=&quot;blob&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7J5Dr/dJMcagx978n/uyqmi7Fko4YkEZPqGTP4lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7J5Dr/dJMcagx978n/uyqmi7Fko4YkEZPqGTP4lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7J5Dr/dJMcagx978n/uyqmi7Fko4YkEZPqGTP4lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7J5Dr%2FdJMcagx978n%2Fuyqmi7Fko4YkEZPqGTP4lk%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;1052&quot; height=&quot;649&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&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;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;프롬프트 보다 설계가 중요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마 디자인에서 이미 토큰, 컴포넌트 구조, 네이밍 등 잘 반영되어 있으면 프롬프트가 간단해도 디자인 시스템을 잘 구축할 수 있다. 이미 디자인 설계가 잘 되어있으면 개발자가 수정할 일도 극히 줄어든다.&lt;br /&gt;AI는 단순히 도구 일뿐 피그마를 어떻게 설계하느냐가 AI 기반으 추출에서 디자인 시스템의 기초라고 생각한다.&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://medium.com/design-bootcamp/designing-with-mcp-server-bridging-design-systems-and-ai-for-developer-friendly-prototypes-4f08b0a0881d&quot;&gt;https://medium.com/design-bootcamp/designing-with-mcp-server-bridging-design-systems-and-ai-for-developer-friendly-prototypes-4f08b0a0881d&lt;/a&gt;&lt;/p&gt;</description>
      <category>AI</category>
      <category>디자인시스템</category>
      <category>클로드코드 Figma MCP</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/158</guid>
      <comments>https://chaeyoung2.tistory.com/158#entry158comment</comments>
      <pubDate>Sun, 29 Mar 2026 16:09:46 +0900</pubDate>
    </item>
    <item>
      <title>TaskMaster AI 설치부터 활용해보기</title>
      <link>https://chaeyoung2.tistory.com/157</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;TaskMaster AI&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-21 오후 4.48.06.png&quot; data-origin-width=&quot;2258&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/40BAM/dJMcabprMYD/ZBs6yqQrQkKEJTdwDMJRkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/40BAM/dJMcabprMYD/ZBs6yqQrQkKEJTdwDMJRkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/40BAM/dJMcabprMYD/ZBs6yqQrQkKEJTdwDMJRkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F40BAM%2FdJMcabprMYD%2FZBs6yqQrQkKEJTdwDMJRkk%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;2258&quot; height=&quot;760&quot; data-filename=&quot;스크린샷 2026-01-21 오후 4.48.06.png&quot; data-origin-width=&quot;2258&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Taskmaster는 당신의 AI를 위한 개인 프로젝트 매니저입니다.&lt;br /&gt;작업을 손쉽게 정리&amp;middot;조사&amp;middot;확장&amp;middot;우선순위화하고 실행까지 도와줍니다.&lt;br /&gt;영구적인 컨텍스트 유지, 드리프트 없는 정확성, 즉각적인 명확함을 제공합니다.&lt;br /&gt;무료이며 오픈소스이고 API 연동도 친화적입니다.&lt;br /&gt;생산적으로 함께 일해봐요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.task-master.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에 나와있듯이 내 개인 프로젝트 매니저다. AI가 옆에서 복잡하고 광대할 수 있는 계획들을 작은 단위의 Task로 나눠준다. 개발하다보면 우선순위가 무엇이고 일정관리를 어떻게 할 것 이며 어느 기능을 구현해야할지 고민할때가 많다.&lt;br /&gt;TaskMaster AI를 사용하면 그 고민을 해결해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TaskMaster AI 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1768982474534&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g task-master-ai&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1768982742648&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master init&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;633&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q37dN/dJMcahJYtnQ/zZv2cfxJCvYwk3Dl6JiS70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q37dN/dJMcahJYtnQ/zZv2cfxJCvYwk3Dl6JiS70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q37dN/dJMcahJYtnQ/zZv2cfxJCvYwk3Dl6JiS70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ37dN%2FdJMcahJYtnQ%2FzZv2cfxJCvYwk3Dl6JiS70%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;633&quot; height=&quot;383&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;init을 실행하면 어떻게 셋팅할것인지 선택할 수 있다.&lt;br /&gt;set up AI IDE rules for better integration? 에 yes를 하면 현재 사용하고 있는 에디터에 맞춤으로 rule을 적용해준다. 나같은 경우 커서를 사용하고 있어 IDE는 커서로 선택했다. 초기 셋팅이 끝나면 &lt;b&gt;.cursor 폴더내 rule&lt;/b&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-filename=&quot;스크린샷 2026-01-21 오후 5.13.29.png&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/si8pD/dJMcahDaWvl/ykUfl3UHKtVUG0y0gsyH7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/si8pD/dJMcahDaWvl/ykUfl3UHKtVUG0y0gsyH7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/si8pD/dJMcahDaWvl/ykUfl3UHKtVUG0y0gsyH7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsi8pD%2FdJMcahDaWvl%2FykUfl3UHKtVUG0y0gsyH7K%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;761&quot; height=&quot;359&quot; data-filename=&quot;스크린샷 2026-01-21 오후 5.13.29.png&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model을 선택할수가 있는데 모델은 3가지로 나뉜다. &lt;i&gt;1. Main model 2.Research Model 3.Fallback Model&lt;/i&gt; 요 3가지인데 Main model이 주로 실제로 사용하는 ai 이고 Research model은 주로 조사,분석, 정보탐색하는 용도이다. Fallback model 같은 경우 두가지의 모델이 안될경우 대체 모델이다.&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-filename=&quot;스크린샷 2026-01-21 오전 10.21.05.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjmexy/dJMcahDbxt4/1kk3VhziBkO1Yn8dEUyNc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjmexy/dJMcahDbxt4/1kk3VhziBkO1Yn8dEUyNc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjmexy/dJMcahDbxt4/1kk3VhziBkO1Yn8dEUyNc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjmexy%2FdJMcahDbxt4%2F1kk3VhziBkO1Yn8dEUyNc0%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;674&quot; height=&quot;154&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.21.05.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 현재 클로드를 유료로 사용중이라 calude-code의/Haiku를 메인 모델로 선택했다. 추후 모델을 다시 선택하고 싶다면 명령어 task-master models --setup 을 입력한뒤 재선택 하면 된다. model을 선택하고 나면 현재 모델은 무엇인지 어떤 모델들이 있는지는 task-master models로 확인 가능하다.&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-filename=&quot;스크린샷 2026-01-21 오전 10.20.06.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGL4D5/dJMcai28aP8/Ryymipg80CdcIW1e1JaoMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGL4D5/dJMcai28aP8/Ryymipg80CdcIW1e1JaoMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGL4D5/dJMcai28aP8/Ryymipg80CdcIW1e1JaoMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGL4D5%2FdJMcai28aP8%2FRyymipg80CdcIW1e1JaoMk%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;825&quot; height=&quot;1032&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.20.06.png&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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-filename=&quot;스크린샷 2026-01-23 오전 10.13.09.png&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFcXG7/dJMcagYzRhv/ogspS4rkQNfLiI9Lfg7bH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFcXG7/dJMcagYzRhv/ogspS4rkQNfLiI9Lfg7bH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFcXG7/dJMcagYzRhv/ogspS4rkQNfLiI9Lfg7bH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFcXG7%2FdJMcagYzRhv%2FogspS4rkQNfLiI9Lfg7bH0%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;328&quot; height=&quot;246&quot; data-filename=&quot;스크린샷 2026-01-23 오전 10.13.09.png&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 셋팅이 끝나면 폴더가 생성된다.&amp;nbsp;&lt;br /&gt;taskmaster폴더는 4개로 나뉘는데 &lt;i&gt;1. docs는 문서 가이드 2.reports는 ai가 만들어준 진행 리포트 3. task는 말그대로 현재 tasks들 4. templates는 요구사항 PRD 탬플릿&lt;/i&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-filename=&quot;스크린샷 2026-01-23 오전 10.19.36.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lU3sL/dJMcai28aY5/AGSAQrZNL5BrgkRmzkP7Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lU3sL/dJMcai28aY5/AGSAQrZNL5BrgkRmzkP7Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lU3sL/dJMcai28aY5/AGSAQrZNL5BrgkRmzkP7Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlU3sL%2FdJMcai28aY5%2FAGSAQrZNL5BrgkRmzkP7Hk%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;612&quot; height=&quot;460&quot; data-filename=&quot;스크린샷 2026-01-23 오전 10.19.36.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;taskmaster는 여러 명령에 걸쳐 ai를 활용하는데 이러한 명령을 사용하려면 별도의 api키가 필요하다. &lt;b&gt;cursor폴더내 mcp.json&lt;/b&gt; 파일을 열어보면 api키를 적용할수 있는 json이 있는데 이중 사용하는 ai 모델이 있다면 env내에 api 키를 넣어주면 된다. 나같은 경우 클로드 코드를 사용할 것이라 추가하지 않았다. 클로드 코드는 별도의 api키는 필요 없고 cli만 설치하면 된다고 함.&lt;br /&gt;&lt;br /&gt;TASK_MASTER_TOLLS는 MCP 서버의 컨텍스트 최적화 환경변수이다. all, standard, core, custom 이 순서대로 토큰 사용량이 줄어드는데 많은 도구를 사용할 필요도 없을거 같고 토큰이 소비 되지 않도록 core(일일 개발 워크플로 사용량)를 선택했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요구사항 문서 파싱하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.29.57.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0UvXw/dJMcac9GhfI/WLMGuTJkXS0CRWC4KEL5lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0UvXw/dJMcac9GhfI/WLMGuTJkXS0CRWC4KEL5lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0UvXw/dJMcac9GhfI/WLMGuTJkXS0CRWC4KEL5lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0UvXw%2FdJMcac9GhfI%2FWLMGuTJkXS0CRWC4KEL5lk%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;1360&quot; height=&quot;924&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.29.57.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;template 폴더에 example_prd.txt 파일이 있는데. PRD 탬플릿 예시 파일이다. 이 파일을 토대로 요구사항 문서를 만들면 된다. 이것도 ai 에게 말하면 된다. 그러면 알잘딱깔센으로 만들어준다. (ㅋㅋ) 당연한거지만 PRD의 문서가 상세할수록 작업 정확도가 높아진다고함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769131813159&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master parse-prd your-prd.txt&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;ai가 잘만들어준 PRD를 우리는 docs파일에 복붙하고 task-master에게 파싱해달라고 요청하면 된다.&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;825&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/driGrB/dJMcaiIP7qj/uRibFusekJLa4uDHfwhzp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/driGrB/dJMcaiIP7qj/uRibFusekJLa4uDHfwhzp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/driGrB/dJMcaiIP7qj/uRibFusekJLa4uDHfwhzp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdriGrB%2FdJMcaiIP7qj%2FuRibFusekJLa4uDHfwhzp0%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;825&quot; height=&quot;1028&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;task-master가 PRD를 파싱하여 task를 만들어줬다. 각 task마다의 status와 중요도(priority), 의존성(Dependencies)까지 확인 할 수 있다. 시각적으로 현재 어떤 task를 완료했고 우선순위가 높은지 한눈에 확인 할 수 가있다. 우리는 이 작업들의 복잡성을 분석할 수 있다. task-master analyze-complexity 명령어로 복잡성을 분석한 후 task-list로 확인하면 &lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;complexity에 복잡도가 생성된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;복잡도의 척도는 1~10점으로 평가되고 report/task-complexity-report.json&lt;/span&gt;에 복잡도를 평가한 리포트가 생성된다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769132675351&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;		{
			&quot;taskId&quot;: 2,
			&quot;taskTitle&quot;: &quot;Mock 데이터 구조 및 관리 설정&quot;,
			&quot;complexityScore&quot;: 5,
			&quot;recommendedSubtasks&quot;: 5,
			&quot;expansionPrompt&quot;: &quot;Mock 데이터 및 서비스 계층을 체계적으로 구성하세요. (1) TypeScript 인터페이스/타입 정의 완성 (2) 다양한 카테고리의 이벤트 Mock 데이터 생성 (3) 세션 및 좌석 배치도 Mock 데이터 생성 (4) 비동기 Mock 서비스 레이어 구현 (5) localStorage 기반 데이터 저장소 구현&quot;,
			&quot;reasoning&quot;: &quot;반복적인 데이터 구조 작업이지만 복잡한 로직은 없습니다. TypeScript 타입 정의, Mock 배열 구성, 간단한 서비스 함수 구현만 필요합니다. 다만 데이터 구조가 정확해야 이후 UI 컴포넌트들이 올바르게 작동하므로 신중함이 필요합니다.&quot;
		},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;comlexityScore가 복잡도 측정 점수이고 recommendedSubtasks가 복잡성의 기준으로 평가된 하위테스크 수이다. 작업에 맞춰 만들어진 프롬프트(expansisonPrompt)도 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서브테스크 생성하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.46.04.png&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj2hvx/dJMcafrQJxi/z1f0IdKghk1LNCvsfO3e3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj2hvx/dJMcafrQJxi/z1f0IdKghk1LNCvsfO3e3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj2hvx/dJMcafrQJxi/z1f0IdKghk1LNCvsfO3e3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj2hvx%2FdJMcafrQJxi%2Fz1f0IdKghk1LNCvsfO3e3K%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;892&quot; height=&quot;117&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.46.04.png&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;task-master는 참 친절하다. list를 확인하면 다음 스텝을 알려준다. task-master next는 다음 task를 진행하겠냐? 이고,&amp;nbsp;&lt;br /&gt;task-master expand --id=&amp;lt;id&amp;gt;는 특정 task의 id를 같이 쓰면 그 Task를 기반으로 서브테스크들을 자동으로 확장해준다.&amp;nbsp;&lt;br /&gt;task-master set-status --id=&amp;lt;id&amp;gt; --status=done으로 특정 id Task의 status값을 done으로 바꿔 작업 상태를 완료 상태로 변경 할 수 도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769133143741&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master expand --id=10&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769133433625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master list --with-subtasks&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.59.07.png&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;841&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVkyr/dJMcaiB4Ipp/1CxPJJVElVsmK38rwRZMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVkyr/dJMcaiB4Ipp/1CxPJJVElVsmK38rwRZMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVkyr/dJMcaiB4Ipp/1CxPJJVElVsmK38rwRZMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVkyr%2FdJMcaiB4Ipp%2F1CxPJJVElVsmK38rwRZMr0%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;1062&quot; height=&quot;841&quot; data-filename=&quot;스크린샷 2026-01-21 오전 10.59.07.png&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;841&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10번의 서브테스크를 16개나 생성해줬다. taskmaster가 아니라 개발자가 하나하나 task마다 플랜을 짠다면 그것도 시간소요와 노동이 꽤 큰일인데 taskmaster가 알아서 짜준다니 참 고마운 일 이 아닐수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task 실행해서 완료하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGQXNa/dJMcaiPBeLz/sfK7ij4w5zEZuEdXmCqQ0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGQXNa/dJMcaiPBeLz/sfK7ij4w5zEZuEdXmCqQ0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGQXNa/dJMcaiPBeLz/sfK7ij4w5zEZuEdXmCqQ0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGQXNa%2FdJMcaiPBeLz%2FsfK7ij4w5zEZuEdXmCqQ0k%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;784&quot; height=&quot;361&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc3a9h/dJMcaiviST7/hAjsbrAYka0gnYY8Tve1q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc3a9h/dJMcaiviST7/hAjsbrAYka0gnYY8Tve1q0/img.png&quot; data-alt=&quot;Task#1 기반으로 Claude Code가 만들어준 결과물&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc3a9h/dJMcaiviST7/hAjsbrAYka0gnYY8Tve1q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc3a9h%2FdJMcaiviST7%2FhAjsbrAYka0gnYY8Tve1q0%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;400&quot; height=&quot;736&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Task#1 기반으로 Claude Code가 만들어준 결과물&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 클로드코드한데 가서 Task를 부탁해봤다. Task1 부터 실행해봤다.&amp;nbsp;&lt;br /&gt;클로드코드가 뚝닥뚝닥~~확인하고 알아서 작업할 동안 나는 그냥 기다리면 댐 ㅋㅋ&amp;nbsp;&lt;br /&gt;완성된 결과물을 보니 Task#1의 플랜이었던 프로젝트 세팅과 기본 레이아웃 구조를 완성해줬다. 사실 초반 세팅과 레이아웃이 제일 귀찮으면서 시간 소요가 꽤 드는 작업인데 5분도 안되서 알아서 만들어주니 사용하는 나는 너무 편하다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치부터 사용하면서 느낀점은 설치가 일단 너무 간편하다. 업무에서 하나의 기능 로직을 구성할때도 어디서부터 해야하지? 막막할때가 많은데 Taskmaster AI를 활용하게 된다면 그런 생각을 하는 시간부터 절약될거같다. 공식문서에 나온 말 그대로 프로젝트 매니저 같은 느낌.&lt;br /&gt;조금더 체계적으로 개발 task를 진행할 수 있겠다 라는 생각이 들었다.&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;i&gt;&lt;b&gt;참고 문서&lt;/b&gt;&lt;/i&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://github.com/eyaltoledano/claude-task-master?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;claude-task-master 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=p-FucXNL9Lc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;짐코딩 TaskMaster 가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>Taskmaster AI</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/157</guid>
      <comments>https://chaeyoung2.tistory.com/157#entry157comment</comments>
      <pubDate>Fri, 23 Jan 2026 11:16:27 +0900</pubDate>
    </item>
    <item>
      <title>Promise와 setTimeout은 왜 실행 순서가 다를까?</title>
      <link>https://chaeyoung2.tistory.com/156</link>
      <description>&lt;pre id=&quot;code_1766556396036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log('시작');    

Promise.resolve().then(() =&amp;gt; {        
  console.log('프로미스1');
});

setTimeout(() =&amp;gt; {                    
  console.log('타임아웃');
}, 0);

Promise.resolve().then(() =&amp;gt; {         
  console.log('프로미스2');
});

console.log('끝');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 순서대로 호출시 콘솔이 어떻게 찍힐까?&lt;br /&gt;microtask queue와 macrotask queue를 모른다면 시작 -&amp;gt; 프로미스1 -&amp;gt; 프로미스2 -&amp;gt; 끝 -&amp;gt; 타임아웃&lt;br /&gt;이렇게 생각할 수 도 있을것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 결과는 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;시작 -&amp;gt; 끝 -&amp;gt; 프로미스1 -&amp;gt; 프로미스2 -&amp;gt; 타임아웃&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Microtask queue, Macrotask queue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 싱글스레드 언어로 한번에 하나의 동작만 실행이 가능하다. 하지만 브라우저 환경에서는 Web API를 통해 비동기를 처리하고 이벤트루프가 콜스택과 태스크큐를 모니터링 하면서 실행 순서를 조율한다.&lt;br /&gt;이때 태스크큐는 마이크로태스크큐와 매크로태스크큐로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Microtask queue&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise, mutation observer, async/await&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Macrotask queue&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout, setInterval, &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;setImmediate&lt;/span&gt;&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;br /&gt;microtask queue가 우선순위가 높다. microtask queue의 모든 콜백들을 처리한 후 macrotask queue를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;microtask queue는 현재 실행중인 js코드가 끝나고 콜스택이 비었을때, 브라우저 렌더링이 되기 전에 모두 처리가 되고 macrotask queue는 렌더링 이후 태스크큐에서 하나씩 실행된다.&lt;/b&gt; 아래 코드예시를 봐보자&lt;/p&gt;
&lt;pre id=&quot;code_1766558238593&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setLoading(true)

Promise.resolve().then(() =&amp;gt; {
	setLoading(false)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 어떻게 될까?&lt;br /&gt;로딩 상태가 true였다가 비동기 작업 후 다시 로딩상태가 false로 변할것만 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Promise는 마이크로테스크기 때문에 js실행이 끝난뒤 렌더링이 되기전에 실행된다.&lt;br /&gt;로딩 상태가 true -&amp;gt; Promise 실행 -&amp;gt; 로딩상태 false 로 바뀌고 나서 렌더링이 실행되기 때문에&lt;br /&gt;이미 화면에서는 false인 상태라 화면에서 로딩상태임을 확인 할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1766559916127&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  document.getElementById(&quot;btn&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
    console.log('클릭!')
    setLoading(true);

    Promise.resolve().then(() =&amp;gt; {
      console.log('Promise 비동기 실행!')
      setLoading(false);
    });
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;프로미스체크.gif&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhwcun/dJMcacBIu6x/Dbzw4jHgNX1njXLAKZhSek/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhwcun/dJMcacBIu6x/Dbzw4jHgNX1njXLAKZhSek/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhwcun/dJMcacBIu6x/Dbzw4jHgNX1njXLAKZhSek/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bhwcun/dJMcacBIu6x/Dbzw4jHgNX1njXLAKZhSek/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;1036&quot; height=&quot;312&quot; data-filename=&quot;프로미스체크.gif&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start 버튼을 눌렀을때 로딩상태가 true -&amp;gt; false일것을 기대하지만 실제로는 계속 false인 상태인것을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766560013130&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setLoading(true)
setTimeout(() =&amp;gt; {
	setLoading(false)
}, 0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 macrotask queue인 settimeout으로 확인해보면 어떨까?&lt;/p&gt;
&lt;pre id=&quot;code_1766560257543&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document.getElementById(&quot;btn&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
	console.log('클릭!')
	setLoading(true);

    setTimeout(() =&amp;gt; {
      console.log('setTimeout 비동기 실행!')
      setLoading(false);            
    },1000)
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;셋타임아웃.gif&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baIAiQ/dJMcaiWboNy/ERpzJTxdkBzlUEWksHVzCk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baIAiQ/dJMcaiWboNy/ERpzJTxdkBzlUEWksHVzCk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baIAiQ/dJMcaiWboNy/ERpzJTxdkBzlUEWksHVzCk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/baIAiQ/dJMcaiWboNy/ERpzJTxdkBzlUEWksHVzCk/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;876&quot; height=&quot;408&quot; data-filename=&quot;셋타임아웃.gif&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;macrotask queue는 현재 실행중인 js와 microtask queue처리가&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;끝난뒤&lt;span&gt;&amp;nbsp;브라우저&amp;nbsp;&lt;/span&gt;&lt;/span&gt;렌더링 이후 실행하기 때문에 등록된 비동기 콜백들을 화면에서 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;await/async도 확인해보자&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766560776625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function test() {
    console.log('A')
    await Promise.resolve()
    console.log('B')
}

// 실행
test()
console.log('C')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await은 Promise가 resolve될때까지 async함수의 실행을 중단한다.&lt;br /&gt;Promise가 resolve되면, await이후의 코드는 마이크로태스크큐로 등록이 되어 현재 실행중인 코드들이 끝난후 실행된다.&amp;nbsp;&lt;br /&gt;따라서 결과가 A-&amp;gt;C-&amp;gt;B 로 실행된다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이상태에서 setTimeout을 추가해보면?&lt;/p&gt;
&lt;pre id=&quot;code_1766561860328&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function test() {
    console.log('A')
    await Promise.resolve()
    console.log('B')
}

test()

setTimeout(() =&amp;gt; {
    console.log('G')
})

console.log('C')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. test 함수 호출&lt;br /&gt;2. 콘솔에 A 출력&lt;br /&gt;3. await을 만나 코드 실행을 중단시키므로 다음 코드 실행&lt;br /&gt;4. test 함수 종료.&lt;br /&gt;5. setTimeout의 콜백을 macrotask queue에 등록&lt;br /&gt;6. 콘솔에 C 출력&lt;br /&gt;7. 모든 코드가 실행됬으므로 콜스택이 비워짐.&lt;br /&gt;8. 마이크로태스크큐부터 실행&lt;br /&gt;9. 콘솔에 B 출력&lt;br /&gt;10. 매크로태스큐 실행&lt;br /&gt;11. 콘솔에 G 출력&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;왜 Microtask Queue와 Macrotask Queue의 차이를 알아야 할까?&lt;/span&gt;&lt;/h2&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; &lt;br /&gt;Microtask queue는 렌더링 직전에 실행된다. 너무 많은 Mircrotask를 생성하거나 오래걸리는 Mircrotask를 실행하면? 브라우저의 렌더링이 지연 된다. 사용자에게 좋지 못함 경험을 주며 성능저하 우려 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의도치 않은 버그를 예방할 수 있다.&lt;/b&gt;&lt;br /&gt;차이를 모르고 코드 실행순서를 기대할경우 의도치 않은 결과나 버그 발생할 확률이 높다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;setTimeout(0)을 지연처리로 쓰지 않게됨.&lt;/b&gt; &lt;br /&gt;setTimeout(0)은 즉시 실행을 보장않고, 모든 마이크로태스크가 처리되고 렌더링이 이루어진 이후에 매크로태스크로 실행된다. 따라서 정확한 지연 시간을 기대하는 용도로 사용하기에는 적합하지 않음.&lt;/li&gt;
&lt;/ol&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;</description>
      <category>javascript</category>
      <category>mic</category>
      <category>이벤트 루프</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/156</guid>
      <comments>https://chaeyoung2.tistory.com/156#entry156comment</comments>
      <pubDate>Wed, 24 Dec 2025 16:49:18 +0900</pubDate>
    </item>
    <item>
      <title>JS 참조와 복사(깊은복사,얕은복사) feat. 리액트에서의 얕은 비교</title>
      <link>https://chaeyoung2.tistory.com/155</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참조 Reference&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;원시타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시타입은 각각 값자체가 메모리에 저장되어 독립적이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-28 오후 1.48.17.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FIRiw/dJMcabvSEvk/nzw9JtG9PsnbVkoQXxjYqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FIRiw/dJMcabvSEvk/nzw9JtG9PsnbVkoQXxjYqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FIRiw/dJMcabvSEvk/nzw9JtG9PsnbVkoQXxjYqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFIRiw%2FdJMcabvSEvk%2Fnzw9JtG9PsnbVkoQXxjYqk%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;465&quot; height=&quot;150&quot; data-filename=&quot;스크린샷 2025-11-28 오후 1.48.17.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;150&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;h3 data-ke-size=&quot;size23&quot;&gt;참조타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조타입 같은 경우 메모리 주소에 저장을 하지만&lt;br /&gt;실제값은 힙(Heep)에 저장되고 변수는 메모리 주소만 가지고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1764306608546&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let obj1 = {name: 'cy'}
let obj2 = obj1

obj2.name = 'cy lee'

console.log(obj1) // {name: 'cy lee'} obj1도 같이 변경&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;obj2 변수가 obj1을 참조하고 있을때&lt;br /&gt;obj2의 name값을 바꾸면 obj1의 name값도 바뀐다.&lt;br /&gt;obj2가 obj1을 참조하고 있기때문에 두개의 변수가 서로 다른 메모리주소를 갖고있지만 같은 참조값을 공유하고 있다는 증거이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-28 오후 2.03.24.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JR0fi/dJMcaiBIZKW/7itydXKno6HQCfOSJWr021/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JR0fi/dJMcaiBIZKW/7itydXKno6HQCfOSJWr021/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JR0fi/dJMcaiBIZKW/7itydXKno6HQCfOSJWr021/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJR0fi%2FdJMcaiBIZKW%2F7itydXKno6HQCfOSJWr021%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;450&quot; height=&quot;174&quot; data-filename=&quot;스크린샷 2025-11-28 오후 2.03.24.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-28 오후 2.06.48.png&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blynii/dJMcacO5s9o/F2IOVJAYYvAONHOdWLrSZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blynii/dJMcacO5s9o/F2IOVJAYYvAONHOdWLrSZ1/img.png&quot; data-alt=&quot;힙(Heep)에 저장된 실제 값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blynii/dJMcacO5s9o/F2IOVJAYYvAONHOdWLrSZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblynii%2FdJMcacO5s9o%2FF2IOVJAYYvAONHOdWLrSZ1%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;325&quot; height=&quot;140&quot; data-filename=&quot;스크린샷 2025-11-28 오후 2.06.48.png&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;힙(Heep)에 저장된 실제 값&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;얕은 복사(shallow&amp;nbsp;copy)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계 최상위 레벨만 독립적인 메모리를 할당한다. 중첩된 객체는 참조를 그대로 복사한다. &lt;br /&gt;즉 새객체를 생성하지만 내부 객체는 참조 상태인것.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764307424803&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메모리주소: 00112
const original = {
	name: 'kim' // 값 자체 저장
	score: [23,12] // 참조주소: 010x
}

// 메모리주소: 00113
const copy1 = {...original}


copy1.name = 'kim2'
original.name = 'kim' // 변경 x&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1764307454954&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;copy.score.push(30)

console.log(original.score) // [23,12,30]
console.log(copy1.score) // [23,12,30]
console.log(original.score === copy1.score) // [23,12,30]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;original객체를 얕은복사를 한게 copy1이다.&lt;br /&gt;copy1객체의 name을 바꾸면 original의 name도 바뀔까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 바뀔거라고 생각하겠지만 바뀌지않는다.&lt;br /&gt;얕은 복사를 했기때문에 original과 copy1은 이미 독립적인 메모리주소 (새로만든 객체) 를 각각 가지고 있다. &lt;br /&gt;서로 다른 객체이고 (original === copy 은 false) name은 원시타입이기 때문에 값이 바뀌지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;score는 배열이다. 객체이기 때문에 두객체가 같은 참조를 하고 있다&lt;br /&gt;그래서 값을 바꾸면 copy1, original 둘다 score의 값이 변경된다.&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-filename=&quot;스크린샷 2025-11-28 오후 2.36.07.png&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enX1aQ/dJMcabWWLrm/OrOJbZ8kCTnvCPzvIdeJw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enX1aQ/dJMcabWWLrm/OrOJbZ8kCTnvCPzvIdeJw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enX1aQ/dJMcabWWLrm/OrOJbZ8kCTnvCPzvIdeJw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenX1aQ%2FdJMcabWWLrm%2FOrOJbZ8kCTnvCPzvIdeJw0%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;446&quot; height=&quot;422&quot; data-filename=&quot;스크린샷 2025-11-28 오후 2.36.07.png&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얕은복사는 새로운 객체를 만들어 내기 때문에 original과 copy1객체가 서로 다른 메모리 주소를 가지고 있지만 그안의 객체는 참조주소를 서로 공유하고 있다. 그래서 score값이 바뀌면 두개의 객체의 score값이 둘다 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;깊은 복사(Deep copy)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얕은복사와 반대로 완전히 독립된 객체로 복사한다. 중첩된 객체도 독립된 객체로 분리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1764308312911&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const original = {
	name: 'mina',
	favorite: ['food','travle']
}

const copy = {...original}
const deep = JSON.parse(JSON.stringify(original))
deep.favorite.push('study')

copy.favorite // ['food', 'travle']
deep.favorite // ['food', 'travle', 'study'] &amp;lt;- deep만 변경&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리액트에서 얕은비교(shallow comparison)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 state가 변경될때 내부적으로 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is&quot;&gt;Object.is&lt;/a&gt;의 알고리즘을 사용해서 이전 state와 새 state의 참조만 비교한다. 내부값이 바뀌어도 참조가 바뀌지 않으면 변경감지 못한다. (리렌더링 x) 그래서 불변성을 지켜줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1764308430296&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function updateState(newstate) {
	if(Object.is(prevState,newState)){
		return 
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prevState와 newState를 비교해서 서로 참조가 같다면 리턴처리 하고 리렌더링을 하지 않고&lt;br /&gt;서로 참조가 다르다면 리렌더링 처리 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1764309133376&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [user,setUser] = useState({name: 'cy'})

user.name = 'my' // name 속성만 변경
setUser(user) // user객체 그대로&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setUser에서 user로 상태를 바꾸면 어떻게 될까.&lt;br /&gt;리액트는 참조가 바뀌지 않는다고 판단하고 리렌더링 하지 않는다. user객체는 그대로고 내부 속성만 변경했으니 참조가 바뀌지 않았다.&lt;/p&gt;
&lt;pre id=&quot;code_1764309295664&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [user,setUser] = useState({name: 'cy'}) // 메모리 주소: 001x2

user.name = 'my' // name 속성만 변경
setUser(user) // 001x2를 다시 전달&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 주소로 보면 이렇다.&lt;br /&gt;Object.is(001x2, 001x2) === true라 판단하고 리렌더링하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1764309363169&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새로운 객체 생성 (참조 변경)
setUser({...user, name: 'my'})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object.is(001x2, 001x3) === false라 판단하고 리렌더링 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;props의 얕은비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조에서 props는 어떤 반응일까?&lt;/p&gt;
&lt;pre id=&quot;code_1764309438732&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
	const [user, setUser] = useState({
		profile: {name: 'cy', age: '20'}
	})
	
	const updateProfile() =&amp;gt; {
		const newUser = {...user}
		newUser.profile.name = 'cy lee'
		setUser(newUser)
	}
	
	return &amp;lt;Child user={user.profile}/&amp;gt;
}


const Child = ({profile}) =&amp;gt; {
	return &amp;lt;div&amp;gt;{profile.name}&amp;lt;/div&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upateProfile함수를 호출하면 Parent컴포넌트는 리렌더링 된다.&lt;br /&gt;새로운 객체로 상태 업데이트를 하면 참조가 다르다고 판단하기 때문. (newUser === user -&amp;gt; false)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props를 내려주고 있는 Child컴포넌트를 봐보자.&lt;br /&gt;Child컴포넌트에 user.profile을 props로 내려주고 있다. 이 데이터는 현재 newUser로 업데이트를 했더라도 profile은 user와 같은 참조이기 때문에 값이 같이 바뀐다. (user.profile === newUser.profile). 리렌더링 유무는 참조와 상관없다.&lt;br /&gt;부모가 리렌더링 되기때문에 동시에 자식 컴포넌트인 Child도 같이 리렌더링 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;React.memo&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 매번 부모가 리렌더링되면 자동으로 리렌더링되는 자식컴포넌트를 막을 순 없을까? memo를 쓰면 막을 수 있다.&amp;nbsp;&lt;br /&gt;memo는 props를 얕은 비교 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1764311841146&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const child = React.memo(({profile}) =&amp;gt; {
	return &amp;lt;div&amp;gt;{profile.name}&amp;lt;/div&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1764312178836&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
    const [user, setUser] = useState({
        profile: {name: 'cy', age: '20'},
        score: [0,1]
    })
	
	const updateProfile() =&amp;gt; {
		setUser((prev) =&amp;gt; {
        	...prev,
            score: [0,1,2]
        })
	}
	
	return &amp;lt;Child user={user.profile}/&amp;gt;
}


const Child = React.memo(({profile}) =&amp;gt; {
	return &amp;lt;div&amp;gt;{profile.name}&amp;lt;/div&amp;gt;
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user의 profile 객체를 child로 props전달하고 memo로 감싸주었다.&lt;br /&gt;updateProfile함수를 호출하면?&lt;br /&gt;profile의 참조는 건들지 않고 score의 값만 변경했다.&lt;br /&gt;어떻게 될까?&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Child컴포넌트는 리렌더링 되지 않는다.&lt;br /&gt;새 객체는 만들어 졌으나 profile의 참조는 유지되므로 memo는 참조가 같다고 판단해 리렌더링을 하지 않는다.&lt;br /&gt;&lt;br /&gt;memo인 상태에서 Child도 리렌더링 되게 하려면?&lt;br /&gt;profile의 참조값을 바꿔주면 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>javascript</category>
      <category>React 얕은비교</category>
      <category>React 참조</category>
      <category>깊은복사</category>
      <category>얕은복사</category>
      <category>참조</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/155</guid>
      <comments>https://chaeyoung2.tistory.com/155#entry155comment</comments>
      <pubDate>Fri, 28 Nov 2025 15:48:23 +0900</pubDate>
    </item>
    <item>
      <title>웹 성능 최적화를 위한 PageVisibility API 알아보기(CPU/GPU 사용률 감소  )</title>
      <link>https://chaeyoung2.tistory.com/154</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;pageVisibliity API&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹페이지가 현재 사용자에게 보이는 상태인지 감지할수있는 API&lt;/b&gt;&lt;br /&gt;문서가 보이지 않을 때 페이지에서 불필요한 작업이 수행되지 않도록 하여 리소스를 절약하고 성능을 개선하는 데 특히 유용.&lt;br /&gt;사용자가 브라우저를 최소화 하거나 다른탭으로 이동했거나 탭을 보고있거나 했을 경우 API는 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event&quot;&gt;visibilitychange&lt;/a&gt; 이벤트를 전송해 상태 변경을 알린다 &lt;b&gt;(가시성 상태 추적)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;document.visibilityState&lt;/span&gt;&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;visible 페이지가 보여지는 상태&lt;/li&gt;
&lt;li&gt;hidden 페이지가 숨겨진 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;document.hidden&lt;/span&gt;&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;true 페이지가 보여지는 상태&lt;/li&gt;
&lt;li&gt;false 페이지가 숨겨진 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 속성의 기능상 차이는 거의 같음. hidden은 단순히 true/false값만 출력함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이점&lt;/b&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;/li&gt;
&lt;li&gt;Cpu/gpu 감소&lt;/li&gt;
&lt;li&gt;배터리 감소(노트북/모바일)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;b&gt;테스트 시나리오&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;영상을 재생한 후 10초 뒤 다른 탭으로 이동&lt;/li&gt;
&lt;li&gt;10초 후 다시 원래 탭으로 복귀&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758777321072&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const video = document.getElementById('video');

document.addEventListener('visibilitychange', function() {
    if (document.hidden) {
        video.pause();
        console.log('탭 숨김 - 영상 일시정지');
    } else {
        video.play();
        console.log('탭 활성 - 영상 재개');
    }
});&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;&lt;b&gt;비교 해보기&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;일반 영상: Page Visibility API 적용 없이 계속 재생&lt;/li&gt;
&lt;li&gt;API 적용 영상: Page Visibility API로 탭 이동 시 일시정지/재개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 탭에서 퍼포먼스 측정을 해봤더니 일반영상과 API적용 했을때의 영상과&lt;b&gt;&lt;i&gt; videoFrameCompositor가 눈에크게 차이가 났다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; VideoFrameCompositor&lt;/b&gt;&lt;br /&gt;비디오 프레임을 실제로 합성 하는 작업.&lt;br /&gt;&lt;br /&gt;탭 활성화 : 매 프레임 마다 화면에 그릴 준비&lt;br /&gt;탭 비활성화: 안보이니까 안그림.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;후.png&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wVFxY/btsQN6aWU2e/MbkfJ7azGZ8xjmQgKMwQN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wVFxY/btsQN6aWU2e/MbkfJ7azGZ8xjmQgKMwQN0/img.png&quot; data-alt=&quot;pageVisibility 적용 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wVFxY/btsQN6aWU2e/MbkfJ7azGZ8xjmQgKMwQN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwVFxY%2FbtsQN6aWU2e%2FMbkfJ7azGZ8xjmQgKMwQN0%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;1324&quot; height=&quot;909&quot; data-filename=&quot;후.png&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pageVisibility 적용 전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;전.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byGiyh/btsQOGpw9vf/OVlKwJZyKMk4qvzUdAKZrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byGiyh/btsQOGpw9vf/OVlKwJZyKMk4qvzUdAKZrK/img.png&quot; data-alt=&quot;pageVisibility 적용 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byGiyh/btsQOGpw9vf/OVlKwJZyKMk4qvzUdAKZrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyGiyh%2FbtsQOGpw9vf%2FOVlKwJZyKMk4qvzUdAKZrK%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;1318&quot; height=&quot;731&quot; data-filename=&quot;전.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pageVisibility 적용 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pageVisibility를 적용 전엔 다른 탭 이동했을 시 계속 프레임이 그려지고 있는 걸 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pageVisibility 적용 전) 일반영상&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;/li&gt;
&lt;li&gt;보이지 않는 GPU/CPU 계속 사용&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;VideoFrameCompositor 계속 사용&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;pageVisibility 적용 후)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;API 적용 영상&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;/li&gt;
&lt;li&gt;리소스 절약&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;VideoFrameCompositor 대폭 감소&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 10초로 테스트 했을때 &lt;b&gt;CPU/GPU 사용량이 60~80%는 감소한다.&lt;/b&gt;&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;탭이 여러개라면??? 효과가 대폭 상승 한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탭이 여러개가 쌓이고 유저들이 대거 진입시 대략 적으로 효과를 얻을 수 있는 시나리오를 AI에이전트에게 물어봤다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✔️100명 사용자, 평균 3-4개 탭 사용&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;총 영상 스트림: 300-400개&lt;/li&gt;
&lt;li&gt;실제로 보는 영상: 100개 (각자 1개씩)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요한 스트림: 200-300개&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&lt;/b&gt;리소스 사용량 계산&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 38px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;pageVisibility API 미적용시&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;pageVisibility API 적용시&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;-300개 영상 모두 풀 렌더링&lt;br /&gt;-CPU/GPU 최대 부하&lt;br /&gt;-네트워크 대역 폭 300배 사용&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;-실제 시청 중인 100개만 렌더링&lt;br /&gt;-나머지 200개는 일시정지&lt;br /&gt;-약 67% 리소스 절약&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web</category>
      <category>pageVisibility API</category>
      <category>Web API</category>
      <category>프론트엔드 성능 최적화</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/154</guid>
      <comments>https://chaeyoung2.tistory.com/154#entry154comment</comments>
      <pubDate>Thu, 25 Sep 2025 14:22:36 +0900</pubDate>
    </item>
    <item>
      <title>공통 컴포넌트를 건드리지 않고 이벤트 제어하기 - 캡처링</title>
      <link>https://chaeyoung2.tistory.com/153</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;캡처링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 요소(winow)에서 타겟 요소 까지 내려가는 단계, &lt;b&gt;하향식 전파&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;/li&gt;
&lt;li&gt;실제 클릭한 타겟까지만 내려가면서 이벤트 실행&lt;/li&gt;
&lt;li&gt;타겟에 도달하면 캡처링 단계 끝&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758242626134&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;wrap&quot;&amp;gt;
      &amp;lt;div id=&quot;parent&quot;&amp;gt;
        &amp;lt;div id=&quot;child&quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758242640286&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wrap.addEventListener('click', () =&amp;gt; console.log('wrap'), true);
parent.addEventListener('click', () =&amp;gt; console.log('parent'), true);
child.addEventListener('click', () =&amp;gt; console.log('child'), true);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. wrap을 클릭하면?&lt;br /&gt;wrap부터 시작 했으므로 끝&lt;br /&gt;-&amp;gt; console에 wrap이 찍힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. parent를 클릭하면?&lt;br /&gt;wrap부터 시작했으므로 끝&lt;br /&gt;-&amp;gt; console에 wrap, parent 찍힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. child를 클릭하면?&lt;br /&gt;wrap -&amp;gt; parent -&amp;gt; child 순으로 끝.&lt;br /&gt;-&amp;gt; 콘솔에 wrap, parent, child 찍힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토글 형식의 드롭다운 버튼 컴포넌트를 사용해야 하는 상황이었다. 이 컴포넌트는 버튼을 클릭할 때마다 CSS 클래스가 토글되면서 하단에 하드코딩된 리스트가 펼쳐지거나 접히는 UI였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-19 오전 9.58.11.png&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmgzP6/btsQGrer6XI/I9H3ryRUexKjFY9T6ysai1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmgzP6/btsQGrer6XI/I9H3ryRUexKjFY9T6ysai1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmgzP6/btsQGrer6XI/I9H3ryRUexKjFY9T6ysai1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmgzP6%2FbtsQGrer6XI%2FI9H3ryRUexKjFY9T6ysai1%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;784&quot; height=&quot;403&quot; data-filename=&quot;스크린샷 2025-09-19 오전 9.58.11.png&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 두 가지였다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Disabled 상태에서의 추가 처리 필요&lt;/b&gt;: 컴포넌트가 비활성화된 상태일 때는 토글 대신 알럿을 띄워야 했다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의도치 않은 렌더링&lt;/b&gt;: 다른 탭으로 이동 후 돌아올 때 컴포넌트가 재렌더링되면서 리스트가 저절로 펼쳐지는 현상이 발생했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 컴포넌트이다 보니 내부에는 이미 고정된 클릭 이벤트가 적용되어 있었고, 직접 수정할 수 없는 상황이었다.  &amp;nbsp;&lt;br /&gt;리스트 상단에 탭이 있었는데 탭이 변경될 때마다 리스트가 자동으로 펼쳐지는 이슈가 있었는데, 이는 공통컴포넌트 내부에서 open함수가 호출되도록 구현되어 있었기 때문이었다. 상위에서 이벤트 제어를 할 필요가 있어야 했는데 내부 로직에 호출된 함수라 건들 수 없었다.&lt;/p&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;span&gt;open&lt;/span&gt; 함수가 호출되도록 구현되어 있었기 때문이었다. 내부 로직에 정의된 함수라 수정할 수 없는 상황이었다.&lt;/li&gt;
&lt;/ul&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;span data-token-index=&quot;0&quot;&gt; 해결 시도&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758243795483&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const wrapper = useRef();
wrapper.current.querySelector('.dropdown').classList.remove('open');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 컴포넌트를 감싸는 wrapper에 ref를 설정하고, querySelector로 내부 엘리먼트를 찾아서 리스트가 펼쳐지거나 disabled시 클래스를 강제로 제거하는 방식으로 접근했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;/li&gt;
&lt;li&gt;코드 복잡성 증가 (다른 로직에도 remove를 처리 해야하는)&lt;/li&gt;
&lt;li&gt;근본적인 해결이 아닌 임시방편적 처리&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 컴포넌트 내부엔 이미 click 이벤트가 정의 되어 있으니 클릭했을때 이벤트를 막아야겠다. 라는 생각이 들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1758245923075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &amp;lt;div @click.capture=&quot;onClickEvent&quot;&amp;gt;
   &amp;lt;공통 컴포넌트/&amp;gt;
 &amp;lt;/div&amp;gt;


onClickEvent(e) {
  e.stopPropagation()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;버튼 컴포넌트 상위에 div를 감싸고 클릭 이벤트를 걸어주었다. &lt;br /&gt;공통 컴포넌트에서 이미 버블링 단계로 이벤트가 일어나고 있기 때문에 상위에서는 capture 옵션을 사용해 캡처링 단계에서 이벤트를 가로채는 방법을 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@click.capture :&lt;/b&gt;&lt;/span&gt; 캡처링 단계. 이벤트를 감지&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;e.topPropagation :&lt;/b&gt; &lt;/span&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;중복으로 흩어졌던 classList.remove 코드를 제거해 로직이 단순해졌으며, 기존에 두가지 이슈가 해결됬다.&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-filename=&quot;스크린샷 2025-09-19 오전 11.48.40.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/67SJH/btsQFvPvbLL/Zbvlz6xlUDrZCFnOlA22W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/67SJH/btsQFvPvbLL/Zbvlz6xlUDrZCFnOlA22W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/67SJH/btsQFvPvbLL/Zbvlz6xlUDrZCFnOlA22W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F67SJH%2FbtsQFvPvbLL%2FZbvlz6xlUDrZCFnOlA22W0%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;586&quot; height=&quot;568&quot; data-filename=&quot;스크린샷 2025-09-19 오전 11.48.40.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Before&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 클릭 -&amp;gt; 버튼 클릭시 버블링이 일어나면서 상위 요소까지 전파 -&amp;gt; 이벤트 제어 x&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;After&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 클릭 -&amp;gt; 상위 요소가 캡처링 단계에서 이벤트를 잡음 -&amp;gt; e.stopPropagation() 호출로 버튼의 이벤트가 버블링해서 올라오지 않음 -&amp;gt; 상위 div에서 이벤트 제어&lt;/p&gt;</description>
      <category>issue</category>
      <category>이벤트 제어</category>
      <category>이벤트 캡처링</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/153</guid>
      <comments>https://chaeyoung2.tistory.com/153#entry153comment</comments>
      <pubDate>Fri, 19 Sep 2025 13:43:44 +0900</pubDate>
    </item>
    <item>
      <title>리액트에서 클로저가 어떻게 사용되고 있는가?</title>
      <link>https://chaeyoung2.tistory.com/152</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;클로저&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수와 그 함수가 선언될 당시의 렉시컬 환경(선언 됬을때의 렉시컬 환경)의 조합.&lt;br /&gt;함수가 외부 스코프 변수를 참조 할 수 있고 외부 함수의 실행이 끝나도 변수에 접근 할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;바닐라 JavaScript에서 클로저 이슈&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1752726822763&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (var i = 0; i &amp;lt; 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문을 돌며 setTimeout 호출 시 콘솔에 i를 출력하는 예시 코드다.&lt;br /&gt;결과가 어떻게 나올까?&lt;br /&gt;&lt;i&gt;클로저를 모르면 0,1,2가 출력된다고 볼 수 있다.&lt;/i&gt;&lt;br /&gt;하지만 클로저는 외부 스코프의 변수를 참조하기 때문에 setTimeout 함수가 1초 후 실행됬을때는 i는 이미 3가 되어 콘솔에는 3이 출력된다.&lt;br /&gt;var는 함수 스코프이기 때문에 반복문을 돌며 새로운 i를 생성하지 않고 하나의 i로 공유가 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리액트에서의 클로저&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 var를 사용할수도 있고 비동기 함수도 직관적으로 쓰기 때문에 코드 동작 결과를 보며 클로저다! 라고 느끼는 경우가 많았다. 그런데 리액트를 사용하다 보면 클로저 라는 개념 자체가 생각이 잘 안들었다.&lt;br /&gt;그래서 리액트에서는 클로저가 안일어나는가?? 라는 생각을 해봤지만 말이 안되는 경우이고 (js로 사용하기 때문) 대부분 리액트는 훅을 사용하기 때문에 구조 자체에서 숨어서 일어나고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. useState의 클로저 캡처&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752729104920&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);

  const countIncrement = () =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(count); // 여전히 0
      setCount(count + 1); // 0 + 1 = 1
    }, 1000);
  };
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Count: {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={countIncrement}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState는 리액트의 상태 매커니즘이다. 상태를 리액트가 관리하고 상태가 변경되면 컴포넌트는 리렌더링 된다.&lt;br /&gt;이때 컴포넌트 전체가 다시 실행이 되지만 state는 이미 리액트가 기억하고 있어 리렌더링 이후에도 최신 상태값이 유지 된다.&lt;br /&gt;&lt;br /&gt;하지만 함수 내부에서 상태값을 클로저를 캡쳐했을때 이슈가 있다.&lt;br /&gt;countIncrement 함수가 생성 될때, 그 시점의 count 값을 클로저로 캡처한다. 이후 리렌더링이 되더라도 함수 내부에서는&lt;br /&gt;이미 캡쳐된 0으로 사용하기 때문에 증가 버튼을 클릭해도 1로 변경된다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752729240942&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);

  const countIncrement = () =&amp;gt; {
    setTimeout(() =&amp;gt; {
      setCount(prev =&amp;gt; {
        return prev + 1; // 항상 최신 상태 기반
      });
    }, 1000);
  };
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Count: {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={countIncrement}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트가 setCount 함수의 콜백 호출시 내부적으로 현재 상태를 전달한다. prev는 항상 최신 상태를 유지하고 있는 것이다.&amp;nbsp;&lt;br /&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;br /&gt;&lt;b&gt;2. 리액트의 훅 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect, useCallback 등 리액트에서 사용하는 훅들은 클로저 문제를 의존성 배열 기반으로 해결한다.&lt;br /&gt;클로저라는 단어를 사용한다기 보단 의존성 배열을 바꾼다 라는 패턴으로 개발을 하기 때문에 클로저 관리를 하는지 모르고 넘어가는 경우가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1752731447199&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  const handleClick = () =&amp;gt; {
    console.log(count); // 항상 최신 count
  };
  
  document.addEventListener('click', handleClick);
  return () =&amp;gt; document.removeEventListener('click', handleClick);
}, [count]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;count를 의존성 배열에 넣어주면 클로저 관리를 useEffect가 알아서 관리해준다. 고로 우리는 신경쓸 필요가 없다는것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;br /&gt;- 비동기 콜백, 이벤트 핸들러 에서 상태를 참조 할때&lt;br /&gt;- 같은 함수에서 여러 상태를 업데이트 하고 리액트가 이를 배치 처리 할때&lt;br /&gt;&lt;br /&gt;이러한 이슈를 마주한다면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;우리는 클로저를 마주하기 쉽기 때문에&lt;span&gt;&amp;nbsp;클로저를&amp;nbsp;&lt;/span&gt;&lt;/span&gt;생각해야한다. useEffect나 함수형 업데이트가 오래된 상태를 해결 해 줄것이다.&lt;br /&gt;함수형 업데이트와 의존성 관리 같은 패턴을 통해 리액트는 클로저를 자연스럽게 해결해준다. 우리는 리액트가 제공하는 패턴으로 인해 클로저라는 단어 자체를 잊어 버리고 사는 것 일 수도 있다. 이것이 리액트가 의도하는 &lt;b&gt;추상화&lt;/b&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>react</category>
      <category>react</category>
      <category>리액트에서 클로저</category>
      <category>클로저</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/152</guid>
      <comments>https://chaeyoung2.tistory.com/152#entry152comment</comments>
      <pubDate>Thu, 17 Jul 2025 15:34:29 +0900</pubDate>
    </item>
    <item>
      <title>튜닝의 끝은 순정이라더니..⭐️ 티스토리 스킨 커스텀후 10개월 써본 후기</title>
      <link>https://chaeyoung2.tistory.com/151</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;  내 개성을 담은 블로그 디자인, 왜 다시 초기화했을까&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 동기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리에서 유명한 HELLO 스킨을 썼었는데 다른 블로그들이랑 너무 똑같아 보였다.&lt;br /&gt;명색에 기술블로그인데 나만의 특색이 있어야 하지 않나.. ㅎㅎ (이유모를 자신감;)&lt;br /&gt;그래서 여러 블로그 레퍼런스를 찾기 시작했다!&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;레퍼런스 수집&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 블로그 스타일은 다음과 같디.&lt;br /&gt;&lt;span&gt;첫째, &lt;/span&gt;&lt;b&gt;블랙 &amp;amp; 화이트를 기반으로 한 깔끔한 느낌&lt;/b&gt;&lt;span&gt;.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;둘째, &lt;/span&gt;&lt;b&gt;블로그 주인(나)인 프로필이 한눈에 들어오는 구조&lt;/b&gt;&lt;span&gt;.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;셋째, &lt;/span&gt;&lt;b&gt;상세 페이지의 폭은 좁게 유지하고&lt;/b&gt;&lt;span&gt;, &lt;/span&gt;&lt;b&gt;velog처럼 플로팅 네비게이션을 구현&lt;/b&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.40.17.png&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVRBUq/btsOwQ8cRGG/TvP1R6byGhBLDqlj8NqcuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVRBUq/btsOwQ8cRGG/TvP1R6byGhBLDqlj8NqcuK/img.png&quot; data-alt=&quot;레퍼런스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVRBUq/btsOwQ8cRGG/TvP1R6byGhBLDqlj8NqcuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVRBUq%2FbtsOwQ8cRGG%2FTvP1R6byGhBLDqlj8NqcuK%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;1518&quot; height=&quot;1294&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.40.17.png&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1294&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;PC 환경에만 최적화할 예정이기 때문에, 여러 사이트의 블로그 레퍼런스를 참고하면서 구체적인 방향을 잡았다.&lt;br /&gt;각 사이트에서 마음에 드는 요소가 있으면 해당 부분만 참고하는 방식으로 진행했다. &lt;span&gt;예를 들어 &lt;/span&gt;&lt;b&gt;프로필 구조가 마음에 들면 그 부분만&lt;/b&gt;&lt;span&gt;, &lt;/span&gt;&lt;b&gt;상세 페이지 스타일이 좋으면 그 부분만&lt;/b&gt;&lt;span&gt; 가져와 조합하는 식으로 스타일을 구성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&lt;span&gt;&amp;nbsp;디자인&lt;/span&gt;&lt;/b&gt;&lt;/h3&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-filename=&quot;edited_스크린샷 2025-06-10 오후 5.42.14.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/civI2Y/btsOv7P9BSH/Zsu0Vqd6xVRcorsn9c2kx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/civI2Y/btsOv7P9BSH/Zsu0Vqd6xVRcorsn9c2kx0/img.png&quot; data-alt=&quot;Theme Color&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/civI2Y/btsOv7P9BSH/Zsu0Vqd6xVRcorsn9c2kx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcivI2Y%2FbtsOv7P9BSH%2FZsu0Vqd6xVRcorsn9c2kx0%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;400&quot; height=&quot;288&quot; data-filename=&quot;edited_스크린샷 2025-06-10 오후 5.42.14.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Theme Color&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;primary 컬러를 형광 으로 정했다.&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;깔끔한 느낌에 유니크한 포인트를 주고 싶었달까 ^__^;;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.47.48.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baaasr/btsOv8askFk/FuWbII1xBJIeclKFDi2kP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baaasr/btsOv8askFk/FuWbII1xBJIeclKFDi2kP0/img.png&quot; data-alt=&quot;홈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baaasr/btsOv8askFk/FuWbII1xBJIeclKFDi2kP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaaasr%2FbtsOv8askFk%2FFuWbII1xBJIeclKFDi2kP0%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;500&quot; height=&quot;609&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.47.48.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;홈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.48.30.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnhvOF/btsOwszJ1eX/4rSQfKKMKCVbxNckosZdfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnhvOF/btsOwszJ1eX/4rSQfKKMKCVbxNckosZdfk/img.png&quot; data-alt=&quot;상세&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnhvOF/btsOwszJ1eX/4rSQfKKMKCVbxNckosZdfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnhvOF%2FbtsOwszJ1eX%2F4rSQfKKMKCVbxNckosZdfk%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;500&quot; height=&quot;818&quot; data-filename=&quot;스크린샷 2025-06-10 오후 5.48.30.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1498&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;폰트 사이즈나 전체적인 레이아웃 구조는 레퍼런스 사이트나 한국의 대표 블로그인 네이버, velog나 요즘IT 처럼 블로그 형식으로 포스팅 하는 사이트들을 많이 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.&lt;span&gt;&amp;nbsp;커스텀 시작&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브로 티스토리 블로그 커스텀 강의를 들었다.&lt;br /&gt;티스토리 치환자를 사용해 커스텀 하는건 크게 어렵지 않았다. HTML,CSS 다룰 줄 알면 쉽게 커스텀 가능할듯.&lt;br /&gt;단지 불편한게 있다면... IDE를 사용해서 커스텀을 못한다는것;&lt;br /&gt;그리고 아예 빈 백지에 커스텀하는게 아니라 기존 구조에서 수정하다보니 꽤나 귀찮았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 레이아웃을 변경할때는 해당 치환자를 찾아서 수정&lt;br /&gt;2. 스타일이 추가되거나 바뀔 경우 클래스를 새로 추가하거나 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.&lt;span&gt;&amp;nbsp;작업 완료 및 테스트&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실질적인 코딩 작업 시간은 3일정도 소요 됬다.&lt;br /&gt;테스트 블로그를 하나 만들어 스킨 테스트를 해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.27.28.png&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5Dy20/btsOx2VpIL4/8gB6hAsNqgkCIM9XLzd5r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5Dy20/btsOx2VpIL4/8gB6hAsNqgkCIM9XLzd5r1/img.png&quot; data-alt=&quot;홈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5Dy20/btsOx2VpIL4/8gB6hAsNqgkCIM9XLzd5r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5Dy20%2FbtsOx2VpIL4%2F8gB6hAsNqgkCIM9XLzd5r1%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;500&quot; height=&quot;292&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.27.28.png&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1504&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;홈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.18.png&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;1494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJEXc/btsOxC3KfLE/S2riNFRIAJKLt4U22xdilK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJEXc/btsOxC3KfLE/S2riNFRIAJKLt4U22xdilK/img.png&quot; data-alt=&quot;상세&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJEXc/btsOxC3KfLE/S2riNFRIAJKLt4U22xdilK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJEXc%2FbtsOxC3KfLE%2FS2riNFRIAJKLt4U22xdilK%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;500&quot; height=&quot;302&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.18.png&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;1494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상세&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.39.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; data-alt=&quot;댓글영역&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO6RFU%2FbtsOxN40r32%2Fgn1zPaaMoq1YkKbMCUYlT1%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;500&quot; height=&quot;463&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.39.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1418&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;br /&gt;원하던 느낌으로 나온 느낌? 공유까지 할려고 준비했으나 하지는 않았다 ... ㅋㅋㅋ&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6.&lt;span&gt; 실제 적용&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;한 10개월 정도 실제 내 블로그에 적용을 해서 사용했다.&lt;/b&gt;&lt;br /&gt;처음엔 만족했었다. 나만의 개성이 담긴 블로그로 느껴졌다.&lt;br /&gt;블로그 스킨을 적용 한 후 바빠서 블로그에 계속 접속도 못하고 신경을 못쓰다가 오랜만에 들어갔더니 뭔가 가독성이 떨어지는 느낌이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;1. 라인 컬러와 라인이 너무 많음&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.39.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; data-alt=&quot;댓글영역&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O6RFU/btsOxN40r32/gn1zPaaMoq1YkKbMCUYlT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO6RFU%2FbtsOxN40r32%2Fgn1zPaaMoq1YkKbMCUYlT1%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;500&quot; height=&quot;463&quot; data-filename=&quot;edited_스크린샷 2025-06-11 오후 5.28.39.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;1418&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 블로그 상세 폭이 좁다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 또한 가독성이 떨어져 보였다. 데스크탑에서 봤을 때 화면이 답답하게 느껴졌고, 예시 코드를 많이 사용하는데도 코드가 잘 보이지 않는 것 같았다. 상세 페이지의 전체 폭은 플로팅 네비게이션을 포함해 1024px로 설정했으며, 실제 본문이 차지하는 영역은 770px 정도였다.여기에 플로팅 네비게이션이 함께 붙어 있고, 그 경계가 라인으로 구분되어 있어서 &lt;span&gt;&lt;b&gt;시각적으로 본문 영역이 더 좁게 느껴졌다.&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;게다가 텍스트 컬러도 라인 컬러와 동일하게 설정해버려서 &lt;span&gt;&lt;b&gt;글에 집중이 잘 되지 않는 느낌&lt;/b&gt;&lt;/span&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;3. 갑자기 급격히 줄어든 방문자 수...&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 내가 블로그에 근 몇개월 동안 신경을 쓰지 못한탓이 크다 (90%정도..)&lt;br /&gt;그래도 스킨의 영향도 조금은 있지 않을까? 라는 생각.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1,2 번 두가지 이유가 컸고 전체적으로 갑자기 구려보였다.&lt;br /&gt;오랜만에 접속했더니 안보였던것들이 보이기 시작했나봄. ㅎㅎ;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7.&lt;span&gt;&lt;span&gt; 결국 순정으로..&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVT7u/btsOIwCw0qI/KNY1TOaycPFPvLcyGFA9qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVT7u/btsOIwCw0qI/KNY1TOaycPFPvLcyGFA9qK/img.png&quot; data-alt=&quot;2025.06 기준 현재 블로그 메인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVT7u/btsOIwCw0qI/KNY1TOaycPFPvLcyGFA9qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVT7u%2FbtsOIwCw0qI%2FKNY1TOaycPFPvLcyGFA9qK%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;1903&quot; height=&quot;910&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2025.06 기준 현재 블로그 메인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리 기본 스킨으로 바꿨다. 헤더 부분을 살짝 커스텀 하긴 했다.&amp;nbsp;&lt;br /&gt;블로그는 전달력이 중요하다고 생각하기 때문에 읽는 사람 입장에서 편안하게 느껴지는 구성을 고민하는거 같다.&lt;br /&gt;&lt;br /&gt;내 블로그도 꽤 많은 사람들이 방문하고 인사이트를 얻어가기 때문에 방문하는 사람들에게 정보 뿐만 아니라 나라는 사람도 기억에 남게끔 하고 싶다라는 생각이 계속 들어 커스텀을 도전해봤다.&lt;br /&gt;결국 실패(?)로 돌아갔지만 연구를 해봐야 할 것 같다.&lt;/p&gt;</description>
      <category>끄적끄적</category>
      <category>기술 블로그</category>
      <category>티스토리 스킨</category>
      <category>티스토리 스킨 커스텀</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/151</guid>
      <comments>https://chaeyoung2.tistory.com/151#entry151comment</comments>
      <pubDate>Thu, 19 Jun 2025 15:16:09 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] javascript | Level1 로또의 최고 순위와 최저 순위</title>
      <link>https://chaeyoung2.tistory.com/150</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;로또 6/45(이하 '로또'로 표기)는 1부터 45까지의 숫자 중 6개를 찍어서 맞히는 대표적인 복권입니다. 아래는 로또의 순위를 정하는 방식입니다.&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: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6개 번호가 모두 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5개 번호가 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4개 번호가 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3개 번호가 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2개 번호가 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6(낙첨)&lt;/td&gt;
&lt;td&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;알아볼 수 없는 번호를&amp;nbsp;0으로 표기하기로 하고, 민우가 구매한 로또 번호 6개가&amp;nbsp;44, 1, 0, 0, 31 25라고 가정해보겠습니다. 당첨 번호 6개가&amp;nbsp;31, 10, 45, 1, 6, 19라면, 당첨 가능한 최고 순위와 최저 순위의 한 예는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당첨 번호 31 10 45 1 6 19 결과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최고 순위 번호&lt;/td&gt;
&lt;td&gt;&lt;b&gt;31&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0&amp;rarr;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0&amp;rarr;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;4개 번호 일치, 3등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최저 순위 번호&lt;/td&gt;
&lt;td&gt;&lt;b&gt;31&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0&amp;rarr;11&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0&amp;rarr;7&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;2개 번호 일치, 5등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;알아볼 수 없는 두 개의 번호를 각각 10, 6이라고 가정하면 3등에 당첨될 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3등을 만드는 다른 방법들도 존재합니다. 하지만, 2등 이상으로 만드는 것은 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;알아볼 수 없는 두 개의 번호를 각각 11, 7이라고 가정하면 5등에 당첨될 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;5등을 만드는 다른 방법들도 존재합니다. 하지만, 6등(낙첨)으로 만드는 것은 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;민우가 구매한 로또 번호를 담은 배열 lottos, 당첨 번호를 담은 배열 win_nums가 매개변수로 주어집니다. 이때, 당첨 가능한 최고 순위와 최저 순위를 차례대로 배열에 담아서 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제한사항&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lottos는 길이 6인 정수 배열입니다.&lt;/li&gt;
&lt;li&gt;lottos의 모든 원소는 0 이상 45 이하인 정수입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0은 알아볼 수 없는 숫자를 의미합니다.&lt;/li&gt;
&lt;li&gt;0을 제외한 다른 숫자들은 lottos에 2개 이상 담겨있지 않습니다.&lt;/li&gt;
&lt;li&gt;lottos의 원소들은 정렬되어 있지 않을 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;win_nums은 길이 6인 정수 배열입니다.&lt;/li&gt;
&lt;li&gt;win_nums의 모든 원소는 1 이상 45 이하인 정수입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;win_nums에는 같은 숫자가 2개 이상 담겨있지 않습니다.&lt;/li&gt;
&lt;li&gt;win_nums의 원소들은 정렬되어 있지 않을 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;입출력&lt;/span&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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;풀이&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1749537426342&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(lottos, win_nums) {
  const result = [];

  for(let i = 0; i &amp;lt; 2; i++) {
    let check = 0;

    for (let j = 0; j &amp;lt; lottos.length; j++) {
      if(win_nums.includes(lottos[j])) {
        check++;
      }
      if(lottos[j] === 0 &amp;amp;&amp;amp; i === 0) {
        check++;
      }
    }

    const rank = (6 - check) === 6 ? 6 : 6 - check + 1; 

    result.push(rank);
  }

  return result
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 지문이 길어서 겁먹었는데 읽어보니 빠르게 풀긴 했다.&lt;br /&gt;&lt;br /&gt;- 최고순위, 최저순위를 담기위해 2번 순회를 하고&lt;br /&gt;- win_nums에는 중복되는 번호가 없다 했으므로 민우의 lotto에 당첨번호가 있으면 카운팅 처리&lt;br /&gt;- 0이면 무조건 당첨번호로 처리.&lt;br /&gt;- 당첨번호가 6개이므로 6에서 카운팅한 숫자만큼빼고 1을 더하면 등수가 나온다.&lt;br /&gt;&lt;br /&gt;다른사람 풀이로 보니 반복문 두번 굳이 안돌고 filter사용해서 배열 매핑처리를 했다. 쉽게 풀 수 있는데 조잡하게 푼 느낌이 없지않아 있는거같다.&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/150</guid>
      <comments>https://chaeyoung2.tistory.com/150#entry150comment</comments>
      <pubDate>Tue, 10 Jun 2025 15:44:20 +0900</pubDate>
    </item>
    <item>
      <title>requestIdleCallback으로 LCP 개선</title>
      <link>https://chaeyoung2.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;js.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfBcFQ/btsNASBat2o/QNc7xY2ZHMvzG54N0a5lI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfBcFQ/btsNASBat2o/QNc7xY2ZHMvzG54N0a5lI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfBcFQ/btsNASBat2o/QNc7xY2ZHMvzG54N0a5lI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfBcFQ%2FbtsNASBat2o%2FQNc7xY2ZHMvzG54N0a5lI1%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;500&quot; height=&quot;500&quot; data-filename=&quot;js.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;requestIdleCallback&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;브라우저의 유휴시간(idle)에 콜백을 실행&lt;/span&gt;&lt;/b&gt;할 수 있는 웹 API이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브라우저가 주요 메인스레드의 작업을 끝낸 후 남은 시간에 우선순위가 낮은 작업을 실행 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;requestIdleCallback은 이벤트루프의 모든큐(태스크 큐,마이크로태스크 큐) 동작이 끝나고 렌더링, requestAnimationFrame 콜백까지 모두 처리 후 유휴시간에 유휴 콜백큐에 등록된 콜백함수가 실행된다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;requestIdleCallback의 실전 활용&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나는 이 함수를 통계 수집 API 요청시 사용했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;tanstack query를 사용중이라 일반적으로 useQuery를 사용해 api를 요청했었는데 페이지 진입시 불러오는 데이터 요청 API 보다 통계 수집 API가 먼저 요청되거나 중간에 요청됬었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;통계수집 같은 경우 사용자 경험에 영향을 미치는 부분이 아니고 백그라운드에서 요청되기를 바랬기 때문에 requestIdleCallback을 사용해서 요청하기에 최적화 된다고 생각했다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745816669568&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    if (typeof window !== 'undefined' &amp;amp;&amp;amp; 'requestIdleCallback' in window) {
      const idleCallbackId = requestIdleCallback(
        () =&amp;gt; {
          trackAnalytics(pathname);
        },
        { timeout: 3000 }, // 최대 3초까지 대기
      );

      return () =&amp;gt; {
        if (typeof window !== 'undefined' &amp;amp;&amp;amp; 'requestIdleCallback' in window) {
          cancelIdleCallback(idleCallbackId);
        }
      };
    }
    // requestIdleCallback 미지원 브라우저
    else {
      const timeoutId = setTimeout(() =&amp;gt; {
        trackAnalytics(pathname);
      }, 3000);

      return () =&amp;gt; clearTimeout(timeoutId);
    }
  }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;span style=&quot;color: #000000;&quot;&gt;timeOut&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;timeOut을 걸면 설정한 시간내에 브라우저가 유휴상태에 진입하지 않았을 경우 강제실행 된다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 설정하지 않을 경우 브라우저가 유휴 상태가 될때까지 콜백은 실행되지 않는다. 보통 분석 요청 같은 경우 2~5초가 적당한 타임인거같다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  호환성 이슈&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-28 오후 2.12.13.png&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caeEih/btsNBzgEzyo/hrvIIPmxMu1NMrtWzcjXdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caeEih/btsNBzgEzyo/hrvIIPmxMu1NMrtWzcjXdk/img.png&quot; data-alt=&quot;requestIdleCallback mdn&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caeEih/btsNBzgEzyo/hrvIIPmxMu1NMrtWzcjXdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaeEih%2FbtsNBzgEzyo%2FhrvIIPmxMu1NMrtWzcjXdk%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;1638&quot; height=&quot;892&quot; data-filename=&quot;스크린샷 2025-04-28 오후 2.12.13.png&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;requestIdleCallback mdn&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 브라우저의 경우 지원하지만 Safari(ios)는 지원되지 않는다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;폴리필을 설정하거나 setTimeout으로 대체 할 수 있다. 나같은 경우에도 setTimeout으로 사용했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순 분석용이고 이 외에는 requestIdleCallback을 사용하진 않기때문에 폴리필까지 사용하기엔 복잡성이 커질거 같았다.&lt;/span&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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LCP(Largest Contentful Paint) 개선&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;lcp-1.png&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKf1E8/btsNDR1dIoR/kuHIm0GmQUxOIMtZ96I9s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKf1E8/btsNDR1dIoR/kuHIm0GmQUxOIMtZ96I9s1/img.png&quot; data-alt=&quot;requestIdleCallback 사용 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKf1E8/btsNDR1dIoR/kuHIm0GmQUxOIMtZ96I9s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKf1E8%2FbtsNDR1dIoR%2FkuHIm0GmQUxOIMtZ96I9s1%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;1452&quot; height=&quot;483&quot; data-filename=&quot;lcp-1.png&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;requestIdleCallback 사용 전&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;lcp-2.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db9d2I/btsNCfhKCNO/EtSSdAAS2yKuHrhlFjvXj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db9d2I/btsNCfhKCNO/EtSSdAAS2yKuHrhlFjvXj1/img.png&quot; data-alt=&quot;requestIdleCallback 사용 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db9d2I/btsNCfhKCNO/EtSSdAAS2yKuHrhlFjvXj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb9d2I%2FbtsNCfhKCNO%2FEtSSdAAS2yKuHrhlFjvXj1%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;1462&quot; height=&quot;453&quot; data-filename=&quot;lcp-2.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;requestIdleCallback 사용 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LCP는 페이지가 처음으로 로드되는 시점으로 뷰포트내에 있는 가장 큰 이미지나 텍스트 블록이 렌더링 되는 시간이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;requestIdleCallback을 사&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;용 후&lt;/span&gt; &lt;b&gt;0.35 -&amp;gt; 0.16으로 54% LCP&lt;/b&gt;&lt;/span&gt;가 개선됬다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브라우저가 유휴상태일때 trackAnalytics함수를 실행 함으로써 페이지 로딩 초기에 중요한 렌더링(이미지, 폰트 등)의 우선순위가 더 올라가 작업을 처리하게 되고 한가할때 trackAnalytics함수를 실행함으로써 LCP 요소에 리소스를 더 많이 할당 할 수 있어 효율적으로 처리가 된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무거운 계산처리나 LazyLoading을 이 메서드를 활용해 성능을 크게 향상 시킬 수 있다&lt;/span&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;
&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>javascript</category>
      <category>lcp</category>
      <category>requestidlecallback</category>
      <category>성능 개선</category>
      <category>후순위 처리</category>
      <author>이챙(leechaeng)</author>
      <guid isPermaLink="true">https://chaeyoung2.tistory.com/148</guid>
      <comments>https://chaeyoung2.tistory.com/148#entry148comment</comments>
      <pubDate>Mon, 28 Apr 2025 14:41:49 +0900</pubDate>
    </item>
  </channel>
</rss>