<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ss_kim.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자</description>
        <lastBuildDate>Sat, 28 Mar 2026 08:35:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. ss_kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ss_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[외적을 이용해서 도형 내부에 마우스가 있는지 판별하기]]></title>
            <link>https://velog.io/@ss_kim/%EC%99%B8%EC%A0%81%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8F%84%ED%98%95-%EB%82%B4%EB%B6%80%EC%97%90-%EC%A0%90-%EC%9D%B4-%EC%9E%88%EB%8A%94%EC%A7%80-%ED%8C%90%EB%B3%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ss_kim/%EC%99%B8%EC%A0%81%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-%EB%8F%84%ED%98%95-%EB%82%B4%EB%B6%80%EC%97%90-%EC%A0%90-%EC%9D%B4-%EC%9E%88%EB%8A%94%EC%A7%80-%ED%8C%90%EB%B3%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 28 Mar 2026 08:35:10 GMT</pubDate>
            <description><![CDATA[<p>그래픽 배치 도구를 개발할 때 가장 중요한 것은 <strong>마우스가 어느 타일 위에 있는가</strong>.</p>
<p>처음에는 마우스 좌표를 특정 그리드 값으로 스냅하는 방식을 사용했지만, 배치할 에셋이 바뀌거나 타일의 크기를 바꾸면 좌표를 다시 계산해서 배치해줘야 하는 번거로움이 있었다.</p>
<p>그래서 <strong>벡터의 외적(Cross Product)</strong>을 활용해 도형 내부에 점이 있는지 확인하는 방식으로 변경했다.</p>
<h3 id="1-외적">1. 외적</h3>
<h4 id="1-1-왜-외적을-사용하는가">1-1 왜 외적을 사용하는가?</h4>
<p>타일이 사각형이라면 위치(<code>x</code>, <code>y</code>)와 크기(<code>width</code>, <code>height</code>)로 판별할 수 있겠지만, 다각형, 회전이 들어간 도형 등은 계산이 매우 번거로워진다.</p>
<p>벡터의 외적은 &quot;선&quot;과 &quot;점&quot;을 각각 벡터로 변환하면
간단한 계산만으로 위치를 판별할 수 있다.</p>
<h4 id="1-2-원리--항상-같은-방향에-있는가">1-2 [원리] : &quot;항상 같은 방향에 있는가?&quot;</h4>
<p>외적은 두 벡터 간의 관계에서 방향을 포함하고 있다.</p>
<p>사각형을 예로 들어, 각 변을 같은 방향(시계 또는 반시계)으로 따라갈 때 어떤 점 $P$가 내부에 있다면, 변을 기준으로 항상 <strong>같은 방향</strong>(오른쪽 또는 왼쪽)에 위치하게 된다.</p>
<h4 id="1-3-공식">1-3. 공식</h4>
<blockquote>
<p>$$cr= AB \cdot AP$$
$$=(AB_x \cdot AP_y) - (AB_y \cdot AP_x)$$
$$= (x_2 - x_1) \cdot (p_y - y_1) - (y_2 - y_1) \cdot (p_x - x_1)$$</p>
<p>$$A = (x_1, y_1)$$
$$B = (x_2, y_2)$$
$$P = (p_x, p_y)$$</p>
</blockquote>
<p>이 값의 부호가 <strong>뱡향</strong>을 의미</p>
<ul>
<li>양수 : 점이 변의 한쪽 방향(예: 왼쪽)</li>
<li>음수 : 반대 방향(예: 오른쪽)</li>
<li>0 : 변 위에 있음</li>
</ul>
<h3 id="2-코드">2. 코드</h3>
<pre><code class="language-typescript"> const isPointInsideTile = (
  px: number,
  py: number,
  q: ReadonlyArray&lt;readonly [number, number]&gt;
): boolean =&gt; {
  // 1. 부동 소수점 오차를 위한 미세값(Epsilon) 설정
  const eps = 1e-9;
  const crs: number[] = [];
  const n = q.length;

  // 2. 사각형의 각 변을 순회하며 외적 계산
  for (let i = 0; i &lt; n; i++) {
    const [x1, y1] = q[i];
    const [x2, y2] = q[(i + 1) % n]; // 다음 꼭짓점 (마지막은 첫 번째와 연결)

    // 벡터1: 변 (x2-x1, y2-y1)
    // 벡터2: 점 (px-x1, py-y1)
    crs.push((x2 - x1) * (py - y1) - (y2 - y1) * (px - x1));
  }

  // 3. 모든 변에 대해 외적의 부호가 일관된지 확인
  // allNonNeg: 모두 왼쪽이거나 변 위에 있음 (양수 또는 0)
  // allNonPos: 모두 오른쪽이거나 변 위에 있음 (음수 또는 0)
  const allNonNeg = crs.every((c) =&gt; c &gt;= -eps);
  const allNonPos = crs.every((c) =&gt; c &lt;= eps);

  return allNonNeg || allNonPos;
};</code></pre>
<h3 id="3-유의점">3. 유의점</h3>
<p>각 변을 순회하며 한 점과의 외적을 계산을 하고, 모든 값이 같은 부호를 가진다면 내부에 위치</p>
<ul>
<li><p>부동 소수점 (<code>eps</code>)
부동 소수점 오차를 위한 미세값(Epsilon) 설정</p>
</li>
<li><p><code>%n</code>
두 점을 연결해 선을 만들 때, 마지막 점은 1번째 점과 연결되어야 하기 때문에 나머지 연산 처리</p>
</li>
<li><p><strong>볼록한 도형이라는 전제</strong>
이 방법은 &quot;볼록한 도형&quot;에서만 정확하게 동작한다.</p>
<p>볼록 도형: 다각형, 마름모
오목 도형: 별, 찌그러진 사각형</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sprite를 활용한 svg 파일 관리하기 ]]></title>
            <link>https://velog.io/@ss_kim/sprite-svg</link>
            <guid>https://velog.io/@ss_kim/sprite-svg</guid>
            <pubDate>Thu, 20 Feb 2025 14:01:58 GMT</pubDate>
            <description><![CDATA[<h2 id="sprite란">Sprite란?</h2>
<ul>
<li>하나의 이미지 파일에 여러 이미지를 붙여넣고 좌표를 통해서 이미지를 불러오는 것</li>
<li>네이버를 개발자 도구로 살펴보면 메일 아이콘의 <code>::before</code>와 <code>::after</code>에 바탕 이미지의 좌표와 메일 이미지 좌표를 불러와서 아이콘을 만든 것을 알 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/3beced02-5f16-46ef-99cb-f256dee17644/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/1cf78c52-dc67-455a-9fbc-a83640082a23/image.png" alt=""></p>
<p><strong>그렇다면 svg 파일은 어떻게??</strong>
<code>&lt;symbol&gt;</code>, <code>&lt;use&gt;</code> 요소를 이용해 이미지 Sprite의 좌표를 가져오듯 사용할 수 있다.</p>
<ul>
<li><code>&lt;symbol&gt;</code> : 각각의 svg 정보가 존재하고 고유한 id값이 존재</li>
<li><code>&lt;use&gt;</code> : href 속성에 id symbol 경로를 넣어 원하는 svg를 렌더링</li>
</ul>
<hr>
<h2 id="과정">과정</h2>
<ol>
<li><p>Figma에서 svg파일 Export
 ❗️ 컴포넌트 이름이 파일명과 <code>&lt;symbol&gt;</code>의 <code>id</code>가 됨
 ❗️ <code>width</code>, <code>height</code>를 정수로 맞추고 export 하는게 브라우저에 랜더링 했을 때 정렬이 잘 되는 느낌(경험상)</p>
</li>
<li><p>Spritebot 다운로드
 여러 개의 svg 파일을 각각 <code>&lt;symbol&gt;</code> 요소로 변경해서 하나의 파일로 생성해주는 도구</p>
<blockquote>
<p><a href="https://github.com/thomasjbradley/spritebot?tab=readme-ov-file">GitHub - thomasjbradley/spritebot</a></p>
</blockquote>
</li>
<li><p><code>sprites.svg</code> 생성
 Spritebot에 Figma에서 다운로드 받은 svg 파일들을 업로드하고 <code>sprites.svg</code> 생성</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/3c3fba36-2fc6-4fb5-bc94-6151ae6d6b40/image.png" alt=""></p>
<ol start="4">
<li><p><code>&lt;use&gt;</code> 요소의 <code>href</code> 속성으로 원하는 svg 불러오기</p>
<pre><code class="language-tsx"> &lt;svg&gt;
   &lt;use href={`/sprites.svg#${id}`}/&gt;
 &lt;/svg&gt;

 // xlinkHref 속성으로도 불러올 수 있음 (차이점은 잘 모룸..)
 &lt;svg&gt;
   &lt;use xlinkHref={`/sprites.svg#${id}`}/&gt;
 &lt;/svg&gt;</code></pre>
</li>
</ol>
<hr>
<h2 id="유의사항">유의사항</h2>
<ul>
<li><p><code>sprite.svg</code> 파일의 <code>fill</code> 속성은 경로의 색상, <code>stroke</code> 속성은 경로의 테두리 색상이다.
<code>fill</code> 속성을 <code>currentColor</code> 값으로 설정하면 부모 요소의 <code>color(폰트)</code> 색상에 따라서 색상을 변경할 수 있음. </p>
<pre><code class="language-jsx">&lt;svg style={{ color }}&gt;
  &lt;use href={`/sprites.svg#${id}`} /&gt;
&lt;/svg&gt;</code></pre>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/a3f37a4c-53ac-42ee-be5c-79307cab494f/image.png" alt=""></p>
<ul>
<li><p><code>&lt;use&gt;</code>의 크기를 조절하는 것이 가능한가?? (잘 모르는 내용임)
 <strong><code>&lt;use&gt;</code>의 <code>width</code>와 <code>height</code>는 부모 <code>&lt;svg&gt;</code>에 의해 제어됨</strong></p>
<ul>
<li><code>&lt;svg&gt;</code>의 <code>viewBox</code>와 <code>width</code>, <code>height</code>가 <code>&lt;use&gt;</code>에 직접 <code>width</code>, <code>height</code>를 설정하는 것보다 더 큰 영향을 준다.</li>
<li><code>&lt;use&gt;</code> 내부의 요소 크기를 조정하려면 <code>transform=&quot;scale(x, y)&quot;</code>을 사용하는 방법이 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>색상과 크기 조절에 대한 자세한 내용은 MDN 웹 문서 <a href="https://developer.mozilla.org/ko/docs/Web/SVG/Tutorial">SVG 튜토리얼</a> 참고.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js + supabase] 카카오 로그인 구현 (2)]]></title>
            <link>https://velog.io/@ss_kim/Next.js-supabase-kaka-login-2</link>
            <guid>https://velog.io/@ss_kim/Next.js-supabase-kaka-login-2</guid>
            <pubDate>Sun, 02 Feb 2025 11:29:06 GMT</pubDate>
            <description><![CDATA[<p>supabase의 Auth Providers를 활용해서 소셜 로그인을 구현한 과정을 기록</p>
<hr>
<blockquote>
<p>Supabase에서 제공하는 Kakao Social Login 문서
<a href="https://supabase.com/docs/guides/auth/social-login/auth-kakao?queryGroups=language&amp;language=js&amp;queryGroups=environment&amp;environment=server&amp;queryGroups=framework&amp;framework=nextjs">https://supabase.com/docs/guides/auth/social-login/auth-kakao?queryGroups=language&amp;language=js&amp;queryGroups=environment&amp;environment=server&amp;queryGroups=framework&amp;framework=nextjs</a></p>
</blockquote>
<p>이전 글에서 카카오 개발자 세팅하는 과정을 소개했지만 supabase의 문서에서도 친절하게 안내해주고 있기에 한 번 읽어보는 것을 추천</p>
<p>차이점이라면 Redirect URI에 supabase가 처리해주는 url(<code>https://&lt;project-ref&gt;.supabase.co/auth/v1/callback</code>)을 추가해준다. 그리고 <code>Kakao Client ID</code>와 <code>Kakao Client Secret</code>
를 supabase 프로젝트에 연결해주는 것.</p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/657a45c8-cce9-4129-96a3-fae665871ccf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/7f1953c5-afa6-41c3-88ed-9e109d544dd7/image.png" alt=""></p>
<hr>
<h2 id="들어가기-전">들어가기 전</h2>
<h3 id="supabase와-nextjs는-어떻게-동작하는-걸까">supabase와 Next.js는 어떻게 동작하는 걸까?</h3>
<p>(Next.js를 완전히 이해하고 진행하는 것이 아니라 틀린 점이 있을 수도 있다..)</p>
<p>supabase에서 제공하는 <code>signInWithOAuth()</code>를 실행한다. 이 때 provider와 redirectTo를 파라미터로 전달하는데, provider 종류를 확인하고 redirect URL에서 교환한 코드로 유저 정보를 받아 세션에 정보를 저장한다.</p>
<p>redirect URL에서 코드를 교환하는 로직을 supabase 문서에서 알려주기 때문에 공식 문서를 잘 따라간다면 크게 문제가 없다.</p>
<hr>
<h2 id="구현">구현</h2>
<h3 id="nextjs-환경에-supabase-auth-세팅하기">Next.js 환경에 supabase Auth 세팅하기</h3>
<blockquote>
<p>supabase 패키지 설치
<a href="https://supabase.com/docs/guides/auth/quickstarts/nextjs">https://supabase.com/docs/guides/auth/quickstarts/nextjs</a></p>
</blockquote>
<blockquote>
<p>Server-side 세팅
<a href="https://supabase.com/docs/guides/auth/server-side/nextjs">https://supabase.com/docs/guides/auth/server-side/nextjs</a></p>
</blockquote>
<ol>
<li><p><strong><code>.env.local</code></strong></p>
<pre><code>NEXT_PUBLIC_SUPABASE_URL=&lt;your_supabase_project_url&gt;
NEXT_PUBLIC_SUPABASE_ANON_KEY=&lt;your_supabase_anon_key&gt;</code></pre></li>
<li><p><strong>client와 server에서 동작하는 각각의 client 만들기</strong>
 Supabase는 브라우저에서 동작하는 <code>Client Component client</code>와 서버에서 동작하는 <code>Server Component Client</code> 두 가지 client를 사용한다.
 즉, 클라이언트 단에서 서버 client 함수인 createServerClient()를 호출하면 에러</p>
<p> 2-1 utils/supabase/client.ts</p>
<pre><code class="language-typescript"> import { createBrowserClient } from &#39;@supabase/ssr&#39;

 export function browserClient() {
       return createBrowserClient(
         process.env.NEXT_PUBLIC_SUPABASE_URL!,
         process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
       )
 }</code></pre>
<p> 2-2 utils.supabase/server.ts</p>
<pre><code class="language-typescript"> import { createServerClient } from &#39;@supabase/ssr&#39;
 import { cookies } from &#39;next/headers&#39;

 export async function createClient() {
       const cookieStore = await cookies()

       return createServerClient(
         process.env.NEXT_PUBLIC_SUPABASE_URL!,
         process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
         {
               cookies: {
                 getAll() {
                       return cookieStore.getAll()
                 },
                 setAll(cookiesToSet) {
                       try {
                         cookiesToSet.forEach(({ name, value, options }) =&gt;
                           cookieStore.set(name, value, options)
                         )
                       } catch {
                     // The `setAll` method was called from a Server Component.
                     // This can be ignored if you have middleware refreshing
                     // user sessions.
                       }
                 },
               },
         }
       )
 }</code></pre>
</li>
<li><p><strong>middleware</strong>
 서버 단에서는 쿠키를 사용할 수 없기 때문에 middleware를 통해서 브라우저의 쿠키에서 auth token을 가져오고 저장한다.</p>
<p> 3-1 middlware.ts</p>
<pre><code class="language-typescript"> import { type NextRequest } from &#39;next/server&#39;
 import { updateSession } from &#39;@/utils/supabase/middleware&#39;

 export async function middleware(request: NextRequest) {
       return await updateSession(request)
 }

 export const config = {
       matcher: [
       ],
 }</code></pre>
<p> 3-2 utils/supabase/middleware.ts</p>
<pre><code class="language-typescript"> import { createServerClient } from &#39;@supabase/ssr&#39;
 import { NextResponse, type NextRequest } from &#39;next/server&#39;

 export async function updateSession(request: NextRequest) {
       let supabaseResponse = NextResponse.next({
         request,
   })

   const supabase = createServerClient(
     process.env.NEXT_PUBLIC_SUPABASE_URL!,
     process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
     {
           cookies: {
             getAll() {
                   return request.cookies.getAll()
             },
             setAll(cookiesToSet) {
                   cookiesToSet.forEach(({ name, value, options }) =&gt;         request.cookies.set(name, value))
                   supabaseResponse = NextResponse.next({
                     request,
                   })
                   cookiesToSet.forEach(({ name, value, options }) =&gt;
         supabaseResponse.cookies.set(name, value, options)
                   )
             },
           },
     }
   )

   // Do not run code between createServerClient and
   // supabase.auth.getUser(). A simple mistake could make it very hard to debug
   // issues with users being randomly logged out.

   // IMPORTANT: DO NOT REMOVE auth.getUser()

   const {
     data: { user },
   } = await supabase.auth.getUser()

   // route protect 로직

   return supabaseResponse
 }</code></pre>
</li>
</ol>
<hr>
<h3 id="로그인-페이지">로그인 페이지</h3>
<ol>
<li><p>로그인 페이지에서는 client component client를 생성
 app/login/page.tsx</p>
<pre><code class="language-tsx"> &#39;use client&#39;;

 import style from &#39;@/app/(anon)/login/page.module.scss&#39;;
 import { browserClient } from &#39;@/utils/supabase/client&#39;;
 import Image from &#39;next/image&#39;;
 import { useEffect, useState } from &#39;react&#39;;

 const Login = () =&gt; {
       const signInWithKakao = async () =&gt; {
         const supabase = browserClient();
         await supabase.auth.signInWithOAuth({
               provider: &#39;kakao&#39;,
               options: {
                 redirectTo: `http:localhost:3000/auth/callback`,
               },
         });
       };

       return (
         &lt;div&gt;
               &lt;button
                 className={style.loginButton}
                 type=&#39;button&#39;
                 onClick={signInWithKakao}
               &gt;
                 &lt;Image
                       src={&#39;/kakao_login_medium_wide.png&#39;}
                       alt=&#39;카카오 로그인 이미지&#39;
                       width={300}
                       height={45}
                 /&gt;
               &lt;/button&gt;
         &lt;/div&gt;
       );
 };

 export default Login;</code></pre>
</li>
<li><p>route handler에서는 server component client를 생성
 /app/auth/callback/route.ts</p>
<pre><code class="language-typescript"> &#39;use server&#39;;

 import { NextResponse } from &#39;next/server&#39;;
 import { createClient } from &#39;@/utils/supabase/server&#39;;

 export async function GET(request: Request) {
       const { searchParams, origin } = new URL(request.url);
       const code = searchParams.get(&#39;code&#39;);
       const next = searchParams.get(&#39;next&#39;) ?? &#39;/&#39;;

       if (code) {
         const supabase = await createClient();
         const { error } = await supabase.auth.exchangeCodeForSession(code);

         if (!error) {
               const forwardedHost = request.headers.get(&#39;x-forwarded-host&#39;); 
               const isLocalEnv = process.env.NODE_ENV === &#39;development&#39;;
               if (isLocalEnv) {
                 return NextResponse.redirect(`${origin}${next}`);
               } else if (forwardedHost) {
                 return NextResponse.redirect(`https://${forwardedHost}${next}`);
               } else {
                 return NextResponse.redirect(`${origin}${next}`);
               }
         }
       }

       return NextResponse.redirect(`${origin}/auth/auth-code-error`);
 }</code></pre>
</li>
</ol>
<p>로그인 버튼을 클릭하면 redirectTo 주소로 코드를 query parameter에 담아 전달되고, app/auth/callback의 route가 실행되면서 코드를 교환해 유저 정보를 세션에 저장한다.</p>
<p>중간에 오류를 몇 번 마주쳤는데, route를 살펴보면 코드가 없거나 에러가 있으면 <code>${origin}/auth/auth-code-error</code> 주소로 이동되어서 어디서 오류가 났는지 확인하기 어렵다. 다음 편에서는 오류를 확인할 수 있는 코드를 추가한 내용을 알아보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js + supabase] 카카오 로그인 구현 (1)]]></title>
            <link>https://velog.io/@ss_kim/nextjs-supabase-kakao-login</link>
            <guid>https://velog.io/@ss_kim/nextjs-supabase-kakao-login</guid>
            <pubDate>Sat, 01 Feb 2025 19:24:09 GMT</pubDate>
            <description><![CDATA[<p>카카오 소셜 로그인을 구현했던 과정을 기록</p>
<p>이번 글은 supabase에서 소셜 로그인을 할 수 있는지 모르고ㅜ 토큰을 발급받아 유저 정보를 반환 받는 과정까지만 구현한 내용이다.</p>
<p>supabase의 <code>Auth Providers</code>를 활용한 내용은 다음편에 작성할 예정</p>
<hr>
<h2 id="카카오-개발자-세팅">카카오 개발자 세팅</h2>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common">https://developers.kakao.com/docs/latest/ko/kakaologin/common</a></p>
<ol>
<li><p>왼쪽의 설정하기 메뉴를 보면 카카오 로그인 활성화 설정, Redirect URI 등록은 필수 사항이라고 안내되어 있다.</p>
<p> 어플리케이션을 등록하고 내 어플리케이션 메뉴로 들어가서 설정해준다.</p>
<p> <img src="https://velog.velcdn.com/images/ss_kim/post/9bb95600-bdbe-4f15-bfec-7388fb6f407c/image.png" alt=""></p>
<p> <img src="https://velog.velcdn.com/images/ss_kim/post/1723cbed-c719-4012-b9ee-c493891b11a4/image.png" alt=""></p>
</li>
</ol>
<ol start="2">
<li><p>동의 항목 설정</p>
<p> <img src="https://velog.velcdn.com/images/ss_kim/post/204fcf0c-58c5-4d92-941f-a12cbee7e236/image.png" alt=""></p>
<p> 닉네임과 프로필 사진은 그냥 설정할 수 있지만 카카오계정(이메일)은 비즈니스 앱으로 등록되어 있어야 한다. (사업자 번호가 없다면 개인 개발자로 등록하면 됨)</p>
<p> <img src="https://velog.velcdn.com/images/ss_kim/post/f036e952-4645-40ce-9143-786499a726cd/image.png" alt=""></p>
</li>
<li><p>플랫폼 메뉴에 도메인도 함께 등록</p>
<p> <img src="https://velog.velcdn.com/images/ss_kim/post/28832c2f-c195-4671-9122-1f9341e9057d/image.png" alt=""></p>
</li>
</ol>
<hr>
<h2 id="페이지-만들기">페이지 만들기</h2>
<h3 id="들어가기-전">들어가기 전</h3>
<p>로그인이 진행되는 과정을 살펴보면, 카카오 계정으로 로그인을 요청하면 카카오에서 Redirect URI로 인증 코드를 query parameter로 전달한다. 이 코드로 다시 카카오에 토큰을 발급받고, 토큰을 통해서 유저 정보를 가져올 수 있다.</p>
<p>✅ 카카오 로그인 과정은 인증 코드 받기 → 토큰 받기 → 유저 정보 받기 순차적으로 진행</p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/1efa4e52-d2f4-46fb-9a30-3356380bccb8/image.png" alt=""></p>
<h3 id="구현-과정">구현 과정</h3>
<ol>
<li><p><strong>카카오 계정으로 로그인 요청하고, 인증 코드 받기</strong>
 1-1 login 버튼 만들기</p>
<pre><code class="language-tsx"> /login/page.tsx

 const Login = () =&gt; {
       // 카카오 REST API 키
     const REST_API_KEY = process.env.NEXT_PUBLIC_KAKAO_LOGIN_REST_API_KEY;
     // 설정한 REDIRECT_URI
     const REDIRECT_URI = process.env.NEXT_PUBLIC_KAKAO_LOGIN_REDIRECT_URI;
     // 로그인을 요청할 주소
       const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?response_type=code&amp;client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}`;

       return (
         &lt;Link href={KAKAO_AUTH_URL}&gt;
             &lt;Image
                 src={&#39;/kakao_login_medium_wide.png&#39;}
                 alt=&#39;카카오 로그인 이미지&#39;
                 width={300}
                 height={45}
                /&gt;
           &lt;/Link&gt;
     );
 }</code></pre>
<blockquote>
<p>Kakao에서 제공하는 로그인 버튼 디자인을 참고해서 만들도록 하자
<a href="https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide">https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide</a> </p>
</blockquote>
<p> 1-2 Redirect URI에서 인증 코드 확인하기</p>
<ul>
<li><p>인증 코드는 REDIRECT_URI<code>?code=####</code> 식으로 전달 받는다. (<code>http://localhost:3000/auth/callback/kakao?code=####</code>)</p>
</li>
<li><p><code>useSearchParams</code>를 이용해 query parameter를 받아온다.</p>
<pre><code class="language-tsx">/auth/callback/kakao/page.tsx

const LoginRedirection = () =&gt; {
    const router = useRouter();
    const authCode = useSearchParams().get(&#39;code&#39;);
  console.log(authCode);

    return (
    &lt;div&gt;
        &lt;h2&gt;loading...&lt;/h2&gt;
    &lt;/div&gt;
);</code></pre>
</li>
</ul>
</li>
</ol>
<hr>
<ol start="2">
<li><p><strong>인증 코드를 전달해 토큰 발급 받기</strong>
 2-1 카카오에 인증 코드를 보내서 토큰 받는 함수 만들기</p>
<pre><code class="language-typescript"> /api/login.ts

 export const getKakaoLoginToken = async (code: string) =&gt; {
       const tokenUrl = &#39;https://kauth.kakao.com/oauth/token&#39;;
       const REST_API_KEY = process.env.NEXT_PUBLIC_KAKAO_LOGIN_REST_API_KEY;
       const REDIRECT_URI = process.env.NEXT_PUBLIC_KAKAO_LOGIN_REDIRECT_URI;

       const params = new URLSearchParams({
         grant_type: &#39;authorization_code&#39;,
         client_id: REST_API_KEY!,
         redirect_uri: REDIRECT_URI!,
         code: code,
       });

       try {
         const response = await fetch(tokenUrl, {
               method: &#39;POST&#39;,
               headers: {
                 &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded;charset=utf-8&#39;,
               },
               body: params,
         });

         if (!response.ok) {
               throw new Error(`${response.status}`);
         }

         const data = await response.json();

         return data;
       } catch (error) {
         console.error(error);
       }
 };</code></pre>
<p> 2-2 Redirect URI 페이지가 렌더링될 때 위에서 만든 토큰 발급 함수를 실행 시키고 로컬 스토리지에 저장하기</p>
<pre><code class="language-tsx"> /auth/callback/kakao/page.tsx

 useEffect(() =&gt; {
     const fetchLoginToken = async (code: string) =&gt; {
           if (typeof code === &#39;string&#39;) {
             try {
                   const data = await getKakaoLoginToken(code);

                   localStorage.setItem(&#39;token&#39;, JSON.stringify(data));

                   return data;
             } catch (error) {
                   console.error(error);
             }
           }
     };

     const handleLogin = async (code: string) =&gt; {
           try {
             // 토큰 받기
             const token = await fetchLoginToken(code);
           } catch (error) {
             console.error(error);
           }
     };

     if (authCode) {
           handleLogin(authCode);
     }
   }, [authCode, router]);</code></pre>
</li>
</ol>
<hr>
<ol start="3">
<li><p><strong>발급 받은 토큰으로 유저 정보 받기</strong>
 3-1 카카오에 토큰 보내서 유저 정보 받는 함수 만들기</p>
<pre><code class="language-typescript"> /api/login.ts

 export interface Token {
     token_type?: string;
       access_token: string;
       refresh_token?: string;
       id_token?: string;
       expires_in?: number;
       refresh_token_expires_in?: string;
       scope?: string;
      token?: string;
     id?: number;
       }

 export const getKakaoUserInfo = async ({ access_token }: Token) =&gt; {
       const userInfoUrl = &#39;https://kapi.kakao.com/v2/user/me&#39;;

       try {
         const response = await fetch(userInfoUrl, {
               method: &#39;GET&#39;,
               headers: {
                 Authorization: `Bearer ${access_token}`,
                 &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded;charset=utf-8&#39;,
               },
         });

         if (!response.ok) {
               throw new Error(`${response.status}`);
         }

         const data = await response.json();

         return data;
       } catch (error) {
         console.error(error);
       }
 };</code></pre>
<p> 3-2 Redirect URI 페이지 렌더링 후 토큰 발급 받고 나서 유저 정보를 요청하고 로컬 스토리지에 저장하기</p>
<pre><code class="language-tsx"> /auth/callback/kakao/page.tsx

 useEffect(() =&gt; {
     const fetchUserInfo = async (token: Token) =&gt; {
           try {
                const data = await getKakaoUserInfo(token);

             localStorage.setItem(&#39;userInfo&#39;, JSON.stringify(data));

             return data;
           } catch (error) {
             console.error(error);
           }
     };

       const handleLogin = async (code: string) =&gt; {
           try {
             // 토큰 받기
             const token = await fetchLoginToken(code);

             // 유저 정보 받기
             if (token) {
                   await fetchUserInfo(token);
             }

           } catch (error) {
             console.error(error);
           }
     };

     if (authCode) {
           handleLogin(authCode);
     }
   }, [authCode, router]);</code></pre>
</li>
</ol>
<hr>
<p>위에서 전달 받은 유저 정보로 supabase의 user 테이블에 저장하면 될 듯하다.</p>
<p>다음 편에서는 Next.js의 route와 supabase의 Auth Provider를 이용해서 간단하게 로그인하고 트리거로 public.user 테이블에 데이터를 생성하는 방법을 알아보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존성 주입을 JS로 이해해보기]]></title>
            <link>https://velog.io/@ss_kim/dependency-injection</link>
            <guid>https://velog.io/@ss_kim/dependency-injection</guid>
            <pubDate>Fri, 10 Jan 2025 00:35:05 GMT</pubDate>
            <description><![CDATA[<h1 id="객체-지향-프로그래밍oop">객체 지향 프로그래밍(OOP)</h1>
<p>먼저 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 상향식(Bottom-up) 해결법</p>
<p>가장 큰 특징 3가지는 아래와 같다.</p>
<ol>
<li><p>은닉성
외부로의 노출을 최소화하여 모듈 간의 결합도를 떨어뜨려 유연함과 유지 보수성을 높임</p>
</li>
<li><p>상속
자식 클래스가 부모 클래스의 특성과 기능을 그대로 물려받는 것을 말하며, 오버라이딩으로 수정 가능</p>
</li>
<li><p>다형성
객체 간 결합력을 낮추면 다양한 기능을 분리하고 연결하는 것이 용이함</p>
</li>
</ol>
<p>이 중 다형성과 관련하여 의존성 주입에 대해 알아보자.</p>
<br>

<h2 id="의존성-주입dependency-injection">의존성 주입(Dependency Injection)</h2>
<p>객체가 필요로 하는 어떤한 기능이나 데이터를 외부에서 생성한 객체(의존성)로 전달하는 것</p>
<h3 id="의존성-주입-방법">의존성 주입 방법</h3>
<ul>
<li><p>constructor 주입
클래스가 생성될 때 1회 호출</p>
<pre><code class="language-javascript"> // camera.js
 class Camera {
   #lens;

   constructor() {
     this.#lens = new Lens();
   }
 }

 const camera = new Camera();</code></pre>
</li>
<li><p>setter 주입
setter 함수를 통해서 주입</p>
<pre><code class="language-javascript"> // camera.js
 class Camera {
   #lens;

   setLens(lens) {
    this.#lens = lens;
   }
 }

 camera.setLens(new Lens());
 camera.setLens(new Lens());</code></pre>
</li>
</ul>
<p>등등이 있다.
비유를 들자면, 내가 사용하고 싶은 객체(카메라)의 추가 기능(줌인)을 위해서 외부에서 생성한 객체(렌즈)를 연결하고 싶다. constructor 주입은 카메라가 만들어질 때 렌즈가 내장되어 있는 것이고, setter는 여러 렌즈를 갈아 끼우는 것으로 간단히 비유할 수 있다.</p>
<p>이 때, 메서드 오버라이딩으로 의존성 객체에 기능을 구현할 수 있다.</p>
<pre><code class="language-javascript">class Lens {
  zoomIn(level) {
    console.log(`Zoomed in by ${level} levels.`);
  }
}

class Camera {
  #lens;

  constructor() {
    this.#lens = new Lens();
  }

  setLens(lens) {
    this.#lens = lens;
  }

  zoomIn(level) {
    this.#lens.zoomIn(level);
  }
}

camera.zoomIn(2); // &quot;Zoomed in by 2 levels.&quot;</code></pre>
<h3 id="js의-취약점">JS의 취약점</h3>
<p>그런데 여기서 JS의 취약점이 드러난다. 타입 검사를 실행하지 않기 때문에 <code>Lens</code> 클래스가 아니더라도 오류가 발생하지 않고 주입이 되며, <code>zoomIn</code>이라는 기능을 가진 아무 객체를 전달하면 메서드가 실행이 된다.</p>
<pre><code class="language-javascript">camera.setLens({
  zoomIn: () =&gt; {
   console.log(&#39;fake&#39;);
  },
});</code></pre>
<p><code>symbol 생성자</code>를 이용한 메서드 오버라이딩으로 메서드 호출은 방지할 수 있지만 주입하는 것을 원천적으로 방지하는 것은 어렵기 때문에 Typescript를 사용하는 것이 좋다.</p>
<pre><code class="language-javascript">const ILens = {
  zoomIn: Symbol(),
  zoomOut: Symbol(),
};

class Lens {
  [ILens.zoomIn](level) {
    console.log(`Zoomed in by ${level} levels.`);
  }
}

class Camera {
  #lens;

  constructor() {
    this.#lens = new Lens();
  }

  setLens(lens) {
    this.#lens = lens;
  }

  zoomIn(level) {
    this.#lens.[ILens.zoomIn](level);
  }
}</code></pre>
<h2 id="interface와-implements">interface와 implements</h2>
<p>Typescript의 interface는 데이터의 기능을 정의할 때 쓰는 type이다.</p>
<p>interface는 접점 즉, 어떤 객체 간 연결점이다. 어떤 기능을 가진 객체를 연결하고자 할 때, 정해진 형식을 미리 정해놓고 연결할 객체(의존성)는 형식을 따라서 기능을 구현한 뒤 주입하는 것</p>
<pre><code class="language-typescript">interface ILens {
  zoomIn(level: number): void;
  zoomOut(level: number): void;
}

class Lens implements ILens {
  zoomIn(level: number): void {
    console.log(`zoom in`);
  }

  zoomOut(level: number): void {
    console.log(`zoom out `);
  }
}</code></pre>
<br>

<hr>
<h2 id="정리">정리</h2>
<ul>
<li><p>OOP의 특징
은닉성, 상속, 다형성</p>
</li>
<li><p>의존성 주입의 장점
객체를 재결합하고 재사용하기가 쉽다.
객체 간 결합도가 낮아 유지보수가 용이하다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토타입]]></title>
            <link>https://velog.io/@ss_kim/prototype</link>
            <guid>https://velog.io/@ss_kim/prototype</guid>
            <pubDate>Mon, 06 Jan 2025 13:46:47 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트는 프로토타입 기반 언어이다. 어떤 객체를 원형(prototype)으로 삼고 이를 복제함으로써 상속과 비슷한 효과를 얻는다.</p>
<pre><code class="language-javascript">var instance = new Constructor();</code></pre>
<h3 id="constructor">Constructor</h3>
<ul>
<li>어떤 생성자 함수(constructor)를 <code>new</code> 연산자와 함께 호출하면</li>
<li>Contructor에서 정의된 내용을 바탕으로 새로운 instance가 생성된다.</li>
<li>이때 instance에는 <code>__proto__</code> 프로퍼티가 자동으로 생성되고,</li>
<li>Contructor의 prototype 프로퍼티를 참조한다.</li>
</ul>
<p>이를 그림으로 간단히 표현하면, 아래와 같다.
<img src="https://velog.velcdn.com/images/ss_kim/post/5e6fa5dc-1308-4497-b35e-26faf81c7157/image.png" alt=""></p>
<h3 id="prototype">prototype</h3>
<p>인스턴스가 사용할 메서드를 저장하는 객체이다.</p>
<blockquote>
<p>ES5.1 명세에는 <code>[[prototype]]</code>으로 정의되어 있고 <code>instance.__proto__</code>와 같은 방식으로 직접 접근하는 것은 허용하지 않는다. <code>Object.getPrototypeOf(instance)</code> 메서드를 통해서 접근할 수 있는데 대부분의 브라우저에서 <code>__proto__</code> 접근 방법을 고수하여 호환성 차원에서 ES6에 도입했다.</p>
</blockquote>
<pre><code class="language-javascript">function Person (name) {
  this._name = name;
  this.getName = function () {
    return this._name
  }
}</code></pre>
<p>만약, 위와 같이 메서드를 prototype이 아닌 인스턴스에 생성할 경우, 생성자를 호출할 때마다 새로운 함수를 만들어 낸다.</p>
<pre><code class="language-javascript">const kim = new Person(&#39;kim&#39;);
const lee = new Person(&#39;lee&#39;);

console.log(kim.getName === lee.getName); // false</code></pre>
<p>그렇기 때문에 객체의 원형인 prototype에 메서드를 저장한다.</p>
<pre><code class="language-javascript">Person.prototype.getName = function () {
  return this._name;
}</code></pre>
<p>메서드 내부의 this는 호출한 주체를 가리키기 때문에 호출할 때에는 <code>__proto__</code>를 생략한다.</p>
<pre><code class="language-javascript">// kim.__proto__.getName() // undefined
kim.getName() // kim</code></pre>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/c3726e06-4ee8-4b6d-80c6-cff6ed9119cc/image.png" alt=""></p>
<ul>
<li><strong>constructor 프로퍼티</strong>
prototype과 <code>__proto__</code> 객체 내부에는 constructor 프로퍼티가 있으며 원래 생성자 함수(자기 자신)을 참조한다.</li>
</ul>
<p>이는 인스턴스로가 자신의 생성자 함수가 무엇인지 알고자 할 때 필요한 수단이기 때문이다.</p>
<pre><code class="language-javascript">var arr = [1, 2];
Array.prototype.constructor === Array // true
arr.__proto__.constructor === Array // true
arr.constructor === Array // true

var arr2 = new arr.constructor(3) // [3]</code></pre>
<hr>
<h2 id="프로토타입-체인">프로토타입 체인</h2>
<p>배열의 내부 구조를 보면 <code>__proto__</code> 객체에 배열 메서드가 존재하고, 또다시 <code>__proto__</code>가 존재한다. 이는 객체의 <code>__proto__</code>이며 <strong>&quot;prototype 객체가 객체&quot;</strong>이기 때문에 <code>Object.prototype</code>과 연결된다.</p>
<p>이처럼 <code>__proto__</code>가 연쇄적으로 이어진 것을 <strong>프로토타입 체인</strong>이라 한다.
<img src="https://velog.velcdn.com/images/ss_kim/post/1eb21994-8715-4c49-8b87-f0eed2922a68/image.png" alt=""></p>
<p>만약 객체에서만 사용할 메서드를 만들어 <code>Object.prototype</code>에 정의하면 어떻게 될까?</p>
<p>모든 prototype의 최상단에는 <code>Object.prototype</code>이기 때문에 다른 데이터 타입에서 호출할 수 있게 된다. 그렇기에 Object에 스태틱 메서드로 부여해야 한다.</p>
<h3 id="메서드-오버라이드">메서드 오버라이드</h3>
<p>만약 인스턴스가 prototype에 정의된 메서드와 동일한 이름의 메서드가 있다면, 인스턴스에 할당한 메서드가 호출된다. 
이를 <strong>메서드 오버라이드</strong>라고 하며, 원본을 삭제하고 교체하는 것이 아니라 덮어씌운다는 이미지로 이해하는 것이 좋다.</p>
<p>덮어씌운다는 것이라면 어떻게 원본에 접근할까?
<code>__proto__</code>에 접근해 호출하되 호출 대상을 전달해준다.</p>
<pre><code class="language-javascript">var Person = function (name) {
    this.name = name;
}

Person.prototype.getName = function () {
    return this.name;
}

var kim = new Person(&#39;kim&#39;);
kim.getName = function () {
    return this.name + &#39;입니다.&#39;;
}

kim.__proto__.getName.call(kim) // kim</code></pre>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>new</code> 연산자로 Contructor를 호출하면 instance가 만들어지는데, 이 instance의 생략 가능한 프로퍼티인 <code>__proto__</code>는 Contructor의 prototype을 참조한다. </li>
<li>prototype에는 메서드나 프로퍼티를 저장할 수 있으며, instance에서 자신의 것처럼 접근할 수 있다.</li>
<li>prototype을 계속 찾아가면 최종적으로 <code>Object.prototype</code>에 도달한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Common JS와 ES module]]></title>
            <link>https://velog.io/@ss_kim/cjs-mjs</link>
            <guid>https://velog.io/@ss_kim/cjs-mjs</guid>
            <pubDate>Sun, 05 Jan 2025 15:30:00 GMT</pubDate>
            <description><![CDATA[<h2 id="모듈">모듈</h2>
<p>개발하는 애플리케이션의 크기가 커지면 언젠간 파일을 기능별로 분리해야 하는 시점이 온다. 이때 분리된 파일 각각을 &#39;모듈(module)&#39;이라고 부른다.</p>
<p>즉, 모듈은 스크립트 파일 하나를 의미하고 모듈을 관리하는 다양한 기능을 제공하는 시스템을 모듈 시스템이라 한다.</p>
<h3 id="장점">장점</h3>
<ol>
<li>유지 보수성 향상
각 기능이 분리되어 있어 수정이나 기능 추가가 용이하며, 의존성을 줄일 수 있다.</li>
</ol>
<ol start="2">
<li>전역 스코프 오염 방지
각 모듈은 독립적인 스코프를 가지므로 전역 변수 충돌을 방지</li>
</ol>
<ol start="3">
<li>의존성 관리
필요한 기능만을 가져와 사용할 수 있어 불필요한 코드 로드를 방지</li>
</ol>
<h2 id="commonjs와-es-module">CommonJS와 ES module</h2>
<h3 id="두가지-시스템인-이유">두가지 시스템인 이유?</h3>
<p>간단히 요약하자면,</p>
<ul>
<li>Javascript는 정적 문서의 상호작용을 위한 짧은 스크립트 코드를 넣기 위해 개발되었다.</li>
<li>Google의 V8 엔진의 등장과 Node.js가 탄생으로 브라우저 이외의 환경에서 Javascript를 실행할 수 있게 되었다.</li>
<li>서버사이드에서도 Javascript가 이용되기 시작하며 모듈화가 필요해졌고, Node.js는 CommonJS 시스템을 채택한다.</li>
<li>이후 여러 키워드를 추가해 가독성을 높이고 비동기 방식으로 실제 사용되는 모듈만 불러오도록 동작하는 ES module이 ES6 표준으로 도입된다.</li>
</ul>
<br>


<h3 id="적용-방법">적용 방법</h3>
<ul>
<li>확장자 설정
파일의 확장자를 <code>.cjs</code> 또는 <code>.mjs</code>로 설정</li>
</ul>
<ul>
<li><code>package.json</code> 설정
 <code>type</code> 속성을 <code>commonjs</code> 또는 <code>module</code>로 설정</li>
</ul>
<h3 id="사용법">사용법</h3>
<ul>
<li><p><strong>CommonJS</strong>
<code>module.js</code> 내보내기</p>
<pre><code class="language-jsx">function sum(x = 0, y = 0) {
  return x + y;
}

module.exports = {
  sum
}</code></pre>
<p><code>main.js</code> 불러오기</p>
<pre><code class="language-jsx">const { sum } = require(&#39;./module&#39;);
// const module = require(&#39;./module&#39;);

console.log(sum(1, 1)); // 2</code></pre>
</li>
<li><p><strong>ES module</strong>
<code>module.js</code> 내보내기</p>
<pre><code class="language-jsx">export default function add(x = 0, y = 0) {
  return x + y;
}</code></pre>
<p><code>main.js</code> 불러오기</p>
<pre><code class="language-jsx">import add from &#39;./module.js&#39;;

 console.log(add(200, 200)); // 400</code></pre>
<br>


</li>
</ul>
<h3 id="우리는-무엇을-사용해야-할까">우리는 무엇을 사용해야 할까?</h3>
<p>표준인 ES module이 우위를 차지하는 추세인 듯 하며 현재 Node.js 최신 버전에서는 ES Module이 공식적으로 사용되고 있다. 그러나 지금까지 많은 라이브러리들이 CommonJS로 작성되었으며, 두 Module System은 기본적으로 동작이 달라 서로 호환되기 어렵기 때문에 동작방식을 이해하고 차이점을 이해하는 것은 중요하다.</p>
<blockquote>
<p>토스 기술 블로그</p>
<p>CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기
<a href="https://toss.tech/article/commonjs-esm-exports-field">https://toss.tech/article/commonjs-esm-exports-field</a></p>
</blockquote>
<hr>
<p>참고
<a href="https://magnificent7.tistory.com/entry/CJS-vs-ESM">https://magnificent7.tistory.com/entry/CJS-vs-ESM</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 컨텍스트]]></title>
            <link>https://velog.io/@ss_kim/execution-context</link>
            <guid>https://velog.io/@ss_kim/execution-context</guid>
            <pubDate>Sun, 05 Jan 2025 11:26:54 GMT</pubDate>
            <description><![CDATA[<h1 id="실행-컨텍스트-execution-context">실행 컨텍스트 (Execution Context)</h1>
<blockquote>
<p>실행 컨텍스트란?
실행할 코드에 제공할 환경 정보(변수 객체, 스코프 체인, this 등)들을 모아놓은 객체</p>
<p>동일한 환경에 있는 코드를 실행할 때 필요한 <strong>환경 정보들을 모아 컨텍스트를 구성</strong>하고, 이를 콜 스택에 쌓아 올렸다가, 가장 위에 있는 컨텍스트와 관련 있는 코드를 실행 하는 식으로 <strong>환경과 순서를 보장</strong>한다.</p>
</blockquote>
<pre><code class="language-javascript">// ---------------------- (1)
var a = 1;
function outer() {
    function inner() {
        console.log(a); // undefined
        var a = 3;
    }
    inner(); // --------- (2)
    console.log(a); // 1
}
outer(); // ------------- (3)
console.log(a); // 1</code></pre>
<p>위 예시의 실행 순서는 어떻게 될까?</p>
<p>자바스크립트 코드가 실행되는 순간 <code>(1)전역 컨텍스트</code>가 콜 스택에 담긴다. 자바스크립트 파일은 브라우저에서 자동 실행하므로 별도의 실행 명령 없이 최상단에서 전역 컨텍스트가 실행되고 순차적으로 코드를 실행한다.</p>
<p><code>(3)outer 함수</code>를 호출하면 그에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성하고 콜 스택에 담는다. outer 컨텍스트가 콜 스택의 최상단이기에 전역 컨텍스트의 코드 실행은 일시중단하고 <code>outer 함수</code>의 내부 코드를 순차적으로 실행한다.</p>
<p>이후 <code>(2)inner 함수</code> 호출되어 inner 실행 컨텍스트가 콜 스택의 최상단에 담기면 outer 컨텍스트의 코드 실행은 일시중단하고 <code>inner 함수</code> 내부 코드를 순차적으로 실행한다.</p>
<p><code>inner 함수</code>의 실행이 종료되면 inner 컨텍스트가 콜 스택에서 제거되고 일시중단되었던 <code>outer 함수</code>의 실행이 이어지고 순차적으로 전역 컨텍스트가 콜 스택에서 제거되면 종료된다.</p>
<blockquote>
<p>💡 <code>inner 함수</code>의 <code>console.log(a)</code>가 undefined인 이유는 지역 변수인 a가 호이스팅되어 초기화 되었기 때문</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/ac4a9555-bae0-429e-99f7-0e7ae89c9a76/image.png" alt=""></p>
<p>컨텍스트가 생성될 때 수집하는 환경 정보는 다음과 같다.</p>
<ol>
<li>Variable Environment</li>
<li>Lexical Environment</li>
<li>ThisBinding</li>
</ol>
<br>

<h2 id="환경-정보">환경 정보</h2>
<ol>
<li><p><strong>Variable Envrionment</strong>
현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보</p>
<p>최초 실행 시 LexicalEnvironment의 스냅샷으로, 담기는 내용은 같지만 변경 사항은 반영되지 않는다.</p>
<p>즉, 실행 컨텍스트를 생성할 때 VariableEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용한다.</p>
</li>
<li><p><strong>Lexical Environment</strong></p>
<blockquote>
<p>어휘적 환경으로 직역할 수 있지만 현재 컨텍스트를 구성하는 식별자와 외부 환경에 대한 정보를 알려주는 사전적인 표현이 어울린다. &lt;&lt;코어 자바스크립트&gt;&gt;</p>
</blockquote>
<p>Variable Environment와 내용은 같지만 변경 사항이 실시간으로 반영된다.</p>
<ul>
<li><p><strong>environmentRecord</strong>
 현재 컨텍스트와 관련된 식별자 정보가 저장(매개변수 식별자, 선언한 함수 그 자체, var 변수 식별자 등)</p>
<p> environmentRecord가 수집되는 과정은 관련된 코드가 실행되기 이전이다. 이는 변수, 함수 등의 선언문을 먼저 실행하는 소스코드의 평과 과정인데 &quot;식별자들을 최상단으로 끌어올린 후 코드를 실행하는 것처럼 동작한다&quot;라고 간주하는 가상의 개념, <strong>호이스팅</strong>이라고도 한다.</p>
</li>
<li><p><strong>outerEnvironmentReference</strong>
  호출된 함수의 상위 컨텍스트의 Lexcical Environment를 참조</p>
<pre><code>- 스코프</code></pre><p>   식별자에 대한 유효 범위</p>
<ul>
<li>스코프 체인
식별자의 유효범위를 안에서부터 바깥으로 차례대로 검색해 나가는 것</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>ThisBinding</strong>
this(호출한 주체에 대한 정보)로 저장된 객체가 저장된다.</p>
<p>함수로서 호출하면 전역 객체가, 메서드로 호출하면 <code>.</code> 앞에 명시된 객체가 this가 된다.
단, 화살표 함수는 this를 바인딩하지 않고 스코프체이닝으로 this를 찾는다</p>
</li>
</ol>
<hr>
<h2 id="정리">정리</h2>
<p>실행 컨텍스트는 자바스크립트 파일이 최로로 실행될 때 전역 컨텍스트를 자동으로 생성하고 이후 주로 함수 실행에 의해 생성된다.</p>
<p>실행 컨텍스트를 생성할 때는 <strong>VariableEnvironment</strong>, <strong>LexicalEnvironment</strong>, <strong>ThisBinding</strong>의 세 가지 정보를 수집한다.</p>
<p>VariableEnvironment와 LexicalEnvironment는 동일한 내용으로 구성되지만 <strong>LexicalEnvironment는 함수 실행 도중 변경 사항이 즉시 반영</strong>된다.</p>
<p>LexicalEnvironment는 <strong>매개변수명, 변수의 식별자, 함수명 등을 수집하는 environmentRecord</strong>와 <strong>상위 컨텍스트의 LexicalEnvironment의 정보를 참조하는 OuterEnvironmentReference</strong>로 구성된다.</p>
<p>this에는 실행 컨텍스트를 활성화하는 당시 지정된 this가 저장된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVC와 Flux]]></title>
            <link>https://velog.io/@ss_kim/MVC%EC%99%80-Flux</link>
            <guid>https://velog.io/@ss_kim/MVC%EC%99%80-Flux</guid>
            <pubDate>Thu, 02 Jan 2025 13:04:35 GMT</pubDate>
            <description><![CDATA[<h2 id="mvc-패턴">MVC 패턴</h2>
<p>Model, View, Controller의 약자. 각각의 역할만 담당하면 되기 때문에 효율적이고 유지보수가 편해진다.</p>
<blockquote>
<p>MDN 참고
<a href="https://developer.mozilla.org/ko/docs/Glossary/MVC">https://developer.mozilla.org/ko/docs/Glossary/MVC</a></p>
</blockquote>
<ul>
<li><p><strong>Model</strong></p>
<ul>
<li>데이터를 관리하고 가공</li>
</ul>
</li>
<li><p><strong>View</strong></p>
<ul>
<li>인터페이스 요소</li>
</ul>
</li>
<li><p><strong>Controller</strong></p>
<ul>
<li>View와 Model을 연결하고 상호 작용을 처리</li>
</ul>
</li>
</ul>
<p><strong>Model에 데이터를 저장</strong>하고 <strong>Controller로 Model을 데이터를 관리</strong>한다. Model이 변경되면 View를 업데이트 하는데, 중요한 점은 View 통해서 Model을 업데이트가 가능한 <strong>양방향 데이터 바인딩</strong>을 지원한다는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/f01da857-c080-4f7e-959b-087074622233/image.png" alt=""></p>
<ul>
<li>View → Model
사용자 입력값을 Model에 반영</li>
<li>Model → View
데이터를 받아서 View에 출력</li>
</ul>
<p>문제는 어플리케이션의 규모가 커져서 Model과 View가 많아지면 데이터 처리가 복잡해지고 예측이 어려워진다.
<img src="https://velog.velcdn.com/images/ss_kim/post/d26afbd6-5532-49ad-9cff-85c5eefe64fa/image.png" alt=""></p>
<p>그래서 페이스북은 데이터의 복잡성을 줄이고 어플리케이션의 확장성을 늘리고자 단방향 데이터 흐름을 가지는 Flux 패턴 구조의 라이브러리를 만든다.</p>
<h2 id="flux-패턴">Flux 패턴</h2>
<p><strong>단방향 데이터 흐름</strong>을 통해 예측 가능하게 어플리케이션의 상태를 관리한다.</p>
<ul>
<li><strong>Action</strong><ul>
<li>사용자와의 상호 작용</li>
</ul>
</li>
<li><strong>Dispatcher</strong><ul>
<li>데이터 흐름을 관리</li>
<li>액션을 스토어에 전달</li>
</ul>
</li>
<li><strong>Store</strong><ul>
<li>화면에 표시할 데이터</li>
<li>액션에 따라서 상태를 업데이트</li>
</ul>
</li>
<li><strong>View</strong><ul>
<li>인터페이스 요소</li>
</ul>
</li>
</ul>
<p>Action(상호작용)이 발생하면 Dispatcher에 전달되고 Store에 변경된 데이터를 쌓다가 한번에 View에 렌더링한다.</p>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/74b14de6-747a-4102-a04f-60f9997ef46d/image.png" alt=""></p>
<p>View에서 Action이 발생하면 Store를 바로 업데이트하는 것이 아니라 Dispatcher를 거쳐 업데이트하기 때문에 데이터 흐름을 따라가기 용이하고 디버깅할 위치를 찾아내기 쉬워진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저(Closure)]]></title>
            <link>https://velog.io/@ss_kim/closure</link>
            <guid>https://velog.io/@ss_kim/closure</guid>
            <pubDate>Wed, 01 Jan 2025 18:26:35 GMT</pubDate>
            <description><![CDATA[<h1 id="클로저closure">클로저(Closure)</h1>
<blockquote>
<p>클로저란?
MDN에서는 &quot;A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).&quot;라고 소개한다. 
&quot;클로저는 함수의 주변 상태(어휘 환경)에 대한 참조와 함께 묶인 함수의 조합이다.&quot;</p>
<p>어떤 함수 A 내부에서 선언한 변수를 참조하는 내부 함수 B를 외부로 전달할 경우, A의 실행 컨텍스트가 종료된 후에도 변수가 사라지지 않는 현상
&lt;&lt; 코어 자바스크립트 &gt;&gt;</p>
</blockquote>
<pre><code class="language-javascript">var outer = function () {
  var a = 1;
  var inner = function () {
    console.log(++a);
  };
  return inner;
};

var outer2 = outer();
outer2(); // 2
outer2(); // 3</code></pre>
<p><code>outer</code> 함수의 반환값으로 <code>inner</code> 함수의 실행 결과가 아닌 함수 자체를 반환한다. 그러면 <code>outer</code> 함수의 실행 컨텍스트가 종료될 때 <code>outer2</code> 변수는 <code>inner</code> 함수를 참조하게 되고, <code>outer2</code>를 호출하면 <code>inner</code> 함수가 실행된다.</p>
<p><code>outer2</code> 함수를 실행했을 때, 왜 같은 값을 반환하지 않고 <code>a</code>의 값이 늘어나는 것일까?</p>
<p><code>outer2</code> 함수를 실행했을 때 흐름을 보면, <code>inner</code> 함수의 <code>environmentRecord</code>에는 <code>a</code> 변수가 없기에 스코프 체이닝에 따라 <code>outer</code>에서 선언한 변수 <code>a</code>에 접근한다. <code>outer</code> 함수는 이미 종료되었지만 <code>inner</code> 함수가 <code>a</code>를 참조하고 있기 때문에 가비지 컬렉터의 수집 대상이 되지 않아 변수 <code>a</code>는 존재할 수 있다.</p>
<p>즉, 어떤 함수 내부에서 선언한 변수가 외부에서 호출될 가능성(참조)이 있다면 가비지 컬렉터의 수집 대상이 되지 않고 함수가 종료된 이후에도 남아 있는 것이다.</p>
<blockquote>
<p>참고
실행 컨텍스트
<a href="https://velog.io/@ss_kim/execution-context">https://velog.io/@ss_kim/execution-context</a></p>
</blockquote>
<br>

<h2 id="메모리-관리">메모리 관리</h2>
<p>클로저를 사용하여 어떤 값의 참조 카운트가 1 이상이라면 메모리에 남아 있게 된다. 그렇기에 필요성이 사라진 시점에서 메모리를 소모하지 않도록 참조 카운트를 0으로 만들어 메모리를 관리해주어야 한다.</p>
<pre><code class="language-javascript">var outer = function () {
  var a = 1;
  var inner = function () {
    console.log(++a);
  };
  return inner;
};

var outer2 = outer();
outer2();
outer2();
outer2 = null; // inner 함수 참조를 끊음</code></pre>
<br>

<h2 id="클로저-활용">클로저 활용</h2>
<p>클로저를 어떤 상황에서 사용할 수 있을까?</p>
<h3 id="1-정보-은닉캡슐화">1. 정보 은닉(캡슐화)</h3>
<p><code>outer</code> 함수는 외부로부터 격리된 공간이다. 외부에서 <code>outer</code> 함수를 실행하는 것은 가능하지만 <code>outer</code> 함수의 내부에 접근하는 것은 불가능하다. 위 예시에서 외부에서 변수 <code>a</code>의 값을 재할당하는 것을 불가능하다.</p>
<h3 id="2-모듈화">2. 모듈화</h3>
<p>클로저를 각각의 변수에 할당하면 함수를 독립적인 부품 형태로 분리할 수 있다.</p>
<pre><code class="language-javascript">var outer = function () {
  var a = 1;
  var inner = function () {
    console.log(++a);
  };
  return inner;
};

var outer2 = outer();
var outer3 = outer();

outer2(); // 2
outer2(); // 3

outer3(); // 2
outer3(); // 3
</code></pre>
<p>이를 활용해 데이터와 메서드를 묶을 수 있다.</p>
<pre><code class="language-javascript">const counter = () =&gt; {
  let value = 0;

  return {
    increase: () =&gt; {
      value = value + 1
    },
    decrease: () =&gt; {
      value = value - 1
    },
}

const counter1 = counter();
counter1.increase();  // 1
counter1.increase();  // 2
counter1.decrease();  // 1

const counter2 = counter();
counter2.decrease();  // -1
counter2.decrease();  // -2
counter2.increase();  // -1</code></pre>
<h3 id="3-react의-hooks">3. React의 Hooks</h3>
<p>함수형 컴포넌트는 렌더링될 때마다 함수를 호출한다. 그렇기에 리렌더링될 때 이전 상태를 기억하고 있어야 하는데, <code>useState</code>의 개념을 클로저를 이용해 비슷하게 표현하면 아래와 같다.</p>
<pre><code class="language-javascript">let state
function useState(initialValue){
  if(state === undefined){
    state = initialValue
  }
  const setState = (newValue) =&gt; {
    state = newValue
  }
  return [state, setState]
}</code></pre>
<p>최초 렌더링 시 <code>initialValue</code>를 <code>state</code>에 할당해서 반환하고, 상태 업데이트 함수 <code>setState</code>를 호출하면 <code>newValue</code>를 <code>state</code>에 재할당 후 반환한다.</p>
<hr>
<p><strong>참고</strong>
<a href="https://velog.io/@radin/js-closure">https://velog.io/@radin/js-closure</a>
<a href="https://yeoulcoding.tistory.com/149#recentEntries">https://yeoulcoding.tistory.com/149#recentEntries</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 카펫]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%B9%B4%ED%8E%AB</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%B9%B4%ED%8E%AB</guid>
            <pubDate>Tue, 03 Dec 2024 08:31:12 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(brown, yellow) {
    var answer = [];

    const oneSide = brown / 2;

    for (let i = 1; i &lt; oneSide; i++) {
        const width = oneSide - i
        const height = i

        if(width &lt; height) break;

        if((width - 2) * height === yellow) return answer = [width, height + 2]
    }

    return answer;
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p><code>brown</code>을 2로 나누면 가로와 위, 아래를 뺀 세로의 합이 되고, 이 때 세로는 <code>yellow</code>의 높이가 된다.
<img src="https://velog.velcdn.com/images/ss_kim/post/dc637ae2-0a0b-4453-aa96-d61e598b62e5/image.png" alt=""></p>
<p><code>yellow</code>의 높이는 최소 1이기 때문에 1부터 <code>brown / 2</code>까지가 되고, 높이가 가로 길이를 넘어설 수 없는 반복문을 돌면서,</p>
<p>가로에서 양 너비 2를 뺀 값(<code>width - 2</code>)과 세로(<code>height</code>)의 곱이 <code>yellow</code>의 갯수와 같으면 값을 반환한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 모의고사]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Tue, 26 Nov 2024 09:31:45 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(answers) {
    var answer = [];

    const a = [1,2,3,4,5]
    const b = [2,1,2,3,2,4,2,5]
    const c = [3,3,1,1,2,2,4,4,5,5]

    const count = [0,0,0]

    answers.forEach((item, i) =&gt; {
        if(item === a[i%5]) count[0]++
        if(item === b[i%8]) count[1]++
        if(item === c[i%10]) count[2]++
    })

    const max = Math.max(...count)

    count.forEach((item, i) =&gt; {
        if(item === max) answer.push(i + 1)
    })

    return answer;
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p>인수로 주어진 answers 배열을 순회하면서 각 정답이 수포자들의 정답과 일치하는지 확인한다.</p>
<p>수포자들의 정답은 일정한 숫자가 반복되는 값이기에 배열로 만들고 각 배열의 length로 나눈 나머지값으로 인덱스를 구할 수 있다.
예를 들어, 1번이 찍는 방식은 [1,2,3,4,5]가 반복되고, 6번 문제는 <code>i % length</code> 즉<code>5 % 5</code>의 값인 0번 인덱스 <code>1</code>로 비교를 하면 됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 최소직사각형]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%B5%9C%EC%86%8C%EC%A7%81%EC%82%AC%EA%B0%81%ED%98%95</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%B5%9C%EC%86%8C%EC%A7%81%EC%82%AC%EA%B0%81%ED%98%95</guid>
            <pubDate>Tue, 26 Nov 2024 07:26:05 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(sizes) {
    var answer = 0;

    sizes.forEach((item, i) =&gt; {
        sizes[i] = item.sort((a, b) =&gt; a - b)
    })

    const w = sizes.map((item) =&gt; item[0])
    const h = sizes.map((item) =&gt; item[1])

    return answer = Math.max(...w) * Math.max(...h)
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p>주어진 sizes 배열 안에 각 명함을 긴 쪽이 0번 인덱스에 위치하도록 정렬하고,
긴 변의 최댓값과 작은 변의 최댓값을 반환</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 무한 스크롤]]></title>
            <link>https://velog.io/@ss_kim/React-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4</link>
            <guid>https://velog.io/@ss_kim/React-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4</guid>
            <pubDate>Tue, 19 Nov 2024 19:38:05 GMT</pubDate>
            <description><![CDATA[<p>데이터를 받아와서 리스트를 구현할 때, 처음 로딩 시간을 줄이고자 모든 데이터를 한 번에 받아오지 않고 여러 번에 나누어(스크롤 할 때마다) 가져오는 것을 구현하기 위해서 TanStack-query의 <code>useInfiniteQuery()</code> 훅을 사용해봤다.</p>
<br>

<hr>
<br>

<h2 id="useinfinitequery">useInfiniteQuery()</h2>
<pre><code class="language-javascript">const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  promise,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam }) =&gt; fetchPage(pageParam),
  initialPageParam: 1,
  ...options,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =&gt;
    lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =&gt;
    firstPage.prevCursor,
})</code></pre>
<ul>
<li><p><code>queryFn</code></p>
<ul>
<li>필수값</li>
<li>데이터를 요청하는데 사용하는 함수</li>
</ul>
</li>
<li><p><code>initialPageParam</code></p>
<ul>
<li>필수값</li>
<li>첫 번째 페이지를 가져올 때 사용하는 매개변수</li>
</ul>
</li>
<li><p><code>getNextPageParam</code></p>
<ul>
<li>필수값</li>
<li>다음 페이지 값으로 전달할 매개변수를 반환해야 함</li>
<li>다음 페이지가 없다면 <code>undefined</code> 또는 <code>null</code>을 반환해야 함</li>
</ul>
</li>
<li><p>훅의 <code>return</code> 반환값</p>
<ul>
<li><p><code>data</code></p>
<ul>
<li>queryFn의 데이터가 저장되어 있는 객체, {pages: [], pageParams: []}</li>
</ul>
</li>
<li><p><code>data.pages</code></p>
<ul>
<li>fetch한 데이터가 저장되는 배열</li>
</ul>
</li>
<li><p><code>data.pageParams</code></p>
<ul>
<li>각 페이지를 fetch하는데 전달한 매개변수가 저장된 배열
<img src="https://velog.velcdn.com/images/ss_kim/post/dfcc4d5b-5708-4327-9068-2036ddfea163/image.png" alt=""> <br>

</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<br>

<h3 id="예제">예제</h3>
<pre><code class="language-jsx">export function Component() {

  const observerRef = useRef();

  const fetchPosts = async (page = 1, items = 10) =&gt; {
    return await pb.collection(&#39;posts&#39;).getList(page, items);
  };

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: ({ pageParam }) =&gt; fetchPosts(pageParam),
    initialPageParam: 1,
    getNextPageParam: (lastPage, allPages) =&gt; {
      if (lastPage.length &lt; 10) return undefined;
      return allPages.length + 1;
    },
  });

  useEffect(() =&gt; {
    if (!observerRef.current) return;

    const observer = new IntersectionObserver(
      (entries) =&gt; {
        if (entries[0].isIntersecting &amp;&amp; hasNextPage &amp;&amp; !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { threshold: 1.0 }
    );

    observer.observe(observerRef.current);

    return () =&gt; {
      if (observerRef.current) {
        observer.unobserve(observerRef.current);
      }
    };
  }, [fetchNextPage, hasNextPage, isFetchingNextPage]);

  const allPosts = data?.pages.flat() || [];

  return (
    &lt;div className={S.component}&gt;
      &lt;Outlet context={{ posts: allPosts, isLoading }} /&gt;
      &lt;div ref={observerRef} style={{ height: &#39;1px&#39; }}&gt;&lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre>
<ul>
<li><code>fetchPosts</code> 함수<ul>
<li>pocketbase API를 사용해 데이터를 가져오며 <code>page</code>는 몇 번째 페이지인지, <code>items</code>는 페이지당 데이터의 갯수를 나타냄</li>
<li><code>page = 1, items = 10</code> 일 경우, 0<del>9번 째 데이터를 가져오고, <code>page = 2, items = 10</code> 일 경우, 10</del>19번 째 데이터를 가져옴</li>
</ul>
</li>
</ul>
<ul>
<li><p><code>getNextPageParam</code></p>
<ul>
<li>다음으로 전달할 <code>pageParam</code> 매개변수가 있는지 확인</li>
<li>items의 갯수가 10개 미만이라면 다음 페이지는 없는 것으로 생각해 undefind를 반환하고 아니라면 <code>page + 1</code>을 <code>nextPageParam</code>으로 반환해서 다음 페이지를 가져옴</li>
</ul>
</li>
<li><p><code>fetchNextPage()</code></p>
<ul>
<li>다음 페이지를 가져오는 함수</li>
<li><code>fetchPosts(nextPageParam)</code>을 실행</li>
</ul>
</li>
<li><p><code>IntersectionObserver</code></p>
<ul>
<li>최하단에 주시 대상 요소를 하나 생성하고 뷰포트에 들어올 경우, <code>fetchNextPage()</code>를 실행</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useOutletContext]]></title>
            <link>https://velog.io/@ss_kim/useOutletContext</link>
            <guid>https://velog.io/@ss_kim/useOutletContext</guid>
            <pubDate>Mon, 18 Nov 2024 14:02:13 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;Outlet&gt;</code> 요소에 props 전달하기</p>
<h2 id="outlet"><code>&lt;Outlet&gt;</code></h2>
<p>react-router-dom을 사용하면 부모 경로 요소에서 자식 경로 요소를 렌더링하기 위해서 <code>&lt;Outlet&gt;</code>을 사용한다. 공식 문서의 예제로 설명하자면, <code>/</code> 이후의 하위 경로에 따라서 <code>&lt;Outlet&gt;</code> 위치에 <code>&lt;DashboardMessages&gt;</code> 또는 <code>&lt;DashboardTasks&gt;</code>가 렌더링 된다.</p>
<pre><code class="language-jsx">function Dashboard() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Dashboard&lt;/h1&gt;

      {/* This element will render either &lt;DashboardMessages&gt; when the URL is
          &quot;/messages&quot;, &lt;DashboardTasks&gt; at &quot;/tasks&quot;, or null if it is &quot;/&quot;
      */}
      &lt;Outlet /&gt;
    &lt;/div&gt;
  );
}

function App() {
  return (
    &lt;Routes&gt;
      &lt;Route path=&quot;/&quot; element={&lt;Dashboard /&gt;}&gt;
        &lt;Route
          path=&quot;messages&quot;
          element={&lt;DashboardMessages /&gt;}
        /&gt;
        &lt;Route path=&quot;tasks&quot; element={&lt;DashboardTasks /&gt;} /&gt;
      &lt;/Route&gt;
    &lt;/Routes&gt;
  );
}</code></pre>
<h2 id="useoutletcontext">useOutletContext</h2>
<p>이 때, 부모/자식 간 공유하려는 상태 또는 데이터가 있다면, <code>&lt;Outlet&gt;</code>에 내장되어 있는 <code>context</code> props를 통해서 간단하게 전달할 수 있다.</p>
<p>부모 요소에서는 <code>context</code>로 전달하고, 자식 요소에서는 <code>useOutletContext()</code> 훅으로 전달 받으면 된다.</p>
<pre><code class="language-jsx">function Parent() {
  const [count, setCount] = React.useState(0);
  return &lt;Outlet context={[count, setCount]} /&gt;;

  // 객체 형태로 전달도 가능
  // return &lt;Outlet context={{count}} /&gt;;
}
</code></pre>
<pre><code class="language-jsx">import { useOutletContext } from &quot;react-router-dom&quot;;

function Child() {
  const [count, setCount] = useOutletContext();
  // const {count} = useOutletContext();

  const increment = () =&gt; setCount((c) =&gt; c + 1);
  return &lt;button onClick={increment}&gt;{count}&lt;/button&gt;;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 힙 - Heap]]></title>
            <link>https://velog.io/@ss_kim/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%9E%99-Heap</link>
            <guid>https://velog.io/@ss_kim/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%9E%99-Heap</guid>
            <pubDate>Sun, 17 Nov 2024 10:41:44 GMT</pubDate>
            <description><![CDATA[<h2 id="힙heap">힙(Heap)</h2>
<p>완전 이진 트리의 일종으로, 최댓값 또는 최솟값을 빠르게 찾아내기 위한 자료구조.
효율적인 데이터 관리와 처리가 필요한 다양한 알고리즘과 시스템에서 사용되며, 주로 우선순위 큐를 구현할 때 사용됨</p>
<blockquote>
<p>우선순위 큐는 큐 자료구조에 우선순위 개념을 도입해 데이터가 들어오면 우선순위에 따라 정렬하고 우선순위가 높은 데이터가 먼저 빠져나가는 구조</p>
</blockquote>
<br>

<h3 id="종류">종류</h3>
<ol>
<li>최대 힙(Max Heap) - 부모 노드의 값이 자식 노드의 값보다 크거나 같음</li>
<li>최소 힙(Min Heap) - 부모 노드의 값이 자식 노드의 값보다 작거나 같음</li>
</ol>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/9d4d5de9-d432-4812-98e9-aaea85ed75b8/image.png" alt=""></p>
<br>

<h3 id="특징">특징</h3>
<ol>
<li><p>완전 이진 트리 구조 - 모든 레벨이 완전히 채워져 있고, 마지막 레벨은 왼쪽에서 오른쪽으로 채워짐</p>
</li>
<li><p>순서 속성 - 반정렬 상태</p>
<ul>
<li>최대 힙은 부모 노드 &gt;= 자식노드</li>
<li>최소 힙은 부모 노드 &lt;= 자식노드</li>
</ul>
</li>
<li><p>중복 허용 - 이진 트리와 달리 중복된 값을 허용</p>
</li>
<li><p>효율적인 연산 - log n의 시간 복잡도를 가짐</p>
</li>
</ol>
<br>

<h3 id="힙의-구현">힙의 구현</h3>
<ul>
<li>힙은 트리 구조이지만 js에서 이를 구현하기 위해서 배열을 사용</li>
<li>노드 번호를 쉽게 구별하기 위해서 배열의 0번 인덱스는 사용하지 않는 것이 편함<ul>
<li>부모 노드 인덱스 = <code>Math.floor(자식 인덱스 / 2)</code></li>
<li>왼쪽 자식 노드 인덱스 = <code>부모 인덱스 * 2</code></li>
<li>오른쪽 자식 노드 인덱스 = <code>(부모 인덱스 * 2) + 1</code></li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ss_kim/post/609d481e-d100-4838-aee4-274e85acc069/image.png" alt=""></p>
<br>

<h4 id="삽입">삽입</h4>
<p>최소 힙을 예로 들자면,</p>
<blockquote>
<ol>
<li>새로운 요소를 마지막 노드에 삽입</li>
<li>부모 노드와 우선 순위를 비교하고 위치를 변경해야 한다면 실행</li>
<li>힙의 성질이 true가 될 때까지 부모 노드와 비교를 반복</li>
</ol>
</blockquote>
<ol>
<li><p>새로운 요소 <code>2</code>를 마지막 노드에 삽입하고 부모 노드와 비교
<img src="https://velog.velcdn.com/images/ss_kim/post/0ab4c97e-f91f-41c7-a6bd-4821127ed088/image.png" alt=""></p>
</li>
<li><p>부모 노드와 위치를 변경하고 다시 부모 노드와 비교
<img src="https://velog.velcdn.com/images/ss_kim/post/7c77a22c-3b70-4ba7-8d88-28680f59f62f/image.png" alt=""></p>
</li>
<li><p>힙의 성질을 만족할 때까지 반복
<img src="https://velog.velcdn.com/images/ss_kim/post/872da52e-f807-44ee-a058-183e81e04420/image.png" alt=""></p>
</li>
</ol>
<br>

<h4 id="삭제">삭제</h4>
<blockquote>
<ol>
<li>루트(최상위) 노드를 삭제하고 마지막 노드를 루트 노드 자리에 옮김</li>
<li>힙의 성질에 맞게 자식 노드와 비교하며 재정렬</li>
</ol>
</blockquote>
<ol>
<li><p>루트 노드 <code>1</code>을 삭제하고 마지막 노드 <code>13</code>을 옮김
<img src="https://velog.velcdn.com/images/ss_kim/post/b561eeb7-6e20-45bb-8ef8-6f0df4940449/image.png" alt=""></p>
</li>
<li><p>우선 순위가 높은 <code>3</code>과 비교해서 재정렬
<img src="https://velog.velcdn.com/images/ss_kim/post/be9e624c-a1fa-4b28-89e9-1ea4b079624a/image.png" alt=""></p>
</li>
<li><p>힙의 성질을 만족할 때까지 반복
<img src="https://velog.velcdn.com/images/ss_kim/post/2a6a1ac7-94c9-42aa-9c2f-8f352f4663b6/image.png" alt="">
<img src="https://velog.velcdn.com/images/ss_kim/post/59d45ea3-70c0-4e5c-9256-f64bffd35c47/image.png" alt=""></p>
</li>
</ol>
<br>

<h4 id="코드">코드</h4>
<pre><code class="language-javascript">class MinHeap {
  // 0번 인덱스는 사용하지 않기 위해 첫 번째 요소로 null 추가
  constructor() {
    this.heap = [null];
  }

  insert(value) {
    this.heap.push(value);
    this.bubbleUp();
  }

  bubbleUp() {
    let index = this.heap.length - 1;
    const insertedNode = this.heap[index]; // 새로 삽입된 노드

    // 비교 대상이 최상위 부모 노드가 될 때까지 반복
    while (index &gt; 1) {
      const parentIndex = Math.floor(index / 2); // 비교할 부모 노드
      // 삽입된 노드의 값이 부모 노드의 값보다 작다면
      // 삽입된 노드 자리에 부모 노드의 값을 넣고, 
      // 다음 비교를 위해 현재 인덱스는 부모 인덱스로 변경
      if (insertedNode &lt; this.heap[parentIndex]) {
        this.heap[index] = this.heap[parentIndex];
        // 구조 분해 할당으로 변경하는 것도 가능
        // [this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
        index = parentIndex;

      // 삽입된 노드의 값이 부모 노드의 값보다 크거나 같다면 반복문 중단
      } else break;
    }

    // 현재 인덱스에 삽입된 노드의 값을 넣어줌
    this.heap[index] = insertedNode;
  }

  remove() {
    // 트리에 노드가 1개일 경우, 삭제 및 반환
    if (this.heap.length === 2) return this.heap.pop();

    const root = this.heap[1];
    // 마지막 노드를 삭제하고 루트 노드로 이동
    this.heap[1] = this.heap.pop();
    this.bubbleDown();
    // 삭제한 노드 반환
    return root;
  }

  bubbleDown() {
    let index = 1;
    const rootNode = this.heap[index]; // 처음 비교할 루트 노드

    // 노드가 마지막 레벨로 갈 때까지 반복
    while ((index * 2) &lt; this.heap.length) {
      const leftChildIndex = index * 2;
      const rightChildIndex = index * 2 + 1;

      // 오른쪽 자식이 존재할 때, 왼쪽과 오른쪽 자식의 크기 비교
      const smallerChildIndex = 
        rightChildIndex &lt; this.heap.length &amp;&amp; this.heap[rightChildIndex] &lt; this.heap[leftChildIndex] 
        ? rightChildIndex 
        : leftChildIndex;

      // 루트 노드가 자식 노드보다 크다면
      // 루트 노드 자리에 자식 노드의 값을 넣고,
      // 다음 비교를 위해 인덱스는 자식 인덱스로 변경
      if (rootNode &gt; this.heap[smallerChildIndex]) {
        this.heap[index] = this.heap[smallerChildIndex];
        index = smallerChildIndex;

      // 루트 노드가 자식 노드보다 크거나 같다면 반복문 중단
      } else break;
    }

    // 현재 인덱스에 처음 루트 노드의 값을 넣어줌
    this.heap[index] = rootNode;
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 주식가격]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9</guid>
            <pubDate>Wed, 13 Nov 2024 08:49:47 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(prices) {
    var answer = [];
      // let count = 0;

    for(let i = 0; i &lt; prices.length; i++) {
        let count = 0;

        if(i == prices.length - 1) answer.push(0)

        for(let j = i + 1; j &lt; prices.length; j++) {
            if(prices[i] &lt;= prices[j]) {
                count++
            } else {
                answer.push(++count)
                count = 0
                break;
            }

            if(j == prices.length - 1) {
                answer.push(count)
                count = 0
            }
        }
    }

    return answer;
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p>처음 반복문은 <code>prices</code> 배열의 각 요소를 순회하고 중첩된 반목문은 각 요소의 다음 인덱스부터 순회하면서 크기를 비교하고 <code>count</code>를 증가시키거나 <code>answer</code>에 <code>push</code>
마지막 비교 요소에 다다르면 <code>answer</code>에 <code>count</code>를 <code>push</code></p>
<p><code>count</code>를 반복문 안에서 선언하면 <code>prices</code>를 순회할 때마다 생성하게 되는데, 반복문 밖에서 선언하고 중첩된 반복문이 끝날 때 0으로 초기화 해주면 효율성이 조금 개선됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 다리를 지나는 트럭]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD</guid>
            <pubDate>Tue, 12 Nov 2024 13:44:13 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(bridge_length, weight, truck_weights) {
    var answer = 0;

    const on = []
    const wait = [...truck_weights]

    while(on.length || wait.length) {
        answer++

        if(answer == on[0]?.[0]) {
            on.shift()
        }

        if(on.reduce((a,c) =&gt; a + c[1], 0) + wait[0] &lt;= weight) {
            on.push([answer + bridge_length, wait.shift()])
        } else {
            if(on[0]) answer = on[0][0] - 1
        }
    }

    return answer;
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p>다리를 건너는 트럭, 대기하는 트럭을 배열로 정리. 이 때, 다리를 건너는 트럭은 <code>[지나가는 시간, 무게]</code>로 설정</p>
<p>예제의 표와 같이 다리를 건너는 트럭, 대기하는 트럭이 모두 0이 될때까지 <code>while문</code> 반복되고 <code>answer</code>가 1씩 증가</p>
<p><code>answer</code>가 다리를 건너는 트럭의 지나가는 시간과 같아지면 <code>shift</code>로 제거</p>
<p>다리를 건너가는 트럭과 다음 올라갈 트럭의 무게가 <code>weight</code> 이하면 지나가는 트럭 배열에 추가
<code>weight</code> 이상이면 <code>answer</code>를 다리를 건너는 맨 앞 트럭의 시간으로 변경해서 반복문 실행</p>
<br>

<hr>
<br>

<p>&lt;처음 작성한 코드&gt;</p>
<pre><code class="language-javascript">    while(on.length || wait.length) {
        answer++

        if(answer &gt; bridge_length) {
            on.shift()
        }

        if(on.reduce((a,c) =&gt; a+c,0) + wait[0] &lt;= weight) {
            on.push(wait.shift())
        } else {
            if(wait.length) on.push(0)
        }
    }</code></pre>
<p>처음 작성한 코드에서는 다리를 건너가는 트럭과 다음 올라갈 트럭의 무게가 <code>weight</code> 이상일 때, 대기 중인 트럭 배열에 <code>0</code>을 추가해 줬는데, 이럴 경우에 다음 트럭이 못 올라가는 경우를 생략하지 않아 실행 횟수가 많아짐</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 프로세스]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</guid>
            <pubDate>Tue, 12 Nov 2024 13:21:38 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(priorities, location) {
    var answer = 0;

    let a = []
    let arr = priorities.map((item, i) =&gt; [i, item])

    for(let i = 0; i &lt; priorities.length; i++) {
        const sliced = arr.slice(i)
        const max = Math.max(...sliced.map((item) =&gt; item[1]))
        const index = sliced.findIndex((item) =&gt; item[1] === max)

        arr = [...a, ...sliced.slice(index), ...sliced.slice(0, index)]
        a.push([sliced[index][0], max])

    }

    return answer = arr.findIndex((item) =&gt; item[0] === location &amp;&amp; item[1] === priorities[location]) + 1
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p><code>priorities</code>를 각 요소와 인덱스로 구성된 새로운 배열로 바꾼 후, 순회하면서 가장 큰 값을 찾아 정렬해나감</p>
<p>반복문 안에 메서드가 사용되어 O(n^2)의 시간 복잡도를 가짐
효율성이 좋지는 않지만 <code>priorities</code>의 길이의 제한 사항이 100이라 통과는 됨</p>
<br>

<hr>
<br>

<p>다른 사람 풀이</p>
<pre><code class="language-javascript">function improvedSolution(priorities, location) {
    let queue = priorities.map((priority, index) =&gt; ({ index, priority }));
    let answer = 0;

    while (queue.length &gt; 0) {
        let current = queue.shift();
        if (queue.some(item =&gt; item.priority &gt; current.priority)) {
            queue.push(current);
        } else {
            answer++;
            if (current.index === location) {
                return answer;
            }
        }
    }
}</code></pre>
<p><code>some</code>메서드를 사용해 queue의 우선 순위 중 맨 앞 요소보다 하나라도 큰 요소가 있다면 queue의 맨 뒤로 보내버림</p>
<p>맨 앞 요소의 우선 순위가 제일 높다면 queue에서 제거하고 카운트</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 알고리즘 Kit - 올바른 괄호]]></title>
            <link>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</link>
            <guid>https://velog.io/@ss_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Kit-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</guid>
            <pubDate>Tue, 12 Nov 2024 05:58:16 GMT</pubDate>
            <description><![CDATA[<p>&lt;코드&gt;</p>
<pre><code class="language-javascript">function solution(s){
    var answer = true;
    const stack = {open:0, value:[]};

    stack.value = s.split(&#39;&#39;).map((item) =&gt; {
        if(item === &#39;(&#39;) {
            stack.open++
            return 1
        } else {
            stack.open--
            if(stack.open &lt; 0) return answer = false
            return -1
        }
    })

    if(stack.value.reduce((a,c) =&gt; a+c, 0) !== 0) return false

    return answer;
}</code></pre>
<br>

<hr>
<br>

<p>&lt;풀이&gt;</p>
<p>괄호를 나누어 순회하면서 <code>&#39;(&#39;</code>는 1로, <code>&#39;)&#39;</code>는 -1로 바꾼다.</p>
<p>앞에서부터 더해가면 괄호가 열릴 때는 1씩 더해지고, 닫히면 1씩 빠짐
-&gt; 괄호는 계속해서 열 수 있기 때문에 양수로 증가하는 것은 상관 없지만 음수가 되면 올바른 구조가 아니고, 최종적으로 0이 되어야 열고 닫는 개수가 맞음</p>
]]></description>
        </item>
    </channel>
</rss>