<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>long_leg</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 25 Nov 2025 23:39:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>long_leg</title>
            <url>https://velog.velcdn.com/images/gyomdyung_/profile/7d72a496-6554-42ce-b6d7-ed3bf7a7e401/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. long_leg. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gyomdyung_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[React Query] useQueryMutation 패턴: Mutation처럼 쓰고 Query처럼 캐싱하기]]></title>
            <link>https://velog.io/@gyomdyung_/React-Query-useQueryMutation-%ED%8C%A8%ED%84%B4-Mutation%EC%B2%98%EB%9F%BC-%EC%93%B0%EA%B3%A0-Query%EC%B2%98%EB%9F%BC-%EC%BA%90%EC%8B%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyomdyung_/React-Query-useQueryMutation-%ED%8C%A8%ED%84%B4-Mutation%EC%B2%98%EB%9F%BC-%EC%93%B0%EA%B3%A0-Query%EC%B2%98%EB%9F%BC-%EC%BA%90%EC%8B%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 25 Nov 2025 23:39:10 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>실무에서 검색 기능을 구현할 때 이런 고민을 해보신 적 있으신가요?</p>
<p>&quot;이건 데이터 조회니까 <code>useQuery</code>를 써야 하나? 아니면 버튼 클릭 시에만 실행되어야 하니까 <code>useMutation</code>을 써야 하나?&quot;</p>
<p>더 나아가 복잡한 필터 조건을 가진 검색 API는 GET이 아닌 POST로 요청해야 하는 경우도 많습니다. REST 원칙상 조회는 GET을 사용해야 하지만, 실무에서는 다음과 같은 이유로 POST를 사용합니다:</p>
<ul>
<li>URL 길이 제한 (2048자)</li>
<li>복잡한 객체 구조를 query string으로 변환하는 어려움</li>
<li>배열, 중첩 객체 등 복잡한 필터 조건 전달의 불편함</li>
<li>보안상 민감한 검색 조건을 URL에 노출하지 않기 위함</li>
</ul>
<p>이런 상황에서 <code>useQuery</code>와 <code>useMutation</code> 중 어느 것을 선택해도 아쉬운 점이 생깁니다. 이 글에서는 두 가지의 장점을 결합한 <code>useQueryMutation</code> 패턴을 소개합니다.</p>
<h2 id="문제-상황">문제 상황</h2>
<h3 id="usequery의-한계">useQuery의 한계</h3>
<p>React Query의 <code>useQuery</code>는 데이터 조회에 최적화된 훅입니다. 하지만 검색 기능 구현 시 다음과 같은 문제가 있습니다:</p>
<p><strong>1. 초기 렌더링 시 자동 실행</strong></p>
<pre><code class="language-typescript">// 문제: 컴포넌트가 마운트되자마자 API 호출됨
const { data } = useQuery({
  queryKey: [&#39;products&#39;, filters],
  queryFn: () =&gt; fetchProducts(filters),
});</code></pre>
<p>사용자가 검색 버튼을 클릭하지 않았는데도 API가 호출됩니다.</p>
<p><strong>2. enabled 옵션 사용 시 코드 복잡도 증가</strong></p>
<pre><code class="language-typescript">const [shouldFetch, setShouldFetch] = useState(false);

const { data } = useQuery({
  queryKey: [&#39;products&#39;, filters],
  queryFn: () =&gt; fetchProducts(filters),
  enabled: shouldFetch, // 추가 상태 관리 필요
});

const handleSearch = () =&gt; {
  setShouldFetch(true); // 상태 변경으로 쿼리 실행
};</code></pre>
<p>검색을 제어하기 위해 별도의 상태를 관리해야 하고, 코드가 장황해집니다.</p>
<p><strong>3. queryKey 의존성 변경 시 의도치 않은 재실행</strong></p>
<pre><code class="language-typescript">const { data } = useQuery({
  queryKey: [&#39;products&#39;, filters], // filters가 변경되면 자동 재실행
  queryFn: () =&gt; fetchProducts(filters),
  enabled: shouldFetch,
});

// 문제: 필터를 변경하는 순간 API가 호출됨 (검색 버튼을 누르지 않았는데도)
const handleFilterChange = (newFilters) =&gt; {
  setFilters(newFilters); // queryKey 변경 → 자동 refetch
};</code></pre>
<h3 id="usemutation의-한계">useMutation의 한계</h3>
<p>반대로 <code>useMutation</code>을 사용하면 어떨까요?</p>
<pre><code class="language-typescript">const { mutate, data } = useMutation({
  mutationFn: (filters: ProductFilters) =&gt; fetchProducts(filters),
});

const handleSearch = () =&gt; {
  mutate(filters); // 수동 실행 ✅
};</code></pre>
<p>수동 실행은 가능하지만, 다음과 같은 Query의 강력한 기능들을 사용할 수 없습니다:</p>
<ul>
<li><strong>캐싱</strong>: 같은 조건으로 재검색 시 캐시된 데이터 사용 불가</li>
<li><strong>keepPreviousData</strong>: 새 데이터 로딩 중 이전 데이터 유지 불가 (화면 깜빡임)</li>
<li><strong>staleTime</strong>: 데이터 신선도 관리 불가</li>
<li><strong>gcTime</strong>: 메모리 효율적인 캐시 정리 불가</li>
<li><strong>refetchOnMount, refetchOnWindowFocus</strong>: 세밀한 refetch 제어 불가</li>
</ul>
<h2 id="해결-방법-usequerymutation">해결 방법: useQueryMutation</h2>
<p><code>useQueryMutation</code>은 Query의 캐싱/최적화 기능과 Mutation의 수동 실행을 결합한 커스텀 훅입니다.</p>
<h3 id="핵심-아이디어">핵심 아이디어</h3>
<ol>
<li><strong>내부 상태로 variables 관리</strong>: <code>useState</code>로 요청 파라미터를 내부에서 관리</li>
<li><strong>Lazy Execution</strong>: <code>enabled: !!variables</code>로 variables가 설정될 때만 쿼리 실행</li>
<li><strong>Dynamic Query Key</strong>: variables를 queryKey에 포함하여 검색 조건별 캐싱</li>
<li><strong>Mutation-like API</strong>: <code>mutate()</code> 함수로 수동 실행</li>
</ol>
<h3 id="구현-코드">구현 코드</h3>
<pre><code class="language-typescript">import {
  keepPreviousData,
  useQuery,
  UseQueryOptions,
} from &#39;@tanstack/react-query&#39;;
import { useCallback, useState } from &#39;react&#39;;

interface UseQueryMutationOptions&lt;TData, TError = Error, TVariables = void&gt; {
  queryKey: unknown[];
  queryFn: (variables: TVariables) =&gt; Promise&lt;ApiResponse&lt;TData&gt;&gt;;
  queryOptions?: Omit&lt;
    UseQueryOptions&lt;ApiResponse&lt;TData&gt;, TError, ApiResponse&lt;TData&gt;, readonly unknown[]&gt;,
    &#39;queryKey&#39; | &#39;queryFn&#39;
  &gt;;
}

export function useQueryMutation&lt;
  TData = unknown,
  TError = Error,
  TVariables extends Record&lt;string, any&gt; | null | undefined = Record&lt;string, any&gt;,
&gt;({ queryKey, queryFn, queryOptions }: UseQueryMutationOptions&lt;TData, TError, TVariables&gt;) {
  // 1. 요청 파라미터를 내부 상태로 관리
  const [variables, setVariables] = useState&lt;TVariables | null&gt;(null);

  // 2. useQuery 래핑
  const query = useQuery&lt;ApiResponse&lt;TData&gt;, TError, ApiResponse&lt;TData&gt;, readonly unknown[]&gt;({
    // variables를 queryKey에 포함하여 검색 조건별 캐싱
    queryKey: generateQueryKeysFromUrl(queryKey + createQueryString(variables)),
    queryFn: () =&gt; queryFn(variables!),

    // 핵심: variables가 설정될 때만 실행
    enabled: !!variables,

    // 기본 최적화 설정
    retry: 1,
    refetchOnMount: false,
    refetchOnWindowFocus: false,
    staleTime: 1000 * 60 * 5,        // 5분간 캐시 유지
    gcTime: 1000 * 60 * 10,          // 10분 후 메모리에서 정리
    placeholderData: keepPreviousData, // 로딩 중 이전 데이터 유지

    // 사용자 정의 옵션으로 오버라이드 가능
    ...queryOptions,
  });

  // 3. Mutation처럼 사용할 수 있는 mutate 함수
  const mutate = useCallback((newVariables: TVariables) =&gt; {
    setVariables(newVariables); // 상태 변경 → useQuery 자동 실행
  }, []);

  // 4. Query의 모든 상태 + mutate 함수 반환
  return {
    ...query,
    mutate,
  };
}</code></pre>
<h3 id="동작-원리">동작 원리</h3>
<ol>
<li><strong>초기 상태</strong>: <code>variables</code>가 <code>null</code>이므로 <code>enabled: false</code> → 쿼리 실행 안 됨</li>
<li><strong>mutate 호출</strong>: <code>mutate(params)</code> 호출 → <code>variables</code> 상태 업데이트</li>
<li><strong>자동 실행</strong>: <code>variables</code>가 설정되어 <code>enabled: true</code> → 쿼리 자동 실행</li>
<li><strong>캐싱</strong>: <code>queryKey</code>에 variables 포함 → 같은 조건 재검색 시 캐시 사용</li>
<li><strong>UX 최적화</strong>: <code>keepPreviousData</code>로 로딩 중에도 이전 데이터 유지</li>
</ol>
<h3 id="apiresponset-패턴">ApiResponse<T> 패턴</h3>
<p>실무에서는 백엔드 응답 구조가 정형화되어 있는 경우가 많습니다. 우리 프로젝트에서는 다음과 같은 응답 구조를 사용합니다:</p>
<pre><code class="language-typescript">export interface ApiResponse&lt;T&gt; {
  status: &#39;success&#39; | &#39;fail&#39;;
  code: string;
  message: string;
  token: string;
  data: T;  // 실제 응답 데이터
}</code></pre>
<p><code>useQueryMutation</code>은 이러한 래핑된 응답 구조를 제네릭으로 처리하여, 타입 안정성을 유지하면서도 일관된 API를 제공합니다.</p>
<h2 id="실제-사용-예시">실제 사용 예시</h2>
<p>프로젝트에서 실제로 사용하고 있는 패턴들을 소개합니다.</p>
<h3 id="1-기본-패턴-api-훅-생성">1. 기본 패턴: API 훅 생성</h3>
<p>먼저 타입을 정의합니다:</p>
<pre><code class="language-typescript">// types.ts
export interface ProductListRequest {
  pageIndex: number;
  pageCount: number;
  categoryL?: string;
  categoryM?: string;
  categoryS?: string;
  keyword?: string;
}

export interface ProductItem {
  productId: string;
  productName: string;
  category: string;
  price: number;
}

export interface ProductListResponse {
  productItemList: ProductItem[];
  totalCount: number;
}</code></pre>
<p>API 함수를 작성합니다:</p>
<pre><code class="language-typescript">// useProductList.ts
import { ApiResponse, axiosInstance } from &#39;@/shared/api&#39;;
import { API_ENDPOINTS } from &#39;@/shared/constants&#39;;
import { useQueryMutation } from &#39;@/shared/hooks&#39;;
import { ProductListRequest, ProductListResponse } from &#39;./types&#39;;

const fetchProductList = async ({ pageCount = 30, ...body }: ProductListRequest) =&gt; {
  const { data } = await axiosInstance.post&lt;ApiResponse&lt;ProductListResponse&gt;&gt;(
    API_ENDPOINTS.PRODUCT.LIST, // &#39;/v3/product/list&#39;
    { ...body, pageCount },
  );
  return data; // ApiResponse&lt;ProductListResponse&gt; 반환
};

export const useProductList = () =&gt; {
  return useQueryMutation&lt;ProductListResponse, Error, ProductListRequest&gt;({
    queryKey: [API_ENDPOINTS.PRODUCT.LIST],
    queryFn: fetchProductList,
  });
};</code></pre>
<h3 id="2-컴포넌트에서-사용">2. 컴포넌트에서 사용</h3>
<pre><code class="language-typescript">import { useProductList } from &#39;@/features/product/api&#39;;

const ProductSearchModal = () =&gt; {
  const {
    mutate: searchProducts,
    data,
    isPending,
    isError
  } = useProductList();

  const [filters, setFilters] = useState({
    categoryL: &#39;&#39;,
    keyword: &#39;&#39;,
  });

  const handleSearch = () =&gt; {
    searchProducts({
      pageIndex: 1,
      pageCount: 30,
      categoryL: filters.categoryL,
      keyword: filters.keyword,
    });
  };

  const handlePageChange = (page: number) =&gt; {
    searchProducts({
      pageIndex: page,
      pageCount: 30,
      categoryL: filters.categoryL,
      keyword: filters.keyword,
    });
  };

  return (
    &lt;div&gt;
      &lt;input
        value={filters.keyword}
        onChange={(e) =&gt; setFilters({ ...filters, keyword: e.target.value })}
      /&gt;
      &lt;button onClick={handleSearch}&gt;검색&lt;/button&gt;

      &lt;Spin spinning={isPending}&gt;
        {data?.data?.productItemList.map((product) =&gt; (
          &lt;ProductCard key={product.productId} product={product} /&gt;
        ))}
      &lt;/Spin&gt;

      &lt;Pagination
        total={data?.data?.totalCount || 0}
        onChange={handlePageChange}
      /&gt;
    &lt;/div&gt;
  );
};</code></pre>
<p><strong>장점</strong>:</p>
<ul>
<li>필터를 변경해도 즉시 API 호출 안 됨 (useQuery의 문제 해결)</li>
<li>검색 버튼 클릭 시에만 <code>mutate()</code> 호출</li>
<li>같은 조건으로 재검색 시 캐시된 데이터 사용</li>
<li>페이지 변경 시 <code>keepPreviousData</code>로 이전 데이터 유지 (깜빡임 없음)</li>
</ul>
<h3 id="3-고급-패턴-초기-로딩-자동화">3. 고급 패턴: 초기 로딩 자동화</h3>
<p>컴포넌트가 마운트될 때 기본값으로 자동 검색이 필요한 경우:</p>
<pre><code class="language-typescript">// useAlarmList.ts
export const useAlarmList = (params?: { pageCount?: number; state?: string }) =&gt; {
  const queryMutate = useQueryMutation&lt;AlarmListResponse, Error, AlarmListRequest&gt;({
    queryKey: [API_ENDPOINTS.HISTORY.ALARM.LIST],
    queryFn: fetchAlarmList,
  });

  // 초기 로딩: 기본값으로 자동 실행
  useEffect(() =&gt; {
    const defaultFormData = getDefaultFormValues(); // { state: &#39;all&#39;, pageIndex: 1 }
    const requestData = {
      ...defaultFormData,
      pageIndex: 1,
      pageCount: params?.pageCount ?? 20,
      state: params?.state ?? null,
    };

    queryMutate.mutate(requestData);
  }, []); // 마운트 시 한 번만

  return queryMutate;
};</code></pre>
<p>사용:</p>
<pre><code class="language-typescript">const AlarmListPage = () =&gt; {
  const { data, isPending, mutate } = useAlarmList({ pageCount: 30 });

  // 마운트 시 자동으로 pageCount: 30으로 알림 조회
  // 이후에는 mutate()로 수동 제어 가능

  return (
    &lt;div&gt;
      &lt;AlarmList items={data?.data?.alarmList || []} /&gt;
      &lt;button onClick={() =&gt; mutate({ state: &#39;unread&#39;, pageIndex: 1, pageCount: 30 })}&gt;
        읽지 않은 알림만 보기
      &lt;/button&gt;
    &lt;/div&gt;
  );
};</code></pre>
<h3 id="4-캐시-전략-커스터마이징">4. 캐시 전략 커스터마이징</h3>
<p>데이터 특성에 따라 캐시 전략을 다르게 설정할 수 있습니다:</p>
<p><strong>실시간 데이터 (캐시 사용 안 함)</strong></p>
<pre><code class="language-typescript">// useAssetList.ts
export const useAssetListQuery = () =&gt; {
  return useQueryMutation&lt;AssetListResponse, Error, AssetListRequest&gt;({
    queryKey: [API_ENDPOINTS.ASSET.LIST],
    queryFn: fetchAssetList,
    queryOptions: {
      staleTime: 0,  // 항상 stale 상태 (즉시 refetch)
      gcTime: 0,     // 즉시 메모리에서 제거
    },
  });
};</code></pre>
<p><strong>정적 데이터 (캐시 적극 활용)</strong></p>
<pre><code class="language-typescript">// usePriceOverviewList.ts
export const usePriceOverviewList = () =&gt; {
  return useQueryMutation&lt;PriceOverviewListResponse, Error, PriceOverviewListRequest&gt;({
    queryKey: [API_ENDPOINTS.REPORT.PRICE_OVERVIEW.LIST],
    queryFn: fetchPriceOverviewList,
    queryOptions: {
      staleTime: 5 * 60 * 1000,  // 5분간 캐시 유지
      gcTime: 10 * 60 * 1000,    // 10분 후 메모리에서 제거
      retry: 2,                  // 실패 시 2번 재시도
    },
  });
};</code></pre>
<h3 id="5-복잡한-폼-상태와-통합">5. 복잡한 폼 상태와 통합</h3>
<p>React Hook Form과 함께 사용하는 패턴:</p>
<pre><code class="language-typescript">const AssetsView = () =&gt; {
  const { control, watch } = useFormContext&lt;FilterFormData&gt;();
  const { mutate: fetchAssets, data, isPending } = useAssetListQuery();

  const [keyword, filters, currentPage, pageCount] = watch([
    &#39;keyword&#39;,
    &#39;filters&#39;,
    &#39;currentPage&#39;,
    &#39;pageCount&#39;,
  ]);

  // 폼 상태를 API 요청 파라미터로 변환
  const queryParams = useMemo(() =&gt; {
    if (!filters || currentPage === undefined) {
      return null;
    }
    return {
      keyword: keyword || &#39;&#39;,
      startDate: filters.startDate,
      endDate: filters.endDate,
      siteList: filters.siteList,
      categoryList: filters.categoryList,
      pageIndex: currentPage,
      pageCount: pageCount || 20,
    };
  }, [keyword, filters, currentPage, pageCount]);

  // 폼 상태 변경 시 자동 검색
  useEffect(() =&gt; {
    if (!queryParams) return;
    fetchAssets(queryParams);
  }, [queryParams, fetchAssets]);

  return (
    &lt;div&gt;
      &lt;FilterForm /&gt;
      &lt;Spin spinning={isPending}&gt;
        &lt;AssetList items={data?.data?.assetInfoList || []} /&gt;
      &lt;/Spin&gt;
    &lt;/div&gt;
  );
};</code></pre>
<h2 id="주의사항">주의사항</h2>
<h3 id="1-초기-렌더링-시-자동-실행이-필요한-경우">1. 초기 렌더링 시 자동 실행이 필요한 경우</h3>
<p><code>useEffect</code>로 초기 로딩 처리:</p>
<pre><code class="language-typescript">useEffect(() =&gt; {
  mutate(defaultParams);
}, []);</code></pre>
<h3 id="2-실시간-데이터-처리">2. 실시간 데이터 처리</h3>
<p>캐시가 오히려 문제가 되는 경우 <code>staleTime: 0</code> 설정:</p>
<pre><code class="language-typescript">queryOptions: {
  staleTime: 0,
  gcTime: 0,
}</code></pre>
<h3 id="3-메모리-관리">3. 메모리 관리</h3>
<p>불필요한 캐시 축적 방지를 위해 적절한 <code>gcTime</code> 설정:</p>
<pre><code class="language-typescript">queryOptions: {
  gcTime: 1000 * 60 * 5, // 5분 후 정리
}</code></pre>
<h3 id="4-타입-안정성">4. 타입 안정성</h3>
<p>제네릭 타입을 명시적으로 정의하여 타입 추론 오류 방지:</p>
<pre><code class="language-typescript">useQueryMutation&lt;
  ResponseType,  // TData
  Error,         // TError
  RequestType    // TVariables
&gt;({ ... })</code></pre>
<h2 id="결론">결론</h2>
<p><code>useQueryMutation</code>은 실무의 복잡한 검색/필터 요구사항을 해결하는 실용적인 패턴입니다.</p>
<p><strong>언제 사용하면 좋을까요?</strong>:</p>
<ul>
<li>✅ 검색/필터 기능</li>
<li>✅ POST로 데이터 조회하는 API</li>
<li>✅ 사용자 액션 기반 데이터 로딩</li>
<li>✅ 페이지네이션 + 필터 조합</li>
<li>❌ 단순 GET 조회 (useQuery 사용)</li>
<li>❌ 실제 데이터 변경 (useMutation 사용)</li>
</ul>
<p>React Query를 사용하면서 검색 기능 구현에 어려움을 겪고 계셨다면, <code>useQueryMutation</code> 패턴을 적용해보시기 바랍니다. 코드가 간결해지고, 성능이 향상되며, 사용자 경험도 개선될 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query Mutation 완벽 가이드: onSuccess, mutate, mutateAsync 제대로 사용하기]]></title>
            <link>https://velog.io/@gyomdyung_/React-Query-Mutation-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-onSuccess-mutate-mutateAsync-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyomdyung_/React-Query-Mutation-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-onSuccess-mutate-mutateAsync-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Aug 2025 02:22:12 GMT</pubDate>
            <description><![CDATA[<p>React Query를 사용하다 보면 mutation의 <code>onSuccess</code>가 실행되지 않거나, <code>mutate</code>와 <code>mutateAsync</code> 중 어떤 것을 써야 할지 헷갈리는 경우가 많습니다. 이 글에서는 <strong>@tanstack/react-query v5</strong>를 기준으로 mutation의 핵심 개념부터 실제 프로젝트에서 겪을 수 있는 함정들까지 상세히 다뤄보겠습니다.</p>
<h2 id="🎯-알게-될-점"><strong>🎯 알게 될 점</strong></h2>
<ul>
<li>React Query mutation의 3가지 <code>onSuccess</code> 실행 방식 이해</li>
<li><code>mutate</code> vs <code>mutateAsync</code> 차이점과 선택 기준</li>
<li>컴포넌트 언마운트 시 발생하는 문제와 해결법</li>
<li>실제 프로젝트에서 사용할 수 있는 베스트 프랙티스</li>
</ul>
<hr>
<h2 id="📖-react-query-mutation의-3가지-패턴"><strong>📖 React Query Mutation의 3가지 패턴</strong></h2>
<p>React Query에서 mutation 성공 후 처리는 크게 3가지 방식으로 할 수 있습니다.</p>
<h3 id="1-훅-레벨에서-onsuccess-정의"><strong>1. 훅 레벨에서 onSuccess 정의</strong></h3>
<pre><code class="language-tsx">const useCreatePackage = () =&gt; {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createPackage,
    onSuccess: (data) =&gt; {
      // 🔥 이 훅을 사용하는 모든 mutate 호출에서 실행
      queryClient.invalidateQueries([&#39;packages&#39;]);
    },
  });
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<p><strong>특징:</strong></p>
<ul>
<li>해당 훅으로 생성한 모든 mutation이 성공할 때마다 실행</li>
<li>캐시 무효화, 전역 상태 업데이트 등 공통 로직에 적합</li>
<li>컴포넌트 마운트 상태와 무관하게 실행</li>
</ul>
<h3 id="2-mutate-호출-시-onsuccess-전달"><strong>2. mutate 호출 시 onSuccess 전달</strong></h3>
<pre><code class="language-tsx">const { mutate } = useCreatePackage();

mutate(packageData, {
  onSuccess: (data) =&gt; {
    // 🔥 이 특정 mutate 호출에만 실행
    navigate(&#39;/packages&#39;);
    setModalOpen(false);
  },
});
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<p><strong>특징:</strong></p>
<ul>
<li>특정 mutate 호출에만 실행</li>
<li>컴포넌트별 특수한 처리(네비게이션, 모달 닫기 등)에 적합</li>
<li>⚠️ <strong>컴포넌트가 언마운트되면 실행되지 않음</strong></li>
</ul>
<h3 id="3-mutateasync로-promise-처리"><strong>3. mutateAsync로 Promise 처리</strong></h3>
<pre><code class="language-tsx">const { mutateAsync } = useCreatePackage();

try {
  const result = await mutateAsync(packageData);
  // 🔥 성공 시 여기서 처리
  navigate(&#39;/packages&#39;);
  showSuccessMessage();
} catch (error) {
  // 🔥 실패 시 여기서 처리
  showErrorMessage(error);
}
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<p><strong>특징:</strong></p>
<ul>
<li>Promise를 반환하여 await으로 결과 대기</li>
<li>복잡한 흐름 제어, 순차적 처리에 적합</li>
<li>try/catch로 명시적인 에러 처리 가능</li>
</ul>
<hr>
<h2 id="⚡-mutate-vs-mutateasync-언제-뭘-써야-할까"><strong>⚡ mutate vs mutateAsync: 언제 뭘 써야 할까?</strong></h2>
<h3 id="mutate-fire-and-forget-방식"><strong>mutate: Fire and Forget 방식</strong></h3>
<pre><code class="language-tsx">const { mutate } = useMutation({ mutationFn: updateUser });

// 🔥 비동기 작업을 시작하고 바로 다음 코드 실행
mutate(userData, {
  onSuccess: (data) =&gt; console.log(&#39;성공!&#39;, data),
  onError: (error) =&gt; console.log(&#39;실패!&#39;, error),
});

console.log(&#39;이 코드는 mutation 완료를 기다리지 않고 즉시 실행됨&#39;);
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="mutateasync-promise-기반-처리"><strong>mutateAsync: Promise 기반 처리</strong></h3>
<pre><code class="language-tsx">const { mutateAsync } = useMutation({ mutationFn: updateUser });

try {
  console.log(&#39;mutation 시작&#39;);
  const result = await mutateAsync(userData);
  console.log(&#39;성공!&#39;, result);
  // 성공 후에만 실행되는 코드
  navigate(&#39;/success&#39;);
} catch (error) {
  console.log(&#39;실패!&#39;, error);
  // 실패 후에만 실행되는 코드
  setError(error.message);
}

console.log(&#39;mutation 완료 후 실행됨&#39;);
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="선택-기준표"><strong>선택 기준표</strong></h3>
<table>
<thead>
<tr>
<th><strong>상황</strong></th>
<th><strong>추천 방법</strong></th>
<th><strong>이유</strong></th>
</tr>
</thead>
<tbody><tr>
<td>단순한 성공/실패 처리</td>
<td><code>mutate</code> + onSuccess/onError</td>
<td>간단하고 직관적</td>
</tr>
<tr>
<td>복잡한 흐름 제어</td>
<td><code>mutateAsync</code> + try/catch</td>
<td>순차적 처리 가능</td>
</tr>
<tr>
<td>여러 mutation 연속 실행</td>
<td><code>mutateAsync</code></td>
<td>에러 처리와 흐름 제어 용이</td>
</tr>
<tr>
<td>성공 후 추가 API 호출 필요</td>
<td><code>mutateAsync</code></td>
<td>순서 보장</td>
</tr>
<tr>
<td>즉시 다음 작업 진행</td>
<td><code>mutate</code></td>
<td>비동기 처리 최적화</td>
</tr>
</tbody></table>
<hr>
<h2 id="🚨-자주-빠지는-함정들과-해결법"><strong>🚨 자주 빠지는 함정들과 해결법</strong></h2>
<h3 id="함정-1-컴포넌트-언마운트-시-onsuccess-미실행"><strong>함정 1: 컴포넌트 언마운트 시 onSuccess 미실행</strong></h3>
<p>이것은 React Query를 사용할 때 가장 흔히 겪는 문제 중 하나입니다.</p>
<pre><code class="language-tsx">// ❌ 위험한 패턴
const CreateItemPage = () =&gt; {
  const { mutate } = useMutation({ mutationFn: api.create });

  const handleSubmit = () =&gt; {
    mutate(data, {
      onSuccess: () =&gt; {
        // 컴포넌트가 언마운트되면 실행되지 않음!
        navigate(&#39;/other-page&#39;);
        showSuccessToast();
      },
    });

    // mutate 호출 직후 다른 페이지로 이동하면?
    navigate(&#39;/loading-page&#39;); // onSuccess가 실행되지 않을 수 있음
  };
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<p><strong>해결법: 훅 레벨에서 처리</strong></p>
<pre><code class="language-tsx">// ✅ 안전한 패턴
const useCreateItem = () =&gt; {
  const navigate = useNavigate();

  return useMutation({
    mutationFn: api.create,
    onSuccess: () =&gt; {
      // 훅 레벨에서 처리하여 컴포넌트 언마운트와 무관
      navigate(&#39;/success-page&#39;);
      showSuccessToast();
    },
  });
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="함정-2-여러-번-연속-mutate-호출-시-예상과-다른-동작"><strong>함정 2: 여러 번 연속 mutate 호출 시 예상과 다른 동작</strong></h3>
<pre><code class="language-tsx">const { mutate } = useMutation({ mutationFn: api.create });

// ❌ 문제가 될 수 있는 패턴
[&#39;item1&#39;, &#39;item2&#39;, &#39;item3&#39;].forEach(item =&gt; {
  mutate(item, {
    onSuccess: () =&gt; {
      console.log(&#39;완료!&#39;); // 마지막 호출에 대해서만 실행될 수 있음
    },
  });
});
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<p><strong>해결법: 훅 레벨에서 공통 처리</strong></p>
<pre><code class="language-tsx">// ✅ 안전한 패턴
const { mutate } = useMutation({
  mutationFn: api.create,
  onSuccess: (data, variables) =&gt; {
    console.log(`${variables} 완료!`); // 모든 호출에 대해 실행
  },
});

[&#39;item1&#39;, &#39;item2&#39;, &#39;item3&#39;].forEach(item =&gt; {
  mutate(item);
});
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="함정-3-onsuccess-실행-순서-오해"><strong>함정 3: onSuccess 실행 순서 오해</strong></h3>
<p>React Query는 정해진 순서로 onSuccess를 실행합니다.</p>
<pre><code class="language-tsx">const mutation = useMutation({
  mutationFn: createItem,
  onSuccess: (data) =&gt; {
    console.log(&#39;1️⃣ 훅 레벨 onSuccess 실행&#39;);
  },
});

mutation.mutate(data, {
  onSuccess: (data) =&gt; {
    console.log(&#39;2️⃣ mutate 레벨 onSuccess 실행&#39;);
  },
});

// 실행 순서: 1️⃣ → 2️⃣
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<hr>
<h2 id="💡-실전-사용-예시"><strong>💡 실전 사용 예시</strong></h2>
<h3 id="예시-1-패키지-생성-역할-분리-패턴"><strong>예시 1: 패키지 생성 (역할 분리 패턴)</strong></h3>
<pre><code class="language-tsx">// API 훅 - 데이터 관련 공통 로직
export const usePackageCreate = () =&gt; {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createPackage,
    onSuccess: () =&gt; {
      // 항상 실행되어야 하는 데이터 동기화
      queryClient.invalidateQueries([&#39;packages&#39;]);
    },
  });
};

// 컴포넌트 - UI 관련 특수 처리
const PackageCreatePage = () =&gt; {
  const { mutate } = usePackageCreate();
  const { openModal } = useModalStore();
  const navigate = useNavigate();

  const handleSubmit = (data) =&gt; {
    mutate(data, {
      onSuccess: () =&gt; {
        // 이 페이지에서만 필요한 UI 처리
        openModal({
          title: &#39;패키지가 생성되었습니다&#39;,
          onConfirm: () =&gt; navigate(&#39;/packages&#39;),
        });
      },
    });
  };
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="예시-2-복잡한-흐름-제어-mutateasync-활용"><strong>예시 2: 복잡한 흐름 제어 (mutateAsync 활용)</strong></h3>
<pre><code class="language-tsx">const BusinessSearchModal = () =&gt; {
  const { mutateAsync: searchBusiness } = useBusinessSearch();
  const { mutateAsync: saveRecentSearch } = useSaveRecentSearch();

  const handleSearch = async (searchData) =&gt; {
    try {
      setLoading(true);

      // 1단계: 사업자 정보 검색
      const { data, status } = await searchBusiness(searchData);

      if (status === &#39;success&#39;) {
        // 2단계: 성공 시 최근 검색어 저장
        await saveRecentSearch(searchData);

        // 3단계: 결과 표시
        onSearchComplete(data);
      }
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  };
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="예시-3-낙관적-업데이트-패턴"><strong>예시 3: 낙관적 업데이트 패턴</strong></h3>
<pre><code class="language-tsx">const BookmarkButton = ({ item, isBookmarked }) =&gt; {
  const { mutateAsync } = useBookmarkToggle();
  const [optimisticBookmark, setOptimisticBookmark] = useState(isBookmarked);

  const handleToggle = async () =&gt; {
    const newBookmarkState = !optimisticBookmark;

    // 즉시 UI 업데이트 (낙관적 업데이트)
    setOptimisticBookmark(newBookmarkState);

    try {
      await mutateAsync({
        bookmark: newBookmarkState,
        itemId: item.id,
      });

      // 성공 시 추가 처리
      showToast(`북마크가 ${newBookmarkState ? &#39;추가&#39; : &#39;제거&#39;}되었습니다`);

    } catch (error) {
      // 실패 시 원래 상태 복원
      setOptimisticBookmark(isBookmarked);
      showErrorToast(&#39;북마크 처리에 실패했습니다&#39;);
    }
  };
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<hr>
<h2 id="🏆-베스트-프랙티스"><strong>🏆 베스트 프랙티스</strong></h2>
<h3 id="1-역할-분리-원칙"><strong>1. 역할 분리 원칙</strong></h3>
<p><strong>데이터 로직은 훅에서, UI 로직은 컴포넌트에서</strong></p>
<pre><code class="language-tsx">// ✅ 권장: 명확한 역할 분리
const useItemCreate = () =&gt; {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createItem,
    onSuccess: () =&gt; {
      // 🎯 데이터 관련 로직만
      queryClient.invalidateQueries([&#39;items&#39;]);
    },
  });
};

const ItemCreatePage = () =&gt; {
  const { mutate } = useItemCreate();

  const handleSubmit = (data) =&gt; {
    mutate(data, {
      onSuccess: () =&gt; {
        // 🎯 UI 관련 로직만
        showSuccessToast(&#39;아이템이 생성되었습니다&#39;);
        navigate(&#39;/items&#39;);
      },
    });
  };
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="2-타입-안전성-확보"><strong>2. 타입 안전성 확보</strong></h3>
<pre><code class="language-tsx">interface CreateItemRequest {
  name: string;
  description: string;
}

interface CreateItemResponse {
  id: number;
  name: string;
  createdAt: string;
}

const useItemCreate = () =&gt; {
  return useMutation&lt;
    CreateItemResponse,
    Error,
    CreateItemRequest
  &gt;({
    mutationFn: createItem,
    onSuccess: (data, variables) =&gt; {
      // data: CreateItemResponse (타입 추론됨)
      // variables: CreateItemRequest (타입 추론됨)
      console.log(`생성된 아이템 ID: ${data.id}`);
    },
  });
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="3-재사용-가능한-패턴-만들기"><strong>3. 재사용 가능한 패턴 만들기</strong></h3>
<pre><code class="language-tsx">// 공통 mutation 훅 패턴
const useOptimisticMutation = &lt;TData, TVariables&gt;(
  mutationFn: MutationFunction&lt;TData, TVariables&gt;,
  queryKey: string[],
  options?: UseMutationOptions&lt;TData, Error, TVariables&gt;
) =&gt; {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn,
    onSuccess: () =&gt; {
      queryClient.invalidateQueries(queryKey);
    },
    onError: (error) =&gt; {
      console.error(&#39;Mutation failed:&#39;, error);
    },
    ...options,
  });
};

// 사용
const usePackageCreate = () =&gt;
  useOptimisticMutation(createPackage, [&#39;packages&#39;]);

const useItemCreate = () =&gt;
  useOptimisticMutation(createItem, [&#39;items&#39;]);
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<h3 id="4-에러-처리-표준화"><strong>4. 에러 처리 표준화</strong></h3>
<pre><code class="language-tsx">const useCreateItem = () =&gt; {
  return useMutation({
    mutationFn: createItem,
    onSuccess: (data) =&gt; {
      queryClient.invalidateQueries([&#39;items&#39;]);
    },
    onError: (error) =&gt; {
      // 공통 에러 처리
      console.error(&#39;Item creation failed:&#39;, error);

      // 사용자에게 친화적인 에러 메시지 표시
      const userMessage = error.code === &#39;VALIDATION_ERROR&#39;
        ? &#39;입력값을 확인해주세요&#39;
        : &#39;서버 오류가 발생했습니다&#39;;

      showErrorToast(userMessage);
    },
  });
};
</code></pre>
<p><a href="http://localhost:63342/markdownPreview/365323843/?_ijt=aineik4qdl28hi694rdbg821ai"></a></p>
<hr>
<h2 id="🎉-마무리"><strong>🎉 마무리</strong></h2>
<p>React Query의 mutation은 올바르게 사용하면 매우 강력한 도구가 됩니다. 핵심은 <strong>역할을 명확히 분리</strong>하고, <strong>컴포넌트 생명주기를 고려</strong>하여 적절한 패턴을 선택하는 것입니다.</p>
<p><strong>기억해야 할 핵심 포인트:</strong></p>
<ul>
<li>공통 로직은 훅 레벨 <code>onSuccess</code>에서</li>
<li>UI 특화 로직은 <code>mutate</code> 호출 시 <code>onSuccess</code>에서</li>
<li>복잡한 흐름은 <code>mutateAsync</code>로</li>
<li>컴포넌트 언마운트 가능성을 항상 고려하기</li>
</ul>
<p>이 가이드가 React Query mutation을 더 안전하고 효율적으로 사용하는 데 도움이 되었기를 바랍니다. 실제 프로젝트에 적용해보시고, 궁금한 점이나 개선할 부분이 있다면 언제든 피드백 주세요! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] 제어의 역전(Ioc), 의존성 주입(DI)]]></title>
            <link>https://velog.io/@gyomdyung_/Nest.js-%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84Ioc-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI</link>
            <guid>https://velog.io/@gyomdyung_/Nest.js-%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84Ioc-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI</guid>
            <pubDate>Tue, 28 Mar 2023 08:29:49 GMT</pubDate>
            <description><![CDATA[<h1 id="제어의-역전---iocinversion-of-control">제어의 역전 - Ioc(Inversion of Control)</h1>
<p>IoC는 프로그래머가 작성한 프로그램이 재사용 라이브러리의 <strong>흐름 제어</strong>를 받게 되는 <strong>소프트웨어 디자인 패턴</strong>을 말한다. 전통적인 프로그래밍에서 흐름은 프로그래머가 작성한 프로그램이 외부 라이브러리의 코드를 호출해 이용한다. 하지만 제어 반전이 적용된 구조에서는 외부 라이브러리의 코드가 프로그래머가 작성한 코드를 호출한다. 설계 목적상 제어 반전의 목적은 다음과 같다.</p>
<ul>
<li>작업을 구현하는 방식과 작업 수행 자체를 분리한다.</li>
<li>모듈을 제작할 때, 모듈과 외부 프로그램의 결합에 대해 고민할 필요 없이 모듈의 목적에 집중할 수 있다.</li>
<li>모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.</li>
</ul>
<h3 id="didependency-injection">DI(<strong>Dependency injection</strong>)</h3>
<p>IoC 컨테이너가 직접 객체의 생명주기를 관리하는 방식이다.
<img src="https://velog.velcdn.com/images/gyomdyung_/post/f2688211-bc9a-4c7c-8e9e-54c9f10a5570/image.png" alt="">
위 사진의 모듈에 클래스들 사이엔 명확한 종속성이나 계층 구조가 있다. 서비스가 올바르게 작동하기 위해서 저장소에 의존하고, 컨트롤러가 올바르게 작동하기 위해서는 서비스에 의존한다. </p>
<pre><code class="language-tsx">export class UsersController {
    constructor(private readonly usersService: UsersService) {}
        ...
}</code></pre>
<p>UsersController는 UsersService에 의존하고 있지만 UsersService 객체의 라이프 사이클에는 전혀 관여하지 않고 있다. 어디선가 자신의 생성자에 주어지는 객체를 가져다 쓰고 있을 뿐이다. 이 역할을 하는것이 IoC 다.</p>
<p>DI는 이렇게 IoC 컨테이너가 직접 객체의 생명주기를 관리하는 방식이다. 예를 들어 A 객체에서 B객체가 필요하다고 할 때, A 클래스에는 B 클래스를 직접 생성하여 사용할 수 있다. 여기서 B의 구현체가 변경되면 문제가 발생한다. A는 B를 직접 참조하고 있으므로 B가 변경될 때마다 컴파일러는 A를 다시 컴파일 해야한다.</p>
<p>이를 해결하려면 B에 대한 인터페이스 IB를 정의하고, A에서는 IB 타입을 이용하면 된다. 하지만 IB의 구현체 B1, B2 등을 직접 생성해야 하는 것은 여전하다. 여기서 IoC의 강력함이 발휘되는 것이다.</p>
<p>IoC를 사용하지 않은 코드와 사용한 코드를 비교해보자</p>
<h3 id="ioc사용-x">IoC사용 X</h3>
<pre><code class="language-jsx">export interface Person {
  getName: () =&gt; string;
}

@Injectable()
export class Dexter implements Person {
  getName() {
    return &#39;Dexter&#39;;
  }
}

@Injectable()
export class Jane implements Person {
  getName() {
    return &#39;Jane&#39;;
  }
}

class MyApp {
    constructor(private person: Person) {}
}</code></pre>
<p>L1~17: Person 인터페이스를 구현하는 2개의 클래스 Dexter, Jane이 있다. 각 클래스는 getName 함수의 구현체가 다르다.</p>
<p>L19~24: MyApp 클래스는 Person 타입의 멤버 변수를 가지고 생성자에서 구현체를 생성.</p>
<h3 id="ioc-사용-o">IoC 사용 O</h3>
<pre><code class="language-jsx">class MyApp {
    constructor(@Inject(&#39;Person&#39;) private person: Person) { }
}</code></pre>
<p>이제 Person 객체의 관리는 IoC가 담당한다. Person은 interface인데 Person을 실제로 구현한 클래스를 어디선가 정의를 해두어야 객체를 생성할 수가 있을 것이다. 이는 모듈에서 선언한다.</p>
<pre><code class="language-tsx">@Module({
  controllers: [UsersController],
  providers: [
        UsersService,
    {
      provide: &#39;Person&#39;,
      useClass:Dexter
    }
  ]
})
export class UserssModule {}</code></pre>
<p>객체로 선언할 때 provide 속성에 토큰을 &#39;Person&#39;으로 주고 있다. 이 토큰은 프로바이더를 가져다 쓸 때 @Inject 데코레이터의 인자로 넘겨준 것과 같다. 만약 Dexter 객체가 아니라 Jane으로 구현을 바꾸고자 한다면 useClass 속성의 클래스 이름만 바꾸면 된다.</p>
<pre><code class="language-tsx">@Module({
  controllers: [UsersController],
  providers: [
        UsersService,
    {
      provide: &#39;Person&#39;,
      useClass:Jane
    }
  ]
})
export class UserssModule {}</code></pre>
<h3 id="di-기능-비유">DI 기능 비유</h3>
<p>고문관한테 아 제발 넌 아무것도 하지마. 그냥 뭐 필요한 거 있으면 얘기해 내가 도와줄테니까. <strong>너는 뭐 하려는 능력을 키우지마, 그냥 부탁해 니 능력 내가 대신 해줄게</strong>. 그러니까 DI는 어떤 것을 필요할 때 필요하다고 말만 하면 프레임워크가 알잘딱해주는 것이다. 이렇게 프로그램에게 뭐 필요하다 요청을 통해 객체 간의 의존성을 낮출 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] Nest 생명주기]]></title>
            <link>https://velog.io/@gyomdyung_/Nest.js-Nest-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@gyomdyung_/Nest.js-Nest-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 08:26:19 GMT</pubDate>
            <description><![CDATA[<p>일반적으로 요청 수명 주기는 다음과 같다.</p>
<ol>
<li>Incoming request</li>
<li>Globally bound middleware</li>
<li>Module bound middleware</li>
<li>Global guards</li>
<li>Controller guards</li>
<li>Route guards</li>
<li>Global interceptors (pre-controller)</li>
<li>Controller interceptors (pre-controller)</li>
<li>Route interceptors (pre-controller)</li>
<li>Global pipes</li>
<li>Controller pipes</li>
<li>Route pipes</li>
<li>Route parameter pipes</li>
<li>Controller (method handler)</li>
<li>Service (if exists)</li>
<li>Route interceptor (post-request)</li>
<li>Controller interceptor (post-request)</li>
<li>Global interceptor (post-request)</li>
<li>Exception filters (route, then controller, then global)</li>
<li>Server response
<img src="https://velog.velcdn.com/images/gyomdyung_/post/19395424-54c5-4f54-bb97-310111645a72/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] AWS Lightsail을 활용한 백엔드 배포]]></title>
            <link>https://velog.io/@gyomdyung_/Nest.js-AWS-Lightsail%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@gyomdyung_/Nest.js-AWS-Lightsail%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Thu, 23 Feb 2023 21:43:34 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-전">시작하기 전</h1>
<ol>
<li>깃헙 레포지토리를 새로 파서 build된 폴더(dist), package.json, package-lock.json 이 3개만 올려두시는 거를 추천합니다. 뭐 엄청 좋은 인스턴스 대여 할 것도 아닐테니 깡 코드 빌드돌리면 뻑이 날 수도 있기 때문입니다.</li>
<li>package.json에 start:prod 내용 바꿔주기</li>
</ol>
<pre><code class="language-bash">&quot;start:prod&quot;: &quot;pm2 start dist/main.js&quot;</code></pre>
<h1 id="vps-가상-사설-서버-구축">VPS: 가상 사설 서버 구축</h1>
<h2 id="1-aws-lightsail-페이지로-이동">1. AWS Lightsail 페이지로 이동</h2>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/7bc818f5-6b03-4a00-b3fd-41a90af7d56a/image.PNG" alt=""></p>
<p>바로 인스턴스 생성 들어가줍니다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/d9a9c3fd-b72d-4313-857c-477bf3492e83/image.PNG" alt=""></p>
<p>위 사진처럼 플랫폼은 Linux/Unix 선택 후 밑의 OS전용 탭에서 Ubuntu골라주시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/028a7da5-705d-4b7c-bb17-1756fdd1794b/image.jpg" alt=""></p>
<p>인스턴스들은 보다시피 3개가 3개월동안 무료인데 째째하게 3.5달러는 고르지 마시고 최소 5달러짜리로 가셔야합니다. (특별한 이유는 없음)</p>
<p>이름은 하고싶은거 입력하시면 됩니다. 그 후에 특별히 할 거 없고 바로 인스턴스 생성</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/dc41f672-3606-4996-bd95-8ca09e31639f/image.jpg" alt=""></p>
<p>다 만들고나면 이렇게 생성이 되는데 아직 완전히 만들어 진것은 아니니 담배한대 피고 와줍니다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/9bb7329e-dbec-4d50-8e06-1bd61506db1e/image.jpg" alt=""></p>
<p>다 생성돼서 들어오면 이런 위와같은 창이 뜹니다. 그러면 표시되어있는 SSH를 사용하여 연결을 눌러 인스턴스로 들어가줍니다.</p>
<p>내가 SSH좀 해봤고 내 터미널에서 바로 들어가도록 하고싶다~ 하면은 맨 밑에 따로 설명하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/2609f51b-c171-4d4e-8e52-d6f1e976691b/image.jpg" alt=""></p>
<p>들어오면 위와같은 화면이 뜹니다. 그러면 아래에 있는 명령어들 복붙해서 필요한 것들 깔아줍시다.</p>
<h3 id="명령어">명령어</h3>
<ul>
<li><strong>sudo apt-get update</strong> (각종 프로그램 설치 및 버전관리 매니저)</li>
<li><strong>sudo apt-get -y upgrade</strong> (아래같은거 뜨면은 그냥 OK하고 넘어가기)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/bfff1aec-a149-4561-8cf5-7e3c60f72dfd/image.PNG" alt=""></p>
<ul>
<li><strong>sudo apt-get install build-essential</strong> (C++, Python등 빌드에 관련한 패키지 묶음 다운로드)</li>
<li><strong>sudo apt-get install curl</strong> (웹 사이트에 접속해서 패키지를 다운받는거)</li>
<li><strong>curl -sL <a href="https://deb.nodesource.com/setup_14.x">https://deb.nodesource.com/setup_16.x</a> | sudo -E bash --</strong></li>
<li><strong>sudo apt-get install -y nodejs</strong></li>
<li><strong>sudo apt-get install git</strong></li>
<li><strong>sudo apt-get install vim</strong> (우분투에서 쓰는 코드 에디터)</li>
<li><strong>touch .gitconfig</strong></li>
<li><strong>git config --global <a href="http://user.name/">user.name</a> gyomdyung</strong> (git config --global <a href="http://user.name/">user.name</a> [깃허브 닉네임])</li>
<li><strong>git config --global user.email seastory624<a href="mailto:amamov@kakao.com">@gmail.com</a></strong> (<strong><strong>git config --global user.email [깃허브 이메일])</strong></strong></li>
<li><strong>git config --global --list</strong></li>
<li><strong>git clone &lt;프로젝트&gt;</strong></li>
<li>cd repository-name</li>
<li><strong>npm i</strong> (npm i --legacy-peer-deps)</li>
<li><strong>sudo npm i -g @nestjs/cli@8</strong></li>
<li><strong>sudo npm i -g pm2</strong></li>
<li><strong>vi .env</strong> (환경변수 붙여 넣기) - 레포지토리에 (중요한).env 파일도 같이 배포하신…..분은 없으실테니 vim 에디터로 만들어줍시다.</li>
<li><strong>sudo npm run start:prod</strong></li>
</ul>
<p>npm run 돌렸는데 아래처럼 뭐 이런 에러 뜨면서 안 되면 </p>
<pre><code class="language-bash">0|main   | [Nest] 44811  - 02/23/2023, 4:13:54 PM   ERROR [NestApplication] Error: listen EACCES: permission denied 0.0.0.0:80 +3ms
0|main   | Error: listen EACCES: permission denied 0.0.0.0:80</code></pre>
<p>sudo su 커맨드를 입력해서 <strong>슈퍼유저로 전환 후</strong>에 pm2 start dist/main.js 쳐서 실행해보세요. 그럼 됩니다.</p>
<h2 id="2-퍼블릭-ip를-고정-ip로-바꾸기">2. 퍼블릭 IP를 고정 IP로 바꾸기</h2>
<p>우선 우리 인스턴스 페이지로 가봅시다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/62653b30-b1ab-4a09-b47d-c9a98e3155b7/image.jpg" alt=""></p>
<p>네트워크 탭을 눌러줍시다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/ffc50ca9-0fd4-4e4f-9614-10ca9e4632c3/image.jpg" alt=""></p>
<p>고정 IP연결 누르면은 고정IP로 바뀌면서 퍼블릭 IP 번호도 바뀌었을거에요.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/eea19b24-adb4-4176-901a-5dacf82614a6/image.png" alt=""></p>
<p>이제 postman에서 <code>[고정IP]/[route]</code> 입력하고 실험해보시면 저렇게 될겁니다.</p>
<p>끝.</p>
<h2 id="번외-터미널로-ssh-연결하는-법">번외. 터미널로 SSH 연결하는 법</h2>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/107fa437-2f6a-4722-a340-3dd28218ed94/image.png" alt=""></p>
<p>여기서 기본 키 다운로드 하면 <code>뭐시깽이.pem</code> 프라이빗 키 파일을 다운받게 되는데 그 파일을 적당한 곳에 보관하고 (필자의 경우 - /Users/jangseong-u/.ssh 폴더에 보관)</p>
<p><strong>본론</strong></p>
<ol>
<li>프라이빗 키를 사용자만 읽고 쓸 수 있도록 하는 작업</li>
</ol>
<pre><code class="language-bash">sudo chmod 400 /path/to/private-key.pem
# sudo chmod 400 ./LightsailDefaultKey-ap-northeast-2.pem 이런식으로</code></pre>
<ol>
<li>아래 명령어를 입력하여 SSH를 통해 Lightsail에서 인스턴스에 연결</li>
</ol>
<pre><code class="language-bash">ssh -i /path/to/private-key.pem ubuntu@public-ip-address
# 해설 ssh -i [pem경로] ubuntu@퍼블릭IP나 고정IP
# ex_)ssh -i ./LightsailDefaultKey-ap-northeast-2.pem ubuntu@6.96.969.696</code></pre>
<p>끝. 만약 안되시면 다른 블로그 글 구글링 하면서 도전해보세요^^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useMemo를 사용해 리액트 앱의 성능 최적화를 하자]]></title>
            <link>https://velog.io/@gyomdyung_/useMemo%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%95%B1%EC%9D%98-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@gyomdyung_/useMemo%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%95%B1%EC%9D%98-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%ED%95%98%EC%9E%90</guid>
            <pubDate>Sat, 14 Jan 2023 12:53:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>useMemo는 메모이제이션 된 값을 반환하는 Hook이다. Memo는 “memoized”를 의미하며 간단하게 설명하면 이전에 계산한 값을 재사용 한다는 의미정도이고 자세한 내용은 <a href="https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98">여기</a>에서 확인해주세요^^</p>
</blockquote>
<h1 id="랜더링마다-호출되는-컴포넌트-함수">랜더링마다 호출되는 컴포넌트 함수</h1>
<p>일반적으로 React의 함수형 컴포넌트는 다음과 같은 구조로 작성된다.</p>
<pre><code class="language-jsx">const Component = ({a, b}) =&gt; {
    const value = 개쩌는_함수(a, b)
  return &lt;OtherComponent&gt;{value}&lt;/OtherComponent&gt;
}</code></pre>
<p>컴포넌트 함수는 렌더링이 일어날 때마다 호출이 된다. 함수가 호출 되면 내부 로직들이 돌아가고, 이를 기반으로 JSX문법으로 마크업 된 UI가 리턴된다. </p>
<p>컴포넌트의 렌더링은 앱을 실행했을때 뿐만이 아니라 자기 자신이나 부모 컴포넌트의 상태 변경이 일어날 때도 렌더링 되기도 한다.</p>
<p>만약에 내가 만든 함수가 너무 대단한 나머지 컴퓨터도 연산하기 힘들어하면 컴포넌트의 재 랜더링이 될 때마다 함수가 호출이 되므로 리액트 앱의 성능이 떨어질 것이다.</p>
<h1 id="함수형-컴포넌트에-memoization-적용">함수형 컴포넌트에 memoization 적용</h1>
<p>랜더링이 일어날 때 마다, 함수의 인자 값들이 항상 바뀌는 게 아니라면 굳이 없을 것이다. 위와 같은 문제는 useMemo Hook을 사용하면 개선할 수 있다. </p>
<p>useMemo Hook의 기본 구조는 아래와 같고 인자들로는 <strong>“생성(create)”</strong> 함수와 그것의 의존성 값의 배열 2개의 인자를 받는다.</p>
<pre><code class="language-jsx">const memoizedValue = useMemo(() =&gt; 개쩌는_함수(a, b), [a, b]);</code></pre>
<p><code>useMemo</code>는 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산 할 것입니다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useRef를 사용하여 특정 DOM 선택 & 컴포넌트 안에 변수를 관리해보자]]></title>
            <link>https://velog.io/@gyomdyung_/useRef%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%ED%8A%B9%EC%A0%95-DOM-%EC%84%A0%ED%83%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%95%88%EC%97%90-%EB%B3%80%EC%88%98%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%B4%EB%B3%B4%EC%9E%90-9juwju8a</link>
            <guid>https://velog.io/@gyomdyung_/useRef%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%ED%8A%B9%EC%A0%95-DOM-%EC%84%A0%ED%83%9D-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%95%88%EC%97%90-%EB%B3%80%EC%88%98%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%B4%EB%B3%B4%EC%9E%90-9juwju8a</guid>
            <pubDate>Wed, 14 Dec 2022 07:13:19 GMT</pubDate>
            <description><![CDATA[<h1 id="1-ref가-무엇일까">1. Ref가 무엇일까?</h1>
<blockquote>
<p>리액트에서 Ref는 그냥 참조를 의미한다. (reference의 준말)</p>
</blockquote>
<ul>
<li>ref의 가장 기본적인 기능은 다른 DOM 요소에 접근해 그것들로 작업할 수 있게 해주는 기능이다.</li>
<li>HTML 작성 시 DOM요소에 이름을 달 경우 id를 사용한다. 이처럼 리액트에서도 DOM을 직접 접근하기 위한 방법이 있는데 이를 ref라고 부른다.</li>
</ul>
<h1 id="2-ref를-사용해야-할-때">2. Ref를 사용해야 할 때</h1>
<h2 id="1-변화는-감지해야-하지만-그-변화가-렌더링을-발생시키면-안되는-값을-다룰-때">1. 변화는 감지해야 하지만 그 변화가 렌더링을 발생시키면 안되는 값을 다룰 때</h2>
<h3 id="state-vs-ref">State vs Ref</h3>
<ul>
<li>State의 변화 → 컴포넌트 렌더링 → 컴포넌트 내부 변수들 초기화</li>
<li>Ref의 변화 → 렌더링 X → 변수들의 값이 유지됨<ul>
<li>State의 변화 → 컴포넌트 렌더링 → Ref의 값은 초기화되지 않음</li>
</ul>
</li>
</ul>
<h3 id="useref를-사용하여-ref-연결하기">useRef를 사용하여 ref 연결하기</h3>
<pre><code class="language-jsx">// App.js
import { useRef } from &#39;react&#39;;

function App() {
    const inputEl = useRef(null);

    const onButtonClick = (e) =&gt; {
        e.preventDefault();
        console.log(inputEl);
    };

    return (
        &lt;form&gt;
            &lt;input ref={inputEl} type=&quot;text&quot; /&gt;
            &lt;button onClick={onButtonClick}&gt;보내기&lt;/button&gt;
        &lt;/form&gt;
    );
}

export default App;</code></pre>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/febf9246-6911-4192-9285-7ace4e33566f/image.png" alt=""></p>
<h2 id="2-dom-요소에-접근할-때">2. DOM 요소에 접근할 때</h2>
<p>Javascript에서 <code>querySelector</code>, <code>getElementById</code>같은 DOM API를 리액트에서도 가끔씩 사용을 해줘야 할 때가 있다 (스크롤바 위치 가져오기, 포커스 설정 등).  그럴 때 <code>useRef</code> Hook 함수를 사용한다.</p>
<h3 id="useref를-사용하여-input에-포커스가-잡히도록-구현">useRef를 사용하여 input에 포커스가 잡히도록 구현</h3>
<pre><code class="language-jsx">// App.js
import { useEffect, useRef } from &#39;react&#39;;

const App = () =&gt; {
    const inputRef = useRef();

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

    const greetHandler = () =&gt; {
        alert(`안녕하세요. ${inputRef.current.value}님`);
    };

    return (
        &lt;div&gt;
            &lt;input type=&quot;text&quot; placeholder=&quot;사용자 이름&quot; ref={inputRef} /&gt;
            &lt;button onClick={greetHandler}&gt;로그인&lt;/button&gt;
        &lt;/div&gt;
    );
};

export default App;</code></pre>
<h2 id="끝으로-사용사례-정리">끝으로 사용사례 정리</h2>
<h3 id="공식문서에서-발췌한-ref의-바람직한-사용-사례">공식문서에서 발췌한 Ref의 바람직한 사용 사례.</h3>
<ul>
<li>포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.</li>
<li>애니메이션을 직접적으로 실행시킬 때.</li>
<li>서드 파티 DOM 라이브러리를 React와 같이 사용할 때.</li>
</ul>
<h3 id="내-경우">내 경우</h3>
<ul>
<li>폼 태그의 데이터 제출용: ref Hook 사용</li>
<li>유효성 검증을 위해 매 입력마다 값이 필요한 경우: state Hook을 사용</li>
</ul>
<p>예) input 태그에서 사용자가 입력한 내용일 관리할 때 state로 관리하는 방법이 있다. 하지만 state로 관리를 하면 매번 키를 누를 때마다 사용자로부터 얻은 값을 업데이트하고, 그것을 state에 저장하고 그 state를 input에 다시 공급한다. 그리고 나중에 그 state를 사용해 인풋을 재설정하고 데이터가 필요한 곳으로 보내기도 한다.</p>
<p>물론 이 방법이 나쁜것은 아니지만 폼 태그에서 제출할 때만 필요한 경우에는 좀 과한 프로세스로 느껴진다. 이 경우 ref를 사용할 수가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useEffect를 사용하여  컴포넌트 렌더링 이후 여러가지 side effect를 수행 해보자]]></title>
            <link>https://velog.io/@gyomdyung_/useEffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9D%B4%ED%9B%84-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-side-effect%EB%A5%BC-%EC%88%98%ED%96%89-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@gyomdyung_/useEffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9D%B4%ED%9B%84-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-side-effect%EB%A5%BC-%EC%88%98%ED%96%89-%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 13 Dec 2022 15:36:29 GMT</pubDate>
            <description><![CDATA[<h1 id="useeffect란">useEffect란?</h1>
<p><code>useEffect</code>는 컴포넌트가 <strong>Mounting</strong>(생성 될 때), <strong>Unmounting</strong>(제거 할 때), <strong>Updating</strong>(props나 state가 바뀔 때 등) <strong>특정 작업</strong>(구독 설정, API 호출, 컴포넌트의 DOM 수정 등) 처리하는 함수이다.</p>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/42c556a2-6e93-4684-9135-1de230716e57/image.png" alt=""></p>
<p><code>useEffect</code>는 첫 번째 인자로 작업을 해야할 코드가 담긴 콜백 함수를 받고, 두 번째 인자(option)로는 Dependency Array(이하 의존성 배열)를 받는다.</p>
<p>만약에 빈 배열을 넣는다면 deps가 없어 변경되는 것이 없으므로 마운트 될 때 처음 한 번만 실행이 되고, 
의존성 배열을 인자로 넣지 않는다면 컴포넌트가 렌더링 될 때마다 <code>useEffect</code>에 등록된 콜백이 실행된다.</p>
<h3 id="useeffect의-구조">useEffect의 구조</h3>
<pre><code class="language-jsx">//1. 렌더링 될 때마다 실행
useEffect(() =&gt; { //작업...});

// 1. 마운트 될 때 실행
// 2. 배열 안의 value값이 바뀔 때 실행.
useEffect(() =&gt; { //작업...}, [value]);</code></pre>
<h3 id="useeffect-실습">useEffect 실습</h3>
<p>아래의 코드를 그대로 가져가 버튼을 눌러보고 input태그에 타이핑을 해가며 마운트 / 언마운트 / 업데이트 타이밍을 알아보자.</p>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;

function App() {
    const [count, setCount] = useState(1);
    const [name, setName] = useState(&#39;&#39;);

    const countUpdateHandler = () =&gt; {
        setCount(count + 1);
    };

    const inputChangeHandler = (e) =&gt; {
        setName(e.target.value);
    };

    useEffect(() =&gt; {
        console.log(&#39;렌더링 될 때 마다 실행&#39;);
    });

    useEffect(() =&gt; {
        console.log(&#39;마운트 될 때 처음 한 번만 실행&#39;);
    }, []);

    // 마운트 + 업데이트
    useEffect(() =&gt; {
        console.log(&#39;마운트 될 때 처음 한 번만 실행 || count state가 변할 때 마다 실행&#39;);
    }, [count]);

    // 마운트 + 업데이트
    useEffect(() =&gt; {
        console.log(&#39;마운트 될 때 처음 한 번만 실행 || name state가 변할 때 마다 실행&#39;);
    }, [name]);

    return (
        &lt;div&gt;
            &lt;button onClick={countUpdateHandler}&gt;Update&lt;/button&gt;
            &lt;span&gt;count: {count}&lt;/span&gt;
            &lt;div&gt;
                &lt;input type=&quot;text&quot; onChange={inputChangeHandler} /&gt;
                &lt;span&gt;name: {name}&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
}

export default App;</code></pre>
<hr>
<h2 id="마운트mount와-렌더render-차이">마운트(Mount)와 렌더(Render) 차이</h2>
<h3 id="렌더링rendering">렌더링(rendering)</h3>
<p>render는 DOM생성을 위해 함수가 호출 될 때.</p>
<h3 id="마운트mount">마운트(Mount)</h3>
<p>컴포넌트의 인스턴스가 생성되어 DOM 상에 삽입이 될 때.</p>
<h2 id="결론">결론</h2>
<p>컴포넌트가 render될 때 mount 과정을 거친다. 
그러나 props나 state가 변경되어 render 될 때는 mount를 거치지 않는다. 결국 mount는 DOM이 생성되고, 웹 브라우저상에 처음으로 나타나는 과정을 말하는 것이다.</p>
<hr>
<h3 id="정리clean-up를-이용하는-effects"><strong>정리(Clean-up)를 이용하는 Effects</strong></h3>
<p>위에서 useEffect는 컴포넌트 렌더링 이후 <strong>특정 작업</strong> 처리하는 함수라고 설명했다. 위의 실습 코드는 <strong>정리(Clean-up)가</strong> 필요하지 않은 코드였지만 useEffect를 사용하다 보면 <strong>정리(Clean-up)</strong>가 필요한 effect가 있다.(구독 설정, API 호출, 컴포넌트의 DOM 수정 등)</p>
<h3 id="effect를-정리clean-up하는-정확한-시점">effect를 정리(Clean-up)하는 정확한 시점</h3>
<p>리액트는 컴포넌트가 마운트 해제되는 때에 <strong>정리(Clean-up)</strong>를 실행한다.</p>
<h3 id="clean-up-실습">C<strong>lean-up 실습</strong></h3>
<pre><code class="language-jsx">// App.js
import { useState } from &#39;react&#39;;
import FactriotRocket from &#39;./components/FactriotRocket&#39;;

function App() {
    const [showTimer, setShowTimer] = useState(false);

    const timerStatusHandler = () =&gt; {
        setShowTimer(!showTimer);
    };

    return (
        &lt;div&gt;
            {showTimer &amp;&amp; &lt;FactriotRocket /&gt;}
            &lt;button onClick={timerStatusHandler}&gt;
                못생긴 남자가 은근히 인기 많은 이유
            &lt;/button&gt;
        &lt;/div&gt;
    );
}

export default App;</code></pre>
<pre><code class="language-jsx">// FactriotRocket.js
import { useEffect } from &#39;react&#39;;

const FactriotRocket = (props) =&gt; {
    useEffect(() =&gt; {
        const Patriot = setInterval(() =&gt; {
            console.log(&#39;같은건 없습니다.&#39;);
        }, 1000);

        return () =&gt; {
            clearInterval(Patriot);
            console.log(&#39;그럼 전 20000&#39;);
        };
    }, []);

    return (
        &lt;div&gt;
            &lt;span&gt;개발자 도구를 열어 콘솔을 보세요!&lt;/span&gt;
        &lt;/div&gt;
    );
};

export default FactriotRocket;</code></pre>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/c605ba06-b6af-4bb8-bff5-4a09e1facc94/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Context API - props를 전역적으로 관리해보자]]></title>
            <link>https://velog.io/@gyomdyung_/React-Context-API-props%EB%A5%BC-%EC%A0%84%EC%97%AD%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@gyomdyung_/React-Context-API-props%EB%A5%BC-%EC%A0%84%EC%97%AD%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 21 Nov 2022 07:40:25 GMT</pubDate>
            <description><![CDATA[<h3 id="usecontext란">UseContext란?</h3>
<blockquote>
<p>useContext함수는 전역적으로 상태관리를 할 수 있는 도구이다. useContext는 전역적으로 상태를 관리하기에 props를 통해 많은 데이터를 전달하거나 잘못된 부분을 수정할 때 복잡한 과정을 줄여줄 수 있는 장점이 있다.</p>
</blockquote>
<h2 id="api-사용방법">API 사용방법</h2>
<h3 id="1-reactcreatecontext">1. <strong>React.createContext</strong></h3>
<p>createContext 함수 import 후 상태관리를 할 값(보통은 객체)을 넣어주면 컨텍스트 객체 생성이 완료된다. createContext에서 반환반은 값은 컴포넌트가 되거나, 컴포넌트를 포함하는 객체가 된다. 컴포넌트를 렌더링 할 때 리액트 트리 상위에서 가장 가까이 있는 <code>Provider</code>로부터 현재 값을 읽음</p>
<pre><code class="language-jsx">// src/store/theme-context.js
import { createContext } from &#39;react&#39;;

const ThemeContext = createContext(defaultValue)
// ThemeContext가 컴포넌트는 아니지만 컴포넌트를 포함할 객체이기에
// 파스칼 케이스로 명명함
export default ThemeContext;</code></pre>
<p>defaultValue매개변수는 트리 안에서 적절한 Provider를 찾이 못했을 때만 쓰이는 값이다. (현실적으로는 컴포넌트를 독립적으로 테스트할 때 유용한 값임)</p>
<h3 id="2-contextprovider">2. Context.Provider</h3>
<p>특정 컴포넌트를 포함한 하위의 모든 컴포넌트의 상태값을 바꾸고 싶을 때는 특정 컴포넌트를 포함하고 있는 부모 컴포넌트의 &lt;Context.Provider&gt; 태그로 감싸주고 value 속성을 추가해 값을 바꾸어 주면 Provider 하위에 있는 컴포넌트 안에서 useContext가 반환하는 값이 Provider의 값과 같아진다.</p>
<pre><code class="language-jsx">import ThemeContext from &#39;./theme-context&#39;;

const ThemeContextProvider = (props) =&gt; {
    const themeContext = { border: &#39;10px solid red&#39; };
    return (
        &lt;ThemeContext.Provider value={themeContext}&gt;
            {props.children}
        &lt;/ThemeContext.Provider&gt;
    );
};

export default ThemeContextProvider;</code></pre>
<h2 id="context-api-적용">Context API 적용</h2>
<pre><code class="language-jsx">// App.js
import { useContext } from &#39;react&#39;;
import { Helmet } from &#39;react-helmet&#39;;
import classes from &#39;./App.module.css&#39;;
import ThemeContext from &#39;./store/theme-context&#39;;
import ThemeContextProvider from &#39;./store/ThemeProvider&#39;;

const App = (props) =&gt; {
    return (
        &lt;ThemeContextProvider&gt;
            &lt;div className={classes.root}&gt;
                &lt;Helmet htmlAttributes={{ lang: &#39;ko&#39; }} /&gt;
                &lt;h1&gt;Hello World&lt;/h1&gt;
                &lt;Sub1 /&gt;
            &lt;/div&gt;
        &lt;/ThemeContextProvider&gt;
    );
};

function Sub1() {
    const theme = useContext(ThemeContext);

    return (
        &lt;div style={theme}&gt;
            &lt;h1&gt;Sub1&lt;/h1&gt;
            &lt;Sub2 /&gt;
        &lt;/div&gt;
    );
}

function Sub2() {
    return (
        &lt;div&gt;
            &lt;h1&gt;Sub2&lt;/h1&gt;
            &lt;Sub3 /&gt;
        &lt;/div&gt;
    );
}

function Sub3(props) {
    const themeCtx = useContext(ThemeContext);
    return (
        &lt;div style={themeCtx}&gt;
            &lt;h1&gt;Sub3&lt;/h1&gt;
        &lt;/div&gt;
    );
}

export default App;</code></pre>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/23ce884a-f099-422c-8888-a382194c1830/image.png" alt=""></p>
<h3 id="3-contextconsumer">3. Context.Consumer</h3>
<pre><code class="language-jsx">&lt;&lt;MyContext.Consumer&gt;
  {(ctx) =&gt; return (/* context 값을 이용한 렌더링 */)}
&lt;/MyContext.Consumer&gt;</code></pre>
<p>Context.Consumer의 자식은 함수여야 한다. 이 함수는 context의 현재값을 받고 리액트 노드를 반환한다. 이 함수가 받는 ctx 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 ctx props와 동일하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] 자바스크립트 - 클래스(Class)]]></title>
            <link>https://velog.io/@gyomdyung_/Javascript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%9E%98%EC%8A%A4Class</link>
            <guid>https://velog.io/@gyomdyung_/Javascript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%9E%98%EC%8A%A4Class</guid>
            <pubDate>Mon, 21 Nov 2022 07:03:15 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가기에-앞서-비유적으로-표현한-용어-정리">들어가기에 앞서 비유적으로 표현한 용어 정리</h3>
<ol>
<li>클래스: object 뽑아내는 기계</li>
<li>인스턴스: 기계로부터 생성되는 object</li>
<li>프로퍼티: 오브젝트의 속성</li>
</ol>
<h2 id="자바스크립트에서-클래스란">자바스크립트에서 클래스란?</h2>
<p>자바스크립트에서 클래스란 인스턴스를 생성하기 위한 생성자 함수다.</p>
<p>자바스크립트에서 클래스는 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕이라고 볼 수 있다. 클래스와 생성자 함수 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않고 클래스가 생성자 함수보다 엄격하며 생성자 함수에서는 제공하지 않는 기능도 제공한다.</p>
<ol>
<li>클래스를 new 연산자 없이 호출하면 에러가 발생한다. 하지만 생성자 함수를 new 연산자 없이 호출하면 일반 함수로서 호출된다.</li>
<li>클래스는 상속을 지원하는 extends와 super 키워드를 제공한다.</li>
<li>클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 <strong><a href="https://www.notion.so/c498ddd3dc1748099b99c3fc7f4696cb">함수 선언문</a>으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생한다</strong>.</li>
<li>클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 실행되며 이를 해제할 수도 없다.</li>
<li>클래스의 counstructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 
[[ Enumerable ]]의 값이 false다. 즉 열거되지 않는다.</li>
</ol>
<p>클래스를 단순히 문법적 설탕이라기 보다는 새로운 객체 생성 메커니즘으로 보는 것이 합당하다. </p>
<hr>
<h2 id="클래스-정의">클래스 정의</h2>
<pre><code class="language-jsx">// 클래스 선언문
class Person {}</code></pre>
<ul>
<li>class 키워드를 사용하여 정의</li>
<li>클래스 이름은 파스칼 케이스로 사용하는 것이 일반적</li>
<li>함수처럼 클래스를 표현식으로도 정의할 수는 있음 (일반적이진 않음)<ul>
<li>이는 클래스는 값처럼 사용할 수 있는 일급 객체라는 것을 의미함.</li>
</ul>
</li>
<li>클래스 몸체에는 0개 이상의 메서드만 정의할 수 있음.<ul>
<li>constructor(생성자), 프로토타입 메서드, 정적 메서드</li>
</ul>
</li>
</ul>
<h3 id="클래스와-생성자-함수의-정의-방식-비교">클래스와 생성자 함수의 정의 방식 비교</h3>
<pre><code class="language-jsx">var Person = (function () {
    // 생성자 함수
    function Person(name) {
        this.name = name;
    }

    // 프로토타입 메서드
    Person.prototype.sayHi = function () {
        console.log(&#39;Hi! My name is &#39; + this.name);
    };

    // 정적 메서드
    Person.sayHello = function () {
        console.log(&#39;Hello&#39;);
    };

    // 생성자 함수 반환
    return Person;
})();</code></pre>
<pre><code class="language-jsx">class Person {
    // 생성자
    constructor(name) {
        this.name = name;
    }

    // 프로토타입 메서드
    sayHi() {
        console.log(`Hi! My name is ${this.name}`);
    }

    // 정적 메서드
    static sayHello() {
        console.log(&#39;Hello&#39;);
    }
}</code></pre>
<hr>
<h2 id="클래스-호이스팅">클래스 호이스팅</h2>
<p>클래스는 함수로 평가된다.</p>
<p>클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 런타임 이전에 먼저 평가되어 함수 객체를 생성한다. 이렇게 평가되어 생성된 함수 객체는 생성자 함수로서 호출할 수 있는 함수 즉, constructor다.</p>
<p>클래스 선언문도 변수 선언, 함수 정의와 마친가지로 호이스팅이 발생한다. 단, 클래스는 let, const 키워드로 선언한 변수처럼 호이스팅 된다. 따라서 클래스 선언문 이전에 TDZ에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.</p>
<hr>
<h2 id="인스턴스-생성">인스턴스 생성</h2>
<p>클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성한다. 함수는 new 연산자 사용 여부에 따라 일반 함수로 호출되는지, 생성자 함수로 호출되던지 하지만 클래스는 인스턴스를 생성하기 위한 존재이므로 반드시 new 연산자와 함께 호출해야 한다.</p>
<ul>
<li>식별자를 사용해 인스턴스를 생성하지 않고 기명 클래스 표현식의 클래스 이름을 사용해 인스턴스를 생성하면 에러가 발생한다.</li>
</ul>
<pre><code class="language-jsx">const Person = class MyClass {};

// 함수 표현식과 마찬가지로 클래스를 가리키는 식별자로 인스턴스를 생성해야 한다.
const me = new Person();

// 클래스 이름 MyClass는 함수와 동일하게 클래스 몸체 내부에서만 유효한 식별자다.
console.log(MyClass); // 참조 에러

const you = new MyClass(); // 참조 에러</code></pre>
<p>기명 함수 표현식처럼 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근이 불가함</p>
<hr>
<h2 id="메서드">메서드</h2>
<p>클래스 몸체에는 0개 이상의 <strong>메서드만</strong> 선언할 수 있다. 정의 가능한 메서드는 
constructor, 프로토타입 메서드, 정적 메서드 세 가지가 있다.</p>
<h3 id="1-constructor">1. constructor</h3>
<p>constructor는 인스턴스를 생성하고 초기화하기 위한 특수한 메서드다.</p>
<ul>
<li>constructor 이름변경 불가</li>
<li>constructor는 클래스 내에 최대 한 개만 존재할 수 있다</li>
<li>constructor는 생략 가능하다 - 빈 constructor가 암묵적으로 생성됨</li>
<li>constructor는 별도의 반환문을 가져선 안된다. 명시적으로 반환하면 인스턴스(this)가 반환되지 못하고 return 문에 명시한 객체만 반환된다.</li>
</ul>
<h3 id="2-프로토타입-메서드">2. 프로토타입 메서드</h3>
<h3 id="3-정적-메서드">3. 정적 메서드</h3>
<h3 id="4-정적-메서드와-프로토타입-메서드의-차이">4. 정적 메서드와 프로토타입 메서드의 차이</h3>
<p>아래의 차이점과 예시를 보고 정적 메서드와 프로토타입 메서드를 무엇을 기준으로 구분하여 정의해야 할지 알아보자.</p>
<ol>
<li>정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다.</li>
<li>정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.</li>
<li>정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.</li>
</ol>
<pre><code class="language-jsx">class Square {
    static area(width, height) {
        console.log(width * height);
    }
}

Square.area(10, 10); // 100</code></pre>
<p>위 예제는 정적 메서드 area를 사용해 사각형의 넓이를 구한 것이다. 이 때 정적 메서드 area는 인스턴스 프로퍼티를 참조하지 않는다. 인스턴스 프로퍼티를 참조해야 한다면 정적 메서드 대신 프로토타입 메서드를 사용해야 한다.</p>
<pre><code class="language-jsx">class Square {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    area() {
        console.log(this.width * this.height);
    }
}

const sq = new Square(10, 10);

sq.area(); // 100</code></pre>
<p>위 예제에서 메서드 내부의 this는 메서드를 소유한 객체가 아니라 메서드를 호출한 객체. 즉 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체에 바인딩된다.</p>
<h3 id="정적-메서드와-프로토타입-메서드-사용-기준">정적 메서드와 프로토타입 메서드 사용 기준</h3>
<p>정적 메서드는 클래스로 호출해야 하므로 정적 메서드 내부의 this는 인스턴스가 아닌 클래스를 가리킨다. 즉, 프로토타입 메서드와 정적 메서드 내부의 this 바인딩이 다르다.</p>
<p>따라서 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 this를 시용해야 하며, 이러한 경우 프로토타입 메서드로 정의해야 한다. 하지만 메서드 내부에서 인스턴스 프로퍼티를 참조해야 할 필요가 없다면 this를 사용하지 않게 된다.</p>
<p>물론 메서드 내부에서 this를 사용하지 않더라도 프로토타입 메서드로 정의할 수 있다. 하지만 반드시 인스턴스를 생성한 다음 인스턴스를 호출해야 하므로 this를 사용하지 않는 메서드는 정적 메서드로 정의하는 것이 좋다.</p>
<hr>
<h2 id="클래스의-인스턴스-생성-과정">클래스의 인스턴스 생성 과정</h2>
<hr>
<h2 id="프로퍼티">프로퍼티</h2>
<h3 id="1-인스턴스-프로퍼티">1. 인스턴스 프로퍼티</h3>
<p>인스턴스 프로퍼티는 constructor 내부에서 정의해야 한다.</p>
<pre><code class="language-jsx">class Person {
    constructor(name) {
        // 인스턴스 프로퍼티
        this.name = name;
    }
}
const me = new Person(&#39;Sung Woo&#39;);
console.log(me); //Person { name: &#39;Sung Woo&#39; }</code></pre>
<p>생성자 함수에서 생성자 함수가 생성할 인스턴스의 프로퍼티를 정의하는 것과 마찬가지로 constructor 내부에서 this에 인스턴스 프로퍼티를 추가한다. 이로써 클래스가 암묵적으로 생성한 빈 객체, 즉 인스턴스에 프로퍼티가 추가도어 인스턴스가 초기화 된다.</p>
<h3 id="2-접근자-프로퍼티">2. 접근자 프로퍼티</h3>
<p>접근자 프로퍼티는 자체적으로 값([[Value]])을 갖을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다. (그냥 게터세터)</p>
<pre><code class="language-jsx">class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // fullName은 접근자 함수로 규정된 접근자 프로퍼티다.
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }

    set fullName(name) {
        [this.firstName, this.lastName] = name.split(&#39; &#39;);
    }
}

const me = new Person(&#39;Sungwoo&#39;, &#39;Jang&#39;);

// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(`${me.firstName} ${me.lastName}`);

// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 할당하면 setter 함수가 호출된다.
me.fullName = &#39;Heegun Lee&#39;;
console.log(me); // {firstName: &#39;Heegun&#39;, lastName: &#39;Lee&#39;}

// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(me.fullName); // Heegun Lee

// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
Object.getOwnPropertyDescriptor(Person.prototype, &#39;fullName&#39;)
// {enumerable: false, configurable: true, get: ƒ, set: ƒ}</code></pre>
<ul>
<li>getter<ol>
<li>인스턴스 프로퍼티에 접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용</li>
<li>무언가를 취득할 때 사용하므로 <strong>반드시 무언가를 반환</strong>해야 함</li>
</ol>
</li>
<li>setter<ol>
<li>프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용</li>
<li>단 하나의 값만 할당받기 때문에 <strong>단 하나의 매개변수만 선언 가능</strong>. </li>
</ol>
</li>
</ul>
<h3 id="private-필드-정의-제안">private 필드 정의 제안</h3>
<p>원래 JS는 캡슐화를 완전히 지원하지 않는다. ES6의 클래스도 생성자 함수와 마찬가지로 다른 클래스 기반 객체지향 언어에서 지원하는 private, public, protected 같은 접근 제한자를 지원하지 않는다. 따라서 인스턴스 프로퍼티는 언제나 public이다. (하지만 private 필드를 정의할 수 있게 된다. 방법설명 간다)</p>
<pre><code class="language-jsx">class Person {
    // 클래스 필드 정의
    #name = &#39;&#39;;

    constructor(name) {
        // private 필드 참조
        this.#name = name;
    }
}
const me = new Person(&#39;jang&#39;);
// private 필드 #name은 클래스 외부에서 참조할 수 없다.
console.log(me.#name); // 문법에러 뜸</code></pre>
<p>private은 클래스 내부에서만 접근 가능하니 이렇게 에러가 뜬다. 접근하고 싶으면 접근자 프로퍼티를 사용한다.</p>
<pre><code class="language-jsx">class Person {
    // 클래스 필드 정의
    #name = &#39;&#39;;

    constructor(name) {
        // private 필드 참조
        this.#name = name;
    }
    get name() {
        return this.#name.trim(); // 양 끝 공백 제거 메서드
    }
}
const me = new Person(&#39;jang&#39;);
console.log(me.name); // jang</code></pre>
<hr>
<h2 id="상속에-의한-클래스-확장">상속에 의한 클래스 확장</h2>
<h3 id="클래스-상속과-생성자-함수-상속">클래스 상속과 생성자 함수 상속</h3>
<ol>
<li>프로토타입 기반 상속 </li>
</ol>
<ul>
<li><p>프로토타입 체인을 통해 다른 객체의 자산을 상속받는 개념</p>
<ol start="2">
<li>상속에 의한 클래스 확장 </li>
</ol>
</li>
<li><p>기존 클래스를 상속받아 새로운 클래스를 확장하여 정의</p>
</li>
</ul>
<p>상속을 통해 super을 확장한 child 클래스 구현 예제</p>
<pre><code class="language-jsx">class Animal {
    constructor(age, weight) {
        this.age = age;
        this.weight = weight;
    }
    eat() {
        return &#39;eat&#39;;
    }
    move() {
        return &#39;move&#39;;
    }
}

class Bird extends Animal {
    constructor(...args) {
        super(...args);
    }
    fly() {
        return &#39;fly&#39;;
    }
}

const bird = new Bird(3, 8);
console.log(bird.eat()); // eat
console.log(bird.move()); // move
console.log(bird.fly()); // fly</code></pre>
<h3 id="동적-상속">동적 상속</h3>
<pre><code class="language-jsx">let condition = true;

class Animal {
    constructor(age, weight) {
        this.age = age;
        this.weight = weight;
    }
    eat() {
        return &#39;eat&#39;;
    }
    informState() {
        return &#39;새&#39;;
    }
}

class IronDragon {
    constructor(age, weight) {
        this.age = age;
        this.weight = weight;
    }
    informState() {
        return &#39;씹새끼&#39;;
    }
}

class Bird extends (condition ? IronDragon : Animal) {
    constructor(...args) {
        super(...args);
    }
    fly() {
        return &#39;fly&#39;;
    }
    informState() {
        return super.informState();
    }
}

const bird = new Bird(3, 8);
console.log(bird.informState());</code></pre>
<p>super 키워드는 함수처럼 호출할 수도있고 this와 같이 식별자 처럼 참조할 수 있는 특수한 키워드다. 이를 활용해 메서드 명이 같아도 실행 되게 구현 한 것이다.</p>
<ul>
<li>super를 호출하면 수퍼클래스의 constructor를 호출한다.</li>
<li>super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.</li>
</ul>
<h3 id="상속-클래스의-인스턴스-생성-과정">상속 클래스의 인스턴스 생성 과정</h3>
<p>상속 관계에 있는 두 클래스가 어떻게 협력하며 인스턴스를 생성하는지 알게되면 super를 더욱 명확하게 이해할 수 있다.</p>
<pre><code class="language-jsx">class Reactangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    getArea() {
        return this.width * this.height;
    }

    toString() {
        return `width = ${this.width}, height = ${this.height}`;
    }
}

class ColorRectangle extends Reactangle {
    constructor(width, height, color) {
        super(width, height);
        this.color = color;
    }
    // 메서드 오버라이딩
    toString() {
        return super.toString() + `, color = ${this.color}`;
    }
}

const colorRectangle = new ColorRectangle(2, 5, &#39;Red&#39;);
console.log(colorRectangle);
// 상속을 통해 getArea 호출
console.log(colorRectangle.getArea()); // 8
// 오버라이딩된 toString 메서드를 호출
console.log(colorRectangle.toString()); // width = 2, height = 5, color = Red</code></pre>
<h3 id="서브-클래스에서-반드시-super를-호출하는-이유"><strong>서브 클래스에서 반드시 super를 호출하는 이유</strong></h3>
<p>서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다. 
그렇기에 서브클래스의 constructor에서 반드시 super를 호출해야 한다.</p>
<h3 id="수퍼클래스의-인스턴스-생성과-this-바인딩">수퍼클래스의 인스턴스 생성과 this 바인딩</h3>
<p>수퍼클래스의 constructor 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체를 생성한다. 이 빈 객체가 클래스가 생성한 인스턴스다. 그리고 인스턴스는 this에 바인딩된다. 따라서 수퍼클래스의 constructor 내부의 this는 생성된 인스턴스를 가리킨다.</p>
<pre><code class="language-jsx">class Reactangle {
    constructor(width, height) {
        console.log(this); // ColorRectangle {}
```![업로드중..](blob:https://velog.io/17ac3f73-9ccf-4751-9398-96a7d1398f42)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useReducer - 복잡한 상태관리 useReducer를 사용해보자]]></title>
            <link>https://velog.io/@gyomdyung_/useReducer-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-useReducer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@gyomdyung_/useReducer-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-useReducer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 21 Nov 2022 06:56:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>useReducer는 useState의 대채 함수이다.</p>
</blockquote>
<h2 id="usereducer-장점">useReducer 장점</h2>
<ol>
<li>한 컴포넌트 내에서 State 업데이트 로직을 컴포넌트로부터 분리가 가능</li>
<li>복잡한 상태를 체계적으로 관리하기 용이함</li>
</ol>
<h2 id="usestate-와-usereducer-차이">useState 와 useReducer 차이</h2>
<table>
<thead>
<tr>
<th>useState()</th>
<th>useReducer()</th>
</tr>
</thead>
<tbody><tr>
<td>독립적인 state 데이터에 적합함</td>
<td>연관된 state 관련 데이터가 있는 경우 고려해야함.</td>
</tr>
<tr>
<td>state 업데이트가 쉽고 몇 가지 업데이트로 제한될 때</td>
<td>복잡한 state 업데이트가 있는 경우 유용함.</td>
</tr>
</tbody></table>
<h2 id="usestate-와-usereducer-차이-1">useState 와 useReducer 차이</h2>
<p>useState를 사용한 상태관리</p>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;
import { Helmet } from &#39;react-helmet&#39;;
import classes from &#39;./App.module.css&#39;;

const App = (props) =&gt; {
    const [number, setNumber] = useState(0);

    const upNumberHandler = () =&gt; {
        setNumber(number + 1);
    };
    const resetNumberHandler = () =&gt; {
        setNumber(0);
    };
    const downNumberHandler = () =&gt; {
        setNumber(number - 1);
    };
    return (
        &lt;div className={classes.root}&gt;
            &lt;Helmet htmlAttributes={{ lang: &#39;ko&#39; }} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;-&quot; onClick={downNumberHandler} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;0&quot; onClick={resetNumberHandler} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;+&quot; onClick={upNumberHandler} /&gt;
            &lt;span&gt;{number}&lt;/span&gt;
        &lt;/div&gt;
    );
};

export default App;</code></pre>
<p>useReducer를 사용한 상태관리</p>
<pre><code class="language-jsx">import { useReducer } from &#39;react&#39;;
import { Helmet } from &#39;react-helmet&#39;;
import classes from &#39;./App.module.css&#39;;

const countReducer = (state, action) =&gt; {
    if (action === &#39;UP&#39;) {
        return state + 1;
    } else if (action === &#39;RESET&#39;) {
        return 0;
    } else if (action === &#39;DOWN&#39;) {
        return state - 1;
    }
};

const App = (props) =&gt; {
    const [number, numberDispatch] = useReducer(countReducer, 0);

    const upNumberHandler = () =&gt; {
        numberDispatch(&#39;UP&#39;);
    };
    const resetNumberHandler = () =&gt; {
        numberDispatch(&#39;RESET&#39;);
    };
    const downNumberHandler = () =&gt; {
        numberDispatch(&#39;DOWN&#39;);
    };
    return (
        &lt;div className={classes.root}&gt;
            &lt;Helmet htmlAttributes={{ lang: &#39;ko&#39; }} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;-&quot; onClick={downNumberHandler} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;0&quot; onClick={resetNumberHandler} /&gt;
            &lt;input type=&quot;button&quot; value=&quot;+&quot; onClick={upNumberHandler} /&gt;
            &lt;span&gt;{number}&lt;/span&gt;
        &lt;/div&gt;
    );
};

export default App;</code></pre>
<h3 id="usereducer-기본-구조">useReducer 기본 구조</h3>
<pre><code class="language-jsx">const [state, dispatch] = useReducer(reducer, initialArg, init);</code></pre>
<ul>
<li>state: 컴포넌트에서 사용할 <code>state</code></li>
<li>dispatch: 컴포넌트 내에서 <code>state</code>의 업데이트를 위해 reducer함수를 실행시키는 함수</li>
<li>reducer: 컴포넌트 “<strong>외부”</strong>에서 state 업데이를 담당하는 함수(위 예에선 countReducer 실행 함수로 보면 됨)</li>
<li>initialArg: 초기값 (초기 state)</li>
<li>init: 초기함수</li>
</ul>
<h3 id="reducer-함수">reducer 함수</h3>
<p>state과 action 객체를 파라미터로 받아 새로운 상태를 반환해주는 함수이다.</p>
<pre><code class="language-jsx">const reducer = (state, action) =&gt; { ... }</code></pre>
<ul>
<li>state: 컴포넌트에서 사용할 상태</li>
<li>action: 업데이트를 위한 정보를 갖고있는 값 (주로 객체임)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 스코프]]></title>
            <link>https://velog.io/@gyomdyung_/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%EC%BD%94%ED%94%84</link>
            <guid>https://velog.io/@gyomdyung_/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%EC%BD%94%ED%94%84</guid>
            <pubDate>Thu, 10 Nov 2022 08:43:35 GMT</pubDate>
            <description><![CDATA[<h1 id="스코프네임-스페이스">스코프(네임 스페이스)</h1>
<p><strong>모든 식별자</strong>(변수 이름, 함수 이름, 클래스  이름 등)<strong>는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 잠조할 수 있는 유효 범위가 결정된다.</strong> 이를 스코프라 한다. <strong>즉, 스코프는 식별자가 유효한 범위를 말한다.</strong></p>
<h3 id="스코프-체인">스코프 체인</h3>
<p>스코프는 함수의 중첩에 의해 계층적 구조를 갖는다. 즉, 중첩 함수의 지역 스코프는 중첩 함수를 포함하는 외부 함수의 지역 스코프와 계층적 구조를 갖는다. 이때 외부 함수와 지역 스코프를 중첩 함수의 상위 스코프라 한다.</p>
<pre><code class="language-jsx">let ediots;
function foo() {
  function bar() {
    ediots = 10;
  }
  bar();
}

function dumb() {
  console.log(ediots);
}
foo();dumb();</code></pre>
<hr>
<h3 id="함수-레벨-스코프let-const-쓰면-무의미">함수 레벨 스코프(let, const 쓰면 무의미)</h3>
<p>지역은 <strong>함수 몸체 내부</strong>를 말하고 지역은 지역 스코프를 만든다. 이는 <strong>코드 블록이 아닌 함수에 의해서만 지역 스코프가 생성된다</strong>는 의미다. (나중에 이 부분을 보완하기 위해 let, const를 사용함)</p>
<p>C나 자바 등을 비롯해 대부분의 프로그래밍 언어는 함수 몸체만이 아닌 모든 코드 블록이 지역 스코프를 만들지만 <strong>var키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다</strong>.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/gyomdyung_/post/fe9f2e7f-8453-4641-a218-1aaadebda3d4/image.png" alt="스코프"></p>
<p>스코프 체인은 물리적인 실체로 존재한다. 자바스크립트 엔진은 코드를 실행하기에 앞서 그림과 유사한 자료구조인 렉시컬 환경을 실제로 생성한다.</p>
<h3 id="렉시컬-환경">렉시컬 환경</h3>
<blockquote>
<p>스코프 체인은 실행 컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다. 전역 렉시컬 환경은 
코드가 로드되면 곧바로 생성되고 함수의 렉시컬 환경은 함수가 호출되면 곧바로 생성된다.</p>
</blockquote>
<hr>
<h3 id="렉시컬-스코프">렉시컬 스코프</h3>
<p>렉시컬 스코프란 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정되는 것을 말한다.
(렉시컬 스코프(lexical scope)는 정적 스코프(static scope)와 같은 말이라 봐도 무방)</p>
<pre><code class="language-jsx">var x = 1;

function foo() {
    var x = 10;
    bar();
}
function bar() {
    console.log(x);
}

foo();
bar()</code></pre>
<p>자바스크립트는 렉시컬 스코프를 따르므로 <strong>함수 호출 위치</strong>가 아닌 <strong>함수를 어디서 정의했는지</strong>에 따라 <strong>상위 스코프를 결정</strong>한다. 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다. 즉, <strong>함수의 상위 스코프는 언제나 자신이 정의된 스코프</strong>다.</p>
<p><strong>함수의 상위 스코프</strong>는 함수 정의가 실행될 때 <strong>정적으로 결정</strong>된다.</p>
<p>결론: 함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정된다.</p>
<p>질문: 위 예제의 결과를 예측하고 그 이유를 말해보시오</p>
<p>답: JS는 렉시컬 스코프를 따릅니다. 위 예제에서 bar 함수는 전역에서 정의된 함수니 bar 함수의 상위 스코프는 언제나 전역 스코프이기에 bar함수는 어떤 식으로 호출을 해도 1이 나옵니다.</p>
]]></description>
        </item>
    </channel>
</rss>