<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>no-pla.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 05 Sep 2024 17:25:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>no-pla.log</title>
            <url>https://velog.velcdn.com/images/no-pla/profile/a32775ae-7e1f-40b1-9ffa-48ae3e867a08/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. no-pla.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/no-pla" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Auth.js에서 Naver OAuth 사용 시 발생하는 에러 해결하기]]></title>
            <link>https://velog.io/@no-pla/Auth.js-Naver-OAuth-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/Auth.js-Naver-OAuth-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 05 Sep 2024 17:25:06 GMT</pubDate>
            <description><![CDATA[<p><code>Auth.js</code>는 OAuth의 표준 스펙을 엄격하게 준수한다. 그렇기에 네이버 로그인의 경우, 특정 값이 스펙에 맞지 않게 반환하기 때문에 에러가 발생했다.</p>
<p><strong>에러 로그</strong></p>
<pre><code>Server error
There is a problem with the server configuration.

Check the server logs for more information.

[auth][error] CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror
[auth][cause]: OperationProcessingError: &quot;response&quot; body &quot;expires_in&quot; property must be a positive number</code></pre><p>이는 <code>expires_in</code>이 정수 형식(<code>number</code>)이어야 하지만, 문자열 형식으로 반환하기 때문에 발생한 에러였다. 이전 버전(v4)에서는 이러한 에러가 발생하지 않았지만, Next-Auth가 v5로 버전을 업그레이드하면서 유사한 문제가 많은 OAuth(인스타그램, OneLogin, Foursquare)에서 대거 발생하고 있다.</p>
<p>이러한 문제점을 찾은 다른 개발자들이 이미 이 문제를 제보했지만, 네이버 측에서는 사이드 이펙트의 우려로 해당 사항을 수정하는 것은 어렵다고 한다.</p>
<p><a href="https://github.com/nextauthjs/next-auth/discussions/9313">관련 Next-Auth discussion</a></p>
<p>이러한 문제를 해결한 예시 중 하나로, 인스타그램의 표준 스펙을 따르지 않아 발생하는 문제를 해결했던 <a href="https://github.com/nextauthjs/next-auth/issues/8868">인터셉터</a>를 참고하여 네이버 OAuth를 사용할 수 있도록 인터셉터를 작성해 보자.</p>
<h3 id="문제-상황">문제 상황</h3>
<ol>
<li><code>oauth4webapi</code>는 Next-Auth v5의 반환 값이 표준 스펙을 준수하는가를 엄격하게 판단함.</li>
<li>네이버 로그인 사용 시 반환되는 <code>expires_in</code> 값이 <a href="https://developers.naver.com/docs/login/devguide/devguide.md">공식 문서</a>에 기재된 것과는 달리 정수 값이 아닌 문자열임.</li>
</ol>
<p>네이버 로그인의 <a href="https://github.com/panva/oauth4webapi/blob/c17ef94bd421ba4c86d415c09109127e2dafb2d4/src/index.ts#L1560">oauth4webapi</a>에서 에러가 발생한 부분은 이 부분이다.</p>
<h3 id="해결-방법">해결 방법</h3>
<p>인스타그램의 인터셉터를 참고하여 네이버 인터셉터를 작성해 보았다.</p>
<p>인스타그램의 인터셉터는 반환값을 수정해 스펙에 맞도록 수정하는 방식으로 동작한다. 위의 예시를 참고하여 반환값을 인터셉팅해서 <code>expires_in</code>을 number로 변환하여 수정해 표준 스펙을 준수하도록 수정을 할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/e3584bcb-fdba-41bc-9827-47cfe1f7ed64/image.jpg" alt=""></p>
<ol>
<li>먼저 <code>fetch</code> 함수를 수정하여, 네이버 로그인 시에는 인터셉터를 적용하고, 다시 복원할 수 있도록 한다.</li>
</ol>
<pre><code class="language-ts">// app/api/auth/[...nextauth]/route.ts
import { GET as AuthGET, POST as AuthPOST } from &quot;@/auth&quot;;
import { naverFetchInterceptor } from &quot;@/interceptor/naver-interceptor&quot;;
import { type NextRequest } from &quot;next/server&quot;;

const originalFetch = fetch;

export async function POST(req: NextRequest) {
  return await AuthPOST(req);
}

export async function GET(req: NextRequest) {
  const url = new URL(req.url);

  if (url.pathname === &quot;/api/auth/callback/naver&quot;) {
    global.fetch = naverFetchInterceptor(originalFetch);
    const response = await AuthGET(req);
    global.fetch = originalFetch;
    return response;
  }
  return await AuthGET(req);
}</code></pre>
<p>다음은 네이버 로그인 시, 실행되는 인터셉터로 옳지 않은 스펙(<code>expires_in</code>)을 올바른 형식으로 수정해 준다.</p>
<pre><code class="language-ts">// interceptor/naver-interceptor.ts
/**
 * This interceptor is used to modify the response of the naver access token request as it does not strictly follow the OAuth2 spec
 * 네이버 아이디 로그인의 `expires_in`이 정수 타입이 아닌 문자열 타입으로 반환됩니다.
 * @param originalFetch
 */
export const naverFetchInterceptor =
  (originalFetch: typeof fetch) =&gt;
  async (
    url: Parameters&lt;typeof fetch&gt;[0],
    options: Parameters&lt;typeof fetch&gt;[1] = {}
  ) =&gt; {
    if (
      url === &quot;https://nid.naver.com/oauth2.0/token&quot; &amp;&amp;
      options.method === &quot;POST&quot;
    ) {
      const response = await originalFetch(url, options);
      const clonedResponse = response.clone();
      const body = await clonedResponse.json();

      body.expires_in = Number(body.expires_in); // 문자열로 되어 있는, expires_in을 숫자로 변환

      const modifiedResponse = new Response(JSON.stringify(body), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });

      return Object.defineProperty(modifiedResponse, &quot;url&quot;, {
        value: url,
      });
    }

    return originalFetch(url, options);
  };
</code></pre>
<p>아래는 auth.ts 파일의 구성이다.</p>
<pre><code class="language-ts">// auth.ts
import { PrismaAdapter } from &quot;@auth/prisma-adapter&quot;;
import NextAuth from &quot;next-auth&quot;;
import Naver from &quot;next-auth/providers/naver&quot;;
import { prisma } from &quot;./prisma&quot;;
import { Provider } from &quot;next-auth/providers&quot;;

export const CustomNaverAuthProvider: Provider = Naver({
  clientId: process.env.AUTH_NAVER_ID,
  clientSecret: process.env.AUTH_NAVER_SECRET,
  authorization: &quot;https://nid.naver.com/oauth2.0/authorize?response_type=code&quot;,
});

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [CustomNaverAuthProvider],
});

export const { GET, POST } = handlers;
</code></pre>
<h4 id="결과">결과</h4>
<p>로그인 창도 제대로 뜨고, 로그인과 DB까지 제대로 동작한다!</p>
<ul>
<li>로그인 창
<img src="https://velog.velcdn.com/images/no-pla/post/be3f2a6d-fefe-451c-9bb4-9ec8a7712531/image.png" alt=""></li>
<li>로그인 유저 정보
<img src="https://velog.velcdn.com/images/no-pla/post/b4da4cf9-cd25-4b74-b45c-3953dd9f7cbd/image.png" alt=""></li>
<li>DB (Provider 네이버로 잘 저장된다.)
<img src="https://velog.velcdn.com/images/no-pla/post/4ea257c2-4938-4d50-9e36-98e2662cb004/image.png" alt=""></li>
</ul>
<p>Auth.js(Next-Auth v5)가 업데이트되면서 표준 스펙을 지키지 않는 OAuth를 사용하지 못하는 에러가 종종 발생하는 것 같다. 이러한 방식으로 특정 OAuth 실행 시에만 인터셉터를 작성하여 표준 스펙을 지킬 수 있도록 수정해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[기술 면접 공부 [1]]]></title>
            <link>https://velog.io/@no-pla/%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@no-pla/%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Mon, 26 Aug 2024 06:22:35 GMT</pubDate>
            <description><![CDATA[<h3 id="cors는-무엇인가요">CORS는 무엇인가요?</h3>
<p>CORS는 <code>교차 출처 리소스 공유</code>라는 뜻으로, 서로 다른 도메인에서 리소스 요청을 제한하는 것을 의미합니다.</p>
<p>기본적으로 브라우저는 다른 출처의 자바스크립트의 실행을 막습니다. 이는 악의적인 스크립트의 실행으로 인한 정보 탈취 등을 막기 위함입니다. 브라우저는 URL, 프로토콜, 포트 및 호스트 명 중 하나라도 다를 경우, 브라우저는 교차 출처라고 간주합니다.</p>
<p>이런 에러를 해결하기 위해, <code>Access-Control-Allow-Origin</code>를 헤더를 설정하여 해결할 수 있습니다.</p>
<hr>
<h3 id="jwtjson-web-token란-무엇인가요">JWT(JSON Web Token)란 무엇인가요?</h3>
<p>JWT는 주고 사용자의 인증 혹은 인가 정보를 JSON 형식으로 서버와 클라이언트 간에 안전하게 주고받기 위해 사용되는 매커니즘입니다.</p>
<p>JWT은 <code>Base64로 인코딩</code>되어있습니다.</p>
<p>JWT은 Header, Payload, Signature의 구조로 되어 있으며, 세 부분을 점(dot)으로 구분합니다. Header 부분에는 토큰의 유형과 서명 알고리즘이, Payload에는 사용자의 인증 인가 정보가, 마지막 Signature 부분에는 Header와 Payload가 비밀 키로 서명되어 작성됩니다.</p>
<p>JWT을 사용하면, 쿠키나 세션을 사용하여 인증/인가를 구현할 때와 비교하면, 사용자의 세션을 DB나 캐시에 저장하여 <code>매번 쿠키로 넘어온 사용자 데이터를 조회하지 않아도 되기 때문에</code>, <code>인프라 비용을 절감</code>할 수 있고 <code>CORS 문제에서 벗어날 수 있다는 장점</code>이 있습니다.</p>
<p>그러나, 사용자가 여러 기기에 로그인한 정보를 띄워주고, 특정 기기에서 로그아웃하는 기능을 구현하기에는 어렵다는 한계가 있습니다. 또한, JWT에 저장된 데이터는 누구나 쉽게 열람할 수 있기에, 보안 면에서 더욱 각별한 주의가 필요합니다.</p>
<hr>
<h3 id="restful-api란-무엇인가요-restful-api의-주요-원칙은-무엇인가요">RESTful API란 무엇인가요? RESTful API의 주요 원칙은 무엇인가요?</h3>
<p>RESTful API란, <code>REST의 기본 원칙</code>을 준수해서 설계된 API를 뜻합니다.</p>
<p>REST API는 <code>URL로 표현되는 자원</code>, <code>HTTP 요청 메서드로 표현되는 행위</code>,<code>데이터를 어떻게 표현할 것인가를 나타내는 표현</code>의 세 가지 요소로 구성됩니다.</p>
<p>RESTful API에서 가장 중요한 기본 원칙으로, 첫 번째로는 URL은 리소스를 표현해야 한다는 것입니다. 유저라는 리소스를 나타낸다면, /users라는 식으로 작성해야 하고, 동사나 행동을 나타내는 단어(getUsers/deleteUsers)를 포함해서는 안됩니다.</p>
<p>두 번째로는 <code>리소스에 대한 행위는 HTTP 요청 메서드로 표현한다는 것입니다.</code> GET, POST, PUT, PATCH, DELETE의 다섯 가지 메서드를 사용하는 방식으로 동작합니다.</p>
<p>그리고 데이터의 표현 방식은 일반적으로 XML과 JSON 형식이 있습니다.</p>
<h3 id="patch와-put의-차이점은-무엇인가요">PATCH와 PUT의 차이점은 무엇인가요?</h3>
<p>PATCH와 PUT 모두 기존에 존재하는 리소를을 업데이트(변경)하는 메서드지만, PUT은 완전히 새로운 리소스로 대체할 때 사용되고, PATCH는 기존에 존재하던 리소스의 일부분을 수정할 때 사용됩니다.</p>
<hr>
<h3 id="검색창에-googlecom을-치면-생기는-일">검색창에 google.com을 치면 생기는 일</h3>
<ol>
<li>사용자가 브라우저 주소 창에 <a href="http://google.com/"><strong>google.com</strong></a>을 입력하면 브라우저는 내가 입력한 텍스트가 검색어인지, url인지 판별한다.</li>
<li>검색어가 URL이면 브라우저는 해당 사이트를 호스팅하는 IP 주소를 찾는다.<ol>
<li>DNS(Domain Name Service) 조회를 수행하여 도메인 이름을 기반으로, 서버의 IP주소를 찾는다. (IP 주소는 기억하기 어렵기 때문에 도메인 네임을 사용한다.)</li>
</ol>
</li>
<li>브라우저가 서버에 연결되면 HTTP 요청을 보내면 서버가 요청을 처리하고, 응답을 다시 전송한다.</li>
<li>다음으로 이제 브라우저가 데이터를 받으면 렌더링 과정이 시작된다.</li>
<li>먼저 HTML과 CSS를 파싱하여 각각 DOM 트리와 CSSOM 트리를 생성한다.<ol>
<li>CSSOM 트리는 렌더링 차단 리소스로 간주되어 완전히 파싱되어야만 렌더 트리 구성이 가능하다. 따라서 first meaningful paint를 빠르게 달성할 수 있도록, css를 head에 배치해야하는 이유이다.</li>
<li>렌더 트리를 구성하는 도중, 자바스크립트 코드를 만나면, 파싱을 중단하고 자바스크립트를 실행하고, 자바스크립트 실행을 마친 뒤에 다시 파싱을 진행한다.</li>
</ol>
</li>
<li>다음으로 파싱이 완료된 DOM 트리와 CSSOM 트리를 결합하여 렌더 트리를 생성한다. 렌더 트리는 실제로 화면에 표시되는 요소들로 이루어진 트리 구조로, 각 요소의 스타일, 크기, 위치 등의 정보를 가지고 있다. display:none 과 같이 화면에 보이지 않는 스타일을 가진 요소는 이 DOM 트리에서 제외된다.</li>
<li>다음으로 렌더 트리를 기반으로 화면에 배치하는 레이아웃을 계산하는 reflow 단계를 거친다. 이 과정에서 뷰포트 크기, 요소의 크기, 위치, 여백 등을 고려하여 요소들의 정확한 배치를 계산한다. 레이아웃 계산이 완료되면 렌더 트리에 반영된다.</li>
<li>마지막으로 브라우저가 렌더 트리의 각 노드를 실제 화면에 그리는 페인팅을 한다. 이 과정에서 각 요소의 스타일, 배치 정보를 기반으로 실제 픽셀로 변환하여 화면에 그린다. 이 단계에서는 컨텐츠가 실제로 그려지는 시점이다.</li>
</ol>
<ul>
<li>리플로우(Reflow) 스타일이나 DOM이 변경되었을 시에 각 요소들의 크기와 위치를 다시 계산하는 과정</li>
<li>리페인팅(Repainting): 요소의 색깔, opacity 등의 시각적 요소만 변경되는 경우</li>
</ul>
<p>화면의 레이아웃이 변경되었을 때는 리플로우와 리페인팅이 발생합니다. 렌더링 최적화를 위해서는 리플로우를 최소화해야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[middleware로 localStorage 관리하기]]></title>
            <link>https://velog.io/@no-pla/middleware%EB%A1%9C-localStorage-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/middleware%EB%A1%9C-localStorage-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 12 Aug 2024 07:40:46 GMT</pubDate>
            <description><![CDATA[<p>리덕스는 언제나 불변성을 유지해야 한다. 그러나 내가 기존에 작성한 코드는 <code>localStorage</code>에 값을 저장하고 삭제하는 등의 동작을 위해 slice 내부에 해당 로직을 작성했다. 그러나, <code>localStorage</code>는 사이드 이펙트를 발생시키기 때문에, 이는 리덕스의 원칙을 위배한다고 볼 수 있따.</p>
<p>그렇기 때문에 리덕스는 이러한 사이드 이펙트를 발생시키는 로직은 <code>middleware</code>에 작성하도록 한다.</p>
<h2 id="middleware-미들웨어">middleware [미들웨어]</h2>
<p>미들웨어는 Request(요청)을 받고, Respond(응답)을 생성하는 프레임워크 사이에서 동작하는 코드다. 리덕스의 미들웨어는 로깅, 비동기 API와의 통신 라우팅 등에 사용된다.</p>
<h3 id="createlistenermiddleware">createListenerMiddleware</h3>
<pre><code class="language-ts">// src/middleware/localStorageMiddleware.ts
import { createListenerMiddleware } from &quot;@reduxjs/toolkit&quot;;

// Best to define this in a separate file, to avoid importing
// from the store file into the rest of the codebase
export const listenerMiddleware = createListenerMiddleware();

export const { startListening, stopListening } = listenerMiddleware;
</code></pre>
<p>먼저, 미들웨어를 설정하기 위해, 인스턴스를 생성해 준다. 그리고 생성한 미들웨어는, configureStore에 추가해 준다.</p>
<pre><code class="language-ts">// src/share/store.ts
import { combineReducers, configureStore } from &quot;@reduxjs/toolkit&quot;;
import gameReducer from &quot;slices/gameSlice&quot;;
import modalReducer from &quot;slices/modalSlice&quot;;
import { listenerMiddleware } from &quot;src/middleware/localStorageMiddleware&quot;;

const rootReducer = combineReducers({
  game: gameReducer,
  modal: modalReducer,
});

export function setupStore(preloadedState?: Partial&lt;RootState&gt;) {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
    middleware: (getDefaultMiddleware) =&gt;
      getDefaultMiddleware().prepend(listenerMiddleware.middleware), // 추가!
  });
}

export type RootState = ReturnType&lt;typeof rootReducer&gt;;
export type AppStore = ReturnType&lt;typeof setupStore&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];
</code></pre>
<h3 id="startlistening">startListening</h3>
<p><code>startListening</code>는 미들웨어의 새로운 리스너를 생성한다.</p>
<p>옵션으로는 다음 4가지 중 하나를 제공해야 한다.</p>
<ol>
<li>type (문자열로 정확히 일치하는 action)</li>
<li>actionCreator (action 생성자를 지정해, 정확히 일치하는 action 1개)</li>
<li>matcher?: Matcher (toolkit의 액션 중 하나와 일치하는 경우)</li>
<li>predicate?: ListenerPredicate (액션과 상태를 기반으로 조건문을 설정)</li>
</ol>
<p>위의 옵션 중 하나를 리스너에 지정하면, 조건에 맞을 경우 effect가 실행된다.</p>
<pre><code class="language-ts">// src/slices/gameSlice.ts
startListening({
  matcher: isAnyOf(dropMarker, reset),
  effect: (_action: Action, listenerApi) =&gt; {
    const newData = (listenerApi.getState() as RootState).game;
    localStorage.setItem(&quot;connect-four&quot;, JSON.stringify(newData));
  },
});</code></pre>
<p>위와 같은 방식으로 미들웨어를 통해 localStorage와 통신을 하면, 리덕스의 원칙을 준수하면서, 사이드 이펙트를 발생시킬 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite 프로젝트 vercel 배포 시, 라우팅이 제대로 작동하지 않는 에러]]></title>
            <link>https://velog.io/@no-pla/Vite-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-vercel-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85%EC%9D%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@no-pla/Vite-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-vercel-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85%EC%9D%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Wed, 31 Jul 2024 08:19:51 GMT</pubDate>
            <description><![CDATA[<p>Vite로 제작한 프로젝트를, vercel로 배포 후, lighthouse 테스트를 진행하던 도중, 메인 페이지(루트)는 제대로 뜨지만, 서브 페이지로 바로 접속을 시도하거나, 새로고침을 할 시에는 404 에러가 발생했다.
<img src="https://velog.velcdn.com/images/no-pla/post/c8db6fcb-97f3-485c-a28b-829b736c7b26/image.png" alt=""></p>
<p>에러가 발생한 이유는, <code>Vercel</code>은 정적 웹 사이트 호스팅을 위한 도구이다. 그리고 내 프로젝트는 Vite(+React), 즉 SPA이기 때문에, 클라이언트 측에서 라우팅을 진행한다. 따라서 정적 웹 사이트 호스팅을 하는 <code>Vercel</code>은, 이러한 SPA의 경로를 인식하지 못해서 발생하게 된다. 따라서 프로젝트 상에서 존재하는 경로일지라도, <code>Vercel</code>은 존재하지 않는다고 판단하기 때문에 404 에러를 반환하는 것이다.</p>
<h2 id="해결-방법">해결 방법</h2>
<p><code>vercel.json</code></p>
<pre><code class="language-json">{
  &quot;rewrites&quot;:  [
    {&quot;source&quot;: &quot;/(.*)&quot;, &quot;destination&quot;: &quot;/&quot;}
  ]
}</code></pre>
<p>루트 폴더에 위의 <code>vercel.json</code> 파일을 추가해 주면 해결할 수 있다.</p>
<p><a href="https://stackoverflow.com/questions/64815012/why-does-react-router-not-works-at-vercel">참고 자료</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux-React 테스팅하기]]></title>
            <link>https://velog.io/@no-pla/Redux-React-%ED%85%8C%EC%8A%A4%ED%8C%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/Redux-React-%ED%85%8C%EC%8A%A4%ED%8C%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 10 Jul 2024 04:57:48 GMT</pubDate>
            <description><![CDATA[<p>리액트-리덕스와 연결되어 있는 리액트 컴포넌트를 테스트해 보자.</p>
<p>먼저, 테스트 코드 작성에 필요한 라이브러리들을 설치해 준다.</p>
<pre><code class="language-bash">npm install --save-dev @testing-library/react @testing-library/react-hooks redux-mock-store
</code></pre>
<h2 id="함수-단위unit-테스팅">함수 단위(Unit) 테스팅</h2>
<p>기본적으로 통합 테스트를 작성하는 것이 좋지만, 단위 테스트를 작성해야 할 때도 있다.
단위 테스트는 이전 상태에 액션을 적용한 후, 새로운 상태를 반환하는 <code>순수 함수</code>다. 대부분의 경우, 명시적인 테스트가 필요 없기 때문에 간단하게 테스트 코드를 작성할 수 있다. 특정 입력 state와 action으로 reducer를 호출하고, 결과가 일치하는지 확인하면 된다.</p>
<p>예시로, 다음과 같은 코드를 테스트하면, </p>
<pre><code class="language-ts">    drop: (state, actions) =&gt; {
      if (state.stop) return;
      if (state.winner !== null) {
        console.warn(`이미 종료된 게임입니다. 승자는 ${state.winner}입니다.`);
        return;
      }

      if (state.board[actions.payload.lineNumber][0] !== null) {
        console.warn(
          `${actions.payload.lineNumber + 1} 열은 이미 전부 채워진 열입니다.`
        );
        return;
      }
      let location: null | number = null;

      /**
       * 만약 선택한 행의 첫 번째 셀이 null이 아니면 리턴.
       */
      for (let i = 6; i &gt;= 0; i--) {
        if (state.board[actions.payload.lineNumber][i] === null) {
          state.board[actions.payload.lineNumber][i] =
            state.currentPlayer === &quot;RED&quot; ? &quot;RED&quot; : &quot;YELLOW&quot;;
          location = i;
          break;
        }
      }

      if (location === 0) {
        state.notMaxLine = state.notMaxLine.filter(
          (lineNumber) =&gt; lineNumber !== actions.payload.lineNumber
        );
      }

      state.markerCount += 1;

      if (state.markerCount &gt;= 7) {
        // 모든 방향을 체크하기 위해 방향 벡터를 사용한다.
        const movement = [
          { dx: 1, dy: 0 }, // 가로
          { dx: 0, dy: 1 }, // 세로
          { dx: -1, dy: 1 }, // 양수 대각선
          { dx: -1, dy: -1 }, // 음수 대각선
        ];

        // 연결 테스트
        const checkDirection = (dx: number, dy: number) =&gt; {
          let count = 1; // 기존 마커도 추가.

          let pnx = actions.payload.lineNumber + dx; // X축 각 방향별로 1칸씩 이동
          let pny = location! + dy; // Y축 각 방향별로 1칸씩 이동

          while (
            // 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
            // 만약 마커가 보드를 넘어갈 경우, 종료.
            pnx &gt;= 0 &amp;&amp;
            pny &gt;= 0 &amp;&amp;
            pnx &lt;= 6 &amp;&amp;
            pny &lt;= 5 &amp;&amp;
            state.board[pnx][pny] === actions.payload.player
          ) {
            count++;
            pnx += dx;
            pny += dy;
          }

          let mnx = actions.payload.lineNumber - dx; // X축 각 방향별로 1칸씩 이동
          let mny = location! - dy; // Y축 각 방향별로 1칸씩 이동

          while (
            // 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
            // 만약 마커가 보드를 넘어갈 경우, 종료.
            mnx &gt;= 0 &amp;&amp;
            mny &gt;= 0 &amp;&amp;
            mnx &lt;= 6 &amp;&amp;
            mny &lt;= 5 &amp;&amp;
            state.board[mnx][mny] === actions.payload.player
          ) {
            count++;
            mnx -= dx;
            mny -= dy;
          }

          return count;
        };

        for (const { dx, dy } of movement) {
          const count = checkDirection(dx, dy);

          if (count &gt;= 4) {
            state.winner = actions.payload.player;

            if (state.winner === &quot;RED&quot;) {
              state.redWin += 1;
            } else {
              state.yellowWin += 1;
            }

            console.log(`${state.winner}가 승리했습니다.`);
            return;
          }
        }
      }

      state.currentPlayer = actions.payload.player === &quot;RED&quot; ? &quot;YELLOW&quot; : &quot;RED&quot;;
      state.timer = 30;
    },</code></pre>
<p>다음과 같이 테스트 코드를 작성할 수 있다.</p>
<pre><code class="language-ts">// src/tests/game.test.tsx
    const previousState = {
      board: [
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
        [null, null, null, null, null, null],
      ],
      currentPlayer: &quot;RED&quot; as &quot;RED&quot; | &quot;YELLOW&quot;,
      markerCount: 0,
      winner: null,
      redWin: 0,
      yellowWin: 0,
      timer: 30,
      stop: false,
      notMaxLine: [0, 1, 2, 3, 4, 5, 6],
    };

gameSlice(previousState, drop({ lineNumber: 0 }))

// slice함수(기본값, reducer(payload))</code></pre>
<p><code>reducer</code>가 있는 <code>gameSlice</code>에 기본값과, 실행할 함수와 <code>payload</code>를 전달해 준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Immutable하게 Redux state 업데이트하기]]></title>
            <link>https://velog.io/@no-pla/Immutable%ED%95%98%EA%B2%8C-Redux-state-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/Immutable%ED%95%98%EA%B2%8C-Redux-state-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 09 Jul 2024 17:01:22 GMT</pubDate>
            <description><![CDATA[<p>리덕스는 이전 상태와 액션을 기반으로 상태를 업데이트하는 순수 함수로, state를 직접 변경해서는 안된다.</p>
<h2 id="로직-작성">로직 작성</h2>
<p><code>React-toolkit</code>의 <code>createSlice</code>를 사용하면 immer가 적용되기 때문에, 그냥 할당해도 순수성을 유지하면서 업데이트가 되기 때문에, 일일히 아래처럼 작성해줄 필요는 없다.</p>
<p>그렇지만 <code>Immer</code>를 사용하지 않고 순수하게 업데이트하는 방식도 작성해 보았다.</p>
<pre><code class="language-ts">interface NestedArrayData {
  arr: (&quot;RED&quot; | &quot;YELLOW&quot; | null)[][];
}

interface ActionsData {
  payload: {
    lineNumber: number;
    location: number;
    currentUser: &quot;RED&quot; | &quot;YELLOW&quot;;
  };
}

export default function nestedArrayUpdate(
  { arr }: NestedArrayData,
  actions: ActionsData
) {
  return arr.map((line, index) =&gt; {
    if (index !== actions.payload.lineNumber) {
      return line;
    }
    return line.map((marker, idx) =&gt; {
      if (idx !== actions.payload.location) {
        return marker;
      }
      return actions.payload.currentUser;
    });
  });
}

const initialArray = [
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
];

const test1 = nestedArrayUpdate(
  {
    arr: initialArray,
  },
  {
    payload: {
      currentUser: &quot;RED&quot;,
      lineNumber: 5,
      location: 5,
    },
  }
);

console.log(&quot;바뀐 객체:&quot;, test1, &quot;기존 객체:&quot;, initialArray);
</code></pre>
<p>map으로 배열을 순회하면서 바꾸고 싶은 위치를 제외하고 모두 그대로 return하고, 바꾸어야 할 부분만 따로 처리해 준다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/9c193b74-346c-4dda-b605-d6f1726dde8e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-Redux/Redux-toolkit state 리셋하기]]></title>
            <link>https://velog.io/@no-pla/React-ReduxRedux-toolkit-state-%EB%A6%AC%EC%85%8B%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/React-ReduxRedux-toolkit-state-%EB%A6%AC%EC%85%8B%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 27 Jun 2024 14:05:04 GMT</pubDate>
            <description><![CDATA[<p>전역 관리 중인 state들을 초기화할 필요가 있었다. 처음에는 state에 initialState를 할당시켜주는 방법을 생각했지만, Redux는 불변성을 유지해주어야 하기 때문에 직접적으로 state를 변경해서는 안된다.</p>
<p>제대로 작동하지 않은 코드.</p>
<pre><code class="language-ts">// src/slices/gameSlice.ts
reset: (state) =&gt; {
    state = initialState;
}</code></pre>
<p>상태들을 초기화하기 위한 방법에는 여러가지가 있지만, 그 중 하나로는 <code>initialState</code>를 리턴해 주는 방법이 있다.</p>
<pre><code class="language-ts">// src/slices/gameSlice.ts
reset: () =&gt; initialState</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Connect four 연결 체크하기]]></title>
            <link>https://velog.io/@no-pla/2%EC%A4%91-%EB%B0%B0%EC%97%B4-%EC%9A%94%EC%86%8C-%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/2%EC%A4%91-%EB%B0%B0%EC%97%B4-%EC%9A%94%EC%86%8C-%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Jun 2024 07:21:36 GMT</pubDate>
            <description><![CDATA[<h2 id="리팩토링-이전">리팩토링 이전</h2>
<p><code>Connect four</code> 게임에서 마커가 4개 이상 연결되었는지를 체크하는 로직을 작성하기 위하여, 1차적으로 코드를 작성해 보았다.</p>
<ol>
<li>열과 행의 숫자를 가져와서 해당 숫자를 기준으로 반복문을 돌면서 연속된 마커 개수를 체크한다.</li>
<li>만약 마커 옆에 다른 마커가 있다면, <code>break</code>로 중지한다.</li>
<li>카운트가 4개 되면 해당 유저가 승리한다.</li>
</ol>
<pre><code class="language-ts">// src/slices/gameSlice.ts
    drop: (state, actions) =&gt; {
      if (state.winner !== null) {
        console.warn(`이미 종료된 게임입니다. 승자는 ${state.winner}입니다.`);
        return;
      }
      if (state.board[actions.payload.lineNumber][0] !== null) {
        console.warn(
          `${actions.payload.lineNumber + 1} 열은 이미 전부 채워진 열입니다.`
        );
        return;
      }

      let location = null;

      /**
       * 만약 선택한 행의 첫 번째 셀이 null이 아니면 리턴.
       * TODO: 위 경우 다시 마커를 둘 수 있도록 처리해야 한다.
       */
      for (let i = 6; i &gt;= 0; i--) {
        if (state.board[actions.payload.lineNumber][i] === null) {
          state.board[actions.payload.lineNumber][i] =
            state.currentPlayer === &quot;RED&quot; ? &quot;RED&quot; : &quot;YELLOW&quot;;
          location = i;
          break;
        }
      }

      state.markerCount += 1;

      let rowCount = 1;
      let colCount = 1;

      if (state.markerCount &gt;= 7) {
        for (let i = 1; i &lt;= 3; i++) {
          if ((rowCount || colCount) &gt;= 4) {
            break;
          }

          // 가로 테스트
          if (
            actions.payload.lineNumber + i &lt;= 6 &amp;&amp;
            state.board[actions.payload.lineNumber + i][location!] ===
              actions.payload.player
          ) {
            rowCount += 1;
          } else {
            break;
          }
        }

        for (let i = 1; i &lt;= 3; i++) {
          if ((rowCount || colCount) &gt;= 4) {
            break;
          }

          if (
            actions.payload.lineNumber - i &gt;= 0 &amp;&amp;
            state.board[actions.payload.lineNumber - i][location!] ===
              actions.payload.player
          ) {
            rowCount += 1;
          } else {
            break;
          }
        }
        // 세로 테스트
        for (let i = 1; i &lt;= 3; i++) {
          if ((rowCount || colCount) &gt;= 4) {
            break;
          }

          if (
            location! + i &lt;= 5 &amp;&amp;
            state.board[actions.payload.lineNumber][location! + i] ===
              actions.payload.player
          ) {
            colCount += 1;
          } else {
            break;
          }
        }

        for (let i = 1; i &lt;= 3; i++) {
          if ((rowCount || colCount) &gt;= 4) {
            break;
          }

          if (
            location! - i &gt;= 5 &amp;&amp;
            state.board[actions.payload.lineNumber][location! - i] ===
              actions.payload.player
          ) {
            colCount += 1;
          } else {
            break;
          }
        }
        // TODO: 대각선 테스트

        if (colCount &gt;= 4 || rowCount &gt;= 4) {
          state.winner = actions.payload.player;
        }
      }

      state.currentPlayer = actions.payload.player === &quot;RED&quot; ? &quot;YELLOW&quot; : &quot;RED&quot;;
    },
</code></pre>
<h3 id="문제점">문제점</h3>
<p>위 코드의 문제점은 반복된 코드가 많고, 대각선은 한 번에 4번을 테스트해야 한다는 것에 있다.</p>
<p>다행히, 중첩 반복문은 존재하지 않지만 가로/세로 체크에 각각 2번, 거기에 대각선까지 4번의 반복문을 작성해야 한다는 점에서 비슷한 코드가 반복된다는 점에서 가독성도 낮고 좋지 않은 코드라고 볼 수 있다.</p>
<p>이러한 문제점을 해결하기 위해 고민하던 중, 코딩 테스트에 자주 등장하는 <code>섬의 개수</code>와 비슷한 방법으로 해결하면 좋지 않을까, 하는 생각이 들어 <code>direction vector</code>을 적용해 보기로 했다.</p>
<p><code>섬의 개수</code>는 이중 배열 내에 있는 모든 값을 탐색하고, 섬일 경우를 체크하는 로직을 사용한다. 섬은 상하좌우/대각선으로 이루어져 있다.</p>
<p>내 경우는 최근에 둔 마커를 기준으로 마찬가지로 상하좌우 대각선 모든 방향을 탐색할 수 있도록 처리하고, 다른 사람이 둔 마커와 빈 공간을 바다라고 생각하고 처리하면 될 것 같다는 생각이 들어 이 방식을 선택했다.</p>
<h3 id="direction-vector">direction vector</h3>
<p>여기에 추가로 연결되어 있는 것만 체크해 준다.
승리 조건 테스트를 위해 모든 방향을 체크해 주어야 한다.</p>
<pre><code class="language-ts">// brute.ts
const board = [
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [null, null, null, null, null, null],
  [&quot;RED&quot;, null, &quot;RED&quot;, &quot;RED&quot;, null, null],
] as (null | &quot;RED&quot; | &quot;YELLOW&quot;)[][];

// 모든 방향을 체크하기 위해 방향 벡터를 사용한다.
const movement = [
  { dx: 1, dy: 0 }, // 오른쪽
  { dx: -1, dy: 0 }, // 왼쪽
  { dx: 0, dy: 1 }, // 위
  { dx: 0, dy: -1 }, // 아래
  { dx: 1, dy: 1 }, // 오른쪽 아래 대각선
  { dx: -1, dy: 1 }, // 왼쪽 아래 대각선
  { dx: 1, dy: -1 }, // 오른쪽 위 대각선
  { dx: -1, dy: -1 }, // 왼쪽 위 대각선
];

let x = 6;
let y = 1;
let currentPlayer = &quot;RED&quot;;

const checkDirection = (x: number, y: number, dx: number, dy: number) =&gt; {
  let count = 1; // 기존 마커도 추가.
  let nx = x + dx; // X축 각 방향별로 1칸씩 이동
  let ny = y + dy; // Y축 각 방향별로 1칸씩 이동

  while (
    // 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
    // 만약 마커가 보드를 넘어갈 경우, 종료.
    nx &gt;= 0 &amp;&amp;
    ny &gt;= 0 &amp;&amp;
    nx &lt;= 6 &amp;&amp;
    ny &lt;= 5 &amp;&amp;
    board[nx][ny] === currentPlayer
  ) {
    count++;
    nx += dx;
    ny += dy;
  }
  return count;
};

for (const { dx, dy } of movement) {
  const count = checkDirection(x, y, dx, dy);
  if (count &gt;= 4) {
    console.log(`${currentPlayer}가 승리했습니다.`);
    break;
  }
}
</code></pre>
<p>위 코드에서는 위, 아래, 오른쪽, 왼쪽, 오른쪽 아래, 오른쪽 위, 왼쪽 위, 왼쪽 아래  대각선을 모두 체크해 준다.</p>
<p>로직은 다음과 같다.</p>
<ol>
<li>이동해야 하는 방향 벡터를 담은 배열을 만들어 준다.</li>
<li>배열에 반복문으로 각 방향 벡터를 하나씩 전달해 준다. 이 방향 값을 가지고, 한 칸씩 이동하면서 연결되어 있는 마커를 체크한다.</li>
<li>이 때 체크하는 칸에 다른 색상의 마커가 있거나, 보드의 너비를 넘었을 경우, 종료한다.</li>
<li>어느 한 방향이라도 <code>count</code>가 4 이상일 경우, 조건을 충족하므로 종료시킨다.</li>
</ol>
<h4 id="문제점-1">문제점</h4>
<p>이 방식의 문제점은, 연결을 테스트하는 로직을 <code>한 방향</code>으로만 테스트한다는 것이다. 위에 있는 board에서처럼 <code>[&quot;RED&quot;, null, &quot;RED&quot;, &quot;RED&quot;, null, null]</code>로 이루어져 있고, <code>board[6][1]</code>의 칸에 두면 분명 4개의 마커가 이어져 있는데도, 카운트를 한 방향으로만 하기 때문에 승자로 처리하지 않는다.</p>
<h3 id="해결">해결</h3>
<p>방향 탐색을 가로, 세로, 양수 대각선, 음수 대각선으로 지정하고 <code>두 번 계산</code>한다.</p>
<pre><code class="language-ts">// brute.ts    
drop: (state, actions) =&gt; {
      if (state.winner !== null) {
        console.warn(`이미 종료된 게임입니다. 승자는 ${state.winner}입니다.`);
        return;
      }

      if (state.board[actions.payload.lineNumber][0] !== null) {
        console.warn(
          `${actions.payload.lineNumber + 1} 열은 이미 전부 채워진 열입니다.`
        );
        return;
      }
      let location: null | number = null;

      /**
       * 만약 선택한 행의 첫 번째 셀이 null이 아니면 리턴.
       */
      for (let i = 6; i &gt;= 0; i--) {
        if (state.board[actions.payload.lineNumber][i] === null) {
          state.board[actions.payload.lineNumber][i] =
            state.currentPlayer === &quot;RED&quot; ? &quot;RED&quot; : &quot;YELLOW&quot;;
          location = i;
          break;
        }
      }

      state.markerCount += 1;

      // 모든 방향을 체크하기 위해 방향 벡터를 사용한다.
      const movement = [
        { dx: 1, dy: 0 }, // 가로
        { dx: 0, dy: 1 }, // 세로
        { dx: -1, dy: 1 }, // 양수 대각선
        { dx: -1, dy: -1 }, // 음수 대각선
      ];

      // 연결 테스트
      const checkDirection = (dx: number, dy: number) =&gt; {
        let count = 1; // 기존 마커도 추가.

        let pnx = actions.payload.lineNumber + dx; // X축 각 방향별로 1칸씩 이동
        let pny = location! + dy; // Y축 각 방향별로 1칸씩 이동

        while (
          // 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
          // 만약 마커가 보드를 넘어갈 경우, 종료.
          pnx &gt;= 0 &amp;&amp;
          pny &gt;= 0 &amp;&amp;
          pnx &lt;= 6 &amp;&amp;
          pny &lt;= 5 &amp;&amp;
          state.board[pnx][pny] === actions.payload.player
        ) {
          count++;
          pnx += dx;
          pny += dy;
        }

        let mnx = actions.payload.lineNumber - dx; // X축 각 방향별로 1칸씩 이동
        let mny = location! - dy; // Y축 각 방향별로 1칸씩 이동

        while (
          // 각 좌표가 보드 내에 있고, 해당 위치에 있는 마커가 현재 유저의 마커와 같은 색상의 마커인지 확인한다.
          // 만약 마커가 보드를 넘어갈 경우, 종료.
          mnx &gt;= 0 &amp;&amp;
          mny &gt;= 0 &amp;&amp;
          mnx &lt;= 6 &amp;&amp;
          mny &lt;= 5 &amp;&amp;
          state.board[mnx][mny] === actions.payload.player
        ) {
          count++;
          mnx -= dx;
          mny -= dy;
        }

        return count;
      };

      for (const { dx, dy } of movement) {
        const count = checkDirection(dx, dy);

        if (count &gt;= 4) {
          state.winner = actions.payload.player;
          console.log(`${state.winner}가 승리했습니다.`);
          return;
        }
      }

      state.currentPlayer = actions.payload.player === &quot;RED&quot; ? &quot;YELLOW&quot; : &quot;RED&quot;;
    },</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[fire event vs user event]]></title>
            <link>https://velog.io/@no-pla/fire-event-vs-user-event</link>
            <guid>https://velog.io/@no-pla/fire-event-vs-user-event</guid>
            <pubDate>Tue, 04 Jun 2024 17:15:24 GMT</pubDate>
            <description><![CDATA[<p><code>React testing library</code>에서 이벤트를 발동시키는 방법에는 아래 두 가지가 있다.</p>
<ol>
<li><code>fire-event</code></li>
<li><code>user-event</code></li>
</ol>
<h3 id="fire-event의-한계점">fire-event의 한계점</h3>
<p>테스팅 라이브러리에 내장된 <code>fire-event</code>는 DOM 이벤트를 전송한다. 개발자가 모든 요소에서 모든 이벤트를 트리거할 수 있도록 해준다. 그러나, 일반적으로 하나의 상호작용에 한개 이상의 이벤트를 수행한다.</p>
<p>예를 들면 <code>input</code> 이벤트가 있다. 이메일의 유효성 검사를 테스트하는 로직을 작성하면 작성해야 하는 최소한의 로직은 다음과 같다.</p>
<ol>
<li>input에 포커스한다.</li>
<li>키보드 입력 이벤트가 발생한다.</li>
</ol>
<p><code>fireEvent</code>를 사용하면 값을 직접 변경한다. 빠르지만, 실제 유저가 상호작용하는 방식과 상대적으로 차이가 있다.
<code>user event</code>를 사용하면 위와 같은 직접적인 이벤트 대신, 사용자의 상호작용과 유사한 방식으로 상호작용한다. 테스트하는 요소가 현재 화면에 보이고, 상호 작용이 가능한지 테스트를 거친 후, 사용자기 직접 상호 작용을 하는 것처럼 DOM을 조작한다.</p>
<h3 id="user-event-사용하기">user-event 사용하기</h3>
<p>컴포넌트가 랜더링 되기 전에, <code>setup()</code> 테스트 자체에서 수행하거나, 설정 함수를 통하여, 호출하는 것이 좋다. 또한 테스팅 시, <strong>중첩을 피해야 하기 때문</strong>에 테스트마다 실행되는 <code>beforeAll</code>, <code>AfterAll</code>등의 함수 내부에 넣어 실행하지 않는 것이 바람직하다.</p>
<pre><code class="language-ts">test(&quot;버튼을 클릭하면 ~~~한다.&quot;, async () =&gt; {
    const user = userEvent.setup();
    render(&lt;MyComponent /&gt;)
    const button = screen.getByRole(&#39;button&#39;, {name: /click me!/i});

    await user.click(button);

    // 테스트 단언 작성
})</code></pre>
<blockquote>
<p><code>user event</code>는 항상 <code>Promise</code>를 반환한다. 즉, <code>user event</code>를 사용할 때마다 비동기 처리를 해주어야 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Parsing error: '>' expected.eslint]]></title>
            <link>https://velog.io/@no-pla/Parsing-error-expected.eslint</link>
            <guid>https://velog.io/@no-pla/Parsing-error-expected.eslint</guid>
            <pubDate>Mon, 03 Jun 2024 07:07:10 GMT</pubDate>
            <description><![CDATA[<p>새로운 프로젝트에 TDD를 사용하여 진행하기 위해, 테스트 코드를 작성하던 도중, <code>render</code>에서 계속 오류가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/b85e4a6b-93b7-4d3b-b5cd-bd30a9ee221f/image.png" alt=""></p>
<p><code>node_modules</code>를 지우고 재설치를 하고 온갖 사이트를 다 뒤져봐도 에러가 무엇인지 찾을 수 없었지만...</p>
<p>문제는 tsx로 된 요소를 테스트 하는데 파일명을 <code>landing.test.ts</code>로 작성하여 발생한 오류였다...</p>
<blockquote>
<p>확장명을 잘 체크하자...!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/no-pla/post/3c80bfb8-c206-4046-80e5-c66e2b2cc955/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[vite 세팅하기]]></title>
            <link>https://velog.io/@no-pla/vite-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/vite-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 31 May 2024 07:45:32 GMT</pubDate>
            <description><![CDATA[<h1 id="vite">Vite</h1>
<p>Vite(<del>yes 비트 not 바이트</del>)는 프랑스어로 &quot;빠르다&quot;를 의미한다. 빠르고 간결한 개발 경험에 초점을 맞춘 <code>빌드 도구</code>로, 다음과 같은 특징을 가진다.</p>
<ol>
<li>네이티브 ESM을 사용하여 빠른 개발 서버를 시작한다.</li>
<li>HMR(Hot Module Replacement)은 코드 변경 시, 변경된 모듈만 업데이트하여, 더 빠른 피드백을 받을 수 있도록 해 준다.</li>
<li>추가적인 모듈 설치 없이 TypeScript, JSX, CSS 등을 사용할 수 있다.</li>
<li>빌드 최적화</li>
<li>개발 서버와 빌드 과정에서 모두 사용 가능한 Rollup 플러그인 인터페이스를 제공한다.</li>
</ol>
<p>Vite는 플러그인 등을 통해, 프레임도구 지원이나, 다른 도구와의 통합이 가능하다. 또한 <code>esnext</code>를 변환 대상으로 하여 빌드를 수행하기 때문에 하위 호환을 생각하지 않아도 된다.</p>
<h2 id="설치">설치</h2>
<pre><code class="language-bash">npm create vite@latest</code></pre>
<p>위 커맨드를 실행해 준다.
그 뒤로는 프레임워크 등의 설정을 개발자의 취향(react, vue-ts ...)에 맞게 설정할 수 있다.</p>
<p>create된 vite 프로젝트에는 index.html 파일이 프로젝트의 루트에 위치해 있다.</p>
<p>이는 추가적 번들링 과정 없이, index.html 파일이 앱의 진입점이 되게끔 하기 위함이다. 여러 html 파일을 앱의 진입점으로 하는 <code>Multi-page apps</code>를 지원한다.</p>
<h4 id="zsh-command-not-found-vite">zsh: command not found: vite</h4>
<p>vite를 설치해 준 다음에 <code>npm run dev</code>를 해주면 위와 같은 에러가 발생하면서 실행이 되지 않는다.</p>
<pre><code class="language-bash">npm install </code></pre>
<p>로 번들을 설치해 주면 정상적으로 동작한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클래스, 인터페이스]]></title>
            <link>https://velog.io/@no-pla/%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@no-pla/%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Thu, 30 May 2024 04:50:25 GMT</pubDate>
            <description><![CDATA[<p>객체 지향 프로그래밍(클래스)의 핵심은 코드를 현실 세계와 유사한 형식을 사용하여, 로직을 논리적으로 분리해 개발자가 이해하기 쉽도록 하는 데에 있다.</p>
<h3 id="클래스와-인스턴스">클래스와 인스턴스</h3>
<p>객체 지향 프로그래밍에서 로직을 분할해 각 로직을 객체를 활용하여 관리할 수 있다. </p>
<p><code>클래스</code>는 여기에서 객체의 청사진이다. 객체는 해당 객체가 어떤 값을 가지고, 어떤 메서드를 가질지 정의되어 있는 클래스를 이용하여 객체를 생성할 수 있다. 클래스를 사용하면 데이터 값만 다른 객체를 쉽게 생성할 수 있다.</p>
<p>이렇게 클래스를 기반으로 생성된 객체를 클래스의 <code>인스턴스</code>라고 한다.</p>
<p>클래스는 다음과 같이 정의한다.</p>
<ul>
<li>변수명의 첫 글자는 대문자로 정한다.</li>
<li>클래스 내부에는 포함되어야 할 값과 타입을 설정할 수 있다.</li>
</ul>
<p>클래스를 이용해 객체를 생성할 때는 <code>new</code> 키워드를 사용한다. new 키워드를 통해 클래스를 생성하면 constructor가 실행되고, 해당 메서드를 호출하기 위해서는 인수를 건네주어야 한다.</p>
<p>클래스 내부에 포함된 함수는 <code>메서드</code>라고 하는데, 클래스에는 <code>constructor</code>라고 하는 특수한 메서드가 포함된다.</p>
<p>constructor 함수는 객체가 생성될 때 실행되어 초기화 작업을 한다.</p>
<pre><code class="language-ts">class Person {
  name: string;
  constructor(n: string) {
    this.name = n;
  }
}</code></pre>
<p>constructor는 객체가 생성될 때 전달 받은 값을 Person 객체의 name 값으로 전달한다.</p>
<h3 id="생성자-함수">생성자 함수</h3>
<p>생성자 함수는 클래스를 초기화할 때 호출되는 유틸리티 함수다. 여기에 추가로 생성된 클래스에 호출 가능한 함수인 메서드를 추가할 수 있다. 메서드의 소괄호는 전달할 매개변수가 없는 경우, 생략할 수 있다.</p>
<pre><code class="language-ts">class Person {
  name: string;
  constructor(n: string) {
    this.name = n;
  }
  sayHi () {
    console.log(`안녕, ${this.name}`)
  }
}</code></pre>
<p>constructor 함수와 마찬가지로, 메서드 내에서 클래스의 변수에 접근하기 위해서라면 <code>this</code> 키워드를 사용해야 한다.</p>
<h4 id="private-변수메서드">private 변수/메서드</h4>
<p>클래스를 사용하거나 수정하는 방법을 제한할 수 있다. 예를 들어 특정 배열에 값을 추가하는 메서드를 작성할 수 있다. 문제는 클래스 외부에서 클래스 내의 배열에 직접 접근하여 값을 추가하거나 삭제하는 등의 조작을 해도 에러가 발생하지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/758d5e78-dd88-40f7-94b0-a4e88f9abd57/image.png" alt=""></p>
<p>이러한 것을 막기 위해, <code>private</code>변수/메서드를 지정할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/8329482a-6580-4b48-8b57-8f6d2d3a7ad8/image.png" alt=""></p>
<p>private 키워드 같은 접근 제한자를 추가해 주면 예기치 못한 동작을 막을 수 있다.</p>
<h3 id="약식-초기화">약식 초기화</h3>
<p> 이전에는 클래스를 생성할 때, 매개변수로 값을 받아오고 constructor 메서드에서 초기화했다. 이를 짧게 단축할 수 있다.</p>
<pre><code class="language-ts">    // 이전
  name: string;
  private id: string
  constructor(n: string) {
    this.name = n;
  }
    // 이후
  constructor(private id: string, public name: string) {

  }
</code></pre>
<p>클래스의 변수 값들은 따로 지정해 주지 않으면 기본적으로 <code>public</code> 변수다. 따라서 명시적으로 표기할 필요는 없으나, 단축형으로 값을 정의함과 동시에 값을 할당할 때에는 <code>public</code> 변수도 명시적으로 작성해 주어야 한다.</p>
<h3 id="readonly">readonly</h3>
<p>변수가 한 번 지정된 이후 변하지 말야아 할 경우에는 <code>readonly</code> 키워드를 추가해 줄 수 있다.</p>
<pre><code class="language-ts">    constructor(private readonly id: string, public name: string) {
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/no-pla/post/a8e0155c-8c95-455d-87d9-f6bdffe5212e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[퀵 정렬]]></title>
            <link>https://velog.io/@no-pla/%ED%80%B5-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@no-pla/%ED%80%B5-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Wed, 29 May 2024 07:07:07 GMT</pubDate>
            <description><![CDATA[<p>퀵 정렬은 배열 내에서 무작위로 숫자를 하나 선택하고 그걸 기준(피벗)으로 잡는다. 그리고 나머지 숫자들을 비교하여 피벗보다 큰 숫자는 피벗의 오른쪽으로, 작은 숫자들은 피벗의 왼쪽으로 이동시킨다. 퀵 정렬도 병합 정렬처럼 분할 정복 패턴과 재귀를 사용한 방식이다.</p>
<p>퀵 정렬을 실행하면 배열은 다음과 같이 정렬된다.</p>
<pre><code>[피벗보다 작은 숫자] &lt; 피벗(기준이 되는 숫자) &lt; [피벗보다 큰 숫자]</code></pre><p>이제 피벗을 기준으로 이동한 피벗보다 작은 숫자와, 피벗보다 큰 숫자들은 다시 하나의 숫자가 남을 때까지 재귀를 사용하여 퀵 정렬을 반복하면 된다.</p>
<p>퀵 정렬은 피벗을 기준으로 나뉜 두 자식 배열에서 다시 피벗을 선정하여 그 자식이 숫자 하나로만 이루어진 자식이 될 때까지의 과정을 log₂n번 반복하여 정렬한다. 그리고 각 단계에서는 숫자와 피벗을 1번만 비교하기 때문에 각 단계에서는 O(n)의 시간 복잡도를 가진다.</p>
<p>따라서 퀵 정렬은 평균적으로 병합 정렬과 비슷한 시간 복잡도를 가지지만, 이미 정렬된 배열 혹은 랜덤으로 피벗을 선택할 때 가장 작은 수(혹은 가장 큰 숫자)를 순서대로 고르게 된다면 모든 단계에서 모든 숫자들이 한 쪽으로 이동하기 때문에 재귀가 n번 발생하여 최악의 경우 시간 복잡도는 O(n²)이 나올 수 있다.</p>
<pre><code class="language-js">/**
 *
 * @param {number[]} arr1
 * @param {number[]} arr2
 * @returns number[]
 */
function merge(arr1, arr2) {
  let answer = [];
  let i = 0;
  let j = 0;

  while (i &lt; arr1.length &amp;&amp; j &lt; arr2.length) {
    if (arr1[i] &lt; arr2[j]) {
      answer.push(arr1[i]);
      i++;
    } else {
      answer.push(arr2[j]);
      j++;
    }
  }

  while (i &lt; arr1.length) {
    answer.push(arr1[i]);
    i++;
  }

  while (j &lt; arr2.length) {
    answer.push(arr2[j]);
    j++;
  }

  return answer;
}

/**
 * @param {number[]} arr
 * @returns number[]
 */
function mergeSort(arr) {
  if (arr.length &lt;= 1) return arr;

  let mid = Math.floor(arr.length / 2);
  let left = mergeSort(arr.slice(0, mid));
  let right = mergeSort(arr.slice(mid));

  return merge(left, right);
}

console.log(mergeSort([1, 53, 23, 92, 54, 111]));
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[병합 정렬]]></title>
            <link>https://velog.io/@no-pla/%EB%B3%91%ED%95%A9-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@no-pla/%EB%B3%91%ED%95%A9-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Wed, 29 May 2024 06:03:58 GMT</pubDate>
            <description><![CDATA[<p>이전까지 배운 정렬 알고리즘은 성능이 좋지 않다.</p>
<p>병합 정렬은 이전에 배운 정렬 알고리즘(버블 정렬, 삽입 정렬, 선택 정렬)의 시간 복잡도 O(n²)를 O(n log n)까지 줄일 수 있다. 병합 정렬은 큰 배열을 더 작은 하위 배열로 나누어 정렬하는 <code>분할 정복(Divide and Conquer)</code> 패턴을 사용한 정렬 방법이다.</p>
<pre><code class="language-js">    [8, 2, 6, 3, 7, 1, 4, 9]
              ↓
  [8, 2, 6, 3] [7, 1, 4, 9]
              ↓
  [8, 2] [6, 3] [7, 1] [4, 9]
              ↓
[8] [2] [6] [3] [7] [1] [4] [9]
              ↓
  [2, 8] [3, 6] [1, 7] [4, 9]
              ↓
   [2, 3, 6, 8] [1, 4, 7, 9]
              ↓
    [1, 2, 3, 4, 6, 7, 8, 9]</code></pre>
<p>단일 요소를 가진 배열로만 이루어질 때까지 분할을 반복한다. 그리고 단일 요소로 이루어진 배열을 다시 정렬하며 병합한다. 그리고 정렬된 두 배열을 비교하여 마찬가지로 정렬된 형태로 병합한다. 그리고 모든 배열을 병합할 때까지 반복한다. 하나의 단계를 병합할 때는 시간복잡도가 그 단계의 숫자의 수, 즉 O(n)이 요구된다. n개의 숫자를 하나가 될 때까지 분할해 나가면 시간 복잡도는 O(log₂n)이 나온다.</p>
<p>즉, 이 패턴을 사용한 방식은 최고/최악 모두 O(n log n)이 나온다. 공간 복잡도도 O(n)으로 다른 정렬 방식(버블 정렬)보다 더 많은 공간을 요구한다.</p>
<pre><code class="language-js">/**
 *
 * @param {number[]} arr1
 * @param {number[]} arr2
 * @returns number[]
 */
function merge(arr1, arr2) {
  let answer = [];
  let i = 0;
  let j = 0;

  while (i &lt; arr1.length &amp;&amp; j &lt; arr2.length) {
    if (arr1[i] &lt; arr2[j]) {
      answer.push(arr1[i]);
      i++;
    } else {
      answer.push(arr2[j]);
      j++;
    }
  }

  while (i &lt; arr1.length) {
    answer.push(arr1[i]);
    i++;
  }

  while (j &lt; arr2.length) {
    answer.push(arr2[j]);
    j++;
  }

  return answer;
}

/**
 * @param {number[]} arr
 * @returns number[]
 */
function mergeSort(arr) {
  if (arr.length &lt;= 1) return arr;

  let mid = Math.floor(arr.length / 2);
  let left = mergeSort(arr.slice(0, mid));
  let right = mergeSort(arr.slice(mid));

  return merge(left, right);
}

console.log(mergeSort([1, 53, 23, 92, 54, 111]));
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘 - 삽입 정렬]]></title>
            <link>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%82%BD%EC%9E%85-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%82%BD%EC%9E%85-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Fri, 17 May 2024 06:24:02 GMT</pubDate>
            <description><![CDATA[<p>삽입 정렬은 버블 정렬, 선택 정렬과 유사한 정렬 알고리즘 패턴이다. 삽입 정렬은 배열을 순회하면서 이미 정렬된 부분과 비교하여 올바른 위치에 삽입하면서 정렬하는 패턴이다.</p>
<ol>
<li>배열의 두 요소를 최초 값으로 설정한다.</li>
<li>이때 선택된 값 중 첫 번째 값은 이미 정렬된 것으로 간주하기 때문에, 두 번째 값과 첫번째 값을 비교하여 두번째 값을 올바른 위치에 삽입한다.</li>
<li>정렬 후 그 다음 값을 비교하여 올바른 위치에 해당 값을 삽입한다.</li>
<li>모든 배열을 순회할 때까지 위 과정을 반복한다.</li>
</ol>
<pre><code class="language-js">function insertionSort(arr) {
  for (let i = 1; i &lt; arr.length; i++) {
    let currentVal = arr[i];
    for (let j = i - 1; j &gt;= 0 &amp;&amp; arr[j] &gt; currentVal; j--) {
      arr[j + 1] = arr[j];
      arr[j] = currentVal;
    }
  }
  return arr;
}

console.log(insertionSort([312, 32, 66, 12, 1, 8, 41, 12]));
</code></pre>
<p>첫 번째 요소는 이미 정렬된 값으로 간주하기 때문에 첫 번째 값의 인덱스를 배열의 두 번째 값인 1로 설정한다. 이 값은 배열을 순회하면서 앞의 값과 크기를 비교한다. currentVal을 기준으로 앞의 값이 현재 값보다 크면, currentVal의 뒤에 해당 값을 삽입한다.</p>
<p>currentVal을 기준으로 앞의 값이 현재 값보다 크면 currentVal의 뒤에 해당 값을 삽입한다. 그리고 currentVal을 한 칸 뒤로 옮기고 앞선 패턴을 반복한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘 - 선택 정렬]]></title>
            <link>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%A0%ED%83%9D-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%A0%ED%83%9D-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Fri, 17 May 2024 05:03:09 GMT</pubDate>
            <description><![CDATA[<p>선택 정렬은 버블 정렬과 유사하지만, 배열을 순회하면서 가장 작은 값을 찾고 그 값의 위치를 교환하면서 정렬하는 방식이다.</p>
<pre><code class="language-js">const arr = [7, 20, 1, 51, 32, 55, 126] // 시작값</code></pre>
<p>위 배열에서 우선 최초 값으로 20을 설정한다. 그리고 배열을 순회하면서 가장 작은 값인 1을 첫 번째 자리와 위치를 교환한다.</p>
<pre><code class="language-js">[1, 20, 7, 51, 32, 55, 126] // 1회차</code></pre>
<p>그리고 다음은 2번째 자리부터 가장 작은 값을 찾고, 또 찾은 가장 작은 수와 위치를 교환한다. 이 과정을 배열을 끝까지 순회할 때까지 반복한다.</p>
<pre><code class="language-js">[1, 7, 20, 32, 51, 55, 126] // 결과</code></pre>
<pre><code class="language-js">
/**
 * @param {number[]} arr
 * @returns number[]
 */
const selectionSort = (arr) =&gt; {
  for (let i = 0; i &lt; arr.length; i++) {
    let lowest = i;
    for (let j = i + 1; j &lt; arr.length; j++) {
      if (arr[j] &lt; arr[lowest]) {
        lowest = j;
      }
    }
    if (i !== lowest) {
      let temp = arr[i];
      arr[i] = arr[lowest];
      arr[lowest] = temp;
    }
  }

  return arr;
};

console.log(selectionSort([7, 20, 1, 51, 32, 55, 126, 13])); // [1, 7, 20, 32, 51, 55, 126]</code></pre>
<p>코드로 작성해 보면 다음과 같다.</p>
<ol>
<li>첫 번째 값을 최솟값(i: 최솟값의 인덱스)으로 설정한다.</li>
<li>최솟값의 다음 요소부터 작은 값이 있는지를 체크하고 루프가 끝날 때 선택된 값과 최솟값의 위치를 바꾼다.</li>
<li>정렬된 최솟값의 다음 값을 다시 최솟값으로 설정한다.</li>
<li>모든 요소를 순회할 때까지 반복한다.</li>
</ol>
<p>선택 정렬 알고리즘은 모든 요소들을 검사해야하기 때문에, 그리 시간복잡도가 좋지 않다. 최악의 경우, O(n²)이 나온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘 - 버블 정렬 알고리즘]]></title>
            <link>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B2%84%EB%B8%94-%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@no-pla/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B2%84%EB%B8%94-%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Thu, 16 May 2024 05:52:04 GMT</pubDate>
            <description><![CDATA[<p>정렬 알고리즘은 배열과 같은 컬렉션 내의 항목을 재배열하는 과정을 말한다. 정렬 알고리즘은 여러 종류가 있는데, 버블 정렬 알고리즘에 대해 정리해 보자.</p>
<h2 id="버블-정렬-bubble-sort">버블 정렬 (Bubble Sort)</h2>
<p>버블 정렬은 가장 비효율적인 정렬 방식 중 하나로, 실제로는 잘 사용되지 않는다. </p>
<p>버블 정렬은 아래와 같은 방식으로 동작한다.</p>
<ol>
<li>두 인접한 값을 비교한다.</li>
<li>앞에 있는 값이 뒤에 있는 값보다 크면 두 값의 위치를 바꾼다.</li>
<li>위 과정을 배열이 정렬될 때까지 반복한다.</li>
</ol>
<p>작동 방식은 아래 링크에서 시각화된 차트를 통해 확인할 수 있다.</p>
<p><a href="https://visualgo.net/en/sorting">버블 정렬 시각화 차트</a></p>
<h3 id="버블-정렬-코드">버블 정렬 코드</h3>
<pre><code class="language-js">const bubbleSort = (arr) =&gt; {
  let noSwaps;
  const swap = (arr, idx1, idx2) =&gt; {
    [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
    noSwaps = false;
  }

  for (let i = arr.length; i &gt; 0; i--) {
    noSwaps = true;
    for (let j = 0; j &lt; i - 1; j++) {
      if (arr[j] &gt; arr[j + 1]) {
        swap(arr, j, j + 1);
      }
    }
    if (noSwaps) break;
  }
  return arr;
}

console.log(bubbleSort([1, 2, 3, 10, 5, 6])); // [1, 2, 3, 5, 6, 10]</code></pre>
<p>버블 정렬은 최적화되지 않은 경우 이미 정렬된 배열에서도 모든 요소를 비교한다. 이를 최적화하기 위해 <code>noSwaps</code> 변수를 사용할 수 있다. <code>noSwaps</code> 변수는 배열을 한 번 순회하는 동안 요소의 교환이 발생했는지를 체크한다. </p>
<ul>
<li>배열의 요소를 하나씩 비교하여 교환이 발생하면 <code>noSwaps</code>를 <code>false</code>로 설정한다.</li>
<li>만약 교환이 발생하지 않았다면 배열이 이미 정렬된 상태이므로 반복을 중단하기 위해 <code>break</code>를 사용한다.</li>
</ul>
<p>이 최적화를 통해 불필요한 반복을 줄여 알고리즘의 효율성을 높일 수 있다.</p>
<h3 id="시간복잡도">시간복잡도</h3>
<p>버블 정렬은 일반적으로는 O(n²)의 복잡도를 가진다. 배열이 거의 정렬된 상태이고, <code>noSwap</code>을 사용한 경우에는 O(n)의 복잡도를 가진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[검색(서칭) 알고리즘]]></title>
            <link>https://velog.io/@no-pla/%EA%B2%80%EC%83%89%EC%84%9C%EC%B9%AD-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@no-pla/%EA%B2%80%EC%83%89%EC%84%9C%EC%B9%AD-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Mon, 13 May 2024 06:07:00 GMT</pubDate>
            <description><![CDATA[<p>서칭 알고리즘은 데이터 내에서 조건을 만족하는 값을 찾을 때 사용되는 알고리즘 패턴이다.
이러한 검색 알고리즘에는 여러 종류가 있다.</p>
<h2 id="선형-검색-알고리즘">선형 검색 알고리즘</h2>
<p>선형 알고리즘은 각 값을 하나 씩 확인하는 패턴으로, 자바스크립트에는 이미 해당 패턴을 사용한 메서드(indexOf, includes 등)가 존재한다. 첫 번째 값부터 마지막 값까지 찾으려는 값을 찾을 때까지 계속해서 조건을 비교하는 패턴이다.</p>
<p>가장 간단한 패턴이지만, 데이터가 많을 경우 비효율적(O(n))일 수 있다.</p>
<pre><code class="language-js">/**
 *
 * @param {number[]} arr
 * @param {number} num
 * @returns number
 */
function linearSearch(arr, num) {
  for (let i = 0; i &lt; arr.length; i++) {
    if (arr[i] === num) {
      return i;
    }
  }
  return -1;
}

console.log(linearSearch([10, 15, 20, 25, 30], 15)); // 1
console.log(linearSearch([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 4)); // 5
console.log(linearSearch([100], 100)); // 0
console.log(linearSearch([1, 2, 3, 4, 5], 6)); // -1
console.log(linearSearch([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 10)); // -1
console.log(linearSearch([100], 200)); // -1
</code></pre>
<h2 id="이진-검색-알고리즘">이진 검색 알고리즘</h2>
<p>이진 검색 알고리즘은 <code>정렬된 데이터</code>에서 사용 가능한 알고리즘 패턴으로, 선형 검색 알고리즘에 비해 개선된 패턴이다.</p>
<p><code>분할 정복</code>을 사용한 검색 알고리즘이다. 먼저 데이터의 중간 값을 구하고, 찾아야 하는 값이 중간 값보다 크면, 앞의 데이터를 소거하고 뒤의 데이터를 가진다. 그리고 남은 데이터로 찾아야 하는 값을 찾을 때까지 계속 반복 수행한다.</p>
<pre><code class="language-js">/**
 *
 * @param {number[]} arr
 * @param {number} num
 * @returns number
 */
function binarySearch(arr, num) {
  let left = 0;
  let right = arr.length - 1;

  while (left &lt;= right) {
    let center = Math.floor((left + right) / 2);

    if (arr[center] === num) {
      return center;
    } else if (arr[center] &gt; num) {
      right = center - 1;
    } else if (arr[center] &lt; num) {
      left = center + 1;
    }
  }
  return -1;
}
</code></pre>
<p>이진 검색 알고리즘의 시간 복잡도는 최악의 경우에도 O(log n)이다</p>
<h3 id="naive-string-search">naive string search</h3>
<p><code>naive string search</code>는 문자열에서 특정 패턴을 가진 문자열을 찾는 데에 사용되는 패턴이다.</p>
<p>문자열을 순회하면서 각 문자열이 패턴과 일치하는지 검사한다. 패턴과 일치하지 않는 문자열이 포함되어 있다면 다시 새롭게 검색을 시작한다. 만약 모든 문자열이 일치(j가 pattern의 길이와 같아짐)할 경우, 패턴을 찾은 것이다.</p>
<pre><code class="language-js">/**
 *
 * @param {string} str
 * @param {string} pattern
 */
function naiveStringSearch(str, pattern) {
  let answer = 0;
  for (let i = 0; i &lt; str.length; i++) {
    for (let j = 0; j &lt; pattern.length; j++) {
      if (pattern[j] !== str[i + j]) {
        break;
      } else if (j === pattern.length - 1) {
        answer += 1;
      }
    }
  }
  return answer;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 공부하기]]></title>
            <link>https://velog.io/@no-pla/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@no-pla/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 09 May 2024 06:25:04 GMT</pubDate>
            <description><![CDATA[<p>코드를 작성하다 보면 잘못된 타입의 변수를 전달해 주거나, 오타로 인해 에러가 발생할 때가 있다. 이런 경우, 규모가 작은 프로젝트라면 쉽게 원인을 찾을 수 있다. 그러나, 프로젝트가 크다면, 어디가 잘못되었는지를 알기 힘들다. 이것을 해결하기 위한 자바스크립트 슈퍼셋, 타입스크립트가 등장했다.</p>
<p>이전까지는 타입스크립트를 사용했지만, <code>interface</code>를 사용한 기본적인 변수들의 타입 지정에 불과했다. 타입스크립트를 제대로 공부해 보기로 했다.</p>
<h2 id="복습">복습</h2>
<p>타입 지정을 해줄 때는 기본적으로 변수 뒤에 콜론과 함께 소문자로 된 타입을 지정해 준다.</p>
<pre><code class="language-ts">function add(n1: number, n2: number) {
  return n1 + n2;
}</code></pre>
<h3 id="타입의-종류">타입의 종류</h3>
<p>타입스크립트에는 자바스크립트가 기본적으로 제공하는 숫자, 문자열, 불리언 타입 외에, 자바스크립트에 존재하지 않는 다른 타입들도 존재한다.</p>
<h4 id="튜플">튜플</h4>
<p>튜플은 배열과 유사하지만, 타입과 길이가 고정된다. </p>
<pre><code class="language-ts">role: [number, string]</code></pre>
<p>위와 같은 방식으로 튜플을 선언해 주면, 겉보기에는 배열과 같지만, 배열의 길이를 바꾸거나, 각 요소의 타입을 바꿀 수 없다.</p>
<p>길이가 2개이고, 첫 번째 요소는 숫자, 두 번째는 문자열인 배열만 들어갈 수 있다.</p>
<p>잘못된 타입(e.g., boolean)을 넣어주면 에러가 발생하기는 하지만, 그러나 위 배열에 push 등의 메서드를 사용하여 길이를 변화시키거나, 숫자나 문자열 타입을 추가하여도 에러가 발생하지 않는다. </p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/d9b37414-555b-485a-a8c3-f56cbdc15f19/image.png" alt=""></p>
<p>타입스크립트가 에러를 잡지 못하기 때문인데, 이러한 에러는 타입스크립트 3.4에서 새롭게 등장한 <code>as const</code>를 사용하면 된다.
<img src="https://velog.velcdn.com/images/no-pla/post/8755dd54-bea2-4577-bed3-11012c6eb985/image.png" alt=""></p>
<h3 id="enum">Enum</h3>
<p>Enum 타입도 타입스크립트에만 존재하는 타입이다. 특정 요소를 숫자로 구분하는 변수를 지정할 수 있다. 그러나 어떤 숫자가 어떤 요소를 뜻하는지 잊어버릴 수 있다. 이를 위해 요소에 라벨을 달아주는 것이 <code>enum</code> 타입이다.</p>
<p>자바스크립트 환경에서 전역 변수를 선언하고 그에 접근할 수 있지만, 이를 enum타입으로 대체할 수 있다.</p>
<pre><code class="language-js">const ADMIN = 0;
const USER = 1;</code></pre>
<p>위처럼 선언된 것을 </p>
<pre><code class="language-ts"></code></pre>
<p>이런 식으로 선언하면 자동으로 각 요소는 0과 1을 가지게 된다. 그리고 개발자도 숫자로 작성된 기억하기 어려운 요소 대신, 문자열로 쉽게 접근할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/7330a743-c3bd-401b-8365-e1ef85edcf06/image.png" alt=""></p>
<p>각 요소는 0부터 순서대로 지정되지면 등호를 사용하여 원하는 숫자를 지정할 수 있다.</p>
<h4 id="any">any</h4>
<p>any는 가장 유연한 타입이다. 이 타입을 지정하면 어떤 타입의 값도 들어올 수 있다. <del>anyscript</del></p>
<p>any만 사용하면 타입스크립트를 사용하는 의미가 없기 때문에, 사용을 지양하는 것이 좋다.</p>
<h4 id="유니언">유니언</h4>
<p>유니언 타입은 변수에 하나 이상의 타입을 지정하고 싶을 때 사용할 수 있다. 만약 특정 배열에 숫자와 문자열이 있다면, 다음과 같이 타입을 지정해줄 수 있다.</p>
<pre><code class="language-ts">const arr: (number | string)[] = [&quot;a&quot;, 410, &quot;b&quot;];</code></pre>
<h4 id="리터럴-타입">리터럴 타입</h4>
<p>리터럴 타입은 해다 변수가 정확히 어떤 값을 가져야하는지 지정해 주는 것이다.</p>
<pre><code class="language-ts">age: &#39;minor&#39; | &#39;adult&#39;</code></pre>
<h4 id="타입-알리어스">타입 알리어스</h4>
<p>여러 개의 타입을 한 번에 선언해 줄 수 있다. <code>type</code> 을 사용하여 생성한 타입은, 여러 번 재사용할 수 있고 필요에 따라 확장이 가능하다.</p>
<pre><code class="language-ts">type human = {
    name: string;
      age: number;
  ...
}</code></pre>
<h4 id="함수-반환-타입">함수 반환 타입</h4>
<p>함수의 리턴 값에도 타입을 지정해 줄 수 있다.</p>
<pre><code class="language-ts">const add = (num1: number, num2: number): number =&gt; {
  return num1 + num2;
};
</code></pre>
<p>그러나 이 경우는 명시적으로 지정하기 보다는 타입스크립트가 스스로 추론할 수 있도록 하는 것이 좋다.</p>
<p>아무것도 반환하지 않는 함수의 리턴 타입은 <code>void</code>이다.</p>
<h4 id="함수-타입">함수 타입</h4>
<p>함수를 다른 변수에 지정했을 때, 타입을 지정하는 법을 알아보자. 아래처럼 add함수를 addNumber에 지정한 다음,</p>
<pre><code class="language-ts">const add = (num1: number, num2: number) =&gt; {
  return num1 + num2;
};

const minus = (num1: number, num2: number) =&gt; {
  console.log(num1 - num2);
};

let addNumbers;

addNumbers = add;
addNumbers = 123;
console.log(addNumbers);

// 위 처럼 숫자 타입을 지정해도 아무런 에러가 발생하지 않는다. 여기에 함수 타입을 지정할 수 있는데, 이 경우는 `함수가 할당되었는가`만 판단하기 때문에, add의 구조로 되어있지 않은 함수를 지정해도 에러가 발생하지 않는다.
let addNum: Function;
addNum = minus;

// 이런 경우에는 인수와 리턴 값을 전달해 주어야 한다.

let addNumber: (a: number, b: number) =&gt; number;
addNumber = add;
addNumber = minus; // 에러 발생

console.log(addNumber(&quot;123&quot;, 123)); // 에러 발생
console.log(addNumber(123, 123));</code></pre>
<p>콜백 함수에도 타입을 지정할 수 있다. 그러나, 콜백 함수에서 아무것도 리턴하지 않는다고 지정했음에도 콜백 함수 내에서 결과값을 리턴에도 에러가 발생하지 않는다.
<img src="https://velog.velcdn.com/images/no-pla/post/e9289589-3397-45ee-813c-6069a8fc3326/image.png" alt=""></p>
<pre><code class="language-ts">const addAndHandle = (n1: number, n2: number, cb: (num: number) =&gt; void) =&gt; {
  const res = cb(n1 + n2);
  console.log(res);
};

addAndHandle(123, 321, (result) =&gt; {
  console.log(result);
  return result; // 에러가 발생하지 않음
});</code></pre>
<h4 id="unknown">unknown</h4>
<p>unknown 타입은 어떤 타입이 들어올 지 모로는 상황에 사용할 수 있다. <code>any</code>타입과 유사해 보이지만 몇 가지 차이점이 있다.</p>
<p><img src="https://velog.velcdn.com/images/no-pla/post/451787b6-ab9d-4b0a-a175-6693ef97535a/image.png" alt=""></p>
<pre><code class="language-ts">let variable: unknown;
let me: string;
variable = 1;
variable = &quot;123&quot;;

me = variable;

if (typeof variable === &quot;string&quot;) {
  me = variable;
}
</code></pre>
<p>위에서 variable에는 문자열이 할당되어 있고, me의 타입은 문자열을 받아야 한다. variable의 타입이 any인 경우에는 아무런 에러도 발생하지 않는다. 그러나 unknown타입인 variable은 me에 할당할 수 없다. unknown타입을 다른 변수에 할당하기 위해서는 추가로 타입 검사가 필요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sliding Window Pattern]]></title>
            <link>https://velog.io/@no-pla/Sliding-Window-Pattern</link>
            <guid>https://velog.io/@no-pla/Sliding-Window-Pattern</guid>
            <pubDate>Thu, 09 May 2024 05:06:16 GMT</pubDate>
            <description><![CDATA[<p>슬라이딩 윈도우 패턴은 배열 등의 항목을 순차적으로 검사하면서 고정된 크기의 하위 집합의 값을 구할 때 유용한 패턴이다. 처음 n개를 배열로 묶어 필요한 값을 계산하고 한 칸씩 뒤로 움직이며 계산하는 패턴이다.</p>
<pre><code class="language-js">const solution = (arr, num) =&gt; {
  if(num &gt; arr.length) return null;
  let maxSum = 0;
  let tempSum = 0; // 현재 윈도우의 합계를 저장
  for(let i = 0; i &lt; num; i++) {
    maxSum += arr[i]; // 초기값
  }
  tempSum = maxSum;
  for(let i = num; i &lt; arr.length; i++) {
      tempSum = tempSum - arr[i - num] + arr[i]; // 맨 앞의 값을 삭제하고 맨 뒤에 값을 더함
    maxSum = Math.max(maxSum, tempSum);
  }
  return maxSum;
}</code></pre>
<p>이 방식을 사용하면 매번 최댓값을 계산할 때마다 새롭게 배열을 생성하지 않아도 되기 때문에 시간복잡도가 낮아진다.</p>
]]></description>
        </item>
    </channel>
</rss>