<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kgh7427_.log</title>
        <link>https://velog.io/</link>
        <description>웹 개발자 고기호입니다.</description>
        <lastBuildDate>Thu, 24 Oct 2024 05:54:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kgh7427_.log</title>
            <url>https://velog.velcdn.com/images/kgh7427_/profile/9403c73c-64a2-4d38-89f2-904781911738/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kgh7427_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kgh7427_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js에서 PWA 설정하기]]></title>
            <link>https://velog.io/@kgh7427_/Next.js%EC%97%90%EC%84%9C-PWA-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/Next.js%EC%97%90%EC%84%9C-PWA-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 24 Oct 2024 05:54:41 GMT</pubDate>
            <description><![CDATA[<h1 id="pwa-설정하기">PWA 설정하기</h1>
<p>Next.js에서 간단하게 PWA로 배포하는 과정을 살펴보자.
이런저런 상세한 옵션은 적지 않고, 바탕화면에 아이콘이 생성되고 실행되기까지의 과정만 적는다.</p>
<h2 id="next-pwa-설치">next-pwa 설치</h2>
<hr>
<p>next-pwa는 Next.js 프로젝트에서 쉽게 PWA설정을 할 수 있게 도와준다.</p>
<pre><code class="language-node">npm install next-pwa</code></pre>
<h2 id="nextconfigmjs-설정">next.config.mjs 설정</h2>
<hr>
<p>루트의 next.config.mjs 파일에서 필요한 부분을 설정한다.</p>
<pre><code class="language-js">/** @type {import(&#39;next&#39;).NextConfig} */

import withPWA from &quot;next-pwa&quot;;

const nextPWA = withPWA({
    dest: &quot;public&quot;,
});

const nextConfig = {};

export default nextPWA(nextConfig);</code></pre>
<h2 id="publicmanifestjson-설정">public/manifest.json 설정</h2>
<p>자세한 옵션 설명은 생략한다.
단, 유의할 점은 icons가 없다면 주소창 옆에 설치 아이콘이 생기지 않는다.
그래서 아이콘 설정과 public 폴더의 아이콘 파일은 설정해주자.</p>
<pre><code class="language-json">{
    &quot;theme_color&quot;: &quot;#222&quot;,
    &quot;background_color&quot;: &quot;#222&quot;,
    &quot;display&quot;: &quot;standalone&quot;,
    &quot;scope&quot;: &quot;/&quot;,
    &quot;start_url&quot;: &quot;/&quot;,
    &quot;name&quot;: &quot;만원 챌린지&quot;,
    &quot;short_name&quot;: &quot;만원 챌린지&quot;,
    &quot;description&quot;: &quot;만원 챌린지 앱으로 소비 습관을 만들어보세요!&quot;,
    &quot;lang&quot;: &quot;ko-KR&quot;,
    &quot;icons&quot;: [
        {
            &quot;src&quot;: &quot;/logo192.png&quot;,
            &quot;sizes&quot;: &quot;192x192&quot;,
            &quot;type&quot;: &quot;image/png&quot;,
            &quot;purpose&quot;: &quot;maskable&quot;
        },
        {
            &quot;src&quot;: &quot;/logo512.png&quot;,
            &quot;sizes&quot;: &quot;512x512&quot;,
            &quot;type&quot;: &quot;image/png&quot;
        }
    ]
}</code></pre>
<h2 id="applayouttsx에-link-태그-넣어주기">app/layout.tsx에 link 태그 넣어주기</h2>
<hr>
<pre><code class="language-tsx">...
&lt;link rel=&quot;manifest&quot; href=&quot;/manifest.json&quot;&gt;&lt;/link&gt;
...</code></pre>
<h2 id="결과-확인">결과 확인</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/60f3ab63-3d0b-4b95-96fc-c3d332b965ca/image.png" alt="">
주소창 옆에 앱을 PWA로 배포한 앱을 다운받는 아이콘이 생성됐다.</p>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/71a70df1-8362-4f4d-abb7-00a895092737/image.png" alt="">
바탕화면에도 설치가 되고, 실행에도 문제가 없다.</p>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://github.com/shadowwalker/next-pwa">https://github.com/shadowwalker/next-pwa</a>
<a href="https://2yh-develop4jeon.tistory.com/m/90">https://2yh-develop4jeon.tistory.com/m/90</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js의 Streaming을 이용한 렌더링과 서버 컴포넌트에서의 데이터 패칭 에러 처리]]></title>
            <link>https://velog.io/@kgh7427_/Progressive-Hydration%EA%B3%BC-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%A8%EC%B9%AD-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@kgh7427_/Progressive-Hydration%EA%B3%BC-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%A8%EC%B9%AD-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Fri, 11 Oct 2024 08:47:11 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-streaming">Next.js Streaming</h1>
<h2 id="기존에-구현하던-렌더링의-순서">기존에 구현하던 렌더링의 순서</h2>
<hr>
<p>Next.js 프로젝트를 교육때 이후로 한 번도 안써봐서 일단 손이 가는 대로 프로젝트를 구현하고 있었다.</p>
<ol>
<li>서버 사이드에서 UI를 렌더링하고 클라이언트로 보낸다.</li>
<li>클라이언트에서 HTML과 CSS를 이용해 UI를 그린다.</li>
<li>Hydration을 이용해 인터렉션이 가능케한다.</li>
<li>useEffect()로 비동기 데이터 패칭을 하고, 바인딩한다.</li>
</ol>
<p>그렇다보니 이렇게 사용자에게 비동기 데이터 패칭해서 가져온 데이터를 보여주는데까지의 시간이 너무 길었다. 그래서 Next.js의 streaming을 이용해 사용자가 보여지기까지의 과정을 좀 더 효율적으로 바꿔보려했다.</p>
<h2 id="suspense을-이용한-렌더링-최적화">Suspense을 이용한 렌더링 최적화</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/78b72723-0d0d-42c8-931d-ec8d42f86deb/image.png" alt=""></p>
<p>Next.js에서 React의 Suspense를 이용하면 위에서 말한 과정의 4번을 크게 개선할 수 있다. 참고로 React 18 부터 서버 컴포넌트에도 Suspense가 적용됐다고 한다.</p>
<p>서버 데이터 패칭은 서버 UI가 그려지기 전에 이루어진다(프리 패칭). 그리고 Suspense를 이용하면 streaming을 이용해 비동기 데이터 패칭이 완료되기 전에 이미 완성된 UI를 사용자가 먼저 볼 수 있다. 그리고 비동기 데이터 패칭이 완료되면 Suspense의 Fallback대신 데이터 바인딩이 완료된 컴포넌트를 볼 수 있다.</p>
<p>따라서 플로우를 보면</p>
<ol>
<li>서버 사이드에서 프리 패칭을 하고, UI를 렌더링하고, 클라이언트로 보낸다</li>
<li>클라이언트에서 HTML과 CSS를 이용해 UI를 그린다.</li>
<li>Hydration을 이용해 인터렉션이 가능케한다.</li>
</ol>
<p>이렇게 세 단계로 줄고, 서버에서 프리 패칭한 데이터가 바인딩된 컴포넌트는 2번 이후에 아무때나 보여지게 된다.</p>
<p>따라서 클라이언트에서 useEffect()로 비동기 데이터 패칭을 하는 시간을 엄청나게 줄일 수 있게 된다.</p>
<h2 id="구현하기">구현하기</h2>
<pre><code class="language-tsx">import Expense from &quot;./Expense&quot;;
import { ExpenseData } from &quot;@/types/expense&quot;;
import { getExpensesByDate } from &quot;@/apis/services/expense&quot;;
import { Suspense } from &quot;react&quot;;

export async function ExpenseContainerServerComponent() {
    const expenses = await getExpensesByDate();

    return (
        &lt;div&gt;
            &lt;ul className=&quot;flex flex-col gap-2&quot;&gt;
                {expenses.map((expense: ExpenseData) =&gt; (
                    &lt;Expense key={expense.id} expense={expense} /&gt;
                ))}
            &lt;/ul&gt;
        &lt;/div&gt;
    );
}

export default async function ExpenseContainer() {
    return (
        &lt;Suspense fallback={&lt;div&gt;데이터를 불러오는 중 입니다...&lt;/div&gt;}&gt;
            &lt;ExpenseContainerServerComponent /&gt;
        &lt;/Suspense&gt;
    );
}
</code></pre>
<h1 id="서버-컴포넌트에서의-데이터-패칭-에러-처리">서버 컴포넌트에서의 데이터 패칭 에러 처리</h1>
<h2 id="errorjs의-한계">error.js의 한계</h2>
<p>error.js는 페이지 단위로 에러를 처리해주기 때문에 하나의 컴포넌트에서 에러 발생시 비효율적인 상황이 발생할 수 있다. 예를들어 비동기 데이터 패칭 컴포넌트에서 데이터 패칭이 실패했다고 가정하자. 그럼 해당 컴포넌트만 회복시켜주면 되는데 error.js는 페이지 전체에 영향을 준다. 따라서 개인적으로 에러 바운더리와 error.js 두개를 상황에 맞게 나누어 써야한다 생각한다.</p>
<h2 id="하지만-서버-컴포넌트에서-에러-바운더리를-사용할-수-없다">하지만 서버 컴포넌트에서 에러 바운더리를 사용할 수 없다.</h2>
<hr>
<pre><code class="language-tsx">&quot;use client&quot;;

import { Component, ErrorInfo, ReactNode } from &quot;react&quot;;

type Props = {
    children: ReactNode;
    onReset: () =&gt; void;
};

type State = {
    hasError: boolean;
};

class ErrorBoundary extends Component&lt;Props, State&gt; {
    constructor(props: Props) {
        super(props);
        this.state = { hasError: false };
    }

    static getDerivedStateFromError(error: Error) {
        return { hasError: true };
    }

    componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        console.log(error.message, errorInfo);
    }

    handleReset = () =&gt; {
        this.setState({ hasError: false });
        this.props.onReset();
    };

    render() {
        if (this.state.hasError) {
            return (
                &lt;div&gt;
                    &lt;h1&gt;데이터를 불러오지 못했습니다...&lt;/h1&gt;
                    &lt;button onClick={this.handleReset}&gt;Reset&lt;/button&gt;
                &lt;/div&gt;
            );
        }

        return this.props.children;
    }
}

export default ErrorBoundary;</code></pre>
<p>에러 바운더리 코드를 보면 클라이언트에서 라이프 사이클을 이용하는 방식이기 때문에 &#39;use client&#39;가 필수다. 따라서 서버에서 에러 바운더리를 사용하지 못한다.</p>
<h2 id="다른-방법을-찾아보자">다른 방법을 찾아보자</h2>
<hr>
<p>이것 저것 삽질하다가 한 문서를 찾았고, 나름 해석해서 구현을 해봤다.
<a href="https://github.com/reactjs/rfcs/blob/ba9bd5744cb922184ec9390515910cd104a30c6e/text/0215-server-errors-in-react-18.md">https://github.com/reactjs/rfcs/blob/ba9bd5744cb922184ec9390515910cd104a30c6e/text/0215-server-errors-in-react-18.md</a></p>
<ol>
<li>서버 사이드에서 데이터 패칭 에러가 발생한다</li>
<li>try catch 문을 이용해 에러 발생시 클라이언트에 보여질 Fallback 컴포넌트를 만들어준다.</li>
<li>해당 컴포넌트에서 회복 로직을 처리해준다.</li>
</ol>
<h2 id="구현">구현</h2>
<pre><code class="language-tsx">&quot;use client&quot;;

import { Suspense } from &quot;react&quot;;
import { ExpenseContainerOnServer } from &quot;./ExpenseContainerOnServer&quot;;

export default function ExpenseContainerOnClient() {
    return (
        &lt;Suspense fallback={&lt;div&gt;데이터를 불러오는 중 입니다...&lt;/div&gt;}&gt;
            &lt;ExpenseContainerOnServer /&gt;
        &lt;/Suspense&gt;
    );
}</code></pre>
<pre><code class="language-tsx">import { ExpenseData } from &quot;@/types/expense&quot;;
import Expense from &quot;./Expense&quot;;
import { getExpensesByDate } from &quot;@/apis/services/expense&quot;;
import ExpenseContainerFetchErrorFallback from &quot;./ExpenseContainerFetchErrorFallBack&quot;;

export async function ExpenseContainerOnServer() {
    try {
        const expenses = await getExpensesByDate();

        throw new Error(&quot;에러 발생&quot;);

        return (
            &lt;div&gt;
                &lt;ul className=&quot;flex flex-col gap-2&quot;&gt;
                    {expenses.map((expense: ExpenseData) =&gt; (
                        &lt;Expense key={expense.id} expense={expense} /&gt;
                    ))}
                &lt;/ul&gt;
            &lt;/div&gt;
        );
    } catch (error) {
        return &lt;ExpenseContainerFetchErrorFallback /&gt;;
    }
}
</code></pre>
<pre><code class="language-tsx">import { getExpensesByDate } from &quot;@/apis/services/expense&quot;;
import { ExpenseData } from &quot;@/types/expense&quot;;
import { useState } from &quot;react&quot;;
import Expense from &quot;./Expense&quot;;

export default function ExpenseContainerFetchErrorFallback() {
    const [expenses, setExpenses] = useState&lt;ExpenseData[]&gt;([]);
    const [isLoading, setIsLoading] = useState(false);
    const [isError, setIsError] = useState(true);

    const handleReset = () =&gt; {
        setIsLoading(true);
        setIsError(false);

        getExpensesByDate()
            .then((response) =&gt; {
                if (response) {
                    setExpenses(response);
                }
            })
            .catch(() =&gt; {
                setIsError(true);
            })
            .finally(() =&gt; {
                setIsLoading(false);
            });
    };

    if (isError) {
        return (
            &lt;div&gt;
                &lt;div&gt;데이터를 가져오지 못했습니다.&lt;/div&gt;
                &lt;button onClick={handleReset}&gt;재요청&lt;/button&gt;
            &lt;/div&gt;
        );
    }

    if (isLoading) {
        return &lt;div&gt;데이터를 불러오는 중 입니다...&lt;/div&gt;;
    }

    return (
        &lt;ul className=&quot;flex flex-col gap-2&quot;&gt;
            {expenses.map((expense: ExpenseData) =&gt; (
                &lt;Expense key={expense.id} expense={expense} /&gt;
            ))}
        &lt;/ul&gt;
    );
}
</code></pre>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming">https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming</a>
<a href="https://github.com/reactjs/rfcs/blob/ba9bd5744cb922184ec9390515910cd104a30c6e/text/0215-server-errors-in-react-18.md">https://github.com/reactjs/rfcs/blob/ba9bd5744cb922184ec9390515910cd104a30c6e/text/0215-server-errors-in-react-18.md</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 Supabase로 토큰 관리하기]]></title>
            <link>https://velog.io/@kgh7427_/Next.js%EC%97%90%EC%84%9C-Supabase%EB%A1%9C-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/Next.js%EC%97%90%EC%84%9C-Supabase%EB%A1%9C-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 09 Oct 2024 15:22:11 GMT</pubDate>
            <description><![CDATA[<h1 id="초기-삽질-과정">초기 삽질 과정</h1>
<p>처음에는 supabase client client를 이용해서 로그인과 회원가입을 구현을 했다. 하지만 미들웨어를 이용해서 새로고침 또는 페이지 이동시 로그인 상태가 아니라면 로그인 페이지로 이동하게 끔 구현을 하고 싶었는데, supabase client client의 경우 토큰을 스토리지에 랜덤한 문자열로 저장을 해서 토큰을 보낼 수가 없었다. 또한 개인적으로 스토리지로 토큰을 관리하는 것을 꺼리기도 했다.</p>
<p>그래서 생각해본 플로우가 브라우저에서 로그인 또는 회원가입을 하면 Next.js API Routes를 이용해 서버에서 supabase server client를 이용해 토큰을 받고 쿠키에 담아 클라이언트로 응답하는 방식이였다.</p>
<p>그런데 막상 구현해보니 supabase server client로 로그인 또는 회원가입을 하면 자동으로 쿠키에 토큰을 담아주는 것을 확인했다. 공식문서를 다시 확인해보니 PKCE 흐름이라는 것을 이용해 토큰을 주고 받고, 로그인 또는 회원가입 인증에 성공하면 supabase server client가 자동으로 세션정보를 쿠키에 담아주고, 브라우저에 응답을 보낼때 응답 헤더에 쿠키를 담아준다는 것을 알게 됐다.</p>
<p>결국 공식 문서에 나와있는 코드의 몇가지 부분을 고쳐서 사용하기만 하면 되는 것이였다...(역시 해답은 공식문서다.)</p>
<h1 id="다이어그램으로-흐름을-짜보자">다이어그램으로 흐름을 짜보자</h1>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/7d18fd0e-bd77-4f34-acba-b4d005f62875/image.png" alt=""></p>
<h1 id="구현을-해보자">구현을 해보자</h1>
<h2 id="1-패키지-설치">1. 패키지 설치</h2>
<pre><code class="language-node">npm install @supabase/ssr @supabase/supabase-js</code></pre>
<h2 id="2-supabase-서버-클라이언트-설정">2. supabase 서버 클라이언트 설정</h2>
<pre><code class="language-ts">import { createServerClient, type CookieOptions } from &#39;@supabase/ssr&#39;
import { cookies } from &#39;next/headers&#39;

export function createClient() {
  const cookieStore = 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>
<h2 id="3-middleware-설정">3. middleware 설정</h2>
<pre><code class="language-ts">// middleware.ts
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: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    &#39;/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)&#39;,
  ],
}</code></pre>
<pre><code class="language-ts">// supabase/middleware.ts
import { createServerClient } from &quot;@supabase/ssr&quot;;
import { NextResponse, type NextRequest } from &quot;next/server&quot;;

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_API_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)
                    );
                },
            },
        }
    );

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

    if (
        !user &amp;&amp;
        !request.nextUrl.pathname.startsWith(&quot;/signIn&quot;) &amp;&amp;
        !request.nextUrl.pathname.startsWith(&quot;/signUp&quot;)
    ) {
        const url = request.nextUrl.clone();
        url.pathname = &quot;/signIn&quot;;
        return NextResponse.redirect(url);
    }

    return supabaseResponse;
}
</code></pre>
<h2 id="4-로그인-페이지-구현">4. 로그인 페이지 구현</h2>
<pre><code class="language-tsx">// signIn.tsx
&quot;use client&quot;;

import { signIn } from &quot;./actions&quot;;
import Button from &quot;./Button&quot;;
import Input from &quot;./Input&quot;;
import Label from &quot;./Label&quot;;
import useSignInForm from &quot;./SignInForm.hook&quot;;

export default function SignInForm() {
    const { value, handleChangeEmail, handleChangePassword } = useSignInForm();

    return (
        &lt;form className=&quot;flex flex-col gap-6 p-6&quot;&gt;
            &lt;div className=&quot;flex flex-col gap-4&quot;&gt;
                &lt;Label htmlFor=&quot;email&quot; text=&quot;이메일&quot;&gt;
                    &lt;Input
                        name=&quot;email&quot;
                        type=&quot;email&quot;
                        placeholder=&quot;이메일을 입력해주세요.&quot;
                        value={value.email}
                        onChange={handleChangeEmail}
                    /&gt;
                &lt;/Label&gt;

                &lt;Label htmlFor=&quot;password&quot; text=&quot;비밀번호&quot;&gt;
                    &lt;Input
                        name=&quot;password&quot;
                        type=&quot;password&quot;
                        placeholder=&quot;비밀번호를 입력해주세요.&quot;
                        value={value.password}
                        onChange={handleChangePassword}
                    /&gt;
                &lt;/Label&gt;
            &lt;/div&gt;

            &lt;Button text=&quot;로그인&quot; formAction={signIn} /&gt;
        &lt;/form&gt;
    );
}</code></pre>
<h2 id="5-server-action으로-로직-구현">5. server action으로 로직 구현</h2>
<pre><code class="language-tsx">&quot;use server&quot;;

import { createClient } from &quot;@/supabase/server&quot;;
import { redirect } from &quot;next/navigation&quot;;

export async function signIn(formData: FormData) {
    const supabase = createClient();

    const data = {
        email: formData.get(&quot;email&quot;) as string,
        password: formData.get(&quot;password&quot;) as string,
    };

    const response = await supabase.auth.signInWithPassword(data);

    if (response.error) {
        return console.log(response.error.message);
    }

    redirect(&quot;/home&quot;);
}</code></pre>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://supabase.com/docs/guides/auth/server-side/advanced-guide">https://supabase.com/docs/guides/auth/server-side/advanced-guide</a>
<a href="https://supabase.com/docs/guides/auth/server-side/nextjs">https://supabase.com/docs/guides/auth/server-side/nextjs</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Local Storage vs Session Storage vs Cookie]]></title>
            <link>https://velog.io/@kgh7427_/Local-Storage-vs-Session-Storage-vs-Cookie</link>
            <guid>https://velog.io/@kgh7427_/Local-Storage-vs-Session-Storage-vs-Cookie</guid>
            <pubDate>Wed, 09 Oct 2024 07:52:29 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<p>먼저 HTTP 프로토콜의 문제점 주요 단점 중 하나를 알아야한다. HTTP 프로토콜은 stateless 프로토콜이다. 이는 서버가 클라이언트의 상태를 저장하지 않는다는 것이다. </p>
<p>가장 간단한 예시로 쇼핑몰 사이트에서 장바구니 기능을 만들어야한다고 해보자. 그러면 브라우저 메모리에 장바구니 목록을 저장해야하는데, 이런 경우 새로고침을 하면 목록이 사라져버린다. 로그인 기능도 마찬가지다. 로그인이 된 상태를 브라우저 메모리에 저장한다하면 새로고침시 로그인이 풀리고, 계속해서 새로 로그인해야한다.</p>
<p>이러한 HTTP의 stateless 특성 때문에 개발된 것이 스토리지와 쿠키다.</p>
<h1 id="공통점">공통점</h1>
<h3 id="브라우저에서-값을-저장하고-읽고-수정할-수-있다">브라우저에서 값을 저장하고, 읽고, 수정할 수 있다.</h3>
<ul>
<li>스토리지와 쿠키가 개발된 이유를 보면 알 수 있듯이 브라우저에서 데이터를 저장, 읽기, 수정, 삭제 모두 할 수 있다.</li>
</ul>
<h3 id="동일-출처-정책same-origin-policy을-따른다">동일 출처 정책(Same-Origin Policy)을 따른다.</h3>
<ul>
<li>같은 도메인에서만 해당 저장소에 접근할 수 있다. 즉, 다른 도메인에서는 저장된 데이터를 읽을 수 없다.</li>
</ul>
<h3 id="key-value-쌍으로-저장할-수-있다">key-value 쌍으로 저장할 수 있다.</h3>
<ul>
<li>따라서 자바스크립트로 관리하기 편하고, 직관적이다.</li>
</ul>
<h3 id="문자열-형식으로-값-저장">문자열 형식으로 값 저장</h3>
<ul>
<li>값으로 오직 문자열만 가능하므로, 객체를 저장하려면 직렬화를 해줘야한다</li>
</ul>
<h1 id="차이점">차이점</h1>
<p><img src="https://velog.velcdn.com/images/kgh7427/post/11e6f156-37a3-4a9e-9cb5-9602d65d3d24/image.png" alt=""></p>
<h2 id="local-storage">Local Storage</h2>
<hr>
<h3 id="만료-시간">만료 시간</h3>
<p>영구적이다. 따라서 오프라인 데이터를 저장하는데 유용하다.</p>
<h3 id="브라우저-세션에-따라-지속적인가">브라우저 세션에 따라 지속적인가?</h3>
<p>그렇다. 브라우저 혹은 탭이 닫혀도 데이터가 유지된다.</p>
<h3 id="접근">접근</h3>
<p>모든 창에서 접근 가능하다.</p>
<h3 id="용량">용량</h3>
<p>5MB 또는 10MB를 최대로 저장 가능하다.</p>
<h3 id="보안">보안</h3>
<ul>
<li>영구적이므로 장기적으로 민감한 데이터를 저장하기에 적합하지 않다. </li>
<li>암호화되지 않은 상태로 저장된다.</li>
<li>XSS(Cross-Site Scripting)공격에 취약하다. <ul>
<li>브라우저 내 악성 스크립트가 로컬 스토리지에 접근해 데이터를 읽거나 수정시킬 수 있다. 로컬 스토리지의 경우 자바스크립트로 접근이 가능해 취약하다.</li>
</ul>
</li>
</ul>
<h3 id="사용해보기">사용해보기</h3>
<pre><code class="language-jsx">// 로컬 스토리지에 데이터 저장
localStorage.setItem(&quot;name&quot;, &quot;John&quot;);
localStorage.setItem(&quot;age&quot;, &quot;30&quot;);

// 로컬 스토리지에서 데이터 조회
const name = localStorage.getItem(&quot;name&quot;);
const age = localStorage.getItem(&quot;age&quot;);

console.log(name); // 출력: John
console.log(age); // 출력: 30

// 로컬 스토리지에서 데이터 삭제
localStorage.removeItem(&quot;age&quot;);

// 로컬 스토리지에서 모든 데이터 삭제
localStorage.clear();</code></pre>
<h2 id="session-storage">Session Storage</h2>
<hr>
<h3 id="만료-시간-1">만료 시간</h3>
<p>브라우저 탭이 닫힐 경우 사라진다.</p>
<h3 id="브라우저-세션에-따라-지속적인가-1">브라우저 세션에 따라 지속적인가?</h3>
<p>아니다. 만료 시간과 연결되어, 브라우저가 닫히거나 탭이 닫히는 경우 사라진다.</p>
<h3 id="접근-1">접근</h3>
<p>각 탭마다 독립적인 저장 공간을 제공해 동일 탭에서만 접근 가능하다.</p>
<h3 id="용량-1">용량</h3>
<p>5MB를 최대로 저장 가능하다.</p>
<h3 id="보안-1">보안</h3>
<ul>
<li>로컬 스토리지와 비교했을 때 사용자가 탭을 켜놓고 있을 때만 데이터를 사용할 수 있기 때문에 이 점에서는 보안성이 나은 편이다.</li>
<li>데이터가 암호화되지 않은 채 저장된다.</li>
<li>XSS 공격에 취약하다.</li>
</ul>
<h3 id="사용해보기-1">사용해보기</h3>
<pre><code class="language-js">// 세션 스토리지에 데이터 저장
sessionStorage.setItem(&quot;name&quot;, &quot;John&quot;);
sessionStorage.setItem(&quot;age&quot;, &quot;30&quot;);

// 세션 스토리지에서 데이터 조회
const name = sessionStorage.getItem(&quot;name&quot;);
const age = sessionStorage.getItem(&quot;age&quot;);

console.log(name); // 출력: John
console.log(age); // 출력: 30

// 세션 스토리지에서 데이터 삭제
sessionStorage.removeItem(&quot;age&quot;);

// 세션 스토리지에서 모든 데이터 삭제
sessionStorage.clear();</code></pre>
<h2 id="cookie">Cookie</h2>
<hr>
<h3 id="생산자">생산자</h3>
<p>스토리지들과 다르게 서버에서 생성이 가능하다. 이때 <code>Set-Cookie</code> 헤더를 사용할 수 있는데, </p>
<h3 id="만료-시간-2">만료 시간</h3>
<p>수동으로 설정이 가능하다. 생산시 <code>Expires</code>또는 <code>Max-Age</code> 옵션을 사용한다.
<code>Expires</code>의 경우 만료 날짜를 설정하고, <code>Max-Age</code>의 경우 몇 초 후에 만료될지를 설정한다.
만약 둘 다 설정한 경우, 브라우저는 <code>Max-Age</code> 값을 사용한다.</p>
<h3 id="브라우저-세션에-따라-지속적인가-2">브라우저 세션에 따라 지속적인가?</h3>
<p>만료 시간을 설정한 경우, 만료 시간이 지나지 않으면 브라우저를 닫거나, 탭이 닫혀도 유지된다.
따라서 만료 시간에 종속적이다.</p>
<h3 id="접근-2">접근</h3>
<p>모든 창에서 접근 가능하다.</p>
<h3 id="모든-http-요청에-보내지는가">모든 HTTP 요청에 보내지는가?</h3>
<p>그렇다. 동일 출처 정책을 따르기 때문에 기본적으로 쿠키가 생성된 도메인에만 자동 전송된다. 다만, 요청에 <code>credentials</code> 설정을 해줘야 쿠키가 자동으로 전송된다.</p>
<pre><code class="language-js">// https://aaa.com 에서 요청
fetch(&#39;https://bbb.com/api&#39;, {
  method: &#39;GET&#39;,
  credentials: &#39;include&#39;  // 쿠키를 포함하여 요청
});</code></pre>
<p>예외적으로 CORS 설정을 통해서 다른 도메인으로 쿠키가 포함된 요청을 보낼 수 있다. 이 경우 서버에서 <code>Access-Control-Allow-Credentials</code> 및 <code>Access-Control-Allow-Origin</code>을 설정을 해주면 된다.</p>
<pre><code class="language-js">// http://bbb.com/api
Access-Control-Allow-Origin: https://aaa.com
Access-Control-Allow-Credentials: true</code></pre>
<h3 id="용량-2">용량</h3>
<p>5KB를 최대로 저장 가능하다.</p>
<h3 id="보안-2">보안</h3>
<ul>
<li>XSS 공격에 취약하다.<ul>
<li><code>HttpOnly</code> 속성을 사용하여 쿠키에 자바스크립트로 접근할 수 없도록 설정한다.</li>
</ul>
</li>
<li>Session Hijacking공격에 취약하다.<ul>
<li>세션 ID의 경우 주로 쿠키에 저장되는데, XXS 공격으로 이를 탈취해 공격자가 피해자의 세션에 접근한다.</li>
<li><code>HttpOnly</code> 속성을 사용해 XSS공격을 방지한다.</li>
<li><code>Secure</code> 속성을 설정해 쿠키가 HTTPS 연결에만 전송되게한다. HTTPS 연결의 경우 세션 ID가 전송될때 암호화시킨다. </li>
</ul>
</li>
<li>CSRF(Cross-Site Request Forgery)공격에 취약하다.<ul>
<li>사용자가 로그인된 상태에서 악성 사이트에 방문시, 사용자의 쿠키가 포함된 요청이 자동으로 서버로 전송된다. 이로인해 사용자의 권한으로 악성 작업이 수행될 수 있다.</li>
<li><code>SameSite</code> 속성을 설정하여 쿠키가 다른 사이트에서 발생한 요청에 포함되지 않도록 할 수 있다. </li>
<li><strong>CSRF 토큰</strong>을 사용해 서버에 들어온 요청이 실제 서버에서 허용한 요청이 맞는지 확인하는 방법도 있다.</li>
<li>세션 유효 기간을 짧게 설정한다.</li>
<li>온라인 게임을 했던 경험으로 동시에 로그인을 못하게도 할 수 있을 듯 하다.</li>
</ul>
</li>
</ul>
<h3 id="사용해보기-2">사용해보기</h3>
<pre><code class="language-js">// 쿠키 설정
document.cookie = &quot;name=John; expires=Thu, 1 Jan 2023 00:00:00 UTC; path=/&quot;;

// 쿠키 조회
const cookies = document.cookie.split(&quot;; &quot;);
cookies.forEach(cookie =&gt; {
  const [key, value] = cookie.split(&quot;=&quot;);
  console.log(key, value);
});

// 쿠키 삭제(만료일을 과거로 설정한다.)
document.cookie = &quot;name=; expires=Thu, 1 Jan 1970 00:00:00 UTC; path=/&quot;;</code></pre>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/html/web-storage-api.md">https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/html/web-storage-api.md</a>
<a href="https://medium.com/@dimplekumari0228/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage-e731a627acb1">https://medium.com/@dimplekumari0228/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage-e731a627acb1</a>
<a href="https://dev.to/gaurbprajapati/cookies-vs-localstorage-vs-sessionstorage-4p6l">https://dev.to/gaurbprajapati/cookies-vs-localstorage-vs-sessionstorage-4p6l</a>
<a href="https://www.geeksforgeeks.org/difference-between-local-storage-session-storage-and-cookies/">https://www.geeksforgeeks.org/difference-between-local-storage-session-storage-and-cookies/</a>
<a href="https://velog.io/@jupiter-j/CSRF%ED%86%A0%ED%81%B0%EC%9D%B4%EB%9E%80">https://velog.io/@jupiter-j/CSRF%ED%86%A0%ED%81%B0%EC%9D%B4%EB%9E%80</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 핵심 이론 강의 2강 정리]]></title>
            <link>https://velog.io/@kgh7427_/SQLD-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0-%EA%B0%95%EC%9D%98-2%EA%B0%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kgh7427_/SQLD-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0-%EA%B0%95%EC%9D%98-2%EA%B0%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 08 Oct 2024 07:56:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/e2c77002-2a72-4ed5-aa2a-25c9e7e89311/image.png" alt="">
이 글은 이기적 영진닷컴의 SQL 개발자 이론+기출 강의를 정리한 내용입니다.</p>
<h1 id="데이터-모델링의-이해">데이터 모델링의 이해</h1>
<h2 id="03-관계relationship-이해하기">03. 관계(Relationship) 이해하기</h2>
<hr>
<h3 id="관계relationship란">관계(Relationship)란?</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/85b1b0cb-b1b8-4ee5-adb9-fdb604528984/image.png" alt=""></p>
<ul>
<li>엔터티 내의 인스턴스들 간에 서로 논리적인 연관성이 있는 상태이다.</li>
<li>직원의 경우 부서에 소속돼 있고, 부서는 직원을 보유한다.<ul>
<li>따라서 직원 엔터티 내부에 부서코드 속성이 존재해야한다.</li>
</ul>
</li>
<li>관계형 데이터 베이스의 특징중 하나이다.</li>
</ul>
<h3 id="관계의-분류---존재행위">관계의 분류 - 존재/행위</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/1892703d-2d5f-4593-ab86-1f3271edf956/image.png" alt=""></p>
<p>(가볍게 이해만하고 암기하고 넘어가기)</p>
<ul>
<li>존재에 의한 관계<ul>
<li>ex) 직원은 부서에 소속되고, 부서는 직원을 보유한다.</li>
</ul>
</li>
<li>행위에 의한 관계<ul>
<li>ex) 고객은 상품을 주문하고, 상품은 고객에게 주문된다.</li>
</ul>
</li>
<li>ERD <ul>
<li>존재/행위 등 관계를 위처럼 선으로 표시한다.</li>
</ul>
</li>
<li>UML 클래스다이어그램<ul>
<li>연관관계(존재)는 실선으로 표기</li>
<li>의존관계(행위)는 점선으로 표시한다.</li>
</ul>
</li>
</ul>
<h3 id="관계선를-표시하는-방법">관계(선)를 표시하는 방법</h3>
<p><em>표기하는 방식에 따라 IE와 Barker 표기법으로 구분된다.</em></p>
<h4 id="관계명-표시">관계명 표시</h4>
<p>관계명을 표시한다. 이때 애매한 동사나 과거형은 피한다.</p>
<ul>
<li>ex) 소속되다, 보유하다, 주문하다, 주문되다.</li>
<li>소속될 것이다(X)</li>
</ul>
<h4 id="관계-차수에-따라-그려보기">관계 차수에 따라 그려보기</h4>
<ul>
<li>관계차수는 1:1, 1:M, M:N같이 엔터티 내 각 인스턴스들이 얼마나 참여하는지를 의미한다.
<img src="https://velog.velcdn.com/images/kgh7427_/post/a2e6f5da-863b-4634-b125-4327d59ec5e9/image.png" alt="">
위 그림에서 부서는 2명 이상의 직원을 가지고 있고, 직원은 한가지의 부서를 가지고 있기 떄문에 1:M의 관계이다.<ul>
<li>BARKER 표기 방식의 경우 2개 이상의 인스턴스를 가지는 경우 까치발 모양을 그려준다.</li>
</ul>
</li>
</ul>
<p><strong>1:1 관계</strong>
<img src="https://velog.velcdn.com/images/kgh7427_/post/f3b86c0a-6f6a-49e2-82a2-8e7f7f5a3ae3/image.png" alt=""></p>
<p>위 그림에서 직원과 직원세부정보는 유일하게 서로를 가지고 있다. 
따라서 1:1 관계이다.
IE 표기 방식과 BARKER 표기 방식이 다르니 유의하자</p>
<p><strong>1:M 관계</strong>
<img src="https://velog.velcdn.com/images/kgh7427_/post/c0944cf0-aff4-4d15-8e50-aea9f1c97b44/image.png" alt=""></p>
<p>위 그림에서 직원은 하나의 부서에 소속되고, 부서는 여러명의 직원을 보유한다.
따라서 1:M 관계이다.</p>
<p><strong>N:M 관계</strong>
<img src="https://velog.velcdn.com/images/kgh7427_/post/6a22353c-65eb-484d-99be-e4d29a14cf8a/image.png" alt=""></p>
<p>위 그림에서 학생은 여러 과목을 수강할 수 있고, 과목은 여러 학생에게 수강된다.
따라서 N:M 관계이다.</p>
<p>관계의 경우 데이터 베이스 내에서 한쪽의 ID를 반대쪽에 속성으로 빌려주는 방식(FK를 사용해 다른 테이블의 ID를 참조한다.)으로 구현되는데
만약 C001이란 학생이 ID가 D001인 수학 과목을 수강한다 가정하면, C002이란 학생도 수학 과목을 수강해서 ID가 D001인 인스턴스가 두개가 생겨버린다. 그렇게 계속 쌓이다보면 한 엔터티에 같은 ID를 가지는 인스턴스가 여러개 생겨버려 관리하기가 힘들어진다.
따라서 N:M 관계의 경우 1:M 또는 M:1로 쪼개야한다(중간 테이블로 나누기)</p>
<p><strong>N:M 관계 쪼개기</strong>
<img src="https://velog.velcdn.com/images/kgh7427_/post/2ec037b8-f7e5-40c6-92f2-c3e2cb35e05d/image.png" alt=""></p>
<p>학생과 과목 사이의 엔터티를 생성하는데 이름은 관계 이름으로 설정한다.</p>
<h4 id="관계선택사양">관계선택사양</h4>
<ul>
<li>엔터티 내 각 인스턴스들이 필수/선택 참여하는지를 의미한다.
<img src="https://velog.velcdn.com/images/kgh7427_/post/04bcd70f-4e27-42d2-b304-bfc402a69f1b/image.png" alt=""></li>
</ul>
<p>IE표기법의 경우
직원은 부서에 속하는데 필수적이므로 O을 표시한다.</p>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/b3948f8a-c21e-4cfd-99e1-d91d2437f84c/image.png" alt=""></p>
<p>BARKER표기법의 경우
부서가 직원을 보유하는데 선택적이므로 점선으로 표시한다.
직원은 부서에 속하는데 필수적이므로 실선으로 표시한다.</p>
<h3 id="관계-표시가-완료된-모습">관계 표시가 완료된 모습</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/5ae8e16c-397d-448c-86c5-96cc022d5681/image.png" alt=""></p>
<h3 id="코드성-통계성이란">코드성, 통계성이란?</h3>
<p>엔터티는 관계를 가지고 있어야 정상이지만, 예외적으로 관계가 없어도 되는 엔터티가 존재한다.
바로 코드성, 통계성 엔터티이다.</p>
<h4 id="코드성">코드성</h4>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/2519f25d-ccdf-43b3-86ac-bb4a7501eed2/image.png" alt=""></p>
<p>코드성의 경우 관계가 너무 많아질 가능성이 높아서, 오히려 관계를 지어주지 않는다.</p>
<h4 id="통계성">통계성</h4>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/f9e8948c-7a03-4bb1-a217-84801a9821aa/image.png" alt=""></p>
<p>통계성의 경우 예를들어 월매출이 있다고 해보자. 이런경우에는 딱히 관계를 지어줄 필요가 없지만 어쨌든 자체만으로 데이터이니 가만히 놔둔다.</p>
<h3 id="관계-체크-사항">관계 체크 사항</h3>
<p>(키워드 정도만 체크하고 넘어가자)
두 엔터티 사이에 관계가 적절한가?</p>
<ol>
<li>두 엔터티 사이에 관심 있는 <strong>연관규칙</strong>이 있는가?</li>
<li>두 엔터티 사이에 <strong>정보의 조합</strong>이 발생하는가?</li>
<li>업무기술서, 장표에 관계연결에 대한 <strong>규칙이 서술</strong>되었는가?</li>
<li>업무기술서, 장표에 관계연결을 가능케 하는 <strong>동사(Verb)</strong>가 있는가?</li>
</ol>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=64c0BgeCLAY&amp;list=PL6i7rGeEmTvpLoDkB-kECcuD1zDt_gaPn&amp;index=2">https://www.youtube.com/watch?v=64c0BgeCLAY&amp;list=PL6i7rGeEmTvpLoDkB-kECcuD1zDt_gaPn&amp;index=2</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트의 라이프 사이클에 대해 알아보기]]></title>
            <link>https://velog.io/@kgh7427_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 08 Oct 2024 06:15:36 GMT</pubDate>
            <description><![CDATA[<h1 id="간단히-알아보기">간단히 알아보기</h1>
<h2 id="라이프-사이클의-세가지-단계">라이프 사이클의 세가지 단계</h2>
<p>리액트의 라이프 사이클은 컴포넌트가 생성되고, 업데이트되고, 제거되는 과정에서 발생하는 여러 단계를 말한다. 주로 세 가지의 단계로 나눌 수 있다.</p>
<ol>
<li>Mounting<ul>
<li>컴포넌트가 처음 화면에 나타날 때 발생한다.</li>
</ul>
</li>
<li>Updating<ul>
<li>컴포넌트의 State 또는 Props가 변경될 떄 발생한다.</li>
</ul>
</li>
<li>Ummounting<ul>
<li>컴포넌트가 화면에서 사라질 때 발생한다.</li>
</ul>
</li>
</ol>
<p>함수형 컴포넌트에서 useEffect를 써본 경험이 많기 때문에 저 세단계가 존재한다고 예상을 하긴했다. 하지만 깊게 알지는 못했기 때문에 이번 기회에 블로깅하면서 정리를 해보려한다.</p>
<h1 id="자세히-알아보기">자세히 알아보기</h1>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/5a315a2c-7975-4ec1-9cf8-445c0ac4e495/image.png" alt=""></p>
<h2 id="0-클래스형-컴포넌트와-함수형-컴포넌트">0. 클래스형 컴포넌트와 함수형 컴포넌트</h2>
<hr>
<h3 id="클래스형-컴포넌트">클래스형 컴포넌트</h3>
<p>함수형 컴포넌트와는 달리 클래스형 컴포넌트는 라이프 사이클과 상태 관리를 기본적으로 처리할 수 있다고한다.</p>
<pre><code class="language-jsx">class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    // 컴포넌트가 처음 마운트될 때 실행되는 로직
  }

  componentDidUpdate() {
    // 컴포넌트가 업데이트될 때 실행되는 로직
  }

  render() {
    return (
      &lt;div&gt;
        &lt;p&gt;You clicked {this.state.count} times&lt;/p&gt;
        &lt;button onClick={() =&gt; this.setState({ count: this.state.count + 1 })}&gt;
          Click me
        &lt;/button&gt;
      &lt;/div&gt;
    );
  }
}</code></pre>
<blockquote>
<p>위 코드에서 우리가 함수형 컴포넌트를 사용할 때 Hook을 Import해 라이프 사이클과 상태를 관리하는 것과 달리 클래스형 컴포넌트의 경우 <code>componentDidMount()</code>, <code>componentDidUpdate()</code>, <code>this.state = { count: 0 }</code> 같은 코드에서 Import문 없이 자체적으로 관리하는 것을 볼 수 있다.</p>
</blockquote>
<h3 id="함수형-컴포넌트">함수형 컴포넌트</h3>
<p>원래 React의 16.8버전 이전에는 useState와 useEffect같은 <strong>Hook</strong>들이 없었다. 따라서 당시에 함수형 컴포넌트는 상태가 없는 단순한 UI를 렌더링하는 정적인 컴포넌트였다. 그래서 자체적으로 상태와 라이프 사이클을 관리할 수 있는 클래스형 컴포넌트를 많이 사용했다. 하지만 <strong>Hook</strong>이 도입된 이후에는 두가지를 관리할 수 있고, 보일러 플레이트도 가벼운 함수형 컴포넌트를 많이 사용하기 시작했다.</p>
<pre><code class="language-jsx">import { useState, useEffect } from &#39;react&#39;;

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() =&gt; {
    // 컴포넌트가 마운트될 때 실행됨
    console.log(&#39;Component mounted&#39;);

    return () =&gt; {
      // 컴포넌트가 언마운트될 때 실행됨
      console.log(&#39;Component unmounted&#39;);
    };
  }, []);

  return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Click me&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<blockquote>
<p>위 코드를 보면 useState와 useEffect 두가지 Hook을 Import해서 자체적이지 않은 방법으로 두가지를 관리를 하고 있다.</p>
</blockquote>
<p>이무튼 이런 역사를 기반으로 생각해보면 <strong>기본적</strong>으로 함수형 컴포넌트는 두가지를 관리하지 못하지만 Hook을 통해 관리를 할 수 있다고 생각하면 될 듯 하다.</p>
<h2 id="1-mounting">1. Mounting</h2>
<hr>
<p>새로운 컴포넌트가 생성되어 DOM에 삽입되는 단계이다. 단 한 번만 발생하며, 초기 렌더링이라고도 많이 말하는 듯 하다. 나는 예시를 참 좋아한다. 예시가 없으면 머릿속에서 글이 구조화 되기가 쉽지 않기 때문이다. 따라서 코드 예시를 먼저 들고 설명을 해보겠다.</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

class ComponentDidMount extends React.Component {
    constructor(props) {
      super(props);
      console.log(&#39;Constructor called&#39;);
      this.state = {
        count: 0
      };
    }

    static getDerivedStateFromProps(props, state) {
      console.log(&#39;getDerivedStateFromProps called&#39;);
      return null;
    }

    componentDidMount() {
      console.log(&#39;componentDidMount called&#39;);
    }

    incrementCount = () =&gt; {
      this.setState(prevState =&gt; ({
        count: prevState.count + 1
      }));
    };

    render() {
      console.log(&#39;render called&#39;);
      return (
        &lt;div&gt;
          &lt;h1&gt;Counter App&lt;/h1&gt;
          &lt;p&gt;Count: {this.state.count}&lt;/p&gt;
          &lt;button onClick={this.incrementCount}&gt;Increment&lt;/button&gt;
        &lt;/div&gt;
      );
    }
}

export default ComponentDidMount;</code></pre>
<h3 id="constructor">constructor()</h3>
<p>constructor 메서드는 해당 단계에서 가장 먼저 호출되는 메서드다. 컴포넌트의 상태를 초기화하고, props를 사용할 수 있게 해준다. 또한 컴포넌트 내에서 사용될 이벤트 핸들러 메서드를 바인딩하는 데 사용된다. </p>
<h3 id="static-getderivedstatefrompropsprops-state">static getDerivedStateFromProps(props, state)</h3>
<p>해석해보면 props로부터 파생된 상태를 가져온다는 걸 알 수 있듯이, props의 변화를 기반으로 상태를 업데이트할 수 있도록 해준다.</p>
<p>인자를 보면 Props, State 두 가지가 있는데 처음에 State가 왜 들어가지? 싶었다. 생각해보면 부모로부터 props를 내려받고 그 값으로 초기화되는 state가 있다면 업데이트 시 두 값이 같다면 불필요한 렌더링을 유발하니, 두 값을 비교하고 업데이트를 하기 때문인 듯 하다.</p>
<p>잘못 사용하면 많은 오류가 발생할 수 있어 초보자들은 피하는 것이 좋다고 한다.</p>
<h3 id="render">render()</h3>
<p>JSX로 HTML을 작성하고 DOM에 삽입하는 단계이다.</p>
<h3 id="componentdidmount">componentDidMount()</h3>
<p>컴포넌트가 처음으로 렌더링된 후(render() 이후)에 호출된다. 함수형 컴포넌트에서 의존성 배열이 빈 useEffect를 생각하면 편할 듯 하다. 따라서 네트워크 요청 등의 사이드 이펙트를 관리하는데 적합하다.</p>
<h2 id="2-updating">2. Updating</h2>
<hr>
<p>컴포넌트가 업데이트되거나 다시 렌더링될 때 발생한다. props나 state가 변경될 때 트리거된다. 마찬가지로 먼저 예시 코드를 보자</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

class UpdatingExample extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        name: &#39;John&#39;,
        changed: false
      };
      console.log(&#39;Constructor called&#39;);
    }

    static getDerivedStateFromProps(props, state) {
      console.log(&#39;getDerivedStateFromProps called&#39;);
      return null;
    }

    shouldComponentUpdate(nextProps, nextState) {
      console.log(&#39;shouldComponentUpdate called&#39;);
      return true;
    }

    getSnapshotBeforeUpdate(nextProps, nextState) {
        console.log(&#39;getSnapshotBeforeUpdate called&#39;);
        return null;
    }

    componentDidUpdate(prevProps, prevState) {
      console.log(&#39;componentDidUpdate called&#39;);
    }

    changeName = () =&gt; {
      this.setState({
        name: &#39;Jane&#39;,
        changed: true
      });
    };

    render() {
      console.log(&#39;render called&#39;);
      return (
        &lt;div&gt;
          &lt;h1&gt;Updating Example&lt;/h1&gt;
          &lt;div&gt;Name {
              this.state.changed ? 
              &lt;h3&gt;{this.state.name}&lt;/h3&gt; : 
              &lt;p&gt;{this.state.name}&lt;/p&gt;}
          &lt;/div&gt;
          &lt;button onClick={this.changeName}&gt;Change Name&lt;/button&gt;
        &lt;/div&gt;
      );
    }
}

export default UpdatingExample;</code></pre>
<h3 id="shouldcomponentupdatenextprops-nextstate">shouldComponentUpdate(nextProps, nextState)</h3>
<p>불필요한 컴포넌트 리렌더링을 방지하는데 사용된다. 기본적으로 props와 state가 변경될 때 컴포넌트가 리렌더링되는데, true 또는 false를 반환해서 조건에 따라 컴포넌트를 리렌더링 할지 말지 결정할 수 있다.</p>
<p>nextProps와 nextState를 인자로 받아서 현재 props와 state와 비교할 수 있다.</p>
<h3 id="getsnapshotbeforeupdateprevprops-prevstate">getSnapshotBeforeUpdate(prevProps, prevState)</h3>
<p>render() 직전에 호출된다. DOM이 업데이트되기 전의 상태를 기록할 수 있다. 주로 DOM의 스크롤 위치, 크기 변경 등의 정보를 기록할 때 유용하다. 반환된 값은 <code>componentDidMount()</code>의 세번 째 인자로 전달된다.</p>
<h3 id="componentdidupdateprevprops-prevstate-snapshot">componentDidUpdate(prevProps, prevState, snapshot)</h3>
<p>render() 이후에 호출된다. 주로 DOM 트리를 동작하거나, 전달된 인자중 하나가 변경될 때 발생하는 사이드 이펙트를 처리하는데 사용된다. useEffect에서 의존성 배열 내부의 데이터가 변경될 때 로직이 실행되는 것을 생각하면 편할 듯하다.</p>
<h2 id="3-ummounting">3. Ummounting</h2>
<hr>
<p>컴포넌트가 DOM에서 제거되고, 더 이상 렌더링되지 않거나 접근할 수 없을 때 발생한다. 이 단계에서 리액트를 컴포넌트와 관련된 리소스를 DOM 트리에서 제대로 정리하기 위해 몇가지 작업을 수행한다.</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

class Child extends React.Component {
    componentDidMount() {
      console.log(&#39;Component mounted&#39;);
    }

    componentWillUnmount() {
      console.log(&#39;Component unmounted&#39;);
    }

    render() {
      return (
        &lt;div&gt;
          &lt;p&gt;Child Component content&lt;/p&gt;
        &lt;/div&gt;
      );
    }
}

export default class UnmountingExample extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        showComponent: true
      };
    }

    toggleComponent = () =&gt; {
      this.setState(prevState =&gt; ({
        showComponent: !prevState.showComponent
      }));
    };

    render() {
      return (
        &lt;div&gt;
          &lt;h1&gt;Main Component&lt;/h1&gt;
          {this.state.showComponent &amp;&amp; &lt;Child /&gt;}
          &lt;button onClick={this.toggleComponent}&gt;
            {this.state.showComponent ? &#39;Unmount&#39; : &#39;Mount&#39;}
          &lt;/button&gt;
        &lt;/div&gt;
      );
    }
}</code></pre>
<h3 id="componentwillunmount">componentWillUnmount()</h3>
<p>컴포넌트가 DOM에서 제거되기 직전에 호출된다. useEffect의 return문을 생각하면 될 듯하다. 그와 마찬가지로 타이머 취소, 이벤트 리스너 제거, 데이터 구조 정리 등 메모리 누수를 방지하기 위한 작업을 하는데 사용된다. 또한 컴포넌트의 모든 상태와 props는 소멸된다.</p>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://medium.com/@arpitparekh54/understanding-the-react-component-lifecycle-a-deep-dive-into-the-life-of-a-react-component-74813cb8dfb5">https://medium.com/@arpitparekh54/understanding-the-react-component-lifecycle-a-deep-dive-into-the-life-of-a-react-component-74813cb8dfb5</a>
<a href="https://massivepixel.io/blog/react-lifecycle-methods/">https://massivepixel.io/blog/react-lifecycle-methods/</a>
<a href="https://levelup.gitconnected.com/react-lifecycle-methods-and-their-equivalents-in-functional-components-5677a3fa623d">https://levelup.gitconnected.com/react-lifecycle-methods-and-their-equivalents-in-functional-components-5677a3fa623d</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 핵심 이론 강의 1강 정리]]></title>
            <link>https://velog.io/@kgh7427_/SQLD-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0-%EA%B0%95%EC%9D%98-1%EA%B0%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kgh7427_/SQLD-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0-%EA%B0%95%EC%9D%98-1%EA%B0%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 07 Oct 2024 06:50:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/06d40394-e9b5-482d-b995-b3a2443560f4/image.png" alt=""></p>
<p>이 글은 이기적 영진닷컴의 SQL 개발자 이론+기출 강의를 정리한 내용입니다.</p>
<h1 id="데이터모델링의-이해">데이터모델링의 이해</h1>
<h2 id="00-그전에-데이터베이스가-뭔지-알아봅시다">00. 그전에 데이터베이스가 뭔지 알아봅시다.</h2>
<hr>
<h3 id="데이터베이스">데이터베이스</h3>
<ul>
<li>여러 데이터들을 모아 통합적으로 관리하는 기술이다.</li>
<li>여러 사람들이 함께 사용하고 공유할 수 있다.</li>
</ul>
<h3 id="데이터베이스가-없었을-때는">데이터베이스가 없었을 때는?</h3>
<ul>
<li>사람들 각자 여러 파일을 가지고 있다.<ul>
<li>한 사람이 파일을 바꾸면 다른 파일들도 똑같이 수정해야함.</li>
</ul>
</li>
</ul>
<h3 id="데이터베이스의-출시">데이터베이스의 출시</h3>
<ul>
<li>데이터를 한 곳에 모아 저장하고 공유를 할 수 있게 되었다.<ul>
<li>한 사람이 파일을 바꾸면 다른 사람들은 바꿀 필요없이 똑같은 데이터베이스의 데이터를 볼 수 있어 편하다</li>
</ul>
</li>
</ul>
<h3 id="데이터베이스가-어떻게-쓰이지">데이터베이스가 어떻게 쓰이지?</h3>
<ul>
<li>로그인 구현<ol>
<li>로그인 페이지에서 아이디와 패스워드 입력</li>
<li>데이터베이스에 저장된 아이디와 패스워드를 찾음</li>
<li>로그인 성공 or 실패 결과 반환 </li>
</ol>
</li>
</ul>
<h3 id="sqld라는-것은-근본적으로-무슨-역할인가">SQLD라는 것은 근본적으로 무슨 역할인가?</h3>
<ul>
<li>데이터베이스에서 원하는 정보를 조회하거나 조작하는 것</li>
</ul>
<h3 id="데이터-모델이란">데이터 모델이란?</h3>
<ul>
<li>현실세계의 대상을 추상화, 단순화, 명확화하여 데이터베이스로 표현한 것<ul>
<li>ex) 직원들의 이름, 연락처, 주소, 재직 여부, 부서 정보 등</li>
</ul>
</li>
</ul>
<h3 id="데이터-모델링">데이터 모델링</h3>
<ul>
<li>집을 만드는 과정과 같이 데이터 모델링에도 과정이 있다.<ol>
<li>요구 사항 접수(집을 만들고 싶어)</li>
<li>개념적 데이터 모델링(단순한 설계도를 일단 그려보자)</li>
<li>논리적 데이터 모델링(좀 더 상세한 설계도를 만들자)</li>
<li>물리적 데이터 모델링(실제로 구축해보자)</li>
<li>데이터베이스에 저장할 수 있게 세팅(집을 만들었다)</li>
</ol>
</li>
</ul>
<h2 id="01-엔터티entity-이해하기">01. 엔터티(ENTITY) 이해하기</h2>
<hr>
<h3 id="엔터티entity-개체란">엔터티(ENTITY, 개체)란?</h3>
<ul>
<li>업무에 필요한 정보를 저장/관리하기 위한 집합적인 명사 개념<ul>
<li>A 회사의 직원들(업무에 필요한 정보) → 직원(직원들을 아우르는 집합적인 명사 개념)</li>
<li>A 회사의 부서들(업무에 필요한 정보) → 부서(부서들을 아우르는 집합적인 명사 개념)</li>
</ul>
</li>
</ul>
<h3 id="인스턴스instance란">인스턴스(INSTANCE)란?</h3>
<ul>
<li>엔터티 집합 내에 존재하는 개별적인 대상<ul>
<li>직원 → 직원A씨, 직원B씨, 직원C씨, 직원D씨, …</li>
<li>부서 → 노사협의부, 인사부, 급여부, 인터넷서비스부, …</li>
</ul>
</li>
</ul>
<h3 id="엔터티의-특징">엔터티의 특징</h3>
<ol>
<li>반드시 업무에서 필요한 대상이고 업무에 사용될 것<ul>
<li>요구사항에서 직원, 부서를 뽑듯이 업무에 필요한 대상이여야 함</li>
</ul>
</li>
<li>유일한 식별자로 식별이 가능할 것<ul>
<li>직원의 이름 X → 동명이인, 주민등록번호 O → 유일함</li>
</ul>
</li>
<li>인스턴스가 2개 이상 존재할 것</li>
<li>속성이 반드시 2개 이상 존재할 것</li>
<li>관계가 하나 이상 존재할 것</li>
</ol>
<h3 id="엔터티의-분류--유무형에-따라-분류-">엔터티의 분류 ( 유무형에 따라 분류 )</h3>
<ul>
<li>유형<ul>
<li>물리적 형태가 있는 엔터티</li>
<li>ex) 직원, 주류, 강사, 고객</li>
</ul>
</li>
<li>개념<ul>
<li>물리적 형태가 없는 엔터티</li>
<li>ex) 부서, 과목, 계급</li>
</ul>
</li>
<li>사건 ( 제일 많이 발생 )<ul>
<li>업무 수행 중에 발생하는 엔터티</li>
<li>ex) 강의, 매출, 주문, 상담</li>
</ul>
</li>
</ul>
<h3 id="엔터티의-분류--발생시점에-따라-분류-">엔터티의 분류 ( 발생시점에 따라 분류 )</h3>
<ul>
<li>기본/키<ul>
<li>본래 업무에 존재하는 정보<ul>
<li>요구사항 분석시 바로 추출되는 것들</li>
</ul>
</li>
<li>독립 생성 가능, 주식별자 보유</li>
<li>ex) 직원, 고객, 상품</li>
</ul>
</li>
<li>중심<ul>
<li>기본 엔터티로부터 발생</li>
<li>업무에 있어 중심 역할</li>
<li>ex) 주문, 매출, 계약</li>
</ul>
</li>
<li>행위<ul>
<li>2개 이상 엔터티로부터 발생</li>
<li>ex) 주문이력 ( 직원이 고객으로부터 주문을 받아서 주문이력이 생겼다 )</li>
</ul>
</li>
</ul>
<h3 id="엔터티의-명명naming-규칙">엔터티의 명명(naming) 규칙</h3>
<ol>
<li>가능한 현업(도메인) 용어를 쓴다.<ul>
<li>ex) 사람 → 고객, 전봇대 → 전주</li>
</ul>
</li>
<li>가능하면 약어를 사용하지 않는다.<ul>
<li>ex) 일별매출정보 → 일매목</li>
</ul>
</li>
<li>단수 명사를 사용한다.<ul>
<li>ex) 직원들 → 직원, 주문 내역들 → 주문내역</li>
</ul>
</li>
<li>엔터티 이름은 유일해야 한다.</li>
<li>엔터티 생성의미대로 이름을 부여한다<ul>
<li>ex) 연락처목록 → 직원연락처목록? 고객연락처목록? 어떤 것이지?</li>
</ul>
</li>
</ol>
<h2 id="02-속성">02. 속성</h2>
<hr>
<h3 id="속성attribute-이란">속성(ATTRIBUTE) 이란?</h3>
<ul>
<li>업무상 관리하기 위해 의미적으로 더는 분리되지 않는 최소의 데이터 단위<ul>
<li>엔터티가 가지는 공통적인 특징을 표현한다.<ul>
<li>ex) 직원ID, 패스워드, 연봉, 이름, 부서, 입사일시, 생년월일 등</li>
<li>이름나이 X → 최소의 데이터 단위가 아니기 때문에 이름과 나이로 나눈다.</li>
</ul>
</li>
</ul>
</li>
<li>이후 속성은 우리가 관리하고자 하는 정보가 된다.</li>
</ul>
<h3 id="엔터티-인스턴스-속성의-관계">엔터티, 인스턴스, 속성의 관계</h3>
<ol>
<li>하나의 엔터티는 2개 이상의 인스턴스를 가진다.</li>
<li>하나의 엔터티는 2개 이상의 속성을 가진다.</li>
<li>속성은 각 인스턴스를 설명해줄 수 있다.</li>
<li>하나의 속성에는 하나의 속성값만 들어간다.</li>
</ol>
<h3 id="식별자란">식별자란?</h3>
<ul>
<li>엔터티 내 유일한 인스턴스를 식별할 수 있는 속성의 집합<ul>
<li>프론트엔드에서 userId를 body에 넣어서 보낸다 → 데이터베이스에서 userId를 이용해 유일한 인스턴스를 찾는다</li>
<li>유저 이름 → 동명이인이 있을 가능성 때문에 식별자가 아니다.</li>
</ul>
</li>
</ul>
<h3 id="속성의-분류특성에-따른-분류">속성의 분류(특성에 따른 분류)</h3>
<ul>
<li>기본<ul>
<li>업무로부터 추출한 속성<ul>
<li>요구사항 분석시 바로 추출</li>
</ul>
</li>
<li>제일 많이 발생</li>
</ul>
</li>
<li>설계<ul>
<li>설계시 규칙화 등이 필요해 만든 속성</li>
<li>코드성이나 일련번호 등<ul>
<li>부서명으로 부서코드(일련번호)를 만든 것</li>
</ul>
</li>
</ul>
</li>
<li>파생<ul>
<li>다른 속성들로부터 계산/변형되어 만들어진 속성</li>
<li>실무에서 지양<ul>
<li>데이터가 추가될 때마다 기존 데이터가 변경돼야함</li>
</ul>
</li>
<li>ex) 부서별연봉합</li>
</ul>
</li>
</ul>
<h3 id="속성의-분류구성방식에-따른-분류">속성의 분류(구성방식에 따른 분류)</h3>
<ul>
<li>PK, FK, 일반속성, 복합속성<ul>
<li>나중에 이야기 할 것</li>
</ul>
</li>
</ul>
<h3 id="속성-명명naming-규칙">속성 명명(Naming) 규칙</h3>
<ul>
<li>가능한 현업 용어를 쓴다.</li>
<li>가능하면 약어를 사용하지 않는다.</li>
<li>명사형을 쓰고 서술식이나 수식어 등을 제한한다.<ul>
<li>ex) 오늘배송된상품 → 일배송상품</li>
</ul>
</li>
<li>가능한 속성 이름은 전체 데이터 모델에서 유일해야 한다.<ul>
<li>실무에서 그렇게 지키지는 않는듯?</li>
<li>ex) 직원 엔터티의 연락처 속성과 고객 엔터티의 연락처 속성</li>
</ul>
</li>
</ul>
<h3 id="도메인domain이란">도메인(DOMAIN)이란?</h3>
<ul>
<li>각 속성이 입력 받을 수 있는 값의 정의 및 범위를 의미</li>
<li>보통 테이블을 만들 때 각 속성마다 자료형 및 제약조건을 줄 때 결정된다.<ul>
<li>ex) 나이는 숫자만 입력받을 수 있고, 입력값은 0~999까지로 한다.</li>
<li>ex) 이름은 문자형으로 입력받을 수 있고, 최대 5자리까지로 한다.</li>
</ul>
</li>
</ul>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=lxiEiAjp7d0&amp;list=PL6i7rGeEmTvpLoDkB-kECcuD1zDt_gaPn">https://www.youtube.com/watch?v=lxiEiAjp7d0&amp;list=PL6i7rGeEmTvpLoDkB-kECcuD1zDt_gaPn</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[렌더링 방식 이해하기(feat. MPA, SPA, CSR, ...)]]></title>
            <link>https://velog.io/@kgh7427_/%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0feat.-MPA-SPA-CSR-</link>
            <guid>https://velog.io/@kgh7427_/%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0feat.-MPA-SPA-CSR-</guid>
            <pubDate>Thu, 03 Oct 2024 05:29:44 GMT</pubDate>
            <description><![CDATA[<h1 id="mpa-vs-spa">MPA vs SPA</h1>
<h2 id="mpamulti-page-application">MPA(Multi-Page Application)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/38ade430-1f8b-4778-8ac3-00822d8bc0c3/image.png" alt=""></p>
<h3 id="mpa란">MPA란?</h3>
<p>여러 페이지로 구성된 웹 어플리케이션이다. </p>
<p>HTML파일이 여러개 있다고 생각하면 편하다.</p>
<h3 id="장점">장점</h3>
<ol>
<li>HTML파일에서 메타 태그를 추가할 수 있기 때문에 SEO에 유리하다.</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li>사용자가 페이지를 이동할 때마다 새로운 HTML을 받아와 렌더링을 시도하기 페이지 전환시 렌더링까지의 시간이 길다. </li>
<li>새로고침이 발생하기 때문에 사용자 경험이 좋지 않다.</li>
</ol>
<h2 id="spasingle-page-application">SPA(Single Page Application)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/628d4a5c-a51e-4600-ad92-2b8d439d4bc9/image.png" alt=""></p>
<h3 id="spa란">SPA란?</h3>
<p>하나의 페이지로 구성된 웹 어플리케이션이다. 
HTML파일이 하나라고 생각하면 편하다. 
깡통인 HTML파일에 자바스크립트를 이용해서 렌더링 및 라우팅을 하게 한다.</p>
<h3 id="장점-1">장점</h3>
<ol>
<li>단일 HTML이기 때문에 페이지 전환시 새로고침이 일어나지 않는다. </li>
<li>자바스크립트가 모든 렌더링과 라우팅을 담당하기 때문에 빠른 페이지 전환이 가능하다.</li>
</ol>
<h3 id="단점-1">단점</h3>
<ol>
<li>HTML파일이 하나기 때문에 메타 태그를 추가하기 어렵고, 크롤러 봇이 자바스크립트를 읽지 못해서 SEO에 취약하다. 이 경우 <strong>React Helmet</strong> 라이브러리를 사용하거나 다른 방법으로 개선해야한다.</li>
</ol>
<h1 id="csr-vs-ssr">CSR vs SSR</h1>
<h2 id="csrclient-side-rendering">CSR(Client-Side Rendering)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/075f6a2b-1164-478b-8f8d-b4307c236ff4/image.png" alt=""></p>
<h3 id="csr이란">CSR이란?</h3>
<p>말 그대로 클라이언트(브라우저)에서 렌더링을 하는 방식이다. SPA와 궁합이 잘맞아 CSR + SPA 조합을 많이 사용한다. 이 경우 브라우저에서 깡통 HTML파일을 받고, 이후에 자바스크립트로 렌더링 및 라우팅, 로직을 관리한다.</p>
<h3 id="장점-2">장점</h3>
<ol>
<li>클라이언트에서 대부분의 로직을 처리하기 때문에 서버 비용을 절약할 수 있다.</li>
<li>초기 로드 후에 페이지 전환이 빠르다.</li>
</ol>
<h3 id="단점-2">단점</h3>
<ol>
<li>크롤러 봇이 깡통 HTML 파일을 보기 때문에 SEO에 적합하지 않다.</li>
<li>사용자가 렌더링 하기 전까지 빈 화면을 보게되는 시간인 TTV(Time To VIew)가 크다. 따라서 코드 분할 등으로 개선해야한다.</li>
</ol>
<h2 id="ssrserver-side-rendering">SSR(Server-Side Rendering)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/81bf2ce6-0d48-4d40-bb5b-64aa1c44faa6/image.png" alt=""></p>
<h3 id="ssr이란">SSR이란?</h3>
<p>말 그대로 서버에서 렌더링을 하는 방식이다. 서버가 완전히 렌더링한 HTML파일을 브라우저에게 전달한다. </p>
<h3 id="장점-3">장점</h3>
<ol>
<li>서버에서 HTML파일을 미리 렌더링하기 때문에 사용자에게 웹 페이지를 빠르게 보여줄 수 있다.</li>
<li>크롤러 봇이 미리 렌더링된 HTML파일을 보기 때문에 SEO에 유리하다.</li>
</ol>
<h3 id="단점-3">단점</h3>
<ol>
<li>서버에서 렌더링 및 많은 로직을 처리하기 때문에 서버 비용이 증가한다.</li>
<li>사용자에게 웹페이지를 빠르게 보여줄 수 있지만, 자바스크립트 파일이 렌더링 되기 전이라 사용자가 이벤트를 동작할 수 없다. 따라서 TTV는 빠르지만, TTI(Time To Interact)까지의 간격이 크다.</li>
</ol>
<h2 id="주의점">주의점</h2>
<hr>
<h3 id="spa와-csr은-같은가">SPA와 CSR은 같은가?</h3>
<p>아니다. </p>
<p>SPA와 CSR의 각 핵심은 SPA는 HTML의 파일의 갯수고, CSR은 어디서 렌더링을 하느냐의 문제이다.</p>
<h2 id="왜-spa에는-ssr을-사용하지-않고-mpa에는-csr을-사용하지-않는가">왜 SPA에는 SSR을 사용하지 않고, MPA에는 CSR을 사용하지 않는가</h2>
<hr>
<h3 id="spa--ssr-조합의-경우">SPA + SSR 조합의 경우</h3>
<p>SPA의 경우 클라이언트 측에서 자바스크립트를 이용해 라우팅을 관리하기 때문에 페이지 전환이 빠르다는 강점을 가지고 있다. 하지만 SSR방식을 사용한다면 페이지 전환시 서버에 렌더링된 HTML 파일을 요청해야하니 SPA의 특성인 빠른 페이지 전환의 장점을 잃게 된다.</p>
<h3 id="mpa--csr-조합의-경우">MPA + CSR 조합의 경우</h3>
<p>MPA의 경우 페이지 전환마다 서버에서 HTML을 요청하는 방식인데, CSR의 경우 깡통인 HTML 파일을 받아오기 때문에 페이지 전환시 서버에 HTML을 요청하는 시간 + 브라우저에서 자바스크립트로 렌더링하는 시간이 합쳐져 초기 로딩 성능이 매우 떨어져 버린다.또한 CSR의 경우 깡통인 HTML 파일을 받아오기 때문에 SEO에 취약하다.</p>
<h3 id="그럼-섞어보자">그럼 섞어보자</h3>
<p><strong>Next.js</strong>을 사용하면 기본적으로 MPA 방식을 사용하고, CSR과 SSR의 장점을 모두 가져갈 수 있다.</p>
<p>예를 들어 다른 페이지로 이동할 가능성이 있는 경우 해당 페이지를 미리 네트워크 요청(프리 페칭)한다. 이런 경우 일반적인 MPA와 달리 Next.js의 자바스크립트 라우터가 이벤트를 가로채 서버에 요청을 보내지 않고, 클라이언트 측에서 프리페칭한 데이터를 사용한다. 따라서 CSR의 장점을 가져 갈 수 있게 한다.</p>
<h1 id="ssg-vs-isr">SSG vs ISR</h1>
<h2 id="ssgstatic-site-generation">SSG(Static Site Generation)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/fdb85609-ed39-4011-9432-5005f5fe5569/image.png" alt=""></p>
<h3 id="ssg란">SSG란?</h3>
<p>SSR과 다르게 빌드 시점에 웹 어플리케이션의 모든 페이지를 렌더링해둔다. 주로 블로그에서 많이 사용되는 듯 하다.</p>
<h3 id="장점-4">장점</h3>
<ol>
<li>이미 빌드 시점에 모든 페이지가 렌더링돼 있기 때문에 사용자가 웹 페이지를 빠르게 볼 수 있다.</li>
<li>렌더링된 HTML을 크롤러 봇이 보기 때문에 SEO에 유리하다.</li>
<li>특정한 상황에서 서버 측 프로세스 혹은, 데이터 베이스가 필요하지 않은 완전한 정적 HTML 기반 사이트를 생성할 수 있다.</li>
</ol>
<h3 id="단점-4">단점</h3>
<ol>
<li>콘텐츠 업데이트 시 사이트를 다시 빌드해야한다.</li>
<li>대규모 웹 어플리케이션에 사용되는 경우 유지보수가 번거롭고, 빌드 시간도 상당히 길어질 수 있다.</li>
</ol>
<h2 id="isrincremental-static-regeneration">ISR(Incremental Static Regeneration)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/463cab56-5eed-45a7-987b-a3121d0a9053/image.png" alt=""></p>
<h3 id="isr이란">ISR이란?</h3>
<p>SSG와 SSR의 장점을 결합해서 페이지 단위로 정적 생성을 사용할 수 있게 한다.</p>
<h3 id="장점-5">장점</h3>
<ol>
<li>SSG처럼 페이지를 미리 렌더링한다.</li>
<li>콘텐츠 수정 시 다시 배포할 필요가 없다.</li>
<li>SEO에 유리하다.</li>
</ol>
<h3 id="단점-5">단점</h3>
<ol>
<li>사용자가 사이트를 방문할 때 수정된 콘텐츠가 아직 제공되지 않았으면 오래된 콘텐츠를 볼 수 있다.</li>
</ol>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://blog.the-compass.kr/csr-ssr-spa-mpa-ede7b55c5f6f">https://blog.the-compass.kr/csr-ssr-spa-mpa-ede7b55c5f6f</a>
<a href="https://leesangwondev.vercel.app/spa%EC%99%80-mpa-%EA%B7%B8%EB%A6%AC%EA%B3%A0-csr-ssr-ssg">https://leesangwondev.vercel.app/spa%EC%99%80-mpa-%EA%B7%B8%EB%A6%AC%EA%B3%A0-csr-ssr-ssg</a>
<a href="https://tapajyoti-bose.medium.com/frontend-rendering-ssg-vs-isg-vs-ssr-vs-csr-when-to-use-which-1bf9f39ff07c">https://tapajyoti-bose.medium.com/frontend-rendering-ssg-vs-isg-vs-ssr-vs-csr-when-to-use-which-1bf9f39ff07c</a>
<a href="https://hanamon.kr/spa-mpa-ssr-csr-%EC%9E%A5%EB%8B%A8%EC%A0%90-%EB%9C%BB%EC%A0%95%EB%A6%AC/">https://hanamon.kr/spa-mpa-ssr-csr-%EC%9E%A5%EB%8B%A8%EC%A0%90-%EB%9C%BB%EC%A0%95%EB%A6%AC/</a>
<a href="https://nextjs.org/docs/pages/api-reference/components/link">https://nextjs.org/docs/pages/api-reference/components/link</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 메서드 활용]]></title>
            <link>https://velog.io/@kgh7427_/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@kgh7427_/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Wed, 04 Sep 2024 04:31:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/664257d6-5a62-401b-961b-09fe2bc262ef/image.png" alt=""></p>
<h1 id="http-메서드-활용">HTTP 메서드 활용</h1>
<h2 id="데이터-전달-방식">데이터 전달 방식</h2>
<hr>
<p>HTTP 메서드를 사용하여 클라이언트와 서버 간 데이터를 주고받는 방식은 크게 두 가지로 나누어진다.</p>
<h3 id="쿼리-파라미터-사용">쿼리 파라미터 사용</h3>
<hr>
<ul>
<li><code>GET</code> 메서드를 사용하여 주로 검색어의 정렬 및 필터를 처리할 때 활용한다.<pre><code class="language-js">// Example: 검색어를 쿼리 파라미터로 전달하여 서버에 GET 요청
const searchQuery = &quot;JavaScript&quot;;
const language = &quot;en&quot;;
</code></pre>
</li>
</ul>
<p>fetch(<code>https://example.com/search?q=${encodeURIComponent(searchQuery)}&amp;lang=${encodeURIComponent(language)}</code>)
  .then(response =&gt; response.json())
  .then(data =&gt; {
    console.log(&quot;Search Results:&quot;, data);
  })
  .catch(error =&gt; {
    console.error(&quot;Error fetching data:&quot;, error);
  });</p>
<pre><code>
### 메시지 바디 사용
---
- `POST`, `PUT`, `PATCH` 메서드를 사용하여 데이터를 서버로 전송할 때 사용된다.
- 주로 회원 가입, 상품 주문, 리소스 등록, 리소스 변경 등의 작업에 사용된다.
```js
// 회원 가입 정보를 메시지 바디로 전달하여 서버에 POST 요청
const userData = {
  username: &quot;newuser&quot;,
  password: &quot;securepassword&quot;,
  email: &quot;newuser@example.com&quot;
};

fetch(&quot;https://example.com/register&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;
  },
  body: JSON.stringify(userData)
})
  .then(response =&gt; response.json())
  .then(data =&gt; {
    console.log(&quot;Registration Successful:&quot;, data);
  })
  .catch(error =&gt; {
    console.error(&quot;Error registering user:&quot;, error);
  });
</code></pre><h2 id="클라이언트에서-서버로-데이터-전송">클라이언트에서 서버로 데이터 전송</h2>
<hr>
<h3 id="정적-데이터-조회">정적 데이터 조회</h3>
<hr>
<ul>
<li>이미지, 정적 텍스트 문서 등을 조회한다.</li>
<li><code>GET</code> 메서드를 사용하며, 일반적으로 쿼리 파라미터 없이 리소스 경로로 단순 조회한다.<pre><code class="language-http">GET /static/star.jpg HTTP/1.1
Host: localhost:8080</code></pre>
</li>
</ul>
<h3 id="동적-데이터-조회">동적 데이터 조회</h3>
<hr>
<ul>
<li>검색 결과, 게시판 목록 정렬 및 필터에 사용된다.</li>
<li><code>GET</code> 메서드를 사용하며, 쿼리 파라미터를 활용하여 동적으로 생성된 데이터를 조회한다.<pre><code class="language-http">GET /search?q=hello&amp;hl=ko HTTP/1.1
Host: www.google.com</code></pre>
</li>
</ul>
<h3 id="html-form을-통한-데이터-전송">HTML Form을 통한 데이터 전송</h3>
<hr>
<ul>
<li>회원 가입, 상품 주문, 데이터 변경등에 사용된다.</li>
<li>전송 방식<ul>
<li>POST : <code>Content-Type: application/x-www-form-urlencoded</code> 을 사용하여 메시지 바디에 데이터를 담아 전송한다</li>
<li>GET : 메시지 바디 없이 쿼리 파라미터를 사용하여 데이터를 전송한다.</li>
<li>파일 전송 : <code>multipart/form-data</code> 를 사용하여 파일과 데이터를 함께 전송한다.<pre><code class="language-http">POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
</code></pre>
</li>
</ul>
</li>
</ul>
<p>username=kim&amp;age=20</p>
<pre><code>
### HTTP API를 통한 데이터 전송
---
- 서버 간 통신, 웹 클라이언트(AJAX)에서 회원 가입, 상품 주문, 데이터 변경등에 사용된다.
- 전송 방식
    - `POST`, `PUT`, `PATCH` : 메시지 바디를 통해 데이터를 전송한다.
    - `GET` : 쿼리 파라미터를 통해 메시지를 전달한다.
- 주요 헤더 : `Content-Type: application/json` (JSON 형식으로 데이터를 전달)
```http
POST /members HTTP/1.1
Content-Type: application/json

{
  &quot;username&quot;: &quot;young&quot;,
  &quot;age&quot;: 20
}</code></pre><h2 id="url-설계-참고-개념">URL 설계 참고 개념</h2>
<hr>
<h3 id="문서document">문서(Document)</h3>
<ul>
<li>단일 개념을 나타내는 리소스</li>
<li>파일, 객체, 인스턴스 등</li>
<li>예시 : <code>/members/100</code>, <code>/files/star.jpg</code></li>
</ul>
<h3 id="컬렉션collection">컬렉션(Collection)</h3>
<ul>
<li>서버가 관리하는 리소스 디렉토리</li>
<li>서버가 리소스의 URI를 생성하고 관리</li>
<li>예시 : <code>/members</code></li>
</ul>
<h3 id="스토어store">스토어(Store)</h3>
<ul>
<li>클라이언트가 관리하는 자원 저장소</li>
<li>클라이언트가 리소스의 URI를 알고 관리</li>
<li>예시 : <code>/files</code></li>
</ul>
<h3 id="컨트롤러controller-컨트롤-uri">컨트롤러(Controller), 컨트롤 URI</h3>
<ul>
<li>문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스를 실행할 때 사용</li>
<li>동사로 된 리소스 경로 사용</li>
<li>예시 : <code>/members/{id}/delete</code></li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>문서의 뭉치가 서버가 관리하는 경우 컬렉션, 클라이언트가 관리하는 경우 스토어, 이 세가지 개념으로 해결하기 어려운 경우 컨트롤러, 컨트롤 URI를 사용한다.</li>
</ul>
<h2 id="http-api-설계-예시">HTTP API 설계 예시</h2>
<hr>
<h3 id="http-api-컬렉션">HTTP API 컬렉션</h3>
<ul>
<li>회원 관리 시스템</li>
<li><code>POST</code>를 통해 데이터를 등록하며, 서버가 리소스 URI를 결정한다.</li>
<li>클라이언트는 등록될 리소스의 URI를 알지 못한다.</li>
<li>API 설계 예시<pre><code>  - 회원 목록 조회: GET /members
  - 회원 등록: POST /members
  - 회원 조회: GET /members/{id}
  - 회원 수정: PATCH, PUT, POST /members/{id}
  - 회원 삭제: DELETE /members/{id}</code></pre></li>
</ul>
<h3 id="http-api-스토어">HTTP API 스토어</h3>
<ul>
<li>파일 관리 시스템</li>
<li><code>PUT</code>을 통해 데이터를 등록하며, 클라이언트가 리소스 URI를 알고 관리한다.</li>
<li>클라이언트가 직접 리소스의 URI를 지정한다.</li>
<li>API 설계 예시<pre><code>  - 파일 목록 조회: GET /files
  - 파일 조회: GET /files/{filename}
  - 파일 등록: PUT /files/{filename}
  - 파일 삭제: DELETE /files/{filename}
  - 파일 대량 등록: POST /files</code></pre></li>
</ul>
<h3 id="html-form-사용">HTML FORM 사용</h3>
<ul>
<li>순수 HTML과 HTML Form을 사용하여 웹 페이지에서 데이터를 전송한다.</li>
<li>HTML Form은 <code>GET</code>과<code>POST</code>만 지원하므로, 복잡한 작업을 위해선 컨트롤 URI를 사용할 수도 있다.
API 설계 예시<pre><code>  - 회원 목록 조회: GET /members
  - 회원 등록 폼: GET /members/new
  - 회원 등록: POST /members/new, POST /members
  - 회원 조회: GET /members/{id}
  - 회원 수정 폼: GET /members/{id}/edit
  - 회원 수정: POST /members/{id}/edit, POST /members/{id}
  - 회원 삭제: POST /members/{id}/delete</code></pre></li>
</ul>
<h3 id="경험-돌아보기">경험 돌아보기</h3>
<p>웹 개발에서 대부분의 API 요청의 경우 컬렉션을 사용하고, 스토어의 경우 AWS S3에 파일을 업로드하는데 사용이 됐던거 같기도 하다. 부트캠프를 했을 때 초반에 담당 멘토님께 HTML Form과 AJAX 중 어떤것을 더 많이 사용하냐 여쭤봤는데, HTML Form은 거의 사용되지 않는다고 하셨다. 나 또한 대부분의 웹개발은 자바스크립트를 사용하므로 UI와 연결이 강하게 돼있는 HTML Form에서 직접 전송하기 보다는 AJAX를 통해서 요청을 하는 편이 더 낫고, 편하다고 생각한다. 하지만 만약 자바스크립트를 사용하지 않는다면 HTML Form을 사용해야하므로 컨트롤러, 컨트롤 URI를 사용할 수도 있을 것 같다.</p>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p><a href="https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC?attributionToken=ChQxMDMxMDMxMDE1NjYzNjU4Nzc5ORANGiNyZWNvbW1lbmRlX3JlY29tbWVuZGVfMTcwMjUyNjQzNDA2NiIXcmVjb21tZW5kZWQtZm9yLXlvdS1jdnIoAA&amp;gad_source=1&amp;gclid=CjwKCAjw59q2BhBOEiwAKc0ijcbbKKiumxpeq8F2EWKzvVXFcwQ4hCiTSu9TM2c37N3-Se6dotQkHBoCTwAQAvD_BwE">https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC?attributionToken=ChQxMDMxMDMxMDE1NjYzNjU4Nzc5ORANGiNyZWNvbW1lbmRlX3JlY29tbWVuZGVfMTcwMjUyNjQzNDA2NiIXcmVjb21tZW5kZWQtZm9yLXlvdS1jdnIoAA&amp;gad_source=1&amp;gclid=CjwKCAjw59q2BhBOEiwAKc0ijcbbKKiumxpeq8F2EWKzvVXFcwQ4hCiTSu9TM2c37N3-Se6dotQkHBoCTwAQAvD_BwE</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite에서 Vitest + RTL + MSW 테스트 세팅 ]]></title>
            <link>https://velog.io/@kgh7427_/Vite%EC%97%90%EC%84%9C-Vitest-RTL-MSW-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@kgh7427_/Vite%EC%97%90%EC%84%9C-Vitest-RTL-MSW-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Tue, 03 Sep 2024 07:29:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/8a00aefd-2444-4dfe-8d60-63b2b89f5b5d/image.png" alt=""></p>
<h1 id="webpack과-jest에서-마이그레이션">Webpack과 Jest에서 마이그레이션</h1>
<h2 id="이유">이유</h2>
<hr>
<p>Jest + RTL에서 MSW를 추가하려 했더니, 호환이 잘 안돼서 의존성을 이것 저것 추가를 해도 결국 해결하지 못해 Vitest로 마이그레이션하기로 했다. 그리고 이왕 이렇게 된 것 Webpack말고 Vite도 사용해보고 싶어서 Vite로 마이그레이션하기로 했다. 또 아직 테스트 코드를 많이 짜보지 않아서 심적으로도 큰 벽은 없었다.</p>
<h1 id="세팅하기">세팅하기</h1>
<h2 id="vite-프로젝트-시작">Vite 프로젝트 시작</h2>
<hr>
<pre><code class="language-bash">npm create vite@latest</code></pre>
<p>먼저 vite 프로젝트 디렉토리를 생성해준다</p>
<pre><code class="language-bash">npm install</code></pre>
<p>필요한 패키지를 설치해준다.</p>
<h2 id="의존성-추가">의존성 추가</h2>
<hr>
<pre><code class="language-bash">npm install -D vitest jsdom msw @testing-library/jest-dom @testing-library/react @testing-library/user-event @vitest/coverage-v8</code></pre>
<h2 id="vitest와-rtl-세팅">Vitest와 RTL 세팅</h2>
<hr>
<h3 id="설정-파일-세팅">설정 파일 세팅</h3>
<pre><code class="language-ts">/// &lt;reference types=&quot;vitest&quot; /&gt;
/// &lt;reference types=&quot;vite/client&quot; /&gt;
import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react-swc&quot;;

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    test: {
        globals: true,
        environment: &quot;jsdom&quot;,
        setupFiles: &quot;./src/test/setup.ts&quot;,
    },
});</code></pre>
<p>맨 위처럼 Triple-Slash Directive로 vitest, vite/client의 타입 선언 파일을 참조하도록 하자. 이거 안하면 아래 defineConfig에서 test 설정할 때 타입 오류가 난다. 그리고 defindeConfig에 test도 설정해주자.</p>
<p>여기서 globals, environment, setupFiles는 다음과 같은 역할을 한다.</p>
<ol>
<li><strong>globals</strong>: Vitest가 <code>describe, it, expect</code>와 같은 함수들을 명시적으로 import하지 않고도 사용할 수 있게 해준다. 단, 타입스크립트를 쓰는 경우 에러가 날 수 있기 때문에, 테스트 파일에서도 import를 해주자.</li>
<li><strong>environment</strong>: Vitest가 테스트를 실행할 때 사용할 환경을 지정한다.</li>
<li><strong>setupFiles</strong>: 테스트 실행 전에 실행될 파일을 지정한다. 예를 들어, Jest DOM의 matcher를 import 하거나, MSW의 모킹 서버를 설정하는 등의 작업을 할 수 있다.</li>
</ol>
<pre><code class="language-ts">// src/test/setup.ts
import &quot;@testing-library/jest-dom&quot;;</code></pre>
<p>setup.ts 파일을 생성해서 <code>jest-dom</code>을 테스트 실행 전에 import하게 한다.
Vitest가 Jest의 API와 호환되는데 <code>jest-dom</code> 은 jest DOM 관련 매처를 사용할 수 있게 해준다.</p>
<pre><code class="language-js">
    &quot;scripts&quot;: {
        &quot;dev&quot;: &quot;vite&quot;,
        &quot;build&quot;: &quot;tsc -b &amp;&amp; vite build&quot;,
        &quot;lint&quot;: &quot;eslint .&quot;,
        &quot;preview&quot;: &quot;vite preview&quot;,
        &quot;test&quot;: &quot;vitest&quot;
    },</code></pre>
<p>package.json에 test script를 넣는다.</p>
<h3 id="컴포넌트-로직">컴포넌트 로직</h3>
<pre><code class="language-tsx">export default function MyComponent() {
    return &lt;div&gt;Hello, world!&lt;/div&gt;;
}</code></pre>
<h3 id="컴포넌트-테스트-로직">컴포넌트 테스트 로직</h3>
<pre><code class="language-tsx">import { render, screen } from &quot;@testing-library/react&quot;;
import { describe, expect, test } from &quot;vitest&quot;;
import MyComponent from &quot;./MyComponent&quot;;

describe(&quot;MyComponent&quot;, () =&gt; {
    test(&quot;render Hello, world!&quot;, () =&gt; {
        render(&lt;MyComponent /&gt;);

        const Welcome = screen.getByText(&quot;Hello, world!&quot;);

        expect(Welcome).toBeInTheDocument();
    });
});</code></pre>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/175f0029-457c-4229-8585-fdcb56706796/image.png" alt=""></p>
<h2 id="msw-세팅">MSW 세팅</h2>
<hr>
<h3 id="browser와-node-세팅으로-나누는-이유">Browser와 Node 세팅으로 나누는 이유</h3>
<p>맨처음에는 <strong>서비스 워커</strong>를 이용해 모킹하는 Browser 세팅만 했더니 브라우저에서 모킹 테스트는 잘 됐지만, VSC에서 Vitest와 RTL과 결합한 테스트는 안되는 현상이 발생했다. 그래서 Node 세팅을 하니 이번에는 VSC에서는 잘 되지만 브라우저에서는 에러가 뜨는 현상이 발생했다. 고민을 해보니 그냥 둘다 세팅하면 되지 않나? 해서 둘 다 세팅을 했다. </p>
<h3 id="handler-세팅">handler 세팅</h3>
<pre><code class="language-ts">// src/test/mocks/handler.ts
import { http, HttpResponse } from &quot;msw&quot;;

export interface UserResponse {
    id: number;
    username: string;
}

const user: UserResponse = {
    id: 1,
    username: &quot;giho&quot;,
};

export const handlers = [
    http.get(&quot;/api/user&quot;, () =&gt; {
        return HttpResponse.json(user);
    }),
];</code></pre>
<p>모킹을 위한 API 세팅이다.</p>
<h3 id="node-세팅">Node 세팅</h3>
<pre><code class="language-ts">import { setupServer } from &quot;msw/node&quot;;
import { handlers } from &quot;./handler&quot;;

export const server = setupServer(...handlers);</code></pre>
<p>주의할 점은 setupServer를 msw/node에서 import해야한다.</p>
<pre><code class="language-ts">import &quot;@testing-library/jest-dom&quot;;
import { beforeAll, afterAll, afterEach } from &quot;vitest&quot;;
import { server } from &quot;./mocks/server&quot;;

beforeAll(() =&gt; server.listen());
afterAll(() =&gt; server.close());
afterEach(() =&gt; server.resetHandlers());</code></pre>
<p>Node의 경우 setup.ts에서 VSC 테스트를 시작하기 전에 서버를 실행하게 끔 한다.</p>
<h3 id="browser-세팅">Browser 세팅</h3>
<pre><code class="language-bash">npx msw init &lt;PUBLIC_DIR&gt; --save</code></pre>
<p>Borwser에서는 서비스 워커를 사용하므로 그에 필요한 스크립트 파일을 만들어줘야한다. Vite의 경우 ./public에 설치하면 되므로</p>
<pre><code class="language-bash">npx msw init ./public --save</code></pre>
<p>이렇게 하면 된다.</p>
<pre><code class="language-ts">import { setupWorker } from &quot;msw/browser&quot;;
import { handlers } from &quot;./handler&quot;;

export const worker = setupWorker(...handlers);</code></pre>
<p>마찬가지로 setupWorker를 msw/browser에서 import하는 것을 주의한다.</p>
<pre><code class="language-tsx">import { StrictMode } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
import App from &quot;./App.tsx&quot;;
import &quot;./index.css&quot;;

async function enableMocking() {
    if (process.env.NODE_ENV !== &quot;development&quot;) {
        return;
    }

    const { worker } = await import(&quot;./test/mocks/browser.ts&quot;);

    // `worker.start()` returns a Promise that resolves
    // once the Service Worker is up and ready to intercept requests.
    return worker.start();
}
enableMocking().then(() =&gt; {
    createRoot(document.getElementById(&quot;root&quot;)!).render(
        &lt;StrictMode&gt;
            &lt;App /&gt;
        &lt;/StrictMode&gt;
    );
});</code></pre>
<p>Browser의 경우 main.tsx에서 실행되게 세팅해야한다.</p>
<h2 id="간단한-테스트-코드-짜보기">간단한 테스트 코드 짜보기</h2>
<h3 id="browser-테스트-코드">Browser 테스트 코드</h3>
<p>Browser에서는 딱히 테스트 코드를 짤 필요없이 컴포넌트 코드와 handler 코드만 있으면 된다.</p>
<pre><code class="language-tsx">// src/app.tsx
import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;
import { UserResponse } from &quot;./test/mocks/handler&quot;;

function App() {
    const [user, setUser] = useState&lt;UserResponse | null&gt;(null);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState&lt;Error | null&gt;(null);

    const fetchUser = async () =&gt; {
        setIsLoading(true);
        try {
            const response = await fetch(&quot;/api/user&quot;);
            if (!response.ok) throw new Error(&quot;에러 발생&quot;);
            const data = await response.json();
            setUser(data);
        } catch (error) {
            if (error instanceof Error) setError(error);
        } finally {
            setIsLoading(false);
        }
    };

    useEffect(() =&gt; {
        fetchUser();
    }, []);

    if (isLoading) return &lt;div&gt;Loading...&lt;/div&gt;;

    if (error) return &lt;div&gt;Error...&lt;/div&gt;;

    return (
        &lt;&gt;
            {user &amp;&amp; (
                &lt;div&gt;
                    &lt;h1&gt;User&lt;/h1&gt;
                    &lt;p&gt;Username: {user.username}&lt;/p&gt;
                &lt;/div&gt;
            )}
        &lt;/&gt;
    );
}

export default App;</code></pre>
<p>간단히 유저 네임을 가져와서 렌더링을 하는 컴포넌트다. fetch의 url에 handler로 만들어 놓은 가짜 API 주소를 넣어준다.</p>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/3d20e5f5-abce-405a-8d05-bc51e761734f/image.png" alt="">
잘 작동한다. 관리자 도구를 보면 <code>[MSW] Mocking enabled.</code> 이렇게 뜨는게 MSW가 잘 작동한다는 표시이다.</p>
<h3 id="node-테스트-코드">Node 테스트 코드</h3>
<p>Node 테스트 코드의 경우 test의 콜백 함수 맨 앞부분에 API를 넣어주면 된다.</p>
<pre><code class="language-tsx">import { describe, expect, test } from &quot;vitest&quot;;
import { render, screen } from &quot;@testing-library/react&quot;;
import App from &quot;./App&quot;;
import { server } from &quot;./test/mocks/server&quot;;
import { http, HttpResponse } from &quot;msw&quot;;

describe(&quot;테스트&quot;, () =&gt; {
    test(&quot;vitest 테스트&quot;, () =&gt; {
        expect(true).toBeTruthy();
    });

    test(&quot;유저 데이터를 불러와서 렌더링한다.&quot;, async () =&gt; {
        render(&lt;App /&gt;);

        expect(screen.getByText(&quot;Loading...&quot;)).toBeInTheDocument();
        expect(await screen.findByText(&quot;User&quot;)).toBeInTheDocument();
    });

    test(&quot;에러가 발생하면 에러 메시지를 렌더링한다.&quot;, async () =&gt; {
        server.use(
            http.get(&quot;/api/user&quot;, () =&gt; {
                return HttpResponse.json(
                    { message: &quot;에러 발생&quot; },
                    { status: 401 }
                );
            })
        );

        render(&lt;App /&gt;);

        expect(screen.getByText(&quot;Loading...&quot;)).toBeInTheDocument();
        expect(await screen.findByText(&quot;Error...&quot;)).toBeInTheDocument();
    });
});</code></pre>
<p><code>.use()</code> 메서드로 기존 핸들러를 오버라이딩해 API 호출이 실패할 경우를 만들어준다.</p>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/4da089c0-9861-471f-a26f-8b13dad2fd7c/image.png" alt="">
테스트가 잘 작동한다.</p>
<p>이제 세팅은 지겨우니까 제발 테스트 코드 좀 짜보자!</p>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p><a href="https://velog.io/@megen07/Vite-React-Vitest-MSW-testing-library%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0">https://velog.io/@megen07/Vite-React-Vitest-MSW-testing-library%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[비동기 처리와 Promise]]></title>
            <link>https://velog.io/@kgh7427_/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%99%80-Promise</link>
            <guid>https://velog.io/@kgh7427_/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%99%80-Promise</guid>
            <pubDate>Tue, 03 Sep 2024 06:14:08 GMT</pubDate>
            <description><![CDATA[<h1 id="비동기-처리의-문제들">비동기 처리의 문제들</h1>
<p>자바스크립트에서는 많은 작업이 비동기로 이루어진다. 과거에는 이러한 비동기 작업을 콜백함수로 처리했지만, 애플리케이션의 규모가 커지면서 코드의 복잡도가 증가하고, 관리가 어려워지는 문제가 발생했다.</p>
<h2 id="콜백-지옥">콜백 지옥</h2>
<hr>
<pre><code class="language-js">step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});</code></pre>
<p>stepN 함수에 콜백이 계속 중첩하며 콜백 지옥이 생성되고, 이로인해 코드의 가독성이 떨어지고, 유지보수가 어려워진다.</p>
<h2 id="오류-처리의-문제">오류 처리의 문제</h2>
<hr>
<pre><code class="language-jsw">try {
    setTimeout(()=&gt;{
            throw new Error(&#39;Error&#39;);
        }, 1000}
} catch { 
    console.error(&#39;에러가 캐치 될까요?&#39;, e);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/4d3eb611-d696-455e-b23c-6c33f34ddc8d/image.png" alt="">
에러 캐치가 안된다!
이는 비동기의 특성으로 인해 콜백함수가 이벤트 루프에 의해 다시 콜스택으로 돌아왔을 때, 이전에 감싸졌던 <code>try ... catch 문</code>과 다른 실행 컨텍스트에서 실행되기 때문이다. </p>
<pre><code class="language-js">try {
    setTimeout(() =&gt; {
        try {
            throw new Error(&quot;Error!&quot;);
        } catch {
            console.error(&quot;에러 발생!&quot;);
        }
    }, 1000);
} catch (e) {
    console.error(&quot;캐치한 에러&quot;, e);
}
</code></pre>
<p>이렇게 콜백 함수 내부에서 감싸주면 에러 처리가 가능하지만, 벌써부터 코드의 가독성이 나빠져 눈살 찌뿌려지기 시작한다.</p>
<p>아무튼 이러한 문제를 해결하기 위해 나온 것이 <code>Promise</code>이다.</p>
<h1 id="promise">Promise</h1>
<h2 id="정의">정의</h2>
<hr>
<p>Promise는 미래의 어떤 시점에 결과를 제공하겠다는 <code>값의 대리자</code> 역할을 한다. Promise는 비동기 작업의 성공 또는 실패를 표현하며, 다음 중 하나의 상태(state)를 가진다.</p>
<ul>
<li><strong>pending</strong>: 약속이 아직 수행 중인 상태</li>
<li><strong>fulfilled (settled)</strong>: 약속이 지켜진 상태</li>
<li><strong>rejected (settled)</strong>: 약속이 어떤 이유에서 지켜지지 않은 상태</li>
</ul>
<h2 id="promise-선언-및-사용-예시">Promise 선언 및 사용 예시</h2>
<hr>
<pre><code class="language-js">const promise = new Promise((resolve, reject) =&gt; {
    if (비동기 처리 성공) {
        resolve(&#39;result&#39;); // fulfilled 상태로 result를 래핑해 반환
    } else { 
        reject(&#39;failure reason&#39;); // rejected 상태로 failure reason을 래핑해 반환
    }
});

promise
  .then(v =&gt; console.log(v))  // fulfilled 상태일 때 실행
  .catch(e =&gt; console.error(e))  // rejected 상태일 때 실행
  .finally(() =&gt; console.log(&#39;finally&#39;));  // 상태와 상관없이 항상 실행</code></pre>
<h2 id="프로미스-체이닝">프로미스 체이닝</h2>
<hr>
<p>Promise는 then, catch, finally 메서드를 이용하여 비동기 작업을 순차적으로 처리할 수 있으며, 이를 <code>프로미스 체이닝</code>이라고 한다.</p>
<pre><code class="language-js">Promise.resolve()
  .then(() =&gt; { /* 작업 1 */ })
  .then(() =&gt; { /* 작업 2 */ })
  .then(() =&gt; { /* 작업 3 */ })
  .catch(() =&gt; { /* 에러 처리 */ })
  .finally(() =&gt; { /* 마무리 작업 */ });</code></pre>
<p>위에 있던 콜백 지옥과 비교해보면 가독성이 매우 좋아 보인다.</p>
<h2 id="마이크로태스크-큐">마이크로태스크 큐</h2>
<hr>
<p>Promise의 후속 처리 메서드(then, catch, finally)의 콜백 함수는 <code>마이크로태스크 큐</code>에 저장되며, 일반 태스크 큐 보다 우선적으로 실행된다.</p>
<blockquote>
<p><a href="https://velog.io/@kgh7427_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84Event-loop">https://velog.io/@kgh7427_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84Event-loop</a></p>
</blockquote>
<pre><code class="language-js">setTimeout(() =&gt; {
    console.log(&quot;1&quot;);
}, 0);

Promise.resolve()
    .then(() =&gt; {
        console.log(&quot;2&quot;);
    })
    .then(() =&gt; {
        console.log(&quot;3&quot;);
    });</code></pre>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/ffdf2693-9def-42d5-9541-084c01d9508e/image.png" alt=""></p>
<h2 id="promise-유틸리티-메서드">Promise 유틸리티 메서드</h2>
<hr>
<h3 id="promiseall">Promise.all</h3>
<ul>
<li>여러개의 Promise를 동시에 관리할 수 있다.</li>
<li>모든 Promise가 성공해야 <code>.then</code> 블록이 실행된다.</li>
<li>하나라도 실패하면 전체가 실패로 간주되어 <code>.catch</code> 블록으로 이동한다.<pre><code class="language-js">function makePayment(url, shouldSucceed) {
  return new Promise((resolve, reject) =&gt; {
      setTimeout(() =&gt; {
          if (shouldSucceed) {
              resolve(`송금 성공: ${url}`);
          } else {
              reject(new Error(`송금 실패: ${url}`));
          }
      }, 1000); // 1초 후에 결과 반환
  });
}
</code></pre>
</li>
</ul>
<p>const p1 = makePayment(&quot;url1&quot;, true); // 성공
const p2 = makePayment(&quot;url2&quot;, false); // 실패
const p3 = makePayment(&quot;url3&quot;, true); // 성공
const p4 = makePayment(&quot;url4&quot;, false); // 실패
const p5 = makePayment(&quot;url5&quot;, true); // 성공</p>
<p>Promise.all([p1, p2, p3, p4, p5])
    .then((results) =&gt; {
        console.log(&quot;all :&quot;, results);
        console.log(&quot;모든 송금이 성공적으로 완료되었습니다.&quot;);
    })
    .catch((error) =&gt; {
        console.error(&quot;송금 중 오류가 발생했습니다.&quot;, error);
    });</p>
<pre><code>![](https://velog.velcdn.com/images/kgh7427_/post/74e8ed85-be27-45c6-b989-70e3f92b4ed4/image.png)


### Promise.allSettled
- 모든 Promse가 완료된 후 결과를 확인할 수 있다.
- 실패한 Promise도 무시하지 않고 결과를 제공한다.
- 모든 Promise가 완료된 후, 실패한 것만 별도로 처리가능하다.
```js
function makePayment(url, shouldSucceed) {
    return new Promise((resolve, reject) =&gt; {
        setTimeout(() =&gt; {
            if (shouldSucceed) {
                resolve(`송금 성공: ${url}`);
            } else {
                reject(new Error(`송금 실패: ${url}`));
            }
        }, 1000); // 1초 후에 결과 반환
    });
}

const p1 = makePayment(&quot;url1&quot;, true); // 성공
const p2 = makePayment(&quot;url2&quot;, false); // 실패
const p3 = makePayment(&quot;url3&quot;, true); // 성공
const p4 = makePayment(&quot;url4&quot;, false); // 실패
const p5 = makePayment(&quot;url5&quot;, true); // 성공

Promise.allSettled([p1, p2, p3, p4, p5]).then((results) =&gt; {
    results.forEach((result) =&gt; {
        if (result.status === &quot;fulfilled&quot;) {
            console.log(result.value);
        } else {
            console.error(result.reason.message);
            // 실패한 송금에 대해 재시도 로직을 추가할 수 있습니다.
        }
    });
});</code></pre><p><img src="https://velog.velcdn.com/images/kgh7427_/post/d7475777-9e1c-4d55-90e3-75a7a9a19311/image.png" alt=""></p>
<h2 id="asyncawait와-promise-중-무엇을-사용할까">async/await와 Promise 중 무엇을 사용할까</h2>
<hr>
<p>async/await는 Promise를 더 쉽게 사용하기 위한 문법 설탕이다. 개인적으로 느끼기엔 병렬처리를 할 때는 Promise가 오히려 직관적이다.</p>
<pre><code class="language-js">const p1 = fetch(&#39;url1&#39;);
const p2 = fetch(&#39;url2&#39;);
const p3 = fetch(&#39;url3&#39;);

Promise.all([p1, p2, p3]).then((results) =&gt; {
  console.log(results); 
});</code></pre>
<p>async/await를 사용해서 병렬처리를 할 때는 뭔가 빙 돌아가는 느낌이 든다.</p>
<pre><code class="language-js">async function fetchInParallel() {
  const p1 = fetch(&#39;url1&#39;);
  const p2 = fetch(&#39;url2&#39;);
  const p3 = fetch(&#39;url3&#39;);

  const [data1, data2, data3] = await Promise.all([p1, p2, p3]);
  console.log(data1, data2, data3);
}</code></pre>
<p>async로 함수를 묶어주고 병렬처리할 때 Promise를 어차피 써야한다. 이럴꺼면 그냥 처음부터 Promise를 사용하는게 낫다고 생각된다.</p>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=0f-jNhnN0Qc&amp;list=PLcqDmjxt30Rt9wmSlw1u6sBYr-aZmpNB3&amp;index=11">https://www.youtube.com/watch?v=0f-jNhnN0Qc&amp;list=PLcqDmjxt30Rt9wmSlw1u6sBYr-aZmpNB3&amp;index=11</a>
<a href="https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/main/JavaScript#promise">https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/main/JavaScript#promise</a>
<a href="https://velog.io/@seul06/JavaScript-%EC%BD%9C%EB%B0%B1-%EC%A7%80%EC%98%A5">https://velog.io/@seul06/JavaScript-콜백-지옥</a>
<a href="https://programmingsummaries.tistory.com/325">https://programmingsummaries.tistory.com/325</a>
모던 자바스크립트 Deep Dive
<a href="https://poiemaweb.com/es6-promise">https://poiemaweb.com/es6-promise</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트 상태 변화 테스트]]></title>
            <link>https://velog.io/@kgh7427_/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%83%81%ED%83%9C-%EB%B3%80%ED%99%94-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@kgh7427_/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%83%81%ED%83%9C-%EB%B3%80%ED%99%94-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 30 Aug 2024 06:52:53 GMT</pubDate>
            <description><![CDATA[<h1 id="컴포넌트-상태-변화-테스트">컴포넌트 상태 변화 테스트</h1>
<hr>
<p>Jest와 React-Testing-Library를 이용해서 컴포넌트 렌더링 테스트를 진행한다.</p>
<p>다음과 같은 스탭으로 점진적으로 발전시키면서 진행한다.</p>
<ol>
<li><strong>상태 초기화 테스트</strong></li>
<li><strong>상태 변화에 따른 UI 변경 테스트</strong></li>
<li><strong>사용자 입력에 따른 상태 변화 테스트</strong></li>
</ol>
<h2 id="step-1-상태-초기화-테스트">Step 1. 상태 초기화 테스트</h2>
<hr>
<h3 id="컴포넌트-코드">컴포넌트 코드</h3>
<pre><code class="language-tsx">import { useState } from &#39;react&#39;;

export default function ToggleButton() {
  const [isOn] = useState(false);

  return &lt;button&gt;{isOn ? &#39;ON&#39; : &#39;OFF&#39;}&lt;/button&gt;;
}</code></pre>
<h3 id="테스트-코드">테스트 코드</h3>
<pre><code class="language-jsx">import { render, screen } from &#39;@testing-library/react&#39;;
import ToggleButton from &#39;./UserProfile&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;토글 버튼 컴포넌트&#39;, () =&gt; {
  test(&#39;초기 상태가 false인 경우 OFF를 렌더링한다.&#39;, () =&gt; {
    render(&lt;ToggleButton /&gt;);

    const buttonElement = screen.getByRole(&#39;button&#39;);

    expect(buttonElement).toHaveTextContent(&#39;OFF&#39;);
  });
});
</code></pre>
<ol>
<li><strong>screen.getByRole로 요소를 찾는다</strong></li>
<li><strong>expect.toHaveTextContent로 textContent가 OFF인지 테스트한다</strong></li>
</ol>
<h3 id="결과">결과</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/c403d143-f57e-4414-bdd6-c896993281a7/image.png" alt=""></p>
<h2 id="step-2-상태-변화에-따른-ui-변경-테스트">Step 2. 상태 변화에 따른 UI 변경 테스트</h2>
<hr>
<h3 id="컴포넌트-코드-1">컴포넌트 코드</h3>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

export default function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  const toggle = () =&gt; {
    setIsOn((prev) =&gt; !prev);
  };

  return &lt;button onClick={toggle}&gt;{isOn ? &#39;ON&#39; : &#39;OFF&#39;}&lt;/button&gt;;
}
</code></pre>
<h3 id="테스트-코드-및-에러-발생">테스트 코드 및 에러 발생</h3>
<pre><code class="language-jsx">import { render, screen } from &#39;@testing-library/react&#39;;
import userEvent from &#39;@testing-library/user-event&#39;;
import ToggleButton from &#39;./ToggleButton&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;토글 버튼 컴포넌트&#39;, () =&gt; {
  test(&#39;초기 상태가 false인 경우 OFF이 렌더링된다.&#39;, () =&gt; {
    render(&lt;ToggleButton /&gt;);

    const buttonElement = screen.getByRole(&#39;button&#39;);

    expect(buttonElement).toHaveTextContent(&#39;OFF&#39;);
  });

  test(&#39;버튼을 클릭하면 ON이 렌더링된다.&#39;, () =&gt; {
    render(&lt;ToggleButton /&gt;);

    const buttonElement = screen.getByRole(&#39;button&#39;);

    // userEvent: 사용자의 행동을 시뮬레이션한다.
    userEvent.click(buttonElement);
    expect(buttonElement).toHaveTextContent(&#39;ON&#39;);
  });
});</code></pre>
<p>위와 같이 테스트 코드를 짜면 <strong>에러</strong>가 발생하는데</p>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/ac4d28ab-c65c-4cd5-bc6b-a0226a9581c2/image.png" alt=""></p>
<p>textContent가 ON이어야 하는데 테스트 결과 OFF를 받는다.</p>
<p>현재 상태가 useState로 관리되기 때문에 상태 변화가 비동기적으로 이루어 지기 때문이다.</p>
<p>따라서 이런 경우 waitFor, findBy, waitForElementToBeRemoved 같은 비동기 테스트를 위한 메서드를 사용해야한다.</p>
<h3 id="수정된-테스트-코드">수정된 테스트 코드</h3>
<pre><code class="language-jsx">import { render, screen } from &#39;@testing-library/react&#39;;
import userEvent from &#39;@testing-library/user-event&#39;;
import ToggleButton from &#39;./ToggleButton&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;토글 버튼 컴포넌트&#39;, () =&gt; {
  test(&#39;초기 상태가 false인 경우 OFF이 렌더링된다.&#39;, () =&gt; {
    render(&lt;ToggleButton /&gt;);

    const buttonElement = screen.getByRole(&#39;button&#39;);

    expect(buttonElement).toHaveTextContent(&#39;OFF&#39;);
  });

  test(&#39;버튼을 클릭하면 ON이 렌더링된다.&#39;, async () =&gt; {
    render(&lt;ToggleButton /&gt;);

    // userEvent: 사용자의 행동을 시뮬레이션한다.
    await userEvent.click(screen.getByRole(&#39;button&#39;));

    // 현재 컴포넌트의 경우 상태 변화가 useState를 통해 이루어져 비동기적으로 이루어진다.
    // 따라서 이런 경우 비동기 테스트를 위한 메서드를 사용해야 한다.
    // waitFor, findBy, waitForElementToBeRemoved 등의 메서드를 사용할 수 있다.
    const buttonElement = await screen.findByRole(&#39;button&#39;);

    expect(buttonElement).toHaveTextContent(&#39;ON&#39;);
  });
});</code></pre>
<h3 id="결과-1">결과</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/df623e4f-f63d-409e-acdf-b36ed4a8ae5e/image.png" alt=""></p>
<h2 id="step-3-사용자-입력에-따른-상태-변화-테스트">Step 3. 사용자 입력에 따른 상태 변화 테스트</h2>
<hr>
<h3 id="컴포넌트-코드-2">컴포넌트 코드</h3>
<pre><code class="language-tsx">import { useState } from &#39;react&#39;;

export default function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
  const [name, setName] = useState(&#39;&#39;);

  const toggle = () =&gt; {
    setIsOn((prev) =&gt; !prev);
  };

  const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setName(e.target.value);
  };

  return (
    &lt;div&gt;
      &lt;button onClick={toggle}&gt;{isOn ? &#39;ON&#39; : &#39;OFF&#39;}&lt;/button&gt;
      &lt;input type=&#39;text&#39; aria-label=&#39;Name&#39; value={name} onChange={handleChange} /&gt;
      &lt;p&gt;{name}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="테스트-코드-1">테스트 코드</h3>
<pre><code class="language-tsx">import { render, screen } from &#39;@testing-library/react&#39;;
import userEvent from &#39;@testing-library/user-event&#39;;
import ToggleButton from &#39;./ToggleButton&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;토글 버튼 컴포넌트&#39;, () =&gt; {
  test(&#39;초기 상태가 false인 경우 OFF이 렌더링된다.&#39;, () =&gt; {
    render(&lt;ToggleButton /&gt;);
    const buttonElement = screen.getByRole(&#39;button&#39;);
    expect(buttonElement).toHaveTextContent(&#39;OFF&#39;);
  });

  test(&#39;버튼을 클릭하면 ON이 렌더링된다.&#39;, async () =&gt; {
    render(&lt;ToggleButton /&gt;);
    await userEvent.click(screen.getByRole(&#39;button&#39;));
    const buttonElement = await screen.findByRole(&#39;button&#39;);
    expect(buttonElement).toHaveTextContent(&#39;ON&#39;);
  });

  test(&#39;입력값이 변경되면 입력값이 렌더링된다.&#39;, async () =&gt; {
    render(&lt;ToggleButton /&gt;);
    const inputElement = screen.getByLabelText(&#39;Name&#39;);
    await userEvent.type(inputElement, &#39;Hello, world!&#39;);
    const displayElement = await screen.findByText(&#39;Hello, world!&#39;);
    expect(displayElement).toBeInTheDocument();
  });
});
</code></pre>
<ol>
<li>screen.getByLabelText()를 이용해 input요소를 가져온다.</li>
<li>userEvent.type()를 이용해 입력 상태를 변화시킨다. 이때 입력이 비동기적으로 이루어지므로 await를 사용한다.</li>
<li>상태 변화시 p태그의 textContent가 비동기적으로 변화하기 때문에 screen.findByText()를 이용한다. 이때 ‘Hello, world’를 넣어서 p태그의 textContent가 ‘Hello, world’되는 것을 기다린다.</li>
<li>expect.toBeInTheDocument()로 렌더링됐는지 테스트한다.</li>
</ol>
<h3 id="결과-2">결과</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/c69b5e23-9a8d-4d7a-8e53-e333316e4473/image.png" alt=""></p>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p>GPT를 이용한 예제 코드 실습</p>
</blockquote>
<blockquote>
<p><a href="https://www.daleseo.com/react-testing-library-async/">https://www.daleseo.com/react-testing-library-async/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트 렌더링 테스트]]></title>
            <link>https://velog.io/@kgh7427_/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@kgh7427_/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 30 Aug 2024 05:27:47 GMT</pubDate>
            <description><![CDATA[<h1 id="컴포넌트-렌더링-테스트">컴포넌트 렌더링 테스트</h1>
<hr>
<p>Jest와 React-Testing-Library를 이용해서 컴포넌트 렌더링 테스트를 진행한다.</p>
<p>다음과 같은 스탭으로 점진적으로 발전시키면서 진행한다.</p>
<ol>
<li><strong>기본 렌더링 테스트</strong></li>
<li><strong>조건부 렌더링 테스트</strong></li>
</ol>
<h2 id="step-1-기본-렌더링-테스트">Step 1. 기본 렌더링 테스트</h2>
<hr>
<h3 id="컴포넌트-코드">컴포넌트 코드</h3>
<pre><code class="language-jsx">export default function UserProfile() {
  return (
    &lt;div&gt;
      &lt;h1&gt;User Profile&lt;/h1&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="테스트-코드">테스트 코드</h3>
<pre><code class="language-jsx">import { render, screen } from &#39;@testing-library/react&#39;;
import UserProfile from &#39;./UserProfile&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;유저 프로필 컴포넌트&#39;, () =&gt; {
  // test: 테스트 케이스를 정의한다.
  test(&#39;렌더링한다.&#39;, () =&gt; {
    // render: 컴포넌트를 테스트 환경에 렌더링한다.
    render(&lt;UserProfile /&gt;);
    // screen: 렌더링된 컴포넌트를 검색한다.
    const titleElement = screen.getByText(/User Profile/i);
    // expect: 특정 값이 특정 조건을 만족하는지 테스트한다.
    expect(titleElement).toBeInTheDocument();
  });
});
</code></pre>
<ol>
<li><strong>describe로 테스트 케이스를 그룹화한다.</strong></li>
<li><strong>test로 테스트 케이스를 정의한다.</strong></li>
<li><strong>render로 컴포넌트를 테스트 환경에 렌더링한다.</strong></li>
<li><strong>screen으로 렌더링된 컴포넌트를 검색한다.</strong></li>
<li><strong>expect로 Document에 존재하는지 테스트한다.</strong></li>
</ol>
<h3 id="결과">결과</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/558974a2-056f-42de-a6c8-a959be1ec608/image.png" alt=""></p>
<h2 id="step-2-조건부-렌더링-테스트">Step 2. <strong>조건부 렌더링 테스트</strong></h2>
<hr>
<h3 id="컴포넌트-코드-1">컴포넌트 코드</h3>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

interface UserProfileProps {
  user: {
    name: string;
  } | null;
}

export default function UserProfile({ user }: UserProfileProps) {
  const [isLoggedIn] = useState(!!user);

  return (
    &lt;div&gt;
      &lt;h1&gt;{isLoggedIn ? `Welcome, ${user?.name}!` : &#39;Please log in.&#39;}&lt;/h1&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="테스트-코드-1">테스트 코드</h3>
<pre><code class="language-jsx">import { render, screen } from &#39;@testing-library/react&#39;;
import UserProfile from &#39;./UserProfile&#39;;

// describe: 테스트를 그룹화한다.
describe(&#39;유저 프로필 컴포넌트&#39;, () =&gt; {
  test(&#39;user가 주어지면 &quot;Welcome, {user.name}!&quot;을 렌더링한다.&#39;, () =&gt; {
    render(&lt;UserProfile user={{ name: &#39;Mark&#39; }} /&gt;);
    const welcomeMessageElement = screen.getByText(/Welcome, Mark!/i);
    expect(welcomeMessageElement).toBeInTheDocument();
  });

  test(&#39;user가 주어지지 않을 때 &quot;Please log in.&quot;을 렌더링한다.&#39;, () =&gt; {
    render(&lt;UserProfile user={null} /&gt;);
    const loginMessageElement = screen.getByText(/Please log in./i);
    expect(loginMessageElement).toBeInTheDocument();
  });
});
</code></pre>
<ol>
<li><strong>user가 주어졌을 때 렌더링을 테스트한다</strong></li>
<li><strong>user가 주어지지 않았을 때 렌더링을 테스트한다.</strong></li>
</ol>
<h3 id="결과-1">결과</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/4a44cac6-49bc-47a3-8c4a-c5a0a551ff98/image.png" alt=""></p>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p>GPT</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 메서드]]></title>
            <link>https://velog.io/@kgh7427_/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@kgh7427_/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Fri, 30 Aug 2024 04:47:25 GMT</pubDate>
            <description><![CDATA[<h1 id="http-메서드">HTTP 메서드</h1>
<h2 id="http-api">HTTP API</h2>
<hr>
<h3 id="api-uri">API URI</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/a820f491-1f42-4570-96e6-7d42dae21240/image.png" alt=""></p>
<ul>
<li><strong>URI (Uniform Resource Identifier)</strong><ul>
<li>웹 리소스를 식별하는 데 사용되는 고유한 문자열이다.</li>
</ul>
</li>
<li><strong>예시</strong><ul>
<li>회원 목록 조회, 회원 조회, 회원 등록, 회원 수정, 회원 삭제</li>
</ul>
</li>
</ul>
<h3 id="리소스와-메서드의-관계">리소스와 메서드의 관계</h3>
<ul>
<li><strong>리소스 (Resource)</strong><ul>
<li>데이터를 나타내는 명사이다.</li>
<li>위 예시에서 <strong>회원</strong>이 리소스가 된다</li>
</ul>
</li>
<li><strong>메서드 (Method)</strong><ul>
<li>리소스를 다루기 위한 행동을 나타낸다.</li>
<li>위 예시에서 조회, 등록, 수정, 삭제가 HTTP 메서드로 표현된다.</li>
</ul>
</li>
</ul>
<h2 id="http-메서드-1">HTTP 메서드</h2>
<hr>
<p>HTTP 메서드는 클라이언트와 서버 간의 상호작용 방식을 정의하는 데 사용된다. 각 메서드는 특정한 목적을 가지고 있다.</p>
<h2 id="get">GET</h2>
<ul>
<li><p><strong>기본 기능</strong></p>
<ul>
<li>리소스를 조회하고, 그 결과를 클라이언트에게 반환한다.</li>
<li>서버의 상태를 변경하지 않는다.</li>
</ul>
</li>
<li><p><strong>메시지 바디 사용</strong></p>
<ul>
<li>일반적으로 메시지 바디를 사용하지 않는다.</li>
<li>일부 서버는 허용할 수 있지만, 표준적이지 않기 때문에 권장되지 않는다.</li>
<li>필요시 <strong>쿼리 파라미터</strong>로 전달하는 것이 일반적이다.</li>
</ul>
</li>
<li><p><strong>사용 예시</strong></p>
<pre><code class="language-jsx">  // members 리소스를 조회하고, 쿼리 파라미터를 이용해 이름이 John인 회원 정보를 조회
  GET /members?name=John</code></pre>
</li>
</ul>
<h2 id="post">POST</h2>
<ul>
<li><p><strong>기본 기능</strong></p>
<ul>
<li>클라이언트가 전달한 데이터를 서버에서 처리하고, 처리된 결과를 반환하거나, 새로운 리소스를 생성한다.</li>
<li>메시지 바디를 통해 데이터가 서버로 전달된다.</li>
</ul>
</li>
<li><p><strong>사용 예시</strong></p>
<ul>
<li><p>신규 리소스 등록</p>
<pre><code class="language-jsx">  // 신규 회원 등록
  POST /members</code></pre>
</li>
<li><p>프로세스 처리</p>
<pre><code class="language-jsx">  // 주문 배달 시작
  POST /orders/{orderId}/start-delivery</code></pre>
</li>
<li><p>다양한 상황에서 사용</p>
<ul>
<li><p>GET, PUT, PATCH 등으로 처리하기 애매한 작업에도 사용된다.</p>
</li>
<li><p>예시</p>
<ul>
<li><p>필터링된 데이터 요청</p>
<pre><code class="language-jsx">// GET의 쿼리 파라미터로는 필터링된 데이터 요청이 적합하지 않음
// 따라서 POST + 메시지 바디를 이용해서 요청한다.
POST /members/search

{
&quot;name&quot;: &quot;John&quot;,
&quot;ageRange&quot;: {
  &quot;min&quot;: 30,
  &quot;max&quot;: 40
},
&quot;sort&quot;: &quot;age&quot;,
&quot;order&quot;: &quot;desc&quot;
}
</code></pre>
</li>
<li><p>상태 변경과 데이터 요청의 결합</p>
<pre><code class="language-jsx">// 데이터베이스에서 주문의 상태 변경
POST /orders/{orderId}/start-delivery

{
&quot;isDelivery&quot;: true
}</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>리소스 생성 여부</strong></p>
<ul>
<li>반드시 새로운 리소스를 생성하지 않을 수도 있다.<ul>
<li>예를 들어, 특정 프로세스를 시작하는 요청이나, 상태 변경을 처리하는 경우</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>정리</strong></p>
<ul>
<li>데이터를 수정하는 것 뿐만 아니라 다른 메서드로 처리하기 어려운 부분에서 사용될 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="put">PUT</h2>
<ul>
<li><p><strong>기본 기능</strong></p>
<ul>
<li>클라이언트가 지정한 리소스를 서버에서 완전히 대체한다</li>
<li>만약 지정된 리소스가 존재하지 않으면, 서버가 새로운 리소스를 생성한다.</li>
</ul>
</li>
<li><p><strong>사용 예시</strong></p>
<ul>
<li><p>회원 정보 전체 업데이트</p>
<pre><code class="language-jsx">// id가 123인 회원의 정보를 새롭게 대체한다.
PUT /members/123

// id가 45인 문서의 내용을 새롭게 대체한다.
PUT /documents/45</code></pre>
</li>
</ul>
</li>
<li><p><strong>클라이언트가 리소스를 식별</strong></p>
<ul>
<li><p>클라이언트가 리소스의 위치를 정확히 알고 있으며, 이를 바탕으로 URI를 명시적으로 지정한다.</p>
<pre><code class="language-jsx">  // 리소스의 위치를 알지 못하므로 메시지 바디 또는 쿼리 파라미터를 이용
  POST /members

  // 유저의 id(리소스의 위치)를 알고 있으므로 URI에 리소스의 ID를 포함해 요청
  PUT /members/123</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="patch">PATCH</h2>
<ul>
<li><p><strong>기본 기능</strong></p>
<ul>
<li>리소스의 일부를 수정하는 데 사용된다.<ul>
<li>PUT과 마찬가지로 클라이언트가 리소스의 위치를 알고 있다.</li>
<li>PUT과 달리, 특정 필드 또는 속성만 변경한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>사용 예시</strong></p>
<ul>
<li><p>회원 이메일 수정</p>
<pre><code class="language-jsx">  // 클라이언트가 리소스의 위치를 알고 있으므로 URI에 id(123)을 포함한다.
  PATCH /members/123

  {
      &quot;email&quot; : &quot;update@email.com&quot;
  }</code></pre>
</li>
</ul>
</li>
<li><p><strong>유연성</strong></p>
<ul>
<li>부분 업데이트를 하기 때문에, 불필요한 수정을 줄이고, 효율성을 높인다.</li>
</ul>
</li>
</ul>
<h2 id="delete">DELETE</h2>
<ul>
<li><p><strong>기본 기능</strong></p>
<ul>
<li>지정된 리소스를 삭제한다.</li>
<li>PUT과, PATCH와 마찬가지로 클라이언트가 리소스의 위치를 알고 있다.</li>
</ul>
</li>
<li><p><strong>사용 예시</strong></p>
<ul>
<li><p>특정 회원 삭제</p>
<pre><code class="language-jsx">// 클라이언트가 리소스의 위치를 알고 있으므로 URI에 id(123)을 포함한다.
DELETE /members/123</code></pre>
</li>
</ul>
</li>
<li><p><strong>안전성 및 고려사항</strong></p>
<ul>
<li>DELETE 요청은 되돌릴 수 없으므로, 중요한 데이터를 삭제하는 경우 추가적인 확인 절차를 두는 것이 좋다.</li>
</ul>
</li>
</ul>
<h2 id="http-메서드의-속성">HTTP 메서드의 속성</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/420d3457-c15b-4cb8-b0f8-3dd3ce5186bd/image.png" alt=""></p>
<p>HTTP 메서드는 다음과 같은 속성들이 존재한다.</p>
<ol>
<li><strong>안전(Safe Methods)</strong></li>
<li><strong>멱등(Idempotent Methods)</strong></li>
<li><strong>캐시가능(Cacheable Methods)</strong></li>
</ol>
<h3 id="안전">안전</h3>
<ul>
<li><strong>정의</strong><ul>
<li>안전한 메서드는 리소스를 변경하지 않는 메서드를 의미한다.</li>
<li>서버의 상태를 변화시키기 않는다. 따라서 메서드를 여러 번 호출하더라도, 서버에 부정적인 영향을 미치지 않는다.</li>
<li>예시<ul>
<li>GET 메서드는 리소스를 조회할 뿐, 리소스를 변경하지 않는다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>주의사항</strong><ul>
<li>안전성은 리소스의 상태 변화에 한정한다<ul>
<li>GET 요청을 한번에 엄청나게 보낸다고 가정해보자, 서버에 과부하를 줄 수 있지만, 이러한 상황은 안전성의 정의에 포함되지 않는다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="멱등">멱등</h3>
<ul>
<li><strong>정의</strong><ul>
<li>동일한 요청을 여러 번 수행하더라도 결과가 동일하게 유지되는 속성을 의미한다.</li>
<li>요청이 중복될 가능성이 있을 때 안정적인 처리를 보장해준다.</li>
</ul>
</li>
<li><strong>멱등성 메서드</strong><ul>
<li>GET<ul>
<li>한 번 조회하든, 두 번 조회하든 동일한 리소스가 반환된다.</li>
</ul>
</li>
<li>PUT<ul>
<li>리소스를 대체하기 때문에, 최종 결과는 항상 동일하다.</li>
</ul>
</li>
<li>DELETE<ul>
<li>리소스를 삭제하기 때문에, 최종 결과는 항상 동일하다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>비역등성 메서드</strong><ul>
<li>POST<ul>
<li>동일한 요청을 두 번 보내면, 각각의 결과가 달라질 수 있다.</li>
<li>예를 들어, 결제 요청을 두 번 보내면 두 번 결제가 될 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>주의 사항</strong><ul>
<li>요청 사이에 외부 요인으로 리소스가 변경되었을 경우에 발생하는 문제는 멱등성의 범위에 포함하지 않는다.</li>
</ul>
</li>
</ul>
<h3 id="캐시가능">캐시가능</h3>
<ul>
<li><strong>정의</strong><ul>
<li>응답 결과를 클라이언트나 중간 캐시 서버에 저장해 두고, 다음 요청 시에 이를 재사용할 수 있는 속성을 의미한다.</li>
</ul>
</li>
<li><strong>실제 고려 사항</strong><ul>
<li>캐시 가능한 메서드<ul>
<li>GET, POST, PATCH, HEAD 등이 있다.</li>
</ul>
</li>
<li>실제 사용<ul>
<li>GET, HEAD가 주로 사용되고, POST와 PATCH의 경우 메시지 바디의 복잡성 때문에 실무에서 캐시되는 경우가 드물다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="기타-메서드">기타 메서드</h2>
<hr>
<h3 id="head">HEAD</h3>
<ul>
<li><strong>기본 기능</strong><ul>
<li>응답의 헤더 부분만 가져오는 메서드다.</li>
<li>본문은 제외된다</li>
</ul>
</li>
<li><strong>사용 예시</strong><ul>
<li>파일 크기 확인<ul>
<li>예를 들어, 영화를 다운로드하고 싶을 때, 파일이 너무 크다면 다운로드를 거절할 수 있게 하기 위해 사용한다. 이 경우 <code>Content-Length</code> 와 같은 헤더 정보를 가져와 파일 크기를 확인하고, 실제로 다운로드할지 결정하는 데 도움이 된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC?srsltid=AfmBOooNtv7KIam6DI3gAnWZNNj8S2dgbot6grksRKUJWGQ4Eb0WNi1W">https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC?srsltid=AfmBOooNtv7KIam6DI3gAnWZNNj8S2dgbot6grksRKUJWGQ4Eb0WNi1W</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[보일러 플레이트에 React-Testing-Library 설정하기]]></title>
            <link>https://velog.io/@kgh7427_/%EB%B3%B4%EC%9D%BC%EB%9F%AC-%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8%EC%97%90-React-Testing-Library-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/%EB%B3%B4%EC%9D%BC%EB%9F%AC-%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8%EC%97%90-React-Testing-Library-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 28 Aug 2024 13:06:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/a2a14ba3-55cf-45a0-91d3-a9a44eb59424/image.png" alt=""></p>
<h1 id="보일러-플레이트에-react-testing-library-설정하기">보일러 플레이트에 React-Testing-Library 설정하기</h1>
<p>이 가이드는 Jest를 설치한 상태에서 React Testing Library(RTL)를 설정하는 방법을 단계별로 안내합니다. 이 과정은 최신 버전의 Jest와 RTL을 사용하는 환경을 구축하는 데 필요한 모든 설정을 포함합니다.</p>
<hr>
<h2 id="1-react-testing-library-설치"><strong>1. React Testing Library 설치</strong></h2>
<p>Jest와 함께 React Testing Library를 설치합니다.</p>
<pre><code class="language-bash">npm install --save-dev @testing-library/react @testing-library/jest-dom
</code></pre>
<ul>
<li><code>@testing-library/react</code>: React 컴포넌트를 테스트하는 데 필요한 주요 라이브러리입니다.</li>
<li><code>@testing-library/jest-dom</code>: Jest와 함께 사용할 수 있는 추가적인 DOM 관련 매처를 제공합니다.</li>
</ul>
<hr>
<h2 id="2-jest-설정-파일-구성"><strong>2. Jest 설정 파일 구성</strong></h2>
<p>Jest 설정 파일을 작성하여 테스트 환경을 <code>jsdom</code>으로 설정합니다. Jest 28 이상에서는 <code>jest-environment-jsdom</code>을 별도로 설치해야 합니다.</p>
<h3 id="jestconfigjs"><strong>jest.config.js</strong></h3>
<pre><code class="language-jsx">module.exports = {
  testEnvironment: &#39;jest-environment-jsdom&#39;,
};
</code></pre>
<p>또는 <strong>package.json</strong>에 직접 설정할 수도 있습니다.</p>
<h3 id="packagejson"><strong>package.json</strong></h3>
<pre><code class="language-json">{
  &quot;jest&quot;: {
    &quot;testEnvironment&quot;: &quot;jest-environment-jsdom&quot;
  }
}
</code></pre>
<h3 id="jest-environment-jsdom-설치"><strong>jest-environment-jsdom 설치</strong></h3>
<pre><code class="language-bash">npm install --save-dev jest-environment-jsdom</code></pre>
<hr>
<h2 id="3-setupteststs-파일-생성"><strong>3. <code>setupTests.ts</code> 파일 생성</strong></h2>
<p>테스트 실행 전 Jest 환경을 설정하는 <code>setupTests.ts</code> 파일을 생성합니다. 이 파일에서 <code>@testing-library/jest-dom</code>을 불러와 기본적인 Jest 매처(matchers) 외에도 DOM 요소를 테스트할 때 유용한 여러 가지 추가적인 매처를 제공합니다.</p>
<h3 id="srcsetupteststs"><strong>src/setupTests.ts</strong></h3>
<pre><code class="language-tsx">import &#39;@testing-library/jest-dom&#39;;
</code></pre>
<p>또한 추가적으로 jest.config,js 파일에 다음을 추가합니다</p>
<pre><code class="language-tsx">  },

  // Jest 실행 전에 실행할 파일을 지정합니다.(RTL 설정 파일을 불러옵니다.)
  setupFilesAfterEnv: [&#39;&lt;rootDir&gt;/src/setupTests.ts&#39;],
};</code></pre>
<hr>
<h1 id="react-testing-library-주요-api"><strong>React Testing Library 주요 API</strong></h1>
<p>React Testing Library는 매우 심플하지만 강력한 API를 제공합니다. 기본적으로 <code>@testing-library/react</code> 패키지에서 <code>render()</code> 함수와 <code>screen</code> 객체를 불러와 사용하며, 유저와 상호작용을 테스트할 때는 선택적으로 <code>fireEvent</code> 객체도 불러와야 합니다.</p>
<hr>
<h2 id="1-주요-api-구성"><strong>1. 주요 API 구성</strong></h2>
<p>React Testing Library는 크게 세 가지로 구성됩니다:</p>
<ul>
<li><strong><code>render()</code> 함수</strong>: DOM에 컴포넌트를 렌더링합니다.</li>
<li><strong><code>screen</code> 객체</strong>: DOM에서 특정 영역을 선택하기 위한 다양한 쿼리 함수를 제공합니다.</li>
<li><strong><code>fireEvent</code> 객체</strong>: 특정 이벤트를 발생시켜 상호작용을 테스트합니다.</li>
</ul>
<hr>
<h2 id="2-render-함수"><strong>2. <code>render()</code> 함수</strong></h2>
<p><code>render()</code> 함수는 인자로 넘어온 JSX를 DOM에 렌더링해줍니다.</p>
<h3 id="사용-예시"><strong>사용 예시</strong></h3>
<pre><code class="language-jsx">import { render } from &quot;@testing-library/react&quot;;
render(&lt;YourComponent /&gt;);
</code></pre>
<p>이렇게 호출하면, <code>YourComponent</code>가 가상 DOM에 렌더링됩니다.</p>
<hr>
<h2 id="3-screen-객체"><strong>3. <code>screen</code> 객체</strong></h2>
<p><code>screen</code> 객체를 통해 DOM에서 특정 영역을 선택하기 위한 다양한 쿼리 함수를 호출할 수 있습니다.</p>
<h3 id="사용-예시-1"><strong>사용 예시</strong></h3>
<pre><code class="language-jsx">import { render, screen } from &quot;@testing-library/react&quot;;

render(&lt;YourComponent /&gt;);
const button = screen.getByText(/click me/i);
</code></pre>
<p>이 예시에서는 <code>getByText</code> 쿼리 함수를 사용하여 &quot;click me&quot;라는 텍스트를 가진 버튼 요소를 선택합니다.</p>
<hr>
<h2 id="4-fireevent-객체"><strong>4. <code>fireEvent</code> 객체</strong></h2>
<p><code>fireEvent</code> 객체는 쿼리 함수로 선택된 영역을 대상으로 특정 사용자 이벤트를 발생시키기 위해 사용합니다.</p>
<h3 id="사용-예시-2"><strong>사용 예시</strong></h3>
<pre><code class="language-jsx">import { render, screen, fireEvent } from &quot;@testing-library/react&quot;;

render(&lt;YourComponent /&gt;);
const button = screen.getByText(/click me/i);
fireEvent.click(button);
</code></pre>
<p>이 예시에서는 <code>fireEvent.click(button)</code>을 통해 버튼 클릭 이벤트를 발생시킵니다.</p>
<hr>
<h2 id="5-종합-예시"><strong>5. 종합 예시</strong></h2>
<p>아래는 위의 API들을 종합적으로 사용한 간단한 예시입니다.</p>
<pre><code class="language-jsx">import { render, screen, fireEvent } from &quot;@testing-library/react&quot;;

render(&lt;YourComponent /&gt;);
const button = screen.getByText(/click me/i);
fireEvent.click(button);
</code></pre>
<p>이 코드는 <code>YourComponent</code>를 렌더링하고, &quot;click me&quot;라는 텍스트를 가진 버튼을 찾아 클릭 이벤트를 발생시킵니다.</p>
<hr>
<p>이와 같이 React Testing Library의 주요 API를 사용하면, 컴포넌트가 DOM에서 어떻게 렌더링되고, 사용자와의 상호작용이 어떻게 이루어지는지를 쉽게 테스트할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[보일러 플레이트에 jest 세팅하기]]></title>
            <link>https://velog.io/@kgh7427_/%EB%B3%B4%EC%9D%BC%EB%9F%AC-%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8%EC%97%90-jest-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/%EB%B3%B4%EC%9D%BC%EB%9F%AC-%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8%EC%97%90-jest-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 28 Aug 2024 13:04:32 GMT</pubDate>
            <description><![CDATA[<h1 id="보일러-플레이트에-jest-세팅하기">보일러 플레이트에 jest 세팅하기</h1>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/b5e4934b-5a94-4866-9ad7-658e049cde82/image.png" alt=""></p>
<h3 id="jest-설치">jest 설치</h3>
<ul>
<li>npm install --save-dev jest</li>
</ul>
<h3 id="스크립트-설정">스크립트 설정</h3>
<pre><code class="language-tsx">{
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;jest&quot;
  }
}</code></pre>
<h3 id="바벨-사용하기">바벨 사용하기</h3>
<pre><code class="language-tsx">npm install --save-dev babel-jest @babel/core @babel/preset-env</code></pre>
<ul>
<li><p>babel-jest</p>
<ul>
<li>jest는 commonjs 모듈을 사용해서 es6 이상의 문법을 es5문법으로 트랜스파일링하는 바벨이 필요함. jest에서 babel을 사용할 수 있게 연결해 주는 브릿지 역할</li>
</ul>
</li>
<li><p>@babel/core</p>
<ul>
<li>바벨의 엔진 역할</li>
</ul>
</li>
<li><p>@babel/preset-env</p>
<ul>
<li>바벨 프리셋(플러그인 뭉치)의 환경 역할</li>
</ul>
</li>
<li><p>babel.config.js</p>
<pre><code class="language-tsx">  module.exports = {
    presets: [
      [&#39;@babel/preset-env&#39;, { targets: { node: &#39;current&#39; } }],
      &#39;@babel/preset-typescript&#39;,
    ],
  };</code></pre>
<p>  아니면 webpack.config에 하던가</p>
</li>
<li><p>ts-jest</p>
<pre><code class="language-tsx">  npm install --save-dev ts-jest</code></pre>
<p>  jest가 타입스크립트의 경우 순수하게 트랜스파일링만 하기 때문에, jest가 테스트가 실행될 때 타입 검사를 하지않음. 따라서 ts-jest를 설치해서 타입스크립트 컴파일러를 사용하게해 트랜스파일하도록하자</p>
</li>
</ul>
<h3 id="타입-가져오기">타입 가져오기</h3>
<pre><code class="language-tsx">npm install --save-dev @types/jest</code></pre>
<p> @jest/globals도 있긴한데 세팅이 귀찮다.</p>
<h3 id="jestconfigjs-세팅">jest.config.js 세팅</h3>
<ul>
<li>npm init jest@lates</li>
</ul>
<pre><code class="language-tsx">import type { Config } from &#39;jest&#39;;

const config: Config = {
  // 기본적으로 ts-jest를 사용하여 TypeScript 파일을 트랜스파일합니다.
  preset: &#39;ts-jest&#39;,

  // 테스트 환경을 Node.js로 설정합니다.
  testEnvironment: &#39;node&#39;,

  // 테스트 파일의 위치를 지정합니다.
  testMatch: [&#39;**/__tests__/**/*.ts?(x)&#39;, &#39;**/?(*.)+(spec|test).ts?(x)&#39;],

  // Jest가 처리할 파일 확장자를 설정합니다.
  moduleFileExtensions: [&#39;ts&#39;, &#39;tsx&#39;, &#39;js&#39;, &#39;jsx&#39;, &#39;json&#39;, &#39;node&#39;],

  // Jest가 테스트할 파일 또는 디렉터리를 지정합니다.
  rootDir: &#39;.&#39;,

  // 테스트 실행 전에 모든 모의 객체를 재설정합니다.
  resetMocks: true,

  // 모든 모의 객체의 상세 정보를 지웁니다.
  clearMocks: true,

  // 변환 규칙을 지정합니다.
  transform: {
    &#39;^.+\\.(ts|tsx)$&#39;: &#39;ts-jest&#39;, // TypeScript 파일 변환
    &#39;^.+\\.(js|jsx)$&#39;: &#39;babel-jest&#39;, // JavaScript 파일 변환
  },

  // 코드 커버리지 수집을 위해 Babel 프로바이더를 사용합니다.
  coverageProvider: &#39;babel&#39;,

  // 코드 커버리지 보고서 생성을 위한 설정
  collectCoverage: true,
  coverageDirectory: &#39;coverage&#39;,
  coverageReporters: [&#39;text&#39;, &#39;lcov&#39;],

  // 모듈 경로 별칭을 tsconfig.json과 일치시킵니다.
  moduleNameMapper: {
    &#39;^@/(.*)$&#39;: &#39;&lt;rootDir&gt;/src/$1&#39;,
  },
};

export default config;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[3장. 함수(1)]]></title>
            <link>https://velog.io/@kgh7427_/3%EC%9E%A5.-%ED%95%A8%EC%88%981</link>
            <guid>https://velog.io/@kgh7427_/3%EC%9E%A5.-%ED%95%A8%EC%88%981</guid>
            <pubDate>Tue, 27 Aug 2024 13:09:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kgh7427_/post/595c4281-08cf-4241-ae1a-e64aace3523a/image.jpg" alt=""></p>
<h1 id="3장-함수1">3장. 함수(1)</h1>
<hr>
<ul>
<li>의도를 분명히 표현하는 함수를 어떻게 구현할 수 있을까?</li>
<li>함수에 어떤 속성을 부여해야 처음 읽는 사람이 프로그램 내부를 직관적으로 파악할 수 있을까?</li>
</ul>
<h2 id="작게-만들어라">작게 만들어라</h2>
<ul>
<li>if, else, while 문에 들어가는 블록은 한 줄이어야 한다.<ul>
<li>중첩 구조가 생길 만큼 함수가 커져서는 안된다는 뜻이다.</li>
<li>따라서 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.</li>
</ul>
</li>
</ul>
<h2 id="한-가지만-해라">한 가지만 해라</h2>
<ul>
<li><p>함수는 한 가지를 해야한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.</p>
</li>
<li><p>한 가지만 하는 지 판단</p>
<ul>
<li><p>해당 함수 아래에서 추상화 수준이 하나인지 판단한다.</p>
<pre><code class="language-tsx">
  1. 페이지가 테스트 페이지인지 판단한다.
  2. 그렇다면 설정 페이지와 해제 페이지를 넣는다.
  3. 페이지를 HTML로 랜더링한다.
</code></pre>
<p>  → 페이지가 테스트 페이지인지 확인 한 후 테스트 페이지라면 설정 페이지와 해제 페이지를 넣는다. 테스트 페이지든 아니든 페이지를 HTML로 렌더링한다.</p>
</li>
<li><p>의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 샘이다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="함수당-추상화-수준은-하나로">함수당 추상화 수준은 하나로</h2>
<ul>
<li>함수가 확실히 한 가지의 일만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야한다.<ul>
<li>특정 코드가 근본 개념인가? 세부사항인가?</li>
</ul>
</li>
</ul>
<h2 id="위에서-아래로-코드-읽기-내려가기-규칙">위에서 아래로 코드 읽기: 내려가기 규칙</h2>
<ul>
<li><p>코드는 위에서 아래로 이야기처럼 읽혀야 좋다.</p>
<ul>
<li><p>한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.</p>
<ul>
<li><p>즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계 낮은 함수가 온다.</p>
<ul>
<li><p>이것을 내려가기 규칙이라 부른다.</p>
<pre><code class="language-tsx">  설정 페이지와 해제 페이지를 포함하려면, 설정 페이지를 포함하고,
  테스트 페이지 내용을 포함하고, 해제 페이지를 포함한다.

      설정 페이지를 포함하려면, ~

      슈트 설정 페이지를 포함하려면, ~

      부모 계층을 검색하려면, ~</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="switch-문">Switch 문</h2>
<ul>
<li><p>Switch 문의 경우 다양한 문제점을 가지는데</p>
<ol>
<li>함수가 길다.</li>
<li>한가지 작업만 수행하지 않는다.</li>
<li>SRP(단일 책임 원칙)를 위반한다.</li>
<li>OCP(개방 폐쇄 원칙)를 위반한다.</li>
</ol>
</li>
<li><p>해결잭</p>
<ul>
<li><p>추상 팩토리 패턴에 꽁꽁 숨긴다</p>
<pre><code class="language-tsx">// TypeHandlers.js
class TypeHandler {
  getMessage() {
      throw new Error(&quot;This method should be overridden&quot;);
  }
}

class TypeAHandler extends TypeHandler {
  getMessage() {
      return &quot;You selected Type A&quot;;
  }
}

class TypeBHandler extends TypeHandler {
  getMessage() {
      return &quot;You selected Type B&quot;;
  }
}

class TypeCHandler extends TypeHandler {
  getMessage() {
      return &quot;You selected Type C&quot;;
  }
}

class DefaultHandler extends TypeHandler {
  getMessage() {
      return &quot;Unknown type&quot;;
  }
}

export class TypeFactory {
  static createHandler(type) {
      switch (type) {
          case &#39;A&#39;:
              return new TypeAHandler();
          case &#39;B&#39;:
              return new TypeBHandler();
          case &#39;C&#39;:
              return new TypeCHandler();
          default:
              return new DefaultHandler();
      }
  }
}</code></pre>
<pre><code class="language-tsx">// App.js
import React, { useState } from &#39;react&#39;;
import { TypeFactory } from &#39;./TypeHandlers&#39;;

function App() {
  const [message, setMessage] = useState(&#39;&#39;);

  const handleClick = (type) =&gt; {
      const handler = TypeFactory.createHandler(type);
      setMessage(handler.getMessage());
  };

  return (
      &lt;div&gt;
          &lt;h1&gt;Select a Type&lt;/h1&gt;
          &lt;button onClick={() =&gt; handleClick(&#39;A&#39;)}&gt;Type A&lt;/button&gt;
          &lt;button onClick={() =&gt; handleClick(&#39;B&#39;)}&gt;Type B&lt;/button&gt;
          &lt;button onClick={() =&gt; handleClick(&#39;C&#39;)}&gt;Type C&lt;/button&gt;
          &lt;button onClick={() =&gt; handleClick(&#39;D&#39;)}&gt;Unknown Type&lt;/button&gt;

          &lt;p&gt;{message}&lt;/p&gt;
      &lt;/div&gt;
  );
}

export default App;</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="서술적인-이름을-사용해라">서술적인 이름을 사용해라</h2>
<ul>
<li>이름이 길어도 괜찮다</li>
<li>이름을 붙일 때는 일관성이 있어야한다.<ul>
<li>모듈 내에서 함수 이름은 같은 문거, 명사, 동사를 사용한다.<ul>
<li>ex) includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage, includeSetupPage 등…</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="reference">reference</h1>
<hr>
<blockquote>
<p>클린 코드(로버트 C 마틴)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[render prop 패턴으로 자식 컴포넌트에 데이터 주입하기]]></title>
            <link>https://velog.io/@kgh7427_/render-prop-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%EC%9E%90%EC%8B%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A3%BC%EC%9E%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kgh7427_/render-prop-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%EC%9E%90%EC%8B%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A3%BC%EC%9E%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 23 Aug 2024 07:59:10 GMT</pubDate>
            <description><![CDATA[<h1 id="render-prop-패턴으로-자식-컴포넌트에-데이터-주입하기">render prop 패턴으로 자식 컴포넌트에 데이터 주입하기</h1>
<h2 id="계기">계기</h2>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/5b3c6b29-8775-4e6f-b344-085acf6d3a41/image.png" alt=""></p>
<p>같이 듣자 프로젝트의 내가 만든 채널들과 구독한 채널들을 볼 수 있는 사이드바이다. 몇달전에 만든 거라 지금 보니 쑥쓰러울 정도로 이상하고 복잡하다. </p>
<p>코드 스플리팅을 하는 겸 괜찮은 아이디어가 떠올라서 블로깅해보려한다.</p>
<hr>
<h2 id="문제점">문제점</h2>
<p>위의 코드에는 다음과 같은 문제점들이 있다.</p>
<ol>
<li>데이터 패칭이 ChannelContainer 컴포넌트에서 이루어질 수 있음에도 부모에서 이루어져 불필요한 prop drilling이 생긴다는 것</li>
<li>1번과 이어져 ChannelContainer에서 데이터 패칭을 하려하는데 공통 컴포넌트라 몇가지 작업이 필요하는 것</li>
<li>프론트엔드에서 비동기 데이터 패칭 컴포넌트는 매우 중요하지만 바깥에서는 명시적으로 알 수 없다는 것</li>
</ol>
<hr>
<h2 id="해결법-생각해보기">해결법 생각해보기</h2>
<ol>
<li>ChannelContainer에 props로 문자열을 넘겨줘서 내부에서 분기처리하기</li>
<li>ChannelContainer를 MyChannelContainer와 SubscribedChannelContainer로 나누기</li>
<li>이번 블로깅의 목적인 데이터 패칭 컴포넌트로 데이터 주입하기</li>
</ol>
<hr>
<h2 id="선택과-이유">선택과 이유</h2>
<p>3번 아이디어를 선택했는데 이유는 다음과 같다</p>
<ol>
<li>데이터 패칭 컴포넌트와 기존 컴포넌트를 분리해서 비동기 데이터 패칭 컴포넌트임을 더 명시적으로 보여주고 싶다.</li>
<li>만들어진 라이브러리를 사용할 때를 제외하고 타입스크립트의 제네릭을 제대로 써본적이 없어 한번 제대로 써보고 싶다.</li>
<li>1, 2번 아이디어는 너무 쉽다.</li>
</ol>
<hr>
<h2 id="구현">구현</h2>
<h3 id="datafetcher-컴포넌트-코드"><strong>DataFetcher 컴포넌트 코드</strong></h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/8907e881-ed2a-4eea-b70b-5c5969da96cc/image.png" alt=""></p>
<ul>
<li>render prop 패턴으로 구현 했다<ul>
<li>cloneElement으로도 구현할 수 있지만 공식문서에서 사용할 것을 추천하지 않는다.</li>
</ul>
</li>
<li>제네릭<ul>
<li>T에는 비동기 데이터 패칭 컴포넌트에 주입될 데이터의 타입을 지정해줬다.</li>
<li>P에는 비동기 데이터 패칭에 필요한 파라미터들의 타입을 지정해줬다.</li>
</ul>
</li>
<li>props<ul>
<li>fetchFunction은 구현해둔 리액트 쿼리 훅이 들어간다.</li>
<li>params에는 리액트 쿼리 훅에 들어갈 파라미터들이 들어간다.</li>
<li>render에는 실제로 렌더링 될 컴포넌트가 리턴이 될 화살표 함수가 들어간다</li>
</ul>
</li>
</ul>
<hr>
<h3 id="datafetcher-컴포넌트-사용"><strong>DataFetcher 컴포넌트 사용</strong></h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/24ef793e-ca0a-4d12-a59d-c239d845a48b/image.png" alt=""></p>
<h1 id="후기">후기</h1>
<p>오히려 더 가독성이 안좋아지고 특별한 장점도 없는 것 같아서 안쓰기로 했다. </p>
<p>그래도 타입스크립트의 제네릭을 제대로 써보고, 여러가지 고민도 해본 것 같다.</p>
<hr>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://ko.react.dev/reference/react/cloneElement">https://ko.react.dev/reference/react/cloneElement</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 루프(Event loop)]]></title>
            <link>https://velog.io/@kgh7427_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84Event-loop</link>
            <guid>https://velog.io/@kgh7427_/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84Event-loop</guid>
            <pubDate>Thu, 22 Aug 2024 07:08:14 GMT</pubDate>
            <description><![CDATA[<h1 id="이벤트-루프">이벤트 루프</h1>
<hr>
<h2 id="서론"><strong>서론</strong></h2>
<h3 id="이벤트-루프event-loop">이벤트 루프(Event loop)</h3>
<ul>
<li><strong>싱글 스레드의 문제점과 해결</strong><ul>
<li>자바스크립트는 <strong>싱글 스레드</strong> 언어로 설계되어 있어, 하나의 작업 시간이 길면 <strong>블로킹</strong> 현상이 발생하여 전체 프로그램이 멈출 수 있다.</li>
<li>이를 해결하기 위해 자바스크립트는 <strong>이벤트 루프</strong>와 <strong>비동기 프로그래밍</strong>을 도입하여, 싱글 스레드 환경에서도 효율적으로 다양한 작업을 처리할 수 있게 한다.</li>
</ul>
</li>
<li><strong>이벤트 루프의 역할</strong><ul>
<li><strong>이벤트 루프</strong>는 자바스크립트 엔진이 <strong>비동기 작업</strong>을 관리하고, 프로그램의 흐름을 제어하는 메커니즘이다. 이를 통해 자바스크립트는 <strong>싱글 스레드</strong> 언어임에도 여러 작업을 동시 처리할 수 있는 것처럼 보이게 한다.</li>
<li><strong>이벤트 루프</strong>를 이해하지 못하면 복잡한 <strong>비동기 코드</strong>나 네트워크 요청 처리 시 성능 저하 또는 <strong>블로킹</strong> 문제가 발생할 수 있다. 예를 들어, 무한 스크롤 기능에서 API 응답이 완료되기 전에 이벤트가 트리거되면 올바르게 동작하지 않을 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="이벤트-루프의-기본-개념"><strong>이벤트 루프의 기본 개념</strong></h2>
<h3 id="콜스택call-stack"><strong>콜스택(Call Stack)</strong></h3>
<blockquote>
<p><a href="https://velog.io/@kgh7427_/%ED%98%B8%EC%B6%9C-%EC%8A%A4%ED%83%9DCall-Stack">https://velog.io/@kgh7427_/호출-스택Call-Stack</a></p>
</blockquote>
<h3 id="힙heap"><strong>힙(Heap)</strong></h3>
<ul>
<li><strong>메모리 할당과 관리</strong><ul>
<li>자바스크립트에서 동적으로 할당되는 <strong>메모리의 저장 공간</strong>이다.</li>
<li>객체와 같은 복잡한 데이터 구조를 저장한다.</li>
<li><strong>자바스크립트 엔진</strong>은 힙에서 필요한 메모리를 할당하고 관리한다.</li>
<li>메모리 누수 방지를 위해 자바스크립트 엔진은 <strong>가비지 컬렉션</strong>을 사용하여 더 이상 참조되지 않는 객체를 힙에서 제거한다.</li>
</ul>
</li>
</ul>
<h3 id="큐queue"><strong>큐(Queue)</strong></h3>
<ul>
<li>콜스택이 비워질 경우 실행될 콜백함수들을 모아둔 대기열이다.</li>
<li><strong>태스크 큐(Task Queue)</strong><ul>
<li>일반적인 비동기 작업들이 대기하는 곳이다.</li>
<li><code>setTimeout</code> , <code>setInterval</code> 등의 작업이 완료되면 해당 콜백 하수가 태스크 큐에 추가된다.</li>
</ul>
</li>
<li><strong>마이크로태스크 큐(Microtask Queue)</strong><ul>
<li>프로미스 같은 더 높은 우선순위의 작업들이 대기하는 곳이다.</li>
<li>태스크 큐보다 먼저 실행된다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="비동기-작업이-이루어지는-과정">비동기 작업이 이루어지는 과정</h2>
<pre><code class="language-tsx">const foo = () =&gt; console.log(&quot;First&quot;);
const bar = () =&gt; setTimeout(() =&gt; console.log(&quot;Second&quot;), 500);
const baz = () =&gt; console.log(&quot;Third&quot;);

bar();
foo();
baz();</code></pre>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/5ffee50c-6fa3-4301-a3db-a02f0779c381/image.gif" alt=""></p>
<ol>
<li><strong>bar() 호출</strong><ul>
<li>자바스크립트 엔진이 실행 컨텍스트를 생성한다.</li>
<li><code>setTimeout</code> 이 비동기 함수이므로 Web API로 비동기 함수를 넘긴다.</li>
<li>실행 컨텍스트를 제거한다.</li>
</ul>
</li>
<li><strong>foo()와 baz()</strong><ul>
<li>일반적인 함수이므로 자바스크립트 엔진이 실행 컨텍스트를 생성하고 내부 코드를 실행한 후 실행 컨텍스트를 제거한다.</li>
</ul>
</li>
<li><strong>web API</strong><ul>
<li><code>setTimeout</code> 을 처리하고 콜백함수를 태스크 큐로 옮긴다.</li>
<li>만약 <code>setTimeout</code> 이 아닌 프로미스 같은 우선 순위가 높은 비동기 작업일 경우 마이크로 태스크 큐로 옮긴다.</li>
</ul>
</li>
<li><strong>이벤트 루프</strong><ul>
<li>콜 스택이 비어 있는 경우 큐에 있는 콜백함수를 콜 스택으로 푸시한다.</li>
</ul>
</li>
<li><strong>콜스택</strong><ul>
<li>콜백 함수를 실행한다</li>
</ul>
</li>
<li><strong>실행 결과</strong> <ul>
<li>First, Third, Second가 출력된다.</li>
</ul>
</li>
</ol>
<hr>
<h1 id="reference">Reference</h1>
<blockquote>
<p><a href="https://inpa.tistory.com/entry/%F0%9F%94%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC">https://inpa.tistory.com/entry/🔄-자바스크립트-이벤트-루프-구조-동작-원리</a></p>
<p><a href="https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/javascript/event-loop.md">https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/javascript/event-loop.md</a></p>
<p><a href="http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4=">http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4=</a></p>
<p><a href="https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif">https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 기본]]></title>
            <link>https://velog.io/@kgh7427_/HTTP-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@kgh7427_/HTTP-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Thu, 22 Aug 2024 04:51:47 GMT</pubDate>
            <description><![CDATA[<h1 id="http-기본">HTTP 기본</h1>
<hr>
<h3 id="모든-것이-http">모든 것이 HTTP</h3>
<ul>
<li>HTTP 메시지로 모든 것을 전송할 수 있음<ul>
<li>HTML, 텍스트, 이미지, 음성, 영상 등의 데이터 등</li>
<li>JSON, XML 같은 API 데이터 등</li>
<li>따라서 서버간 데이터 전송 시에 주로 HTTP를 사용함</li>
</ul>
</li>
</ul>
<h3 id="http11">HTTP/1.1</h3>
<ul>
<li>현재 가장 많이 사용되는 버전이며, 이후 HTTP/2와 HTTP/3은 성능 개선에 집중적이고, 사용 비율도 점차 증가하는 추세임</li>
</ul>
<h3 id="기반-프로토콜">기반 프로토콜</h3>
<ul>
<li>HTTP/1.1과 HTTP/2는 TCP를 기반으로 함</li>
<li>HTTP/3은 UDP를 기반으로 함</li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li>HTTP는 클라이언트-서버 구조를 따르며, 무상태 프로토콜(Stateless)로 비연결성(Connectionless)을 특징으로 함</li>
<li>HTTP 메시지는 단순하면서도 확장이 가능해 다양한 응용 프로그램에서 사용될 수 있음</li>
</ul>
<h3 id="클라이언트-서버-구조">클라이언트-서버 구조</h3>
<ul>
<li>클라이언트는 요청을 보내고, 서버는 그에 대한 응답을 반환하는 방식임</li>
<li>클라이언트가 서버가 될 수 있고, 서버가 클라이언트가 될 수 있음</li>
</ul>
<h3 id="클라이언트와-서버의-역할-분리">클라이언트와 서버의 역할 분리</h3>
<ul>
<li>클라이언트는 주로 UI와 UX에 집중함</li>
<li>서버는 비즈니스 로직과 데이터 처리에 집중함</li>
<li>이러한 역할 분리로 각각 독립적으로 발전할 수 있는 구조를 가지게 됨</li>
</ul>
<h3 id="stateful과-stateless">stateful과 stateless</h3>
<ul>
<li>stateful은 서버가 클라이언트의 이전 상태(보냈던 데이터)를 기억하는 것임</li>
<li>stateless는 서버가 이전 상태를 보존하지 않는 것을 의미 함</li>
</ul>
<h3 id="상태-유지와-무상태의-예시">상태 유지와 무상태의 예시</h3>
<ul>
<li>고객이 상품을 구매할 때 상태를 유지하는 경우<ul>
<li>점원은 고객이 구매하려는 상품, 수량, 결제 수단 등을 기억함</li>
</ul>
</li>
<li>고객이 상품을 구매할 때 상태를 유지하지 않는 경우<ul>
<li>점원은 각 요청 시마다 모든 정보를 다시 받아야 하므로, 고객이 이전 요청의 정보를 매번 포함하여 보내야한다<ul>
<li>상품 → 상품, 수량 → 상품, 수량, 결제 수단 → …</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="무상태의-장점">무상태의 장점</h3>
<ul>
<li>중간에 서버(점원)가 바뀌어도 이전 요청의 정보를 포함한 전체 데이터를 서버에 보내기 때문에 서버(점원)가 바뀌더라도 문제없이 요청을 처리할 수 있음<ul>
<li>따라서 서버의 수를 무한대로 확장할 수 있다는 점에서 유용함<ul>
<li>예를들어 대규모 트래픽이 발생하는 이벤트나 예약 시스템에서 무상태 구조를 활용하면, 서버 확장을 통해 안정적인 대응이 가능함.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="무상태의-한계">무상태의 한계</h3>
<ul>
<li>모든 상황에서 무상태 구조를 사용할 수는 없음<ul>
<li>단순한 소개 페이지에 경우에는 서버가 굳이 이전 상태를 가지고 있지 않아도 되서 상관없지만 로그인 같은 기능은 서버가 쿠키, 세션 같은 데이터를 보존하고 있어야하기 때문에
사용하면 안됨</li>
</ul>
</li>
<li>클라이언트가 큰 데이터 뭉치자체를 보내기 때문에 데이터가 무거워지는 단점도 있음</li>
<li>따라서 최소한의 범위에서 필요한 경우만 사용하는 것이 옳다</li>
</ul>
<h3 id="비연결성">비연결성</h3>
<ul>
<li>연결을 유지하는 모델<ul>
<li>서버가 모든 클라이언트와의 연결을 유지함.<ul>
<li>따라서 자원 소모가 지속적임</li>
</ul>
</li>
</ul>
</li>
<li>연결을 유지하지 않는 모델<ul>
<li>요청, 응답후 TCP/IP 연결을 종료<ul>
<li>따라서 자원 소모가 적어짐</li>
</ul>
</li>
</ul>
</li>
<li>비연결성<ul>
<li>HTTP는 기본적으로 연결을 유지하지 않는 모델임</li>
<li>보통 1시간 동안 수천명이 서비스를 사용해도 실제로 동시에 처리하는 요청은 적음(수천명이 동시에 버튼을 누르지는 않는다) 따라서 비연결성이 적합함</li>
</ul>
</li>
</ul>
<h3 id="비연결성의-한계와-극복">비연결성의 한계와 극복</h3>
<ul>
<li>한계<ul>
<li>매번 요청할 때마다 TCP/IP 연결을 새로 맺어야함<ul>
<li>3-way-handshake하는 시간이 추가</li>
</ul>
</li>
</ul>
</li>
<li>극복<ul>
<li>HTTP 지속 연결(Persistent Connections)이 도입됨(HTTP/1.1)<ul>
<li>지정한 timeout동안 연결을 끊지 않게 지정해서 다수의 TCP 핸드셰이킹을 줄임</li>
</ul>
</li>
<li>HTTP/2, HTTP/3에서 추가적인 최적화가 이루어짐</li>
</ul>
</li>
</ul>
<h3 id="http-메시지">HTTP 메시지</h3>
<p><img src="https://velog.velcdn.com/images/kgh7427_/post/affc6b39-d2d0-47a9-9e1e-17e5604a572d/image.png" alt=""></p>
<ul>
<li>모든 바이너리 데이터를 담을 수 있음</li>
<li>시작라인<ul>
<li>요청 메시지 경우<ul>
<li>HTTP 메소드, 요청 대상, HTTP-version</li>
</ul>
</li>
<li>응답 메시지 경우<ul>
<li>HTTP 버전, HTTP 상태 코드, 이유 문구</li>
</ul>
</li>
</ul>
</li>
<li>HTTP 헤더<ul>
<li>header-field = field-name &quot;:&quot; OWS field-value OWS (OWS:띄어쓰기 허용)<ul>
<li>HTTP 전송에 대한 모든 부가 정보가 포함</li>
<li>메시지 바디의 내용, 바디 크기, 압축, 인증, 클라 정보, 캐시 등…</li>
</ul>
</li>
</ul>
</li>
<li>HTTP 메시지 바디<ul>
<li>실제 전송할 데이터</li>
<li>byte로 표현할 수 있는 모든 데이터 전송 가능</li>
</ul>
</li>
</ul>
<h3 id="http의-장점">HTTP의 장점</h3>
<ul>
<li>단순하고 확장이 가능해 다양한 응용 분야에서 널리 사용된다!</li>
</ul>
<h1 id="reference">Reference</h1>
<hr>
<blockquote>
<p><a href="https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC?srsltid=AfmBOor233Qfwz765jdrp2WYMn3AskbtF4Oa6t_T1cPHXR38WjXqsnP3"><strong>모든 개발자를 위한 HTTP 웹 기본 지식 강의 | 김영한</strong></a></p>
</blockquote>
<blockquote>
<p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-HTTP-09-HTTP-30-%EA%B9%8C%EC%A7%80-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%ED%86%B5%EC%8B%A0-%EA%B8%B0%EC%88%A0#persistent_connection_keep-alive">https://inpa.tistory.com/entry/WEB-🌐-HTTP-09-HTTP-30-까지-알아보는-통신-기술#persistent_connection_keep-alive</a></p>
</blockquote>
<blockquote>
<p><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Messages">https://developer.mozilla.org/ko/docs/Web/HTTP/Messages</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>