<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>berry_hennie.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요</description>
        <lastBuildDate>Wed, 13 May 2026 00:00:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>berry_hennie.log</title>
            <url>https://velog.velcdn.com/images/berry_hennie/profile/ad6ee035-a09b-48fa-8f02-21138fc35a1a/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. berry_hennie.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/berry_hennie" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[QueryClient 인스턴스는 어떻게 전역으로 공유되는가]]></title>
            <link>https://velog.io/@berry_hennie/queryclient-contextapi</link>
            <guid>https://velog.io/@berry_hennie/queryclient-contextapi</guid>
            <pubDate>Wed, 13 May 2026 00:00:47 GMT</pubDate>
            <description><![CDATA[<p>QueryClientProvider의 역할</p>
<p>-&gt; 아래 예제 코드를 보자.</p>
<pre><code class="language-tsx">
import { QueryClient, QueryClientProvider } from &#39;@tanstack/react-query&#39;;

  const queryClient = new QueryClient({                                                                                  
    defaultOptions: {                                                                                                       
      queries: {                                                    
        staleTime: 1000 * 60 * 5, // 5분
        retry: 1,
      },
    },
  });

  export function App() {
    return (
      &lt;QueryClientProvider client={queryClient}&gt;
        &lt;MyApp /&gt;
      &lt;/QueryClientProvider&gt;
    );
  }</code></pre>
<p>여기서 QueryClientProvider의 역할은 무엇인가?
-&gt;  QueryClientProvider는 생성된 queryClient 인스턴스를 하위 컴포넌트 트리 전체에서 사용할 수 있도록 주입하는 역할을 한다. 이는 React의 Context API를 통해 동작한다.</p>
<p>만약에 실제로 QueryClientProvider 를 구현하게 된다면 실제 아래와 같은 예시일것이다.</p>
<pre><code class="language-tsx">// QueryClientProvider 내부 동작 (단순화)
  const QueryClientContext = React.createContext&lt;QueryClient | null&gt;(null);

  export function QueryClientProvider({ client, children }) {
    return (
      &lt;QueryClientContext.Provider value={client}&gt;
        {children}
      &lt;/QueryClientContext.Provider&gt;
    );
  }

  export function useQueryClient() {
    const client = React.useContext(QueryClientContext);
    if (!client) throw new Error(&#39;No QueryClient set&#39;);
    return client;
  }
</code></pre>
<p> Context API의 3단계 패턴과 완전히 동일하다.</p>
<p>  ┌──────┬──────────────────┬────────────────┐
  │ 단계 │       API        │      역할      │
  ├──────┼──────────────────┼────────────────┤
  │ 1    │ createContext()  │ 저장 공간 생성 │
  ├──────┼──────────────────┼────────────────┤
  │ 2    │ Context.Provider │ 값 주입        │
  ├──────┼──────────────────┼────────────────┤
  │ 3    │ useContext()     │ 값 꺼내기      │
  └──────┴──────────────────┴────────────────┘</p>
<p> 즉, QueryClientProvider는 이 패턴을 라이브러리가 대신 구현해준 것이다. useContext의 역할은 새로운 클라이언트를 만드는게 아니라 Provider에 저장된 값의 참조를 반환하기 때문에 항상 같은 인스턴스를 반환한다.</p>
<p>번외로 아래와 같이 소비하는 컴포넌트의 예제 코드이다.</p>
<pre><code class="language-tsx"> import { useQueryClient, useMutation } from &#39;@tanstack/react-query&#39;;

  function TodoItem({ id }: { id: number }) {                                                                            
    const queryClient = useQueryClient(); // QueryClient 인스턴스 꺼내기

    const deleteMutation = useMutation({
      mutationFn: () =&gt; deleteTodo(id),
      onSuccess: () =&gt; {
        // 삭제 후 목록 캐시 무효화 → 자동 리페치
        queryClient.invalidateQueries({ queryKey: [&#39;todos&#39;] });
      },
    });

    return (
      &lt;button onClick={() =&gt; deleteMutation.mutate()}&gt;
        삭제
      &lt;/button&gt;
    );
  }

</code></pre>
<p> QueryClient 생성 후, 아래와 같이 래퍼 컴포넌트로 커스터마이징해서 사용할 수도 있다.</p>
<pre><code class="language-tsx">function QueryClientWithAlerts({ children }: { children: ReactNode }) {
  const alertApi = useApi(alertApiRef)
  const alertApiLatest = useRef(alertApi)
  alertApiLatest.current = alertApi

  const [queryClient] = useState(
    () =&gt;
      new QueryClient({
        defaultOptions: {
          queries: {
            retry: 0,
            refetchOnWindowFocus: false,
            refetchOnReconnect: false,
          },
        },
        mutationCache: new MutationCache({
          onError: error =&gt; {
            alertApiLatest.current.post({
              message: error instanceof Error ? error.message : &#39;요청 처리 중 오류가 발생했습니다.&#39;,
              severity: &#39;error&#39;,
            })
          },
        }),
      }),
  )

  return &lt;QueryClientProvider client={queryClient}&gt;{children}&lt;/QueryClientProvider&gt;
}
</code></pre>
<p>순수한 QueryClientProvider를 그대로 노출하지 않고, 필요한 기능을 추가한 래퍼 컴포넌트로 감싸서 사용하는 패턴이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React의 기본]]></title>
            <link>https://velog.io/@berry_hennie/basic-react</link>
            <guid>https://velog.io/@berry_hennie/basic-react</guid>
            <pubDate>Mon, 11 May 2026 00:10:27 GMT</pubDate>
            <description><![CDATA[<h3 id="1indexhtml">1.index.html</h3>
<ul>
<li>브라우저에서 접속시 가장 기준점이 되는 파일로, /public 폴더에 위치하며, 루트요소는 리액트앱의 실행결과를 출력한다.</li>
</ul>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
  &lt;html lang=&quot;ko&quot;&gt;
    &lt;head&gt;                                                                            &lt;meta charset=&quot;UTF-8&quot; /&gt;
      &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
      &lt;title&gt;My App&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;  // 루트요소라고 부른다
    &lt;/body&gt;
  &lt;/html&gt;
</code></pre>
<h3 id="2-indexjs">2. index.js</h3>
<ul>
<li>리액트앱이 실행할때 가장 먼저 호출되는 엔트리 파일이다.</li>
</ul>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import App from &#39;./App&#39;;

  const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
  root.render(
    &lt;React.StrictMode&gt;
      &lt;App /&gt;
    &lt;/React.StrictMode&gt;
  );</code></pre>
<ul>
<li>createRoot(container , {option})을 인자로 가진다. 컨테이너는  React가 마운트할 Dom요소를 의미한다. createRoot 함수 자체가 동시성을 제공하는 스위치 역할을 한다. 실제 동시서을 하려면 훅을 사용해야하지만, 18이전 버전에서 사용한다고 한들 실제 적용되지 않는다. createRoot 자체가 이 앱을 Concurrent스케쥴러로 돌려라고 알려주는 역할이다.</li>
</ul>
<p>이전 버전(18이전 버전)은 동시성을 제공하지 않는다.(아래코드는 이전 버전)</p>
<pre><code class="language-jsx">import ReactDOM from &#39;react-dom&#39;;
ReactDOM.render(&lt;App/&gt;, document.getElementById(&#39;root&#39;)); </code></pre>
<h3 id="3-dom까지-반영되는-과정">3. Dom까지 반영되는 과정</h3>
<p>리액트는 가상DOM을 쓴다는것은 많이 알려져있다. 실제Dom까지 반영되는 과정은 아래와 같다. 
크게 2가지 과정으로 나눠진다. 상태 변경 → 렌더링(컴포넌트 실행) → Reconciliation(Diff) → Commit(DOM 반영)</p>
<ul>
<li>상태변경 : state, props, store와 같은 값들이 변경시를 의미한다.</li>
<li>렌더링: 컴포넌트 함수를 실행하는 과정을 의미한다.(계산 과정이다)</li>
<li>Reconciliation : VDom의 이전과 새VDom의 diff를 찾아내는 과정이다.</li>
<li>commit : 실제 dom에 반영하는 과정</li>
</ul>
<p>이때 commit 직후 Paint이전에 호출되는 훅은 useLayoutEffect이며 Paint이후에 호출되는 훅은 useEffect이다.</p>
<p>cf)Paint란? 브라우저가 픽셀을 화면에 그리는 단계이다.(리액트가 관여하는 단계는 아니고 브라우저가 관여하는 단게이다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[backstage] 플러그인 개발 후기]]></title>
            <link>https://velog.io/@berry_hennie/backstage-plugin</link>
            <guid>https://velog.io/@berry_hennie/backstage-plugin</guid>
            <pubDate>Sat, 09 May 2026 03:06:14 GMT</pubDate>
            <description><![CDATA[<h3 id="왜-backstage는-모든-것을-플러그인으로-쪼개놓았을까">왜 Backstage는 모든 것을 플러그인으로 쪼개놓았을까?</h3>
<p>Backstage를 처음 접하고 yarn new 명령어로 백엔드 플러그인을 생성해 보면, 보편적인 라이브러리 구성이나 폴더 구조가 생각보다 복잡하게 나뉘어 있다는 느낌을 받게 됩니다. 단순히 기능을 만드는 것을 넘어 &quot;왜 굳이 플러그인이라는 개념을 도입했을까?&quot; 혹은 &quot;왜 백엔드 앱과 플러그인 앱을 엄격하게 분리했을까?&quot;라는 근본적인 의문이 생기기 마련입니다.</p>
<p>이 설계의 배경에는 Spotify가 겪었던 대규모 조직의 개발 효율성 고민이 녹아 있습니다. 수천 명의 개발자가 사용하는 포털을 단 한 팀이 전담해서 만드는 것은 현실적으로 불가능합니다. 그래서 도입된 것이 바로 &#39;플러그인 중심 아키텍처&#39;입니다.</p>
<p>이 구조의 가장 큰 목적은 코드 소유권의 분산에 있습니다. 인프라 팀, 보안 팀, 데이터 팀 등 각 전문 부서가 자신들이 필요한 도구를 독립적인 플러그인 형태로 직접 개발하고 관리하게 함으로써, 중앙 팀의 병목 현상을 없애고 각 팀에 자율성을 부여한 것입니다.</p>
<p>또한, 이는 시스템의 안정성과도 직결됩니다. 특정 팀에서 개발한 플러그인에 이슈가 발생하더라도 서비스 전체가 마비되는 최악의 상황을 방지합니다. 프론트엔드에서는 에러 바운더리를 통해 해당 컴포넌트만 에러를 표시하고, 백엔드 역시 독립된 라우트로 동작하기 때문에 문제가 발생한 특정 기능만 멈출 뿐 나머지 카탈로그나 템플릿 기능은 정상적으로 유지됩니다.</p>
<p>결국 Backstage가 앱(App)과 플러그인(Plugin)을 철저히 나눈 이유는 명확합니다. 앱은 인증이나 데이터베이스 연결 같은 공통 기반 시설을 제공하는 &#39;표준 껍데기&#39; 역할을 하고, 플러그인은 그 위에서 돌아가는 &#39;독립적인 비즈니스 로직&#39;이 됩니다. 이러한 구조 덕분에 개발자는 공통 설정에 신경 쓰지 않고 오직 기능 개발에만 집중할 수 있으며, 기업은 필요한 플러그인만 선택적으로 조합해 우리 회사만의 맞춤형 포털을 완성할 수 있게 되는 것입니다.</p>
<h3 id="코드백앤드-플러그인-살펴-보기">코드(백앤드 플러그인) 살펴 보기</h3>
<h4 id="plugints-뜯어보기">plugin.ts 뜯어보기</h4>
<p>plugin.ts는 Backstage 백엔드 플러그인의 진입점(Entry Point)입니다. 단순히 코드가 시작되는 지점을 넘어, 플러그인이 동작하기 위해 필요한 부품들을 정의하고 조립하는 Composition Root(조립 레이어) 역할을 수행합니다.</p>
<ul>
<li><p>이 파일은 비즈니스 로직을 직접 수행하지 않습니다. 대신 &quot;우리 플러그인은 로거가 필요하고, 설정값이 필요해&quot;라고 선언한 뒤, 준비된 부품들을 router, service, repository에 공급하여 하나의 기계처럼 맞물려 돌아가게 만듭니다.</p>
</li>
<li><p>계층 구조상의 위치: Router, Service, Repository 어느 계층에도 속하지 않는 최상위 레이어입니다. 모든 계층의 인스턴스를 생성하고 연결하는 &#39;DI 컨테이너&#39;로서 존재합니다.</p>
</li>
</ul>
<h4 id="선언만-했는데-어떻게-주입di이-될까">선언만 했는데 어떻게 주입(DI)이 될까?</h4>
<p>가장 궁금해하신 &quot;단순히 deps에 선언만 했는데 어떻게 인스턴스가 자동으로 들어오는가?&quot;에 대한 답은 Backstage 프레임워크의 의존성 주입(Dependency Injection) 엔진에 있습니다.</p>
<p>시스템은 init 함수를 실행하기 전, deps에 적힌 서비스들의 인스턴스를 미리 준비합니다.</p>
<p>준비된 인스턴스들을 init({ logger, config, ... }) 처럼 함수의 인자로 쏙 넣어줍니다.</p>
<p>개발자는 인스턴스를 직접 생성(new Logger())할 필요 없이, 이미 완성된 객체를 받아서 쓰기만 하면 됩니다.</p>
<p>결론: 맞습니다. 선언이 곧 요청입니다. Backstage 프레임워크가 일종의 &#39;중매인&#39; 역할을 하여, 필요한 서비스와 플러그인을 런타임에 자동으로 연결해 주는 구조입니다.</p>
<pre><code class="language-ts">export const exampleBackendPlugin = createBackendPlugin({
  pluginId: &#39;example&#39;,
  register(env) {
    env.registerInit({
      // 1. [선언] &quot;이 서비스 토큰들에 해당하는 인스턴스를 가져다줘!&quot; (주문서)
      deps: {
        logger: coreServices.logger,
        config: coreServices.rootConfig,
        httpRouter: coreServices.httpRouter,
        // 필요하다면 더 추가 가능 (예: database: coreServices.database)
      },

      // 2. [수령 및 조립] 시스템이 위에서 요청한 인스턴스들을 init 함수의 인자로 배달함
      async init({ logger, config, httpRouter }) {
        // 여기서부터는 배달된 진짜 객체(logger, config 등)를 사용해서 
        // 로직을 조립하기만 하면 됩니다.

        httpRouter.use(
          await createRouter({
            logger,
            config,
          }),
        );
      },
    });
  },
});</code></pre>
<h3 id="routerts-뜯어보기">Router.ts 뜯어보기</h3>
<ul>
<li>http의 요청을 받아 주입받은 서비스를 통해 로직을 처리하고 응답을 돌려주는 역할을 한다. 보통 Router - Service - Repository 계층을 이야기한다.</li>
</ul>
<pre><code class="language-ts">import { Router } from &#39;express&#39;;
import { HttpAuthService, LoggerService } from &#39;@backstage/backend-plugin-api&#39;;

// 1. 필요한 부품들을 객체(Options) 형태로 정의합니다.
export interface RouterOptions {
  logger: LoggerService;
  httpAuth: HttpAuthService;
}

export async function createRouter(
  options: RouterOptions,
): Promise&lt;express.Router&gt; {
  const { logger, httpAuth } = options;
  const router = Router();

  // 2. 실제 API 핸들러 작성
  router.get(&#39;/hello&#39;, async (req, res) =&gt; {
    // [보안 체크] 말씀하신 allow 옵션을 사용하는 핵심 지점입니다!
    const credentials = await httpAuth.credentials(req, {
      allow: [&#39;user&#39;, &#39;service&#39;], // 사람과 서비스 모두 허용
    });

    logger.info(`Request received from: ${credentials.principal.userEntityRef}`);

    res.json({ status: &#39;ok&#39;, message: &#39;Hello from Backstage!&#39; });
  });

  return router;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[backstage][catalog]The Life of an Entity]]></title>
            <link>https://velog.io/@berry_hennie/backstagecatalogThe-Life-of-an-Entity</link>
            <guid>https://velog.io/@berry_hennie/backstagecatalogThe-Life-of-an-Entity</guid>
            <pubDate>Sat, 14 Mar 2026 02:31:55 GMT</pubDate>
            <description><![CDATA[<h2 id="the-life-of-an-entity">The Life of an Entity</h2>
<p>카탈로그 백앤드의 내부 동작 방식에 관련된 설명이다. 주로 카탈로그를 설치하거나 확장하려는 대상으로 한다.</p>
<h3 id="핵심개념">핵심개념</h3>
<ul>
<li>카탈로그는 허버 역할을 한다. 다양한 출처에서 엔티티를 수집하고 데이터베이스에 저장한뒤, 자동 처리 후 API를 통해 외부에 제공한다.</li>
</ul>
<p>개발자가 커스터마이징 할 수 있는 주요 확장 포인트는 3가지이다.</p>
<ul>
<li>entity provider- 외부 소스에서 원시 데이터 카탈로그 공급</li>
<li>Policy- 엔티티 형태에 대한 기본 규칙 설정</li>
<li>Processor- 원시 데이터를 검증,분석, 변환하며 최종 형태로 만듬</li>
</ul>
<divider/>


<p>3단계 처리 흐름</p>
<p>1.단계(수집)</p>
<ul>
<li>Entity Provider가 외부 소스에서 데이터를 가져와 데이터 베이스에 저장</li>
<li>기본 Provider: 사용자 등록 URL처리용, 앱 설정 처리용</li>
<li>엔티티가 kind, metadata.name 등 기본 스키마를 갖추는지 최소한의 유효성 검사 수행</li>
<li>Provider가 엔티티 삭제를 알리면 -&gt; 해당 엔티티 관련 데이터 즉시 삭제</li>
</ul>
<p>2단계. 처리(Processing) </p>
<ul>
<li>수집된 엔티티를 Processor들이 순서대로 처리</li>
<li>각 Processor는 엔티티를 수정하거나, 관계(오류), 새 엔티티등을 생성 </li>
<li>오류가 발생하면 이전 정상 버전을 유지하고 오류를 정보를 별도 저장</li>
</ul>
<p>3단계 스티칭(stiching)
리 단계의 모든 결과물을 하나의 최종 엔티티로 조합
처리된 엔티티 + 오류 + 관계(들어오는 것 + 나가는 것 모두) 통합
엔티티 해시값을 비교해서 변경이 있을 때만 재스티칭 (성능 최적화)
현재 이 단계는 커스터마이징 불가 → 수정이 필요하면 수집/처리 단계에서 해야 함</p>
<p>예시 코드는 아래에 대한 설명이다.</p>
<p>전체 Entity.yaml 파일</p>
<pre><code class="language-yaml">
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-service
  description: &quot;주문 처리 서비스&quot;
  tags:
    - backend
    - order
  annotations:
    github.com/project-slug: my-org/my-service
spec:
  type: service
  lifecycle: production
  owner: team-backend
  dependsOn:
    - component:payment-service
    - resource:my-database

</code></pre>
<ol start="2">
<li>카탈로그 설정(app-config.yaml)</li>
</ol>
<pre><code class="language-yaml"># app-config.yaml
catalog:
  orphanStrategy: keep  # 고아 엔티티 유지 설정

  locations:
    # 단일 파일 등록
    - type: url
      target: https://github.com/my-org/my-service/blob/main/catalog-info.yaml

    # 여러 파일을 한번에 등록
    - type: url
      target: https://github.com/my-org/my-repo/blob/main/all-services.yaml
</code></pre>
<ol start="3">
<li>Entity Provider(Typescript)</li>
</ol>
<pre><code class="language-typescript">import {
  EntityProvider,
  EntityProviderConnection,
} from &#39;@backstage/plugin-catalog-node&#39;;
import { Entity } from &#39;@backstage/catalog-model&#39;;

export class MyCustomProvider implements EntityProvider {
  private connection?: EntityProviderConnection;

  // Provider의 고유 식별자
  getProviderName(): string {
    return &#39;MyCustomProvider&#39;;
  }

  // 백엔드 시작 시 연결
  async connect(connection: EntityProviderConnection): Promise&lt;void&gt; {
    this.connection = connection;
    await this.refresh();
  }

  // 외부 소스에서 엔티티를 가져와 카탈로그에 전달
  async refresh(): Promise&lt;void&gt; {
    const entities: Entity[] = [
      {
        apiVersion: &#39;backstage.io/v1alpha1&#39;,
        kind: &#39;Component&#39;,
        metadata: {
          name: &#39;my-service&#39;,
          namespace: &#39;default&#39;,
        },
        spec: {
          type: &#39;service&#39;,
          lifecycle: &#39;production&#39;,
          owner: &#39;team-backend&#39;,
        },
      },
    ];

    // 카탈로그에 엔티티 목록을 통째로 교체(upsert)
    await this.connection?.applyMutation({
      type: &#39;full&#39;,
      entities: entities.map(entity =&gt; ({
        entity,
        locationKey: &#39;my-custom-provider&#39;,
      })),
    });
  }
}</code></pre>
<ol start="4">
<li>Processor</li>
</ol>
<pre><code class="language-typescript">import {
  CatalogProcessor,
  CatalogProcessorEmit,
} from &#39;@backstage/plugin-catalog-node&#39;;
import { Entity } from &#39;@backstage/catalog-model&#39;;
import { LocationSpec } from &#39;@backstage/plugin-catalog-common&#39;;

export class MyCustomProcessor implements CatalogProcessor {
  getProcessorName(): string {
    return &#39;MyCustomProcessor&#39;;
  }

  // 엔티티를 받아서 변환하거나 관계를 추가
  async preProcessEntity(
    entity: Entity,
    _location: LocationSpec,
    emit: CatalogProcessorEmit,
  ): Promise&lt;Entity&gt; {

    // Component 타입일 때만 처리
    if (entity.kind !== &#39;Component&#39;) {
      return entity;
    }

    // 태그 자동 추가
    const tags = entity.metadata.tags ?? [];
    if (!tags.includes(&#39;auto-tagged&#39;)) {
      tags.push(&#39;auto-tagged&#39;);
    }

    // 관계(Relation) 추가
    emit({
      type: &#39;relation&#39;,
      relation: {
        source: { kind: &#39;Component&#39;, namespace: &#39;default&#39;, name: entity.metadata.name },
        type: &#39;ownedBy&#39;,
        target: { kind: &#39;Group&#39;, namespace: &#39;default&#39;, name: &#39;team-backend&#39; },
      },
    });

    // 수정된 엔티티 반환
    return {
      ...entity,
      metadata: {
        ...entity.metadata,
        tags,
      },
    };
  }
}</code></pre>
<p>backend에 등록</p>
<pre><code class="language-typescript">
import { MyCustomProvider } from &#39;./providers/MyCustomProvider&#39;;
import { MyCustomProcessor } from &#39;./processors/MyCustomProcessor&#39;;

export default async function createPlugin(env: PluginEnvironment) {
  const builder = await CatalogBuilder.create(env);

  // Provider 등록
  builder.addEntityProvider(new MyCustomProvider());

  // Processor 등록
  builder.addProcessor(new MyCustomProcessor());

  const { processingEngine, router } = await builder.build();
  await processingEngine.start();

  return router;
}</code></pre>
<hr>
<h2 id="흐름-요약">흐름 요약</h2>
<pre><code>[외부 소스 (GitHub, LDAP 등)]
        ↓
  Entity Provider   ← MyCustomProvider
        ↓
   (수집/Ingestion)
        ↓
    Processor       ← MyCustomProcessor
        ↓
   (처리/Processing)
        ↓
    Stitching (자동)
        ↓
  [카탈로그 API로 노출]

</code></pre><p><img src="https://velog.velcdn.com/images/berry_hennie/post/d7f2bbc8-277a-4bac-ad23-f75630cdd951/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[backstage][catalog] overview]]></title>
            <link>https://velog.io/@berry_hennie/backstagecatalog-overview</link>
            <guid>https://velog.io/@berry_hennie/backstagecatalog-overview</guid>
            <pubDate>Sat, 14 Mar 2026 02:15:05 GMT</pubDate>
            <description><![CDATA[<h2 id="what-is-catalog">what is Catalog?</h2>
<p>중앙화된 시스템으로, 생태계에 있는 모든 소프트웨어(서비스, 웹사이트, 라이브러리, 데이터파이프라인 등)의 소유권과 메타데이터를 추적 및 관리합니다.</p>
<p>카탈로그는 코드와 함께 저장되는 메타데이터 YAML파일이라는 개념을 중심으로 구축되며 이 파일들은 수집되어 backstage에서 시각화됩니다.</p>
<p>핵심 개념 요약</p>
<ul>
<li>중앙화된 관리 - 흩어진 소프트웨어 정보를 한곳에서 관리</li>
<li>소유권 추적 - 각 소프트웨어가 누구의 것인지 명확히 파악</li>
<li>yaml 파일 - 코드 저장소에 함꼐 보관되는 메타데이터 정의파일</li>
<li>자동 수집 및 시각화 - yaml  파일을 읽어 backstage UI에서 보여줌</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<p>/catalog로 들어가면 볼 수 있거나 공식문서따라 했다면 <a href="http://localhost:3000">http://localhost:3000</a> 에서 확인할 수 있다.</p>
<h2 id="adding-components-to-the-catalog">Adding components to the catalog</h2>
<p>소프트웨어 카탈로그의 원천은 소스컨트롤에 저장된 메타데이터 YAML파일이다.
저장소에는 하나 또는 여러개의 메타데이터 파일이 포함될 수 있으며, 보통 저장소 루트에 위치하지만 어디에 두어도 상관없다.</p>
<p>컴포넌트를 추가하는 방법은 3가지 방법이 있다.</p>
<ol>
<li>수동으로 컴포넌트 등록</li>
</ol>
<ul>
<li><code>/create</code> 페이지에서 REGISTERING COMPONENT 버튼 클릭</li>
<li>소스 컨트롤에 있는 YAML 파일의 전체 URL 입력</li>
<li>자사가 관리하지 않는 소프트웨어도 등록 가능</li>
</ul>
<ol start="2">
<li>backstage를 통해 새 컴포넌트 등록</li>
</ol>
<ul>
<li><p>Backstage Software Templates으로 만든 소프트웨어는 카탈로그에 자동 등록</p>
</li>
<li><p>catalog.yaml 설정 파일에 URL을 직접 명시해서 등록 가능</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/berry_hennie/post/02b81b2d-e78a-40f4-8a3b-6b7ee9273298/image.png" alt=""></p>
<p>핵심요약</p>
<p>backstage는 여러 인프라 도구를 하나의 개발자 포털로 통합하여, 팀이 여러 UI를 왔다갔다 인지적 부담을 줄여주는것이 핵심 목적이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[backstage] Technical overview]]></title>
            <link>https://velog.io/@berry_hennie/backstage-Technical-overview</link>
            <guid>https://velog.io/@berry_hennie/backstage-Technical-overview</guid>
            <pubDate>Sun, 01 Mar 2026 01:09:59 GMT</pubDate>
            <description><![CDATA[<h2 id="purpose">Purpose</h2>
<pre><code>Backstage is an open source framework for building developer portals that was created at Spotify to simplify end-to-end software development. As Spotify grew, their infrastructure became more fragmented and teams couldn&#39;t find the APIs they were supposed to use, or who owned a service, or documentation on anything.

Backstage is powered by a centralized software catalog and utilizes an abstraction layer that sits on top of all of your infrastructure and developer tooling, allowing you to manage all of your software, services, tooling, and testing in one place.

Backstage uses a plugin-architecture which allows you to customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software.

Backstage is now a CNCF incubation project.</code></pre><ol>
<li>목적 및 탄생 배경</li>
</ol>
<p>-&gt; Backstage는 end-to-end 소프트웨어 개발을 단순화하기 위해 만든 오픈소스프레임워크입니다. </p>
<ol start="2">
<li>핵심구조 : 추상화 계층(Abstraction Layer)</li>
</ol>
<p>-&gt; Backstage는 모든 인프라와 개발도구 위에 위치하는 <strong>추상화 계층</strong>을 활용합니다.</p>
<ol start="3">
<li>확장성과 표준화(Plugins&amp; Templates)</li>
</ol>
<p>-&gt; 플러그인 아키텍쳐 : 다양한 기존 플러그인을 가져다 쓰거나 직접 만들어서 기능을 무한히 확장 할 수 있습니다.</p>
<p>-&gt; 자동화된 템플릿(Software Templates) : 새로운 마이크로 서비스를 만들떄 팀이 사용할 수 있는 템플릿을 제공합니다. 이는 모든 팀이 회사의 <strong>베스트프랙티스</strong>를 따르게 하고 서비스간의 일관성을 유지하도록 합니다.</p>
<ol start="4">
<li>문서화 및 커뮤니티</li>
</ol>
<p>-&gt; TechDocs : 모든 소프트웨어 문서를 생성, 유지 관리, 검색할 수 있는 기능을 제공합니다.</p>
<h2 id="core-feature">Core Feature</h2>
<ol>
<li>인증 및 신원 (Authentication and Identity)
내용: 사용자 로그인 및 신원 확인 기능입니다. 기본 제공되는 인증 제공자(Google, GitHub, Okta 등)를 통해 사용자를 식별하고, 외부 리소스에 대한 접근 권한을 위임(delegating)할 수 있습니다.</li>
</ol>
<p>실무 의미: &quot;회사 계정으로 로그인하면, 내가 권한을 가진 프로젝트들만 자동으로 연결된다&quot;는 뜻입니다.</p>
<ol start="2">
<li>쿠버네티스 (Kubernetes)
내용: 서비스가 로컬 환경이나 실제 운영(Production) 환경 어디에 있든, 그 <strong>상태(Health)</strong>를 개발자가 직접 확인할 수 있게 해주는 도구입니다.</li>
</ol>
<p>실무 의미: 복잡한 인프라 명령어를 몰라도, Backstage 화면에서 내 서비스가 잘 돌아가는지(Pod 상태 등) 바로 볼 수 있습니다.</p>
<ol start="3">
<li>알림 (Notifications)
내용: 플러그인이나 외부 서비스가 개별 사용자나 그룹에게 메시지를 보낼 수 있는 수단을 제공합니다.</li>
</ol>
<p>실무 의미: &quot;내 코드 리뷰가 승인되었을 때&quot;나 &quot;배포가 실패했을 때&quot; 포털 내에서 알림을 받을 수 있습니다.</p>
<ol start="4">
<li>권한 (Permissions)
내용: 특정 데이터, API, 또는 화면상의 액션에 대해 사용자가 어떤 접근 권한을 가질지 규칙을 강제하는 기능입니다.</li>
</ol>
<p>실무 의미: 보안이 중요한 프로젝트는 담당 팀원만 수정하거나 볼 수 있게 제한할 수 있습니다.</p>
<ol start="5">
<li>검색 (Search)
내용: Backstage 생태계 내의 모든 정보를 검색합니다. 검색 결과의 디자인을 커스터마이징하거나, 회사 고유의 검색 엔진을 연동할 수도 있습니다.</li>
</ol>
<p>실무 의미: &quot;서비스 이름, API 이름, 문서 내용&quot;까지 한 번에 검색해서 찾아낼 수 있는 &#39;사내 구글&#39; 같은 역할입니다.</p>
<ol start="6">
<li>소프트웨어 카탈로그 (Software Catalog)
내용: 모든 소프트웨어(서비스, 웹사이트, 라이브러리, ML 모델, 데이터 파이프라인 등)와 이를 구동하는 인프라의 메타데이터를 담고 있는 중앙 집중식 시스템입니다. UI를 통해 조회 및 검색이 가능합니다.</li>
</ol>
<p>실무 의미: 앞서 말한 &#39;우리 회사의 디지털 지도&#39; 그 자체입니다.</p>
<ol start="7">
<li>소프트웨어 템플릿 (Software Templates)
내용: Backstage 내부에서 새로운 컴포넌트를 생성하도록 돕는 도구입니다. 코드의 뼈대(Skeletons)를 불러오고 변수를 입력받은 뒤, GitHub 같은 곳에 자동으로 게시(Publish)해 줍니다.</li>
</ol>
<p>실무 의미: &quot;새 프로젝트 시작할 때 설정하는 데만 이틀 걸리던 걸, 버튼 클릭 한 번으로 5분 만에 끝낸다&quot;는 기능입니다.</p>
<ol start="8">
<li>테크독스 (TechDocs)
내용: &#39;Docs-like-code(코드처럼 다루는 문서)&#39; 솔루션입니다. 문서는 코드와 함께 Markdown 파일로 작성되며, Backstage에서 예쁘게 렌더링되어 보여집니다.</li>
</ol>
<p>실무 의미: 문서를 별도 위키(Wiki)에 적는 게 아니라, 코드 저장소(Git) 안에 넣어서 관리하므로 코드와 문서의 동기화가 아주 쉬워집니다.</p>
<h2 id="plugin-architecture-overview">Plugin Architecture Overview</h2>
<p>백스테이지는 3가지 타입의 플러그인을 제공한다.</p>
<ul>
<li>standalone : API를 요청하지 않고 오로지 브라우저에서 계산된 결과값을 보여주는 서비스</li>
<li>Service backend : 회사내의 API를 요청해서 통신할 수 있는 시스템</li>
<li>Third-party backend : 회사내가 아닌 외부 시스템 API 요청해서 통신하는 시스템</li>
</ul>
<h2 id="software-catalog-system-model">Software Catalog System Model</h2>
<p>소프트웨어 카탈로그의 시스템 모델은 &#39;Entity&#39;를 기반으로 하며 크게 두가지 주요 유형으로 모델링 합니다. : 핵심 엔티티(Core Entity)와 조직 엔티티입니다.</p>
<ol>
<li>Core Entity</li>
</ol>
<ul>
<li>Component : 소스코드(git repo)</li>
<li>API : 서비스간의 대화 규칙</li>
<li>Resource : 인프라 자원(DB, S3...)</li>
<li>핵심질문 : 무엇(what)이 돌아가고 있는가?</li>
</ul>
<ol start="2">
<li>조직 엔티티
소프트웨어를 만드는 사람과 조직의 구조를 정의합니다. 기술자산과 사람을 연결시키는 고리</li>
</ol>
<ul>
<li>User(사용자) : 개별 개발자</li>
<li>Group(그룹) : 팀, 부서, 본부단위</li>
<li>핵심질문 : who(누가) 이 소프트웨어를 책임지는가?</li>
</ul>
<p>왜 이두가지를 묶어서 관리할까?
-&gt; 단순히 이코드는 이팀꺼다 라는 메모만 하는게 아니라 시스템적으로 관계를 맺기 위함입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[backstage] what is Backstage(overview)]]></title>
            <link>https://velog.io/@berry_hennie/backstage-overview</link>
            <guid>https://velog.io/@berry_hennie/backstage-overview</guid>
            <pubDate>Sun, 01 Mar 2026 00:51:32 GMT</pubDate>
            <description><![CDATA[<p>회사에서 팀을 이동하게 되었는데 사용하는 프레임워크가 BackStage라서 관련 문서를 읽어 보게되었다.</p>
<h2 id="what-is-backstage">what is Backstage?</h2>
<pre><code>Backstage is an open source framework for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy.

Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end.</code></pre><ol>
<li>What is Backstage?
정의: 스포티파이(Spotify)에서 만든 개발자 포털(Internal Developer Portal, IDP) 구축용 오픈소스 프레임워크.</li>
</ol>
<p>목표: 개발자가 인프라 설정이나 문서 찾기에 쏟는 시간을 줄이고, <strong>&#39;코딩 그 자체&#39;</strong>에 집중할 수 있는 환경(End-to-End)을 만드는 것.</p>
<ol start="2">
<li>핵심 개념: Software Catalog (소프트웨어 카탈로그)
의미: 기업 내 흩어진 모든 디지털 자산(서비스, API, 라이브러리, 파이프라인 등)의 &#39;메타데이터&#39; 저장소.</li>
</ol>
<p>실제 데이터: 단순히 이름만 나열한 리스트가 아니라, 아래 정보를 연결한 관계형 지도입니다.</p>
<p>“이 서비스는 어떤 팀이 만들었고, 어떤 API를 제공하며, 소스 코드와 배포 상태는 어디서 보나?” 라는 질문에 즉각 답을 줍니다.</p>
<ol start="3">
<li>&#39;질서 회복&#39;과 &#39;자율성&#39;의 공존 (중요)
질서 회복: 마이크로서비스가 수백 개로 늘어나면 &quot;누가 뭘 만들었는지&quot; 모르는 혼돈이 옵니다. Backstage는 이를 표준화된 형식으로 시각화하여 관리의 복잡성을 제거합니다.</li>
</ol>
<p>자율성 보존 (No Compromising Autonomy): 보통 중앙 집중 관리를 하면 &quot;무조건 이 툴만 써!&quot;라고 강제하기 마련인데, Backstage는 <strong>&quot;어떤 툴을 쓰든 상관없지만, 그 정보는 우리 포털에 등록해줘&quot;</strong>라는 방식입니다.</p>
<p>즉, 팀마다 선호하는 기술 스택을 유지하면서도(자율성), 정보 공유는 일원화(표준화)된 포털에서 이루어지게 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS] 기기별 화면 비율이 달라도 배경 이미지 속 특정 위치에 요소 고정시키기]]></title>
            <link>https://velog.io/@berry_hennie/overlay</link>
            <guid>https://velog.io/@berry_hennie/overlay</guid>
            <pubDate>Thu, 05 Feb 2026 02:41:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/berry_hennie/post/06fd8c96-45d4-4875-ac9c-4cfed37643a5/image.png" alt=""></p>
<ol>
<li>이전 방식의 핵심 의도 요약</li>
</ol>
<pre><code class="language-css">.container { height: 100vh; background-size: contain; }: 
</code></pre>
<p>브라우저 화면 높이를 꽉 채우되, 이미지가 잘리는 것(cover)을 방지하고 이미지의 전체 요소(캐릭터, 꽃 등)가 화면 안에 다 들어오게 하려는 의도였습니다.</p>
<p>imageAreaGuide: 이름(nameOverlay)과 하단 정보(informationOverlay)를 묶어서 이미지 내부의 특정 위치(예: 하트 장식 옆)에 배치하기 위한 기준점(Anchor) 역할을 수행하려 했습니다.</p>
<ol start="2">
<li>왜 문제가 발생했었나? </li>
</ol>
<p>이론적으로는 맞지만, 실제 브라우저에서는 다음과 같은 <strong>&quot;공간의 불일치&quot;</strong>가 일어났습니다.</p>
<p>박스의 크기 ≠ 이미지의 크기: container 박스는 항상 화면 꽉 차게(100vh) 그려지지만, 그 안의 background-image는 contain 설정 때문에 실제로는 박스보다 작아져서 위아래에 빈 공간(여백)이 생겼습니다.</p>
<p>기준점의 이탈: imageAreaGuide가 container를 기준으로 위치를 잡다 보니, 화면이 긴 폰에서는 <strong>글자가 이미지(그림) 위가 아니라 이미지 바깥의 빈 공간(회색 여백)</strong>으로 밀려 나갔던 것입니다.</p>
<p>아래와 같은 방식으로 수정을 했습니다.
<img src="https://velog.velcdn.com/images/berry_hennie/post/ec2a34fe-468b-4f07-b786-c63cd36cb6d8/image.png" alt=""></p>
<p>지금은 <strong><code>&lt;img&gt;</code> 태그와 aspect-ratio</strong>를 사용함으로써, 이미지 그 자체의 크기가 곧 박스의 크기가 되도록 했습니다. bottom: 19% 같은 수치가 &quot;화면 전체&quot;가 아니라 <strong>&quot;진짜 이미지 높이&quot;</strong>를 기준으로 계산되어 기종마다 일정하게 나오는 것입니다.</p>
<p>또한 아래와 같이 postion을 통해서 bottom의 위치를 고정 시켰습니다.</p>
<ol>
<li><p>position: relative (부모: imageAreaGuide)
이 설정이 없으면 자식인 nameOverlay는 이미지를 기준으로 움직이지 않고, 브라우저 전체 화면을 기준으로 움직이려고 합니다.
역할: &quot;이제부터 이 이미지 박스 안이 너(글자)의 세상이야&quot;라고 기준점을 선언하는 것입니다.</p>
</li>
<li><p>position: absolute (자식: nameOverlay, informationOverlay)
이 설정이 있어야 글자가 이미지의 일반적인 흐름(위에서 아래로 나열되는 방식)에서 벗어나, 이미지 위에 둥둥 떠서 원하는 곳으로 이동할 수 있습니다.
역할: 부모 박스의 왼쪽 위(0,0)를 기준으로 bottom: 19.5% 같은 좌표를 찍어 정확한 위치에 안착하게 합니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[4. 리액트의 고급개념]]></title>
            <link>https://velog.io/@berry_hennie/react-portal</link>
            <guid>https://velog.io/@berry_hennie/react-portal</guid>
            <pubDate>Sun, 18 Jan 2026 01:32:07 GMT</pubDate>
            <description><![CDATA[<h2 id="포털">포털</h2>
<p>1.포털이란? 어떻게 생성하는가?
:: 자식 컴포넌트를 부모 컴포넌트 DOM과 독립적으로 생성할 수 있는 기능이다. 보통 팝업창, 툴팁 같은 경우 포탈을 많이 사용한다. </p>
<pre><code class="language-tsx">import ReactDOM from &#39;react-dom&#39;;
import &#39;./App.css&#39;;

const Modal = ({ isOpen, onClose, title, children }) =&gt; {
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    &lt;div className=&quot;modal-overlay&quot; onClick={onClose}&gt;
      {/* e.stopPropagation()은 모달 내부 클릭 시 닫히지 않게 함 */}
      &lt;div className=&quot;modal-content&quot; onClick={(e) =&gt; e.stopPropagation()}&gt;
        &lt;div className=&quot;modal-header&quot;&gt;{title}&lt;/div&gt;
        &lt;div className=&quot;modal-body&quot;&gt;{children}&lt;/div&gt;
        &lt;div className=&quot;modal-footer&quot;&gt;
          &lt;button onClick={onClose}&gt;취소&lt;/button&gt;
          &lt;button className=&quot;confirm-btn&quot;&gt;확인&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;,
    document.getElementById(&#39;modal-root&#39;)
  );
};

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[3. 훅: 함수 컴포넌트에 state와 다른 기능 추가하기]]></title>
            <link>https://velog.io/@berry_hennie/hook</link>
            <guid>https://velog.io/@berry_hennie/hook</guid>
            <pubDate>Mon, 12 Jan 2026 22:48:16 GMT</pubDate>
            <description><![CDATA[<h2 id="훅-소개와-목적">훅 소개와 목적</h2>
<ol>
<li><p>훅이란?
:: 함수 컴포넌트가 리액트 생명주기와 state를 연결하는 수단이다.</p>
</li>
<li><p>훅의 도입 배경은?
:: 컴포트간 재사용이 어렵고, 로직의 캡슐화가 어려웠다.</p>
</li>
</ol>
<p>e.g) 창의 너비를 감지하는 custom hook을 생성해보자 </p>
<pre><code class="language-tsx">import { useState, useEffect } from &#39;react&#39;;

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() =&gt; {
    const handleResize = () =&gt; setWidth(window.innerWidth);
    window.addEventListener(&#39;resize&#39;, handleResize);

    // 언마운트 시 리스너 제거 (정리)
    return () =&gt; window.removeEventListener(&#39;resize&#39;, handleResize);
  }, []);

  return width; // 현재 너비 값만 반환
}
</code></pre>
<p>위에 custom hook 을 A컴포넌트에서 사용할 수 있고 B컴포넌트에서 사용할 수 있다.</p>
<pre><code class="language-tsx">function Navbar() {
  const width = useWindowWidth(); // 로직 재사용

  return (
    &lt;nav&gt;
      {width &lt; 768 ? &lt;MobileMenu /&gt; : &lt;DesktopMenu /&gt;}
    &lt;/nav&gt;
  );
}
</code></pre>
<h2 id="훅을-이용한-지역-state-관리">훅을 이용한 지역 state 관리</h2>
<ol>
<li><p>useState란? 
:: 함수 컴포넌트에 state를 추가하고 관리할 수 있는 훅이다. </p>
</li>
<li><p>updater함수를 사용하는것이 항상 좋은가?
:: 동일한 이벤트 핸들러 내에서는 여러 state 업데이트를 하는 경우 예상하는 데이터 결과를     받기 위해서는 updater함수를 사용하는 것이 좋다.</p>
</li>
</ol>
<pre><code class="language-tsx">
const handleClick = () =&gt; {
    setCounter((prevData) =&gt; prevData + 1);
      setCounter((prevData) =&gt; prevData + 1);
      setCounter((prevData) =&gt; prevData + 1);

}</code></pre>
<p>만약에  <code>setCounter(data + 1)</code> 3번 호출하면 기존 값에서 +1된 값만 나올 수 있다. 그렇기 떄문에, 하나의 이벤트 핸들러에 state를 여러번 호출해야한다면 예상된 결과값을 얻기 위해서는 updater함수를 호출하는것이 좋다.</p>
<ol start="3">
<li>useReducer훅이란 무엇인가? 어떻게 사용하는가?</li>
</ol>
<p>:: useReducer는 useState의 대체제이다. 값이 여러 복잡한경우 state로 관리하지 않고 useReducer를 통해 값을 관리한다.</p>
<p>e.g) 
CounterReducer.tsx를 작성해준다.</p>
<pre><code class="language-tsx">interface CounterState {
    count: number;
}

interface CounterAction {
    type: &#39;INCREMENT&#39; | &#39;DECREMENT&#39;;
}

function counterReducer(state: CounterState, action: CounterAction): CounterState {
    switch (action.type) {
        case &#39;INCREMENT&#39;:
            return {count: state.count + 1};
        case &#39;DECREMENT&#39;:
            return {count: state.count - 1};
        default:
            throw new Error(&#39;Unhandled action type&#39;);
    }
}

const initialState: CounterState = {count: 0};


export {
    counterReducer,
    initialState
}
</code></pre>
<p>Counter.tsx에서 사용예시</p>
<pre><code class="language-tsx">import {useReducer} from &#39;react&#39;;
import {counterReducer, initialState} from &quot;../reducer/count&quot;;

function Calculator() {
    const [state, dispatch] = useReducer(counterReducer, initialState);

    return (
        &lt;&gt;
            &lt;div&gt;
                &lt;p&gt;Count : {state.count}&lt;/p&gt;
                &lt;button onClick={() =&gt; dispatch({type: &#39;INCREMENT&#39;})}&gt;+&lt;/button&gt;
                &lt;button onClick={() =&gt; dispatch({type: &#39;DECREMENT&#39;})}&gt;-&lt;/button&gt;
            &lt;/div&gt;
        &lt;/&gt;
    )
}

export default Calculator;</code></pre>
<p>실행과정을 설명하자면, 사용자가 이벤트를 발생하면 등록된 이벤트 핸들러 내에서 상태를 바꾸고 싶은 dispatch를 호출한다. 이때 호출된 dispatch 안에 action인자를 넣어 호출한다.
그러면 등록된 reducer는 action인자와 이전 상태값을 전달받아 값을 조작한다.</p>
<h2 id="훅을-이용한-전역-state를-관리">훅을 이용한 전역 state를 관리</h2>
<h2 id="리액트-애플리케이션에서-부수-효과-실행하기">리액트 애플리케이션에서 부수 효과 실행하기</h2>
<p>1.useEffect훅 내의 반응형 의존성이 로직에 어떤 영향을 미치는가?</p>
<p>아래와 같은 특성이 있다.</p>
<pre><code class="language-tsx">
useEffect(() =&gt; {
},[]) // mounted시에 실행

useEffect(() =&gt; {
},[state]) // mounted와 state값이 변경이 될떄 실행

useEffect(() =&gt; {
}) // 리렌더링 시에
</code></pre>
<ol start="2">
<li>useEffect 훅 내에서 설정 및 정리 함수는 얼마나 자주 호출되는가?
:: mounted, 리렌더링, unmounted직전에 호출된다.</li>
</ol>
<pre><code class="language-tsx">useEffect(() =&gt; {
  // 1. 마운트된 직후 (Mounted)
  console.log(&quot;마운트됨!&quot;);

  return () =&gt; {
    // 2. 언마운트 직전 (Before Unmount)
    console.log(&quot;사라지기 직전!&quot;);
  };
}, []);
</code></pre>
<ol start="3">
<li>useLayoutEffect 훅은 무엇이고 어떻게 동작하는가?
:: 화면에 다시 그리기전에 호출되는 특수한 종류의 effect훅으로, state가 업데이트할때 컴포넌트가 깜빡이는 경우에 사용된다.</li>
</ol>
<h2 id="애플리케이션-최적화">애플리케이션 최적화</h2>
<ol start="4">
<li><p>메모이제이션이 무엇인가? 리액트에서 어떻게 구현할 수 있는가?
:: 값비싼 함수의 결과를 캐싱 해서 웹 애플리 케이션에서 속도를 높일 수 있는 훅이다. 리액트에서 useMemo()와 useCallback()훅을 통해 구현 할 수 있다.</p>
</li>
<li><p>useMemo() 훅을 설명 할 수 있는가?
:: useMemo는 함수의 계산 결과를 캐싱하는데 사용된다. </p>
</li>
</ol>
<p>5.1 useMemo를 사용하면 좋을떄는? 
:: 1. 렌더링시에 정렬, 필터링과 같은 비용이 많이 드는 계산 2. prop변경이 없을때 리렌더링을 건너뛰려는 경우 </p>
<p>(프로파일러 섹션은 지연되는 컴포넌트를 식별할 수 있는 탭이다)</p>
<ol start="6">
<li>언제 useMemo 훅 대신 useCallback훅을 사용해야 하는가?
:: props로 함수를 전달할경우 매번 새로운 함수가 생성이 된다. 그렇기 때문에 useCallback을 사용해서 함수가 props로 전달시에 useCallback으로 사용시 리렌더링을 막을 수 있다.</li>
</ol>
<h2 id="ref훅을-사용한-dom-노드-접근">ref훅을 사용한 DOM 노드 접근</h2>
<p>ref는 내장브라우저 API와 같이 외부 시스템을 이용할때가 유용하다.</p>
<ol>
<li>ref 콘텐츠가 재생성되는것을 어떻게 막는가?
:: useRef 훅은 초깃값을 useState 훅처럼 인자를 받는다. 만약에 useRef의 초깃값을 무거운 객체로 한다면 리렌더링시마다 계속 새로운 값으로 계산된다. 이는 애플리케이션 성능에 영향을 미칠 수 있다.</li>
</ol>
<pre><code class="language-tsx">function MyComponent() {
  const ref = useRef(null); // 처음엔 가벼운 null로 시작

  // 실제 값이 필요할 때만 체크해서 생성
  function getHeavyObject() {
    if (ref.current === null) {
      ref.current = new HeavyObject(); // 딱 한 번만 실행됨
    }
    return ref.current;
  }

  // ... 필요할 때 getHeavyObject() 호출
}
</code></pre>
<ol start="2">
<li>렌더링 메서드에서 ref에 접근하는것이 가능한가?
가능하지만 렌더링 프로세스에서 ref.current값을 읽거나 쓰는것을 권장하지 않는다.</li>
</ol>
<ol start="3">
<li>ref 인스턴스에서 일부 메서드을 노출 시키는 방법은 무엇인가?</li>
</ol>
<pre><code class="language-tsx">useImperativeHandle(ref,() =&gt; {
    open : () =&gt; ref.current.invokeDialog(),
    close :  () =&gt; ref.current.closeDialog(),
    reset : () =&gt; ref.current.clearData()
})</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Context API 란?]]></title>
            <link>https://velog.io/@berry_hennie/react-context-api</link>
            <guid>https://velog.io/@berry_hennie/react-context-api</guid>
            <pubDate>Sun, 11 Jan 2026 03:07:04 GMT</pubDate>
            <description><![CDATA[<p>보통 props를 통해 상위 컴포넌트가 하위 컴포넌트한테 데이터를 전달을 한다. 이를 props drilling이라고 하는데 잘못하면 불필요한 컴포넌트의 리렌더링까지 일으 킬 수 있다. 이를 위헤 전역적으로 데이터를 관리할 수 가 있는데 스토어를 사용하거나 context API를 사용해서 데이터를 전역적으로 관리할 수 있다.</p>
<p>그렇다면 스토어를 사용해야할때와 context API를 사용해야할떄는 언제인가?</p>
<p>간단한 테마(토글로 dark,light모드) 혹은 로그인 상태 혹은 locale와 같은 단순하지만 자주 변하지 않는 값들을 전역적으로 관리할때는 Context를 사용하는것이 좋다. 반면에 비즈니스로직이라던지 복잡하거나 잦은 상태로 데이터가 변경이 되는 경우라면 스토어를 사용하는것을 권장한다.</p>
<p>Context API의 사용예제</p>
<ol>
<li>color에 대한 Context를 만든다.<pre><code class="language-tsx"></code></pre>
</li>
</ol>
<p>const ColorContext = createContext({
    state: {color: &#39;blue&#39;, subColor: &#39;red&#39;},
    actions: {
        setColor: (color: string) =&gt; {
        },
        setSubColor: (subColor: string) =&gt; {
        }
    }
});</p>
<p>export default ColorContext;</p>
<pre><code>
2. Context Provider를 생성한다.
-&gt; ColorProvider는 상태를 직접 관리하고, 이 상태와 업데이트 함수를 value로 묶어 하위 컴포넌트에 제공하는 역할을 합니다.


```tsx
import ColorContext from &quot;../contexts/color&quot;;
import ColorConsumer from &quot;./ColorConsumer&quot;;
import {useState} from &quot;react&quot;;

const RANDOM_COLORS = [&#39;red&#39;, &#39;orange&#39;, &#39;yellow&#39;, &#39;green&#39;, &#39;blue&#39;, &#39;indigo&#39;, &#39;violet&#39;];
const RANDOM_SUB_COLORS = [&#39;pink&#39;, &#39;cyan&#39;, &#39;lime&#39;, &#39;teal&#39;, &#39;lavender&#39;, &#39;brown&#39;, &#39;gray&#39;];

const ColorProvider = () =&gt; {
    const [color, setColor] = useState({color: &#39;orange&#39;, subColor: &#39;blue&#39;});
    const value = {
        state: color,
        actions: {
            setColor: () =&gt; {
                const color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
                setColor(prev =&gt; ({...prev, color}));
            },
            setSubColor: () =&gt; {
                const subColor = RANDOM_SUB_COLORS[Math.floor(Math.random() * RANDOM_SUB_COLORS.length)];
                setColor(prev =&gt; ({...prev, subColor}));
            }
        }
    };


    return (
        &lt;&gt;
            &lt;ColorContext.Provider value={value}&gt;
                {/* 자식 컴포넌트들 */}
                &lt;ColorConsumer/&gt;
            &lt;/ColorContext.Provider&gt;
        &lt;/&gt;
    )
}


export default ColorProvider;
</code></pre><ol start="3">
<li>Context.Consumer를 생성한다.</li>
</ol>
<p>-&gt; ColorConsumer 컴포넌트는 useContext Hook을 사용하여 Provider가 제공한 값을 구독하고 소비합니다.</p>
<pre><code class="language-tsx">import ColorContext from &quot;../contexts/color&quot;;
import {useContext} from &quot;react&quot;;

const ColorConsumer = () =&gt; {
    // &lt;ColorContext.Consumer&gt;&lt;/ColorContext.Consumer&gt; 방식 대신 useContext 훅을 사용
    const {state, actions} = useContext(ColorContext);

    return (
        &lt;&gt;
            &lt;div&gt;
                &lt;h1 style={{color: state.color}}&gt;This is the main color&lt;/h1&gt;
                &lt;h2 style={{color: state.subColor}}&gt;This is the sub color&lt;/h2&gt;
                &lt;button onClick={actions.setColor}&gt;Change Color&lt;/button&gt;
                &lt;button onClick={actions.setSubColor}&gt;Change Sub Color&lt;/button&gt;
            &lt;/div&gt;

        &lt;/&gt;
    )
}


export default ColorConsumer;

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트 인터뷰 가이드] 리액트의 기본 개념과 이해(part2)]]></title>
            <link>https://velog.io/@berry_hennie/part2</link>
            <guid>https://velog.io/@berry_hennie/part2</guid>
            <pubDate>Tue, 30 Dec 2025 22:56:29 GMT</pubDate>
            <description><![CDATA[<h3 id="가상-dom">가상 DOM</h3>
<ul>
<li><p>가상 DOM이란?
:: 웹페이지 UI는 DOM이라는 Tree로 나타낸다. 리액트 컴포넌트에 의해 생성된 실제 DOM의 메모리상 경량화된 가상 표현이다. 리액트는 가상 DOM을 생성해서 변경된 데이터를 감지해서 화면에 리렌더링을 일으킨다. 이 과정을 재조정(reconciliation)이라고 한다.</p>
</li>
<li><p>가상 DOM은 어떻게 작동하는가?
:: 애플리 케이션이 처음 렌더링시 가상DOM을 생성한다 -&gt; 데이터가 변경이 되면 새로운 가상DOM이 생성된 후에 이전 가상DOM과 비교한다.(재조정) -&gt; 변경사항을 실제 DOM에 적용한다. -&gt; repaint과정이 일어난다.</p>
</li>
</ul>
<h3 id="리액트-애플리케이션에서의-데이터-흐름과-통신">리액트 애플리케이션에서의 데이터 흐름과 통신</h3>
<ul>
<li><p>리액트에서 단방향 데이터 흐름을 설명할 수 있는가?
:: 리액트는 props를 통해 부모 컴포넌트에서 자식 컴포넌트에게 데이터를 전달하는 데이터 단방향을 따른다. 그 반대 방향은 업데이트 할 수 없다. </p>
</li>
<li><p>단방향 데이터의 이점은 무엇인가?
:: 쉬운 디버깅과 오류 발생이 감소된다. </p>
</li>
</ul>
<h3 id="리액트에서-dom-요소-접근">리액트에서 DOM 요소 접근</h3>
<ul>
<li>refs란 무엇인가? 어떻게 refs를 생성하는가?
:: refs란 리액트 render로 생성된 DOM 노드 혹은 리액트 요소에 접근하는데 사용된다.참조는 refs.current로 접근이 가능하다.</li>
</ul>
<pre><code class="language-jsx">
const SignUpForm = () =&gt; {

    const inputRef = useRef(null);

    useEffect(()=&gt; {
        inputRef.current.focus();
    },[])

      return(
        &lt;&gt;
            &lt;input type=&quot;email&quot; ref={inputRef} /&gt;
            &lt;button&gt;이메일 확인 &lt;/button&gt;
          &lt;/&gt;
    )
}</code></pre>
<ul>
<li><p>refs의 주요 목적은 무엇인가?
:: 리액트의 선언적 방식으로 구현하기 어려운 즉, DOM의 직접 접근해야할때 같은 경우를 위해 ref가 필요하다. 예시로는 스크롤 위치 기억, 포커스 등 이 있다.</p>
</li>
<li><p>state와 refs의 차이점은?
:: 변경사항이 있더라도 refs는 리렌더링 되지 않으며 state는 리렌더링이 된다. 화면에 리렌더링이 되지 않은 채 리렌더링간의 정보를 저장하고 싶다면 refs를 사용해야한다. </p>
</li>
</ul>
<h3 id="컨텍스트-api를-사용한-전역-상태-관리">컨텍스트 API를 사용한 전역 상태 관리</h3>
<ul>
<li><p>프롭드롤링이란? 
:: 부모 컴포는트에서 자식 컴포넌트에게 전달되는 데이터를 props라고 하고 그 과정을 props drilling이라고 한다.</p>
</li>
<li><p>컨텍스트에 대해 설명할 수 있는가?
:: 컨텍스트 데이터는 중앙에 위치해 컴포넌트 트리에 각 직접 수동으로 전달하지 않고도 사용할 수 있다.</p>
</li>
</ul>
<ol>
<li>context를 생성한다.</li>
</ol>
<pre><code class="language-tsx">import React from &#39;react&#39;;

// 테마 상태를 위한 타입 정의
export type Theme = &#39;light&#39; | &#39;dark&#39;;

// Context가 가질 값과 업데이트 함수를 정의
export interface ThemeContextValue {
  theme: Theme;
  toggleTheme: () =&gt; void;
}

// 기본값 정의 (실제 Provider가 없을 경우 사용될 값)
export const ThemeContext = React.createContext&lt;ThemeContextValue&gt;({
  theme: &#39;light&#39;,
  toggleTheme: () =&gt; console.warn(&#39;Provider not mounted&#39;)
});</code></pre>
<ol start="2">
<li>컨텍스트를 제공한다.<pre><code class="language-tsx">import { Theme, ThemeContext, ThemeContextValue } from &#39;./ThemeContext&#39;;
</code></pre>
</li>
</ol>
<p>interface ThemeProviderProps {
  children: ReactNode;
}</p>
<p>export const ThemeProvider = ({ children }: ThemeProviderProps) =&gt; {
  const [theme, setTheme] = useState<Theme>(&#39;light&#39;);</p>
<p>  // 테마를 토글하는 함수 정의
  const toggleTheme = useCallback(() =&gt; {
    setTheme(currentTheme =&gt; (currentTheme === &#39;light&#39; ? &#39;dark&#39; : &#39;light&#39;));
  }, []);</p>
<p>  // Context Value 객체를 useMemo로 감싸 불필요한 리렌더링 방지
  const contextValue: ThemeContextValue = useMemo(() =&gt; ({
    theme,
    toggleTheme,
  }), [theme, toggleTheme]);</p>
<p>  return (
    // Context.Provider를 사용하여 모든 하위 컴포넌트에 값을 제공
    &lt;ThemeContext.Provider value={contextValue}&gt;
      {children}
    &lt;/ThemeContext.Provider&gt;
  );
};</p>
<pre><code>3. 컨텍스트를 사용한다.

```tsx

import React, { useContext } from &#39;react&#39;;
import { ThemeContext } from &#39;./ThemeContext&#39;;

const ThemeToggler = () =&gt; {
  // Context의 값 (toggleTheme 함수)을 가져옵니다.
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    &lt;button onClick={toggleTheme}&gt;
      테마 전환 ({theme === &#39;light&#39; ? &#39;Dark Mode&#39; : &#39;Light Mode&#39;})
    &lt;/button&gt;
  );
};

export default ThemeToggler;
</code></pre><ul>
<li>컨텍스트의 목적은 ?
:: props drilling 없이 전역 데이터에 접근하지 위해 </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트 인터뷰 가이드] 리액트의 기본 개념과 이해]]></title>
            <link>https://velog.io/@berry_hennie/interveiw-guide-chapter2-1</link>
            <guid>https://velog.io/@berry_hennie/interveiw-guide-chapter2-1</guid>
            <pubDate>Mon, 29 Dec 2025 22:48:02 GMT</pubDate>
            <description><![CDATA[<h3 id="리액트-소개">리액트 소개</h3>
<h3 id="jsx">JSX</h3>
<h3 id="요소와-컴포넌트를-이용한-뷰-구축">요소와 컴포넌트를 이용한 뷰 구축</h3>
<ul>
<li><p>컴포넌트란 무엇인가? 
:: 컴포넌트란 UI를 더 작은 조각으로 나누어 독립적이며, 재사용가능한 코드 블록이다.</p>
</li>
<li><p>컴포넌트를 생성하는 방법에는 무엇이 있는가?
:: 2가지가 있다. 함수형 컴포넌트와 클래스형 컴포넌트.</p>
</li>
<li><p>요소와 컴포넌트의 차이는?
:: 화면에 무엇을 그릴지 적어 놓는 UI 객체 이다. 즉 설계도의 역할을 하며, 설계도를 생성하기 위한 틀이 컴포넌트이다.</p>
</li>
</ul>
<pre><code class="language-javascript">
// 이것은 컴포넌트 (틀) 입니다.
function Welcome(props) {
  return &lt;h1&gt;Hello, {props.name}&lt;/h1&gt;; // 요소들을 반환함
}

// 이것은 요소 입니다.
const element = &lt;Welcome name=&quot;홍길동&quot; /&gt;;
</code></pre>
<ul>
<li>순수컴포넌트란?
:: 순수컴포넌트란 props랑 state가 동일하다면 동일한 출력을 렌더링 하는 컴포넌트를 의미한다. Memoize API를 사용해서 함수 컴포넌트 내에서 순수 컴포넌트를 구현 할 수 있다.</li>
</ul>
<p>cf) 순수하지 않는 컴포넌트란?
:: 렌더링 도중에 외부 상태를 바꾸거나 무작위 값을 생성하면 순수성이 꺠진다.</p>
<pre><code class="language-javascript">function RandomComponent() {
  const [count, setCount] = useState(0);

  // 렌더링 도중에 Math.random()을 호출함 -&gt; 출력값이 매번 달라짐!
  const randomValue = Math.random(); 

  // 렌더링 도중에 외부 변수를 수정함 (Side Effect)
  document.title = `클릭 횟수: ${count}`; 

  return &lt;div&gt;{randomValue}&lt;/div&gt;;
}
</code></pre>
<pre><code class="language-javascript">function PureComponent() {
  const [count, setCount] = useState(0);

  // 딴짓(Side Effect)은 렌더링이 끝난 뒤에 따로 실행!
  useEffect(() =&gt; {
    document.title = `클릭 횟수: ${count}`;
  }, [count]);

  return &lt;button onClick={() =&gt; setCount(count + 1)}&gt;{count}&lt;/button&gt;;
}
</code></pre>
<ul>
<li>고차 컴포넌트란 무엇인가?
:: 컴포넌트를 받아서 새로운 컴포넌트를 반환하는 함수를 의미한다.</li>
</ul>
<pre><code class="language-javascript">
mport React from &#39;react&#39;;

// 이것이 고차 컴포넌트(HOC)입니다.
const withLoading = (WrappedComponent) =&gt; {
  // 새로운 컴포넌트를 반환합니다.
  return ({ isLoading, ...props }) =&gt; {
    // 1. 로딩 중일 때 보여줄 공통 로직
    if (isLoading) {
      return &lt;div&gt;데이터를 불러오는 중입니다... 잠시만 기다려주세요.&lt;/div&gt;;
    }

    // 2. 로딩이 끝났을 때 원래 컴포넌트에 나머지 props를 전달하며 렌더링
    return &lt;WrappedComponent {...props} /&gt;;
  };
};

export default withLoading;
</code></pre>
<h3 id="props와-state를-활용한-컴포넌트-데이터-제어">props와 state를 활용한 컴포넌트 데이터 제어</h3>
<ul>
<li>리액트에서 props란 무엇인가?
:: props는 property를 나타내며, 개별 값 또는 여러 값을 담고 있는 객체 형태로 컴포넌트에 전달되는 인수이다. props는 컴포넌트 트리 내에서 컴포넌트간에 통신하는 채널로 작동한다. 아래가 예시이다.</li>
</ul>
<pre><code class="language-javascript">

function Employee(props) {
    const {name, age} = props

    return (
        &lt;&gt;
            &lt;span&gt;Name : {name}&lt;/span&gt;
            &lt;span&gt;Age : {age}&lt;/span&gt;
        &lt;/&gt;
    )
}
</code></pre>
<ul>
<li><p>리액트의 state에 대해서 설명할 수 있는가?
:: 컴포넌트에 속하는 속성값을 저장하는 내장된 자바스크립트 객체이다. state에서 중요한 부분은 state값이 바뀌면 컴포넌트가 리렌더링된다.</p>
</li>
<li><p>props와 state의 가장 큰 차이점은?
:: props는 불변이며 컴포넌트간 데이터 전달에 역할을 한다. props는 읽기 전용이다. 반면 state는 동적인 데이터로써, 내부 함수 setState를 통해서 값을 변경하며 불변성을 유지한다. 그리고 비동기적으로 변경된다. 둘의 공통점은 데이터가 변경시에 리액트가 감지하여 리렌더링을 일으킨다.</p>
</li>
</ul>
<ul>
<li>리액트는 어떻게 여러개의 state를 일괄적으로 처리하는가?
:: 리액트는 각 state가 업데이트될떄 컴포넌트가 리렌더링 되는것을 방지한다. 리액트 내장된 훅을 사용해서 이벤트 핸들러내에서 업데이트를 그룹화함으로써 애플리케이션의 성능을 최적화 한다.</li>
</ul>
<ul>
<li><p>자동일괄처리를 방지할 수 있는가?
:: flushSync API를 함수를 통해서 방지할 수 있다.</p>
</li>
<li><p>어떻게 state내부의 객체를 업데이트 하는가?
:: 직접 업데이트가 아닌 setState를 사용해서 업데이트한다.</p>
</li>
</ul>
<pre><code class="language-javascript">setWeather({
    ...weather,
      [e.target.name] : e.target.value
})
</code></pre>
<ul>
<li>어떻게 중첩된 state 객체를 업데이트 하는가?
:: 별도의 중첩된 객체를 생성하거나 혹은 전개연산자를 활용해서 업데이트를 한다. 전통적인 방식이 번거롭다면, 서드파티인 imer를 활용해서 업데이트 할 수 있다.</li>
</ul>
<h3 id="key-prop의-중요성">Key Prop의 중요성</h3>
<ul>
<li>key prop이란 무엇이며, 그 목적은 무엇인가?
:: 리스트 아이템을 렌더링시에 포함되어야 하는 특별할 속성이다. 각 아이템의 값이 변경이 되면 리렌더링이 되어야 하는데, 아이템 안에서 변경이 되었는지 식별 할 수 있는 값이다.</li>
</ul>
<h3 id="이벤트-처리">이벤트 처리</h3>
<ul>
<li>합성 이벤트란(synthetics) 무엇인가?
:: 브라우저의 실제 네이티브 이벤트 객체를 감싼 크로스 브라우저 래퍼이다. 아래 예시처럼 이벤트를 발생시킬때 실행되는 이벤트 핸들러의 인자에 리액트에서는 synthetics 이벤트를 전달받는다</li>
</ul>
<pre><code class="language-javascript">
  const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
        const { name, value } = e.target;
        setUserInfo(prevState =&gt; ({
            ...prevState,
            [name]: value
        }));
    }
</code></pre>
<h3 id="리액트-이벤트-핸들러와-html-이벤트-핸들러의">리액트 이벤트 핸들러와 HTML 이벤트 핸들러의</h3>
<ul>
<li>차이점은 무엇인가?
::: 명명 규칙( HTML은 이벤트명을 소문자로 리액트는 카멜케이스), 기본 동작 방지, 함수 호출하는 부분에서 차이를 둔다</li>
</ul>
<p>명명규칙</p>
<pre><code class="language-jsx">&lt;button onclick=&quot;handleClick()&quot;&gt;SignUp&lt;/button&gt; // html
&lt;button onClick={handleClick}&gt;SignUp&lt;/button&gt; // react
</code></pre>
<p>기본동작 방지 : 이벤트의 기본동작을 방지한다. HTML은 이벤트 핸들러 내부에서 fals를 리턴하면서 방지하고 리액트에선 event.preventDefault라는 메서드를 호출 해서 방지할 수 있다.</p>
<pre><code class="language-html">&lt;form onsubmit=&quot;console.log(&#39;form을 제출!&#39;); return false&quot;&gt;
  ...

&lt;/form&gt;</code></pre>
<pre><code class="language-javascript">
const handleSubmit = (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    event.preventDefault // 기본동작 방지
}

&lt;form onSubmit={handleSubmit}&gt;
  ...

&lt;/form&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[greedy]]></title>
            <link>https://velog.io/@berry_hennie/greedy</link>
            <guid>https://velog.io/@berry_hennie/greedy</guid>
            <pubDate>Tue, 21 Oct 2025 22:49:39 GMT</pubDate>
            <description><![CDATA[<h3 id="그리디란">그리디란?</h3>
<p>각 단계에서 최적의 해를 선택하는 방식이다. 그러나 각 단계에 최적의 해가 전체의 최적의 해가 되지는 못한다. 그리디의 단점이다.</p>
<p>단적인 예시가 아래 슬라이딩 윈도우로 풀었던(
<a href="https://velog.io/@berry_hennie/sliding-window">https://velog.io/@berry_hennie/sliding-window</a>) 카드게임이다.</p>
<p>양쪽의 최대 숫자를 뽑는다고 해서 총합이 최대가 되지는 못한다.</p>
<p><code>[1,30,3,5,6,7]</code> 카드가 이런 순서일 경우 각 단계에서 최대의 숫자를 뽑는다고 해서 총합이 최대가 되지 못하는 경우이다.</p>
<h3 id="그리디의-예시">그리디의 예시</h3>
<ol>
<li>버스를 최대로 태울 수 있는 무게 구하기</li>
</ol>
<pre><code class="language-python">def solution(weight:list[int], limit:int):

    weight_list = sorted(weight)
    answer = []
    cnt = 0
    for _weight in weight_list:
        if cnt + _weight &lt;=limit:
            answer.append(_weight)
            cnt+=_weight
    print(answer)
    return len(answer)</code></pre>
<p>-&gt; 보통 그리디는 정렬을 기반으로 한다. limit전까지 최소의 무게를 태워 최대를 태울 수 있도록 한다.</p>
<ol start="2">
<li>선긋기 문제</li>
</ol>
<p>한번의 선긋기 수직선상에 한점에서 다른점까지 선을 긋는 것이다. 선을 그을때는 이미 선이 있는 위치에서 겹쳐 그릴 수도 있다.
수직선은 0번부터 m번 지점까지 길이를 갖고 있다.
연속적인 선을 긋기 위해서 최소 횟수로 선을 그을 수 있는 방법을 구하시오</p>
<pre><code class="language-python">
def solution(m: int, nums: list[list[int, int]]) -&gt; int:
    sorted_nums = sorted(nums, key=lambda x:x[0])

    current_start = 0
    current_end = 0
    result = []
    while current_end &lt; m:
        current_nums = [elm for elm in sorted_nums if elm[0]&lt;=current_start]
        if not current_nums:
            break

        current_nums_sort = sorted(current_nums, key=lambda x:-x[1])
        result.append(current_nums_sort[0])
        current_start = current_nums_sort[0][1]
        current_end= current_nums_sort[0][1]


    return len(result)

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[슬라이딩윈도우]]></title>
            <link>https://velog.io/@berry_hennie/sliding-window</link>
            <guid>https://velog.io/@berry_hennie/sliding-window</guid>
            <pubDate>Tue, 21 Oct 2025 22:39:48 GMT</pubDate>
            <description><![CDATA[<p>슬라이딩 윈도우란?</p>
<p>연속된 데이터에서 일정한 크기의 창을 설정하고 이 윈도우를 하나씩 이동 시키면서 데이터를 효율적으로 처리하는 기법이다.</p>
<p>문제</p>
<p>N개의 카드가 있고, 양쪽 끝 왼쪽 끝카드를 둘 중 하나 가져갈 수 있다. 양끝에서 가져가는 방식으로 k개의 카드를 가져갈 수 있다. 일렬로 놓여진 각 카드의 숫자가 매개변수 nums로 주어지고 현수가 가져갈 수 있는 카드의 갯수가 k개 주어지면 최대점수를 반환하는 프로그램을 작성해라</p>
<pre><code class="language-python">def solution(nums:list[int],k:int):
    total = sum(nums)
    n = len(nums)
    picked = n-k
    answer = 0
    for i in range(n):
        slice_window =nums[i:i+picked]
        _sum = sum(slice_window)
        if total - _sum &gt; answer and len(slice_window) == picked:
            answer = total- _sum

    print(answer)
       return answer</code></pre>
<p><code>slice_window =nums[i:i+picked]</code> 가 슬라이딩 윈도우가 되어서 각 nums를 순회한다. 해당 합이 최소일 경우 총합- sum(slice_window)가 최대가 된다.</p>
<p>해당 방식으로 풀면 slice_window를 제외하면 양끝을 혹은 한쪽끝과 연속된 수열로 카드를 뽑을 수 있어 원하는 조건을 충족해서 풀 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파일 업로드 구현]]></title>
            <link>https://velog.io/@berry_hennie/file-upload</link>
            <guid>https://velog.io/@berry_hennie/file-upload</guid>
            <pubDate>Thu, 16 Oct 2025 07:02:49 GMT</pubDate>
            <description><![CDATA[<p>파일 업로드 구현관련해서 바닐라JS와 React 코드를 비교하기로 한다.</p>
<p>가장 먼저, 바닐라JS에서의 코드이다.</p>
<pre><code class="language-javascript">
function selectImage() {
  const input = document.createElement(&#39;input&#39;);
  input.type = &#39;file&#39;;
  input.accept = &#39;image/*&#39;;
  input.onchange = (e) =&gt; {
    const file = e.target.files[0];
    if (file) {
      console.log(file);
    }
  };
  input.click();
}

// 버튼 클릭 시 실행
document.getElementById(&#39;uploadBtn&#39;).onclick = selectImage;</code></pre>
<p>동적으로 태그를 추가해서 구현하는것이 일반적인 케이스이다.</p>
<p>React에서의 코드는</p>
<pre><code class="language-tsx">
import { useRef } from &#39;react&#39;;

function ImageUpload() {
  const fileInputRef = useRef&lt;HTMLInputElement&gt;(null);

  const handleButtonClick = () =&gt; {
    fileInputRef.current?.click();
  };

  const handleFileChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files?.[0];
    if (file) {
      // 파일 처리 로직
      console.log(file);
    }
  };

  return (
    &lt;div&gt;
      &lt;button onClick={handleButtonClick}&gt;이미지 선택&lt;/button&gt;
      &lt;input
        ref={fileInputRef}
        type=&quot;file&quot;
        accept=&quot;image/*&quot;
        onChange={handleFileChange}
        style={{ display: &#39;none&#39; }}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이떄 브라우저는 client가 서버에게 파일 저장하라고 하기전에 임시로 파일을 메모리에만 임시 보관한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useMemo를 통한 무한 렌더링 방지 ]]></title>
            <link>https://velog.io/@berry_hennie/usememo</link>
            <guid>https://velog.io/@berry_hennie/usememo</guid>
            <pubDate>Thu, 16 Oct 2025 02:26:05 GMT</pubDate>
            <description><![CDATA[<p>일단 발생되는 코드는 아래와 같다.</p>
<ol>
<li>query 코드</li>
</ol>
<pre><code class="language-tsx">export function usePublishingGroupQuery() {
  const { businessId, propertyId } = useAuth();

  const {
    data: publishingGroup,
    isLoading: isPublishingGroupLoading,
    isFetching: isPublishingGroupFetching,
    error: publishingGroupError,
    refetch: refetchPublishingGroup,
  } = useQuery({
    enabled: !!businessId &amp;&amp; !!propertyId,
    queryKey: generateDeployGroupsKey(),
    queryFn: () =&gt; fetchDeploysGroups({ business_id: businessId, property_id: propertyId }),
    staleTime: Infinity,
    select: (data: PublishingGroupResponseData) =&gt; data.result.deployGroups,
  });

  const deployedPublishingGroup = useMemo(() =&gt; publishingGroup?.filter((data) =&gt; data.project_id), [publishingGroup]);

  return {
    publishingGroup,
    deployedPublishingGroup,
    isPublishingGroupLoading,
    isPublishingGroupFetching,
    publishingGroupError,
    refetchPublishingGroup,
  };
}
</code></pre>
<ol start="2">
<li><p>호출하는 컴포넌트 코드</p>
<pre><code class="language-tsx">const TVPortal: React.FC&lt;TVPortalProps&gt; = ({ setPublishGroup, publishGroup }) =&gt; {
const { t } = useTranslation();
const { deployedPublishingGroup } = usePublishingGroupQuery();
const { editor } = useEditorQuery();
const { setPublishData, publishData } = useRoomManagerStore((state) =&gt; state.publish);

useEffect(() =&gt; {
 if (editor &amp;&amp; deployedPublishingGroup) {
   const _publishData = deployedPublishingGroup.map((data) =&gt; {
     const findOne = editor.result?.projects?.find((_data) =&gt; _data.project_id === data.project_id);
     const modified_date = findOne?.project?.modified_date;
     return {
       ...data,
       modified_date,
       project_id: data.project_id,
     };
   });
   if (_publishData?.length &gt; 0) {
     setPublishData(_publishData as TVPublishDeployData[]);
     setPublishGroup(_publishData[0].deploy_id);
   }
 }
}, [editor, deployedPublishingGroup, setPublishData, setPublishGroup]);

</code></pre>
</li>
</ol>
<p>...... // 나머지는 생략 
}</p>
<pre><code>

### 🔄 왜 useMemo를 사용해야 했을까?

#### 📌 문제 상황
usePublishingGroupQuery에서 deployedPublishingGroup을 다음과 같이 정의했을 때:

```typescript
const deployedPublishingGroup = publishingGroup?.filter((data) =&gt; data.project_id);</code></pre><p>이 코드가 매 렌더링마다 새로운 배열 객체를 생성하게 되면서, TVPortal 컴포넌트의 useEffect가 무한 호출되는 문제가 발생하였다.</p>
<h4 id="⚙️-실행-흐름-요약">⚙️ 실행 흐름 요약</h4>
<ol>
<li>usePublishingGroupQuery 호출 → deployedPublishingGroup 생성 (새로운 배열 참조)</li>
<li>TVPortal의 useEffect 실행 (의존성 배열에서 deployedPublishingGroup의 변경 감지)</li>
<li>setPublishData 호출 → 상태 변경</li>
<li>상태 변경으로 컴포넌트 리렌더링</li>
<li>다시 1번으로 돌아가면서 무한 반복</li>
</ol>
<h4 id="🧠-왜-무한-호출이-발생했을까">🧠 왜 무한 호출이 발생했을까?</h4>
<p>deployedPublishingGroup은 배열을 새로 생성하기 때문에, 값이 같더라도 참조가 매번 달라짐
useEffect는 참조가 바뀌면 실행되므로, 렌더링마다 실행됨
setPublishData는 상태를 변경하므로 또다시 렌더링 발생 → 무한 루프</p>
<pre><code class="language-typescript">const { setPublishData, publishData } = useRoomManagerStore((state) =&gt; state.publish);</code></pre>
<p>만약에 useEffect에서 setPublishData호출하는 부분이 없어 스토어에 구독하는 데이터가 없다면 TVPortal은 리렌더링이 되지 않을것이다. 그러나 데이터가 변경됨을 감지함으로써 리렌더링이 발생하고 deployedPublishingGroup 데이터가 변경이 됨으로써 또 다시 무한호출로 이어진다.</p>
<p>스토어의 데이터가 변경되어야 하고, 변경후에는 리렌더링이 되어야 한다. 그렇다면 무한호출을 막을 수 있는 방법은 deployedPublishingGroup의 데이터의 값자체가 변경되지 않았음에도 불구하고 새로운 데이터 객체가 생성됨으로써 새로운 데이터라고 판단하는 부분을 고쳐야한다.</p>
<p>그렇기에 useMemo훅을 사용해서 실제 데이터값이 변경되었는지를 확인하고 의존성 배열로써 publishingGroup이 변경될떄만 새로운 객체를 생성해서 해결하였다.</p>
<h4 id="✅-해결-방법-usememo-사용">✅ 해결 방법: useMemo 사용</h4>
<pre><code class="language-typescript">const deployedPublishingGroup = useMemo(  () =&gt; publishingGroup?.filter((data) =&gt; data.project_id),  [publishingGroup]);</code></pre>
<p>publishingGroup이 변경될 때만 deployedPublishingGroup을 새로 계산
값이 변경되지 않으면 같은 참조를 유지하므로 useEffect가 다시 실행되지 않음
결과적으로 불필요한 리렌더링과 무한 호출을 방지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[무한호출]]></title>
            <link>https://velog.io/@berry_hennie/infinite-loop</link>
            <guid>https://velog.io/@berry_hennie/infinite-loop</guid>
            <pubDate>Sat, 13 Sep 2025 01:15:42 GMT</pubDate>
            <description><![CDATA[<h2 id="react-무한-루프-왜-발생할까-🤔">React 무한 루프, 왜 발생할까? 🤔</h2>
<p>React 컴포넌트에서 무한 루프는 주로 useEffect 내에서 객체(Object)나 배열(Array) 상태를 잘못 업데이트할 때 발생합니다.</p>
<h3 id="1-문제의-핵심-새로운-객체-참조">1. 문제의 핵심: 새로운 객체 참조</h3>
<p>React 렌더링 과정: 상태 변경 → 컴포넌트 재실행(리렌더링) → useEffect 의존성 비교</p>
<p>무한 루프의 원인:</p>
<p>useEffect 내부에서 setState로 새로운 객체를 생성하여 상태를 업데이트합니다. 상태가 변경되었으므로 리렌더링이 발생합니다.</p>
<p>리렌더링 후 useEffect가 다시 실행되면서 또다시 새로운 객체를 생성하여 setState를 호출합니다.</p>
<p>1번으로 돌아가 무한 반복됩니다.</p>
<p>JavaScript에서 { a: 1 }과 { a: 1 }은 내용이 같아도 서로 다른 메모리 주소를 가진 별개의 객체입니다. React는 이 주소(참조)가 다르기 때문에 상태가 변경되었다고 판단하고 리렌더링을 계속 발생시킵니다.</p>
<pre><code class="language-typescript">// ❌ 무한 루프가 발생하는 코드 예시
useEffect(() =&gt; {
  // 렌더링될 때마다 &#39;새로운&#39; 객체가 생성되어 상태가 계속 변경됨
  setSomeObject({ key: &#39;value&#39; }); 
}, [someObject]); // 의존성 배열에 객체 자신이 포함되어 있는 경우
</code></pre>
<h3 id="2-원시값primitive은-괜찮은-이유">2. 원시값(Primitive)은 괜찮은 이유</h3>
<p>반면, boolean, number, string과 같은 원시값은 값이 같으면 React가 리렌더링을 생략합니다.</p>
<pre><code class="language-typescript">
// ✅ 괜찮은 코드 예시
// device.powerMode가 &#39;on&#39;으로 동일하다면 계속 true가 전달됨
setPowerStatus(device.powerMode === &#39;on&#39;);

// React는 기존 상태가 true인데 또 true로 업데이트하라는 요청을 받으면
// 상태 변경이 없다고 판단하고 리렌더링을 하지 않습니다.
</code></pre>
<p>객체 상태, 어떻게 안전하게 다룰까? ✅
객체 상태를 업데이트하면서 무한 루프를 피하는 핵심은 <strong>&quot;의존성이 실제로 변경되었을 때만 새로운 객체를 생성&quot;</strong>하는 것입니다.</p>
<h3 id="🥇-1-usememo-사용-가장-추천">🥇 1. useMemo 사용 (가장 추천)</h3>
<p>useMemo는 의존성 배열의 값이 변경될 때만 새로운 객체를 생성하고, 그렇지 않으면 이전에 생성했던 객체를 그대로 반환합니다.</p>
<p>장점: 코드가 깔끔하고, 의도치 않은 객체 재생성을 막아 성능 최적화와 무한 루프 방지에 효과적입니다. device 같은 값이 초기 로딩 시 undefined여도 안전하게 처리됩니다.</p>
<pre><code class="language-typescript">
// device의 특정 속성들이 바뀔 때만 softApData 객체가 새로 생성됨
const softApData = useMemo(() =&gt; ({
  softApMode: device?.softApMode || 0,
  softApPassword: device?.softApPassword || &#39;&#39;,
  // ...
}), [device?.softApMode, device?.softApPassword]); // 실제 값들을 의존성으로 지정

useEffect(() =&gt; {
  setSoftApPopupData(softApData);
}, [softApData]); // softApData의 참조가 바뀔 때만 실행됨</code></pre>
<h3 id="2-조건부로-상태-업데이트">2. 조건부로 상태 업데이트</h3>
<p>상태를 업데이트하기 전에 새로운 데이터와 이전 데이터를 비교하여, 실제로 내용이 다를 때만 업데이트를 실행하는 방법입니다.</p>
<p>단점: 객체가 복잡하면 비교 로직(예: JSON.stringify)이 번거롭고 성능에 부담을 줄 수 있습니다.</p>
<h3 id="3-상태를-개별적으로-분리">3. 상태를 개별적으로 분리</h3>
<p>하나의 큰 객체 대신 여러 개의 useState를 사용하여 각 속성을 개별적인 상태로 관리합니다. 이는 객체 참조 문제 자체를 원천적으로 방지합니다.</p>
<p>단점: 관리해야 할 상태가 너무 많아지면 코드가 복잡해질 수 있습니다.</p>
<h4 id="결론">결론</h4>
<p>React에서 객체 상태를 다룰 때는 <strong>참조 동일성(Reference Equality)</strong>을 이해하는 것이 무한 루프를 방지하는 핵심입니다. useMemo는 이러한 문제를 가장 우아하고 효율적으로 해결해 주는 최고의 방법입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입 호환성]]></title>
            <link>https://velog.io/@berry_hennie/type</link>
            <guid>https://velog.io/@berry_hennie/type</guid>
            <pubDate>Mon, 01 Sep 2025 22:07:19 GMT</pubDate>
            <description><![CDATA[<h2 id="타입은-집합이다">타입은 집합이다.</h2>
<p>타입은 여러개의 값을 포함하는 &#39;집합&#39;이다. number라는 집합은 여러개의 숫자 리터럴로 이루어진 집합이다.
e.g) -20, Infinity, 1234123,2,....</p>
<p>예를 들어 20이라는 타입에 속하는 요소인 숫자 20은 20이라는 타입 외에도 number라는 타입에 속한다.
즉 Number 타입이라는 거대한 집합에 포함되는 부분집합으로 number literal Type이 존재한다.</p>
<p>그리고 이러한 넘버타입처럼 다른타입을 포함하는 타입을 슈퍼타입(부모타입)이라고 부른다.
반대로 숫자리터럴 처럼 서브타입(자식타입)이라고 부른다.</p>
<h2 id="타입-호환성">타입 호환성</h2>
<p>타입 호환성이란 A와 B라는 타입이 존재할시 A타입을 B타입으로 취급해도 괜찮을지 판단하는 것을 의미한다.
만약에 호환이 된다면 A는 B로 타입을 호환된다고 한다.
예를 들어 숫자타입과 숫자 리터럴 타입을 예시로 들 수 있다.</p>
<p>숫자 타입은 -&gt; 숫자리터럴 타입으로 호환되지 않는다. 반대로 숫자리터럴 타입은 number타입으로 호환된다.</p>
<pre><code class="language-typescript">
let num1 : number = 10;
let num2 : 10 = 10;

num1 = num2; // 문제 되지 않는다
num2 = num1; // 호환되지 않기 떄문에 에러남
</code></pre>
<p>왜 에러가 나는 걸까?
num2는 숫자 리터롤 타입이고 num1은 number 타입이다. 즉 숫자리터럴 타입에 더 큰 슈퍼타입을 할당할 수 없다. 반대로 num1은 숫자 타입이고 num2는 숫자리터럴 타입이기에 숫자타입에(슈퍼타입) 숫자 리터럴 타입(서브타입)으로 할당 할 수 있다.</p>
<p>그리고 특별히 서브타입의 값을 슈퍼타입으로 취급하는것은 업 캐스팅이라고 부르고 반대는 다운 캐스팅이라고 부른다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] Generic 타입은 언제 쓰면 유용할까?]]></title>
            <link>https://velog.io/@berry_hennie/typescriptgeneric-type</link>
            <guid>https://velog.io/@berry_hennie/typescriptgeneric-type</guid>
            <pubDate>Mon, 11 Aug 2025 13:40:28 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-타입스크립트-제네릭generic은-왜-필요할까">🤔 타입스크립트, 제네릭(Generic)은 왜 필요할까?</h2>
<p>타입스크립트를 처음 배울 때 Generic이라는 개념을 마주치면 &#39;이건 대체 왜 있는 걸까?&#39; 하는 생각을 한 번쯤 해보셨을 겁니다. 저도 그랬으니까요! 하지만 제네릭은 타입스크립트를 강력하게 만들어주는 핵심 기능 중 하나입니다.</p>
<p>타입스크립트 공식 문서에서는 제네릭을 이렇게 설명합니다.</p>
<p>&quot;단일 타입이 아닌 다양한 타입에서 작동하는 재사용 가능한 컴포넌트를 만들기 위해 도입되었습니다.&quot;</p>
<p>쉽게 말해, 타입을 함수의 파라미터처럼 사용하는 기능이죠. 이를 통해 우리는 유연하면서도 타입 안정성을 잃지 않는 코드를 작성할 수 있습니다.</p>
<ol>
<li>반복되는 코드
제네릭이 왜 필요한지 알려면, 제네릭이 없는 상황을 상상해보는 것이 가장 좋습니다. 어떤 값을 받아서 그대로 돌려주는 identity 함수가 있다고 해봅시다.</li>
</ol>
<p>만약 number 타입과 string 타입을 모두 지원하고 싶다면, 우리는 각 타입에 맞는 함수를 따로 만들어야 합니다.</p>
<pre><code class="language-typescript">
// 숫자를 위한 identity 함수
function identityNumber(arg: number): number {
  return arg;
}

// 문자열을 위한 identity 함수
function identityString(arg: string): string {
  return arg;
}
</code></pre>
<p>기능은 완전히 똑같은데, 단지 타입이 다르다는 이유만으로 함수를 계속 중복해서 만들어야 하죠. 타입이 100개라면 함수도 100개를 만들어야 할까요? 정말 비효율적입니다.</p>
<ol start="2">
<li>재사용 ✨
이때 제네릭이 마법처럼 등장합니다. 제네릭을 사용하면 이 모든 타입을 아우르는 단 하나의 함수를 만들 수 있습니다.</li>
</ol>
<pre><code class="language-typescript">
function identity&lt;T&gt;(arg: T): T {
  return arg;
}
</code></pre>
<p>여기서 <T>가 바로 제네릭을 사용한다는 표시입니다. T는 <strong>타입 변수(Type Variable)</strong>로, 실제 타입이 들어올 자리를 잠시 맡아두는 플레이스홀더입니다.</p>
<p>이제 identity 함수는 number를 넣으면 number를 반환하고, string을 넣으면 string을 반환하는 똑똑한 함수가 되었습니다. 코드 중복은 사라지고 타입 안정성은 그대로 유지됩니다.</p>
<ol start="3">
<li><p>타입의 제약</p>
<p>때로는 제네릭에 특정 조건을 걸고 싶을 때가 있습니다. &quot;아무 타입이나 받지는 말고, 특정 프로퍼티를 가진 타입만 받았으면 좋겠어!&quot; 와 같은 경우죠. 이것을 제네릭 제약(Generic Constraints) 이라고 합니다.</p>
</li>
</ol>
<p>length 프로퍼티를 가진 타입만 처리하는 함수를 만들어 볼까요?</p>
<pre><code class="language-typescript">// &#39;length&#39; 프로퍼티가 있어야 한다는 인터페이스 정의
interface LengthWise {
  length: number;
}

// T는 LengthWise 인터페이스를 만족하는 타입만 가능!
function logLength&lt;T extends LengthWise&gt;(item: T): void {
  console.log(`&#39;item&#39;의 길이는 ${item.length}입니다.`);
}</code></pre>
<p>extends 키워드를 사용해서 타입 T가 반드시 LengthWise 구조를 가져야 한다고 제약을 걸었습니다. 이제 logLength 함수는 length 프로퍼티를 가진 값만 인자로 받을 수 있습니다.</p>
<pre><code class="language-typescript">logLength([1, 2, 3]);      // ✅ 배열은 length가 있으니 통과!
logLength(&quot;test&quot;);         // ✅ 문자열도 length가 있으니 통과!
logLength({ length: 10 }); // ✅ length 프로퍼티가 있는 객체도 통과!

// logLength(4); // ❌ Error! &#39;number&#39; 타입에는 &#39;length&#39; 프로퍼티가 없습니다.</code></pre>
<ol start="4">
<li>재사용 가능한 자료구조 🧱
제네릭의 진정한 힘은 재사용 가능한 컴포넌트나 자료구조를 만들 때 드러납니다. 예를 들어, 어떤 타입의 데이터든 담을 수 있는 Stack 클래스를 만들어 보겠습니다.</li>
</ol>
<pre><code class="language-typescript">class Stack&lt;T&gt; {
  private items: T[] = [];

  // T 타입의 아이템만 push 가능
  push(item: T) {
    this.items.push(item);
  }

  // T 타입의 아이템을 pop (없으면 undefined)
  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }
}</code></pre>
<p>이제 이 Stack 클래스는 제네릭 덕분에 number 전용 스택, string 전용 스택, 심지어 User 객체 전용 스택으로도 변신할 수 있습니다.</p>
<pre><code class="language-typescript">
const numberStack = new Stack&lt;number&gt;();
numberStack.push(10);
// numberStack.push(&quot;hello&quot;); // ❌ Error!

const stringStack = new Stack&lt;string&gt;();
stringStack.push(&quot;world&quot;);</code></pre>
]]></description>
        </item>
    </channel>
</rss>