<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jazziron</title>
        <link>https://velog.io/</link>
        <description>혼신의 힘을 다하다 🤷‍♂️</description>
        <lastBuildDate>Tue, 23 Jan 2024 14:43:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jazziron</title>
            <url>https://images.velog.io/images/dev_jazziron/profile/a37d5181-1765-4701-9d04-eb4dc3e8dc2f/img.gif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jazziron. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_jazziron" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Performance & Request Waterfalls]]></title>
            <link>https://velog.io/@dev_jazziron/Performance-Request-Waterfalls</link>
            <guid>https://velog.io/@dev_jazziron/Performance-Request-Waterfalls</guid>
            <pubDate>Tue, 23 Jan 2024 14:43:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://tanstack.com/query/latest/docs/react/guides/request-waterfalls#request-waterfalls--react-query">tanstack query v5 Performance &amp; Request Waterfalls</a></p>
<p>Request Waterfalls (요청폭포)가 발생할 수 있는 몇가지 예시를 설명하며, 해결방법을 제시합니다.</p>
<p>요청 폭포는 리소스(CODE, CSS, Image, DATA)에 대한 요청이 <em>리소스에 대한 다른 요청이 완료될 때 까지 시작되지 않을 때 발생합니다.</em></p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/29f482a8-c042-4781-b360-f84c8c116e9c/image.png" alt=""></p>
<h1 id="dependant-query-종속쿼리">Dependant Query (종속쿼리)</h1>
<p>두 번째 쿼리에 enabled 옵션 설정으로 종속쿼리로 정의되어 있습니다.
첫 번째 쿼리가 실행 완료 후 두 번째 쿼리가 실행됩니다. 
-&gt; 요청폭포 발생</p>
<pre><code class="language-typescript">import { useQuery } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;

const Dependant = () =&gt; {
  const { data: todoList, isFetched } = useQuery({
    queryKey: [&#39;todo&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/todos&#39;),
  });
  const { data: postList } = useQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/posts&#39;),
    enabled: isFetched,
  });

  return (
    &lt;div style={{ display: &#39;flex&#39; }}&gt;
      &lt;section&gt;
        &lt;h2&gt;Todo List&lt;/h2&gt;
        {todoList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
      &lt;section&gt;
        &lt;h2&gt;Post List&lt;/h2&gt;
        {postList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
    &lt;/div&gt;
  );
};
export default Dependant;
</code></pre>
<p>network waterfall이 생성되고있습니다. 따라서 서비스의 Loading이 길어지는 현상이 발생합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/ff54453f-752a-40e8-9df6-8b34e1fcc18d/image.png" alt=""></p>
<h1 id="usesuspensequery">useSuspenseQuery</h1>
<p>Suspense와 함께 React 쿼리를 사용하는 경우 발생합니다.</p>
<pre><code class="language-typescript">// SuspenseQuery.ts
import { useQuery, useSuspenseQuery } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;

const SuspenseQuery = () =&gt; {
  const { data: todoList } = useSuspenseQuery({
    queryKey: [&#39;todo&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/todos&#39;),
  });
  const { data: postList } = useSuspenseQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/posts&#39;),
  });
  const { data: photoList } = useSuspenseQuery({
    queryKey: [&#39;photo&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/photos&#39;),
  });
  const { data: userList } = useSuspenseQuery({
    queryKey: [&#39;users&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/users&#39;),
  });

  return (
    &lt;div style={{ display: &#39;flex&#39; }}&gt;
      &lt;section&gt;
        &lt;h2&gt;Todo List&lt;/h2&gt;
        {todoList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
      &lt;section&gt;
        &lt;h2&gt;Post List&lt;/h2&gt;
        {postList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
      &lt;section&gt;
        &lt;h2&gt;photo List&lt;/h2&gt;
        {photoList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
      &lt;section&gt;
        &lt;h2&gt;user List&lt;/h2&gt;
        {userList?.data.map(({ id, title }: { id: number; title: string }) =&gt; &lt;div key={id}&gt;{title}&lt;/div&gt;)}
      &lt;/section&gt;
    &lt;/div&gt;
  );
};
export default SuspenseQuery;</code></pre>
<pre><code class="language-typescript">// QueryTest.tsx
import { Suspense } from &#39;react&#39;;
import SuspenseQuery from &#39;./SuspenseQuery&#39;;

const QueryTest = () =&gt; {
  return (
    &lt;Suspense fallback={&lt;div&gt;...loading&lt;/div&gt;}&gt;
      &lt;SuspenseQuery /&gt;
    &lt;/Suspense&gt;
  );
};
export default QueryTest;
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/7c040939-dd5c-4658-9252-3b87978accff/image.png" alt="SuspenseQuery"></p>
<h2 id="usesuspnesequeries-해결-방법">useSuspneseQueries 해결 방법</h2>
<p><code>useSuspneseQueries</code> 를 사용하면 간단하게 해결 할 수 있습니다.</p>
<pre><code class="language-typescript">// QueryTest.tsx
  const [todoList, postList, photoList, userList] = useSuspenseQueries({
    queries: [
      { queryKey: [&#39;todo&#39;], queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/todos&#39;) },
      {
        queryKey: [&#39;posts&#39;],
        queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/posts&#39;),
      },
      {
        queryKey: [&#39;photo&#39;],
        queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/photos&#39;),
      },
      {
        queryKey: [&#39;users&#39;],
        queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/users&#39;),
      },
    ],
  });</code></pre>
<p>network waterfall 해결되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/b551ef43-63ab-4ee8-8985-70e566e5c762/image.png" alt=""></p>
<h1 id="nested-component-waterfalls">Nested Component Waterfalls</h1>
<p>중첩 구성 요소 폭포는 상위 구성 요소와 <strong>하위 구성 요소 모두에 쿼리가 포함</strong>되어 있고
상위 구성 요소는 쿼리가 완료될 떄까지 하위 구성 요소를 _렌더링하지 않는 경우_입니다.</p>
<p><code>useQuery</code>, <code>useSuspenseQuery</code> 모두 발생할 수 있습니다.</p>
<pre><code class="language-typescript">
// User.tsx
import { useQuery, useSuspenseQuery } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;
import Comments from &#39;./Comments&#39;;

const Users = () =&gt; {
  const { data: userList, isPending } = useQuery({
    queryKey: [&#39;users&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/users&#39;),
  });

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

  return (
    &lt;div&gt;
      &lt;ul&gt;
        {userList?.data.map((user: any) =&gt; {
          return &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;;
        })}
      &lt;/ul&gt;
      &lt;Comments postId={1}&gt;&lt;/Comments&gt;
    &lt;/div&gt;
  );
};
export default Users;
</code></pre>
<pre><code class="language-typescript">
// Comments.tsx
import { useQuery } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;

const Comments = ({ postId }: { postId: number }) =&gt; {
  const { data: comments, isPending } = useQuery({
    queryKey: [&#39;comments&#39;],
    queryFn: () =&gt; axiosClient.get(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`),
  });

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

  return &lt;div&gt;{comments?.data.id}&lt;/div&gt;;
};
export default Comments;

</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/4e2794d7-de2e-4d68-9e0b-222d0d4953a4/image.png" alt=""></p>
<h3 id="nested-component-waterfalls-해결방법">Nested Component Waterfalls 해결방법</h3>
<p>상위 항목에서 <code>&lt;User&gt;</code> 컴포넌트를 가져오는 동안 해당 ID는 렌더링 시 이미 사용 가능하므로 동시에 기사와 댓글을 가져올 수 없는 이유가 없습니다.</p>
<p>평탄화를 위해 상위 항목으로 끌어올려 해결 할 수 있습니다.</p>
<pre><code class="language-typescript">import { useQuery } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;
import Comments from &#39;./Comments&#39;;
import Users from &#39;./Users&#39;;

interface AnswerProps {
  postId?: number;
}

const Answer = ({ postId = 1 }: AnswerProps) =&gt; {
  const { data: commentsData, isPending: commentsPending } = useQuery({
    queryKey: [&#39;comments&#39;],
    queryFn: () =&gt; axiosClient.get(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`),
  });

  const { data: usersData, isPending: usersPending } = useQuery({
    queryKey: [&#39;users&#39;],
    queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/users&#39;),
  });

  return (
    &lt;&gt;
      &lt;Users usersData={usersData} /&gt;
      {commentsPending ? &#39;Loading comments...&#39; : &lt;Comments commentsData={commentsData} /&gt;}
    &lt;/&gt;
  );
};
export default Answer;
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/2b2b75da-3221-42be-abc6-fa94e343e0dc/image.png" alt=""></p>
<p><code>useQueries</code> 를 사용하여 두 쿼리를 하나로 결합하는 방식이 더 좋습니다.</p>
<pre><code class="language-typescript">import { useQueries, useQuery, useSuspenseQueries } from &#39;@tanstack/react-query&#39;;

import { axiosClient } from &#39;../../api/apiClient&#39;;
import Comments from &#39;./Comments&#39;;
import Users from &#39;./Users&#39;;

interface AnswerProps {
  postId?: number;
}

const Answer = ({ postId = 1 }: AnswerProps) =&gt; {
  const queryResults = useQueries({
    queries: [
      {
        queryKey: [&#39;users&#39;],
        queryFn: () =&gt; axiosClient.get(&#39;https://jsonplaceholder.typicode.com/users&#39;),
      },
      {
        queryKey: [&#39;comments&#39;],
        queryFn: () =&gt; axiosClient.get(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`),
      },
    ],
  });

  return (
    &lt;&gt;
      &lt;Users usersData={queryResults[0].data} /&gt;
      &lt;Comments commentsData={queryResults[1].data} /&gt;
    &lt;/&gt;
  );
};
export default Answer;
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/5ac3bd3e-a8d0-4f9b-ae06-415bd7beb9f4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useQueries]]></title>
            <link>https://velog.io/@dev_jazziron/useQueries</link>
            <guid>https://velog.io/@dev_jazziron/useQueries</guid>
            <pubDate>Sun, 14 Jan 2024 09:36:31 GMT</pubDate>
            <description><![CDATA[<h1 id="parallel">Parallel</h1>
<p>몇 가지 상황을 제외하고 쿼리 여러 개가 선언된 일반적인 상황인 경우, 쿼리 함수들은 병렬로 요청되어 처리됩니다.
이런 특징은 쿼리 처리의 <code>동시성</code>을 극대화 시킵니다.</p>
<p>다중의 쿼리를 동시에 수행하는 경우, 렌더링이 거듭되는 사이에 계속 쿼리의 호출되어야 된다면
쿼리를 수행하는 로직이 hook 규칙에 어긋날 수도 있습니다.
<code>useQueries</code>를 사용하여 해당 문제를 해결할 수 있습니다.</p>
<h2 id="usequeries">useQueries</h2>
<p><a href="https://tanstack.com/query/latest/docs/react/reference/useQuery">useQueries</a></p>
<pre><code class="language-typescript">const getPosts = async (id: number) =&gt; {
  return await axios.get(`https://jsonplaceholder.typicode.com/post/{id}`);
};

const queryResults = useQueries({
  queries: [
    {
      queryKey: [&quot;posts&quot;, 1],
      queryFn: () =&gt; getPosts(1),
      staleTime: Infinity,
    },
    {
      queryKey: [&quot;posts&quot;, 2],
      queryFn: () =&gt; getPosts(2),
      staleTime: 0,
    },
    // ...
  ],
});
</code></pre>
<h2 id="queries-combine">Queries Combine</h2>
<p><a href="https://tanstack.com/query/v5/docs/react/reference/useQueries#combine">Queries Combine 공식문서</a></p>
<p><code>useQueries</code> hook이 반환한 모든 쿼리 결과가 포함된 배열을 단일 값으로 결합하여 사용하기 위해서는 <code>combine</code> 옵션을 사용하여 처리할 수 있습니다.</p>
<ul>
<li>combinedQueries는 <code>data</code>, <code>pending</code> 프로퍼티를 갖습니다.<ul>
<li>결합하면 쿼리 결과의 나머지 다른 프로퍼티들은 손실됩니다</li>
</ul>
</li>
</ul>
<pre><code class="language-typescript">const getPosts = async (id: number) =&gt; {
  return await axios.get(`https://jsonplaceholder.typicode.com/post/{id}`);
};

const ids = [1,2,3]
const combinedQueries = useQueries({
  queries: ids.map(id =&gt; (
    { queryKey: [&#39;post&#39;, id], queryFn: () =&gt; getPosts(id) },
  )),
  combine: (results) =&gt; {
    return ({
      data: results.map(result =&gt; result.data),
      pending: results.some(result =&gt; result.isPending),
    })
  }
})

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Query Key]]></title>
            <link>https://velog.io/@dev_jazziron/Query-Key</link>
            <guid>https://velog.io/@dev_jazziron/Query-Key</guid>
            <pubDate>Sun, 14 Jan 2024 09:19:10 GMT</pubDate>
            <description><![CDATA[<h1 id="기본-개념">기본 개념</h1>
<p>useQuery의 queryKey는 <code>배열</code> 로 지정해줘야 합니다.
문자열만 포함된 배열이 될 수도 있고, 여러 문자열과 중첩된 객체로 구성된 복잡한 형태일 수도 있습니다.</p>
<pre><code class="language-typescript">
// An individual todo
useQuery({ queryKey: [&#39;todo&#39;, 5], ... })

// An individual todo in a &quot;preview&quot; format
useQuery({ queryKey: [&#39;todo&#39;, 5, { preview: true }], ...})


useQuery([&#39;todos&#39;], fetchTodos)

// 🚨 잘못된 사용 (query key 중복)
useInfiniteQuery([&#39;todos&#39;], fetchInfiniteTodos)

// ✅ 사용 가능
useInfiniteQuery([&#39;infiniteTodos&#39;], fetchInfiniteTodos)
</code></pre>
<ul>
<li><code>queryKey</code> 를 기반으로 쿼리 캐싱을 관리하는 것이 핵심입니다.</li>
<li><code>queryClient.setQueryData</code> 등과 같은 특정 쿼리에 접근이 필요한 경우
초기 설정한 포맷을 지켜야 재대로 쿼리에 접근할 수 있습니다.</li>
</ul>
<h1 id="query-key-factory">Query Key Factory</h1>
<p><a href="https://github.com/lukemorales/query-key-factory">Query Key Factory</a></p>
<ul>
<li><code>typescript</code> 지원</li>
<li>규칙성 있는 KEY 네이밍</li>
<li>모든 키는 직렬화 가능한 객체가 있는 키를 포함</li>
<li>간단하게 사용</li>
</ul>
<h2 id="표준화-된-키">표준화 된 키</h2>
<p>직렬화 가능한 객체가 있는 키 포함, <code>tanstack/query</code> 규칙</p>
<pre><code class="language-typescript">
import { createQueryKeys } from &quot;@lukemorales/query-key-factory&quot;;

export const todos = createQueryKeys(&#39;todos&#39;, {
  detail: (todoId: string) =&gt; [todoId],
  list: (filters: TodoFilters) =&gt; ({
    queryKey: [{ filters }],
  }),
});

// =&gt; createQueryKeys output:
// {
//   _def: [&#39;todos&#39;],
//   detail: (todoId: string) =&gt; {
//     queryKey: [&#39;todos&#39;, &#39;todo&#39;, todoId],
//   },
//   list: (filters: TodoFilters) =&gt; {
//     queryKey: [&#39;todos&#39;, &#39;list&#39;, { filters }],
//   },
// }
</code></pre>
<h2 id="종속적인-쿼리-선언">종속적인 쿼리 선언</h2>
<p>종속적이거나 상위 컨텍스트와 관련된 쿼리 선언을 할 수 있습니다.</p>
<pre><code class="language-typescript">export const users = createQueryKeys(&#39;users&#39;, {
  detail: (userId: string) =&gt; ({
    queryKey: [userId],
    queryFn: () =&gt; api.getUser(userId),
    contextQueries: {
      likes: {
        queryKey: null,
        queryFn: () =&gt; api.getUserLikes(userId),
      },
    },
  }),
});

// =&gt; createQueryKeys output:
// {
//   _def: [&#39;users&#39;],
//   detail: (userId: string) =&gt; {
//     queryKey: [&#39;users&#39;, &#39;detail&#39;, userId],
//     queryFn: (ctx: QueryFunctionContext) =&gt; api.getUser(userId),
//     _ctx: {
//       likes: {
//         queryKey: [&#39;users&#39;, &#39;detail, userId, &#39;likes&#39;],
//         queryFn: (ctx: QueryFunctionContext) =&gt; api.getUserLikes(userId),
//       },
//     },
//   },
// }

export function useUserLikes(userId: string) {
  return useQuery(users.detail(userId)._ctx.likes);
};
</code></pre>
<h2 id="간단한-key-접근">간단한 KEY 접근</h2>
<p>key 접근이 간단하여 캐시를 편리하게 관리하며, 무효화 할 수 있습니다.</p>
<pre><code class="language-typescript">users.detail({ status: &#39;completed&#39; }).queryKey; // =&gt; [&#39;users&#39;, &#39;detail&#39;, userId]
users.detail._def; // =&gt; [&#39;users&#39;, &#39;detail&#39;]
</code></pre>
<h2 id="편리한-타입-선언">편리한 타입 선언</h2>
<pre><code class="language-typescript">import { mergeQueryKeys, inferQueryKeyStore } from &quot;@lukemorales/query-key-factory&quot;;

import { users } from &#39;./users&#39;;
import { todos } from &#39;./todos&#39;;

export const queries = mergeQueryKeys(users, todos);

export type QueryKeys = inferQueryKeyStore&lt;typeof queries&gt;;</code></pre>
<h2 id="queryfunctioncontext-편리한-사용">QueryFunctionContext 편리한 사용</h2>
<p>전체 타입의 안정성을 확보하고 useQuery에 전달된 <code>queryKey</code> 에서 <code>QueryFunctionContext</code> 의 타입을 추론하는 것 입니다. </p>
<pre><code class="language-typescript">import type { QueryKeys } from &quot;../queries&quot;;
// import type { TodosKeys } from &quot;../queries/todos&quot;;

type TodosList = QueryKeys[&#39;todos&#39;][&#39;list&#39;];
// type TodosList = TodosKeys[&#39;list&#39;];

const fetchTodos = async (ctx: QueryFunctionContext&lt;TodosList[&#39;queryKey&#39;]&gt;) =&gt; {
  const [, , { filters }] = ctx.queryKey;
  return api.getTodos({ filters, page: ctx.pageParam });
}

export function useTodos(filters: TodoFilters) {
  return useQuery({
    ...queries.todos.list(filters),
    queryFn: fetchTodos,
  });
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[useQuery Option]]></title>
            <link>https://velog.io/@dev_jazziron/useQuery-Option</link>
            <guid>https://velog.io/@dev_jazziron/useQuery-Option</guid>
            <pubDate>Sun, 14 Jan 2024 09:04:15 GMT</pubDate>
            <description><![CDATA[<h1 id="enabled">enabled</h1>
<p>쿼리가 자동으로 실행되지 않도록 설정할 수 있습니다.
보통 버튼 클릭이나 특정 이벤트를 통해 요청을 시도할 경우 같이 사용합니다.</p>
<ul>
<li><code>enabled</code> : boolean</li>
<li><code>false</code> 설정하면 쿼리가 자동으로 실행되지 않습니다.<ul>
<li>useQuery 반환 값 중 status가 <code>pending</code> 상태로 시작합니다.</li>
</ul>
</li>
</ul>
<p>enabled: false를 설정한 경우 queryClient가 쿼리를 다시 가져오는 방법 중
<code>invalidateQueries</code> 와 <code>refetchQueries</code> 를 무시합니다.</p>
<pre><code class="language-typescript">
const getPosts = async () =&gt; {
  return await axios.get(`https://jsonplaceholder.typicode.com/posts`);
};

const usePosts = (enabled:boolean) =&gt; {
  return useQuery({
    queryKey: [&quot;todo&quot;],
    queryFn: getPosts,
    enabled: false,
  });
} 

const App = () =&gt; {
  const [enabled, setEnabled] = useState(false);
  const {data} = usePosts(enabled);
  const handleSearchPosts = () =&gt; {
    setEnabled(true);
  }

  return (
    &lt;div&gt;
      {data.map((body: string, idx: number) =&gt; (
        &lt;div key={idx}&gt;{body}&lt;/div&gt;
      ))}
      &lt;button onClick={handleSearchPosts}&gt;조회하기&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h1 id="select">select</h1>
<ul>
<li>select : (data:TData) =&gt; unknown</li>
<li><code>select</code> 옵션을 사용하여 쿼리 함수에서 반환된 데이터의 일부를 변환하거나 선택할 수 있습니다.</li>
<li>반환된 데이터 값에는 영향을 주지만, ** 쿼리 캐시에 저장되는 내용에는 영향을 주지 않습니다.**</li>
</ul>
<pre><code class="language-typescript">const getPosts = async () =&gt; {
  return await axios.get(`https://jsonplaceholder.typicode.com/posts`);
};

const usePosts = () =&gt; {
    return useQuery({
      queryKey: [&quot;todo&quot;],
      queryFn: getPosts,
      select: (data) =&gt; {
          return data.map((post: Data) =&gt; post.body);
      },
    });
}

const App = () =&gt; {
  const data = usePosts();
  return (
    &lt;div&gt;
      {data.map((body: string, idx: number) =&gt; (
        &lt;div key={idx}&gt;{body}&lt;/div&gt;
      ))}
      &lt;button onClick={handleSearchPosts}&gt;조회하기&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h1 id="placeholderdata">placeholderData</h1>
<ul>
<li>placeholderData: TData | (previousValue: TData | undefined; previousQuery: Query | undefined,) =&gt; TData</li>
<li>placeholderData를 설정하면 쿼리가 <code>pending</code> 상태인 동안 특정 쿼리에 대한 placeholder data로 사용됩니다.</li>
<li>placeholderData는 캐시에 유지되지 않으며, 서버 데이터와 관계 없는 보여주기용 가짜 데이터입니다.</li>
<li>placeholderData에 함수를 제공하는 경우 <ul>
<li>첫 번째 인자로 이전에 관찰된 쿼리 데이터를 수신</li>
<li>두 번째 인자는 이전 쿼리 인스턴스</li>
</ul>
</li>
</ul>
<pre><code class="language-typescript">const getPosts = async () =&gt; {
  return await axios.get(`https://jsonplaceholder.typicode.com/posts`);
};

const usePosts = () =&gt; {
  const placeholderData = useMemo(() =&gt; getPosts(), []);
    return useQuery({
      queryKey: [&quot;todo&quot;],
      queryFn: getPosts,
      placeholderData: placeholderData,
    });
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jotai Provider]]></title>
            <link>https://velog.io/@dev_jazziron/Jotai-Provider</link>
            <guid>https://velog.io/@dev_jazziron/Jotai-Provider</guid>
            <pubDate>Mon, 24 Apr 2023 03:12:43 GMT</pubDate>
            <description><![CDATA[<h1 id="provider"><a href="https://jotai.org/docs/core/provider">Provider</a></h1>
<h2 id="정의">정의</h2>
<p>atom의 값은 글로벌에 존재하는 <code>atom</code> 에 저장되는 것이 아닌 <code>Context</code> 와 비슷한 컴포넌트 트리 상에 저장됩니다.</p>
<p>Provider를 사용하면 <code>atom</code> 이 저장되는 <code>Context</code> 를 제공할 수 있습니다.
➡ 참조하는 <strong>Provider</strong> 가 다르면 같은 atom을 사용해도 값이 다릅니다. </p>
<ul>
<li>초기값을 선언</li>
<li>store 별로 업데이트 영역을 나누는 경우</li>
<li>개발 중 debug 정보 확인이 필요한 경우</li>
</ul>
<pre><code class="language-typescript">const Root = () =&gt; (
  &lt;Provider&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
)</code></pre>
<blockquote>
<p><strong>Provider</strong> 지정하지 않는다면 기본 저장소가 사용 됩니다. (provider-less mode)</p>
</blockquote>
<pre><code class="language-typescript">const Root = () =&gt; (
    &lt;App /&gt;
)</code></pre>
<h2 id="store-prop">Store Prop</h2>
<p><strong>Provider</strong> 는 선택적으로 <code>store</code> 를 사용할 수 있습니다.</p>
<pre><code class="language-typescript">const myStore = createStore()

const Root = () =&gt; (
  &lt;Provider store={myStore}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
)</code></pre>
<p>next13 &amp; Typescript 에서 <strong>Provider</strong> Store Test
➡ <code>getServerSideProps</code> 초기값 주입 </p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/19e22770-2a68-45b6-bde1-f3918032c940/image.png" alt=""></p>
<pre><code class="language-typescript">import { Provider, WritableAtom, atom, createStore, useAtom } from &quot;jotai&quot;;
import { useHydrateAtoms } from &quot;jotai/utils&quot;;
import { NextApiRequest, NextApiResponse } from &quot;next&quot;;

const countAtom = atom(0);
const myStore = createStore();

interface ProviderTestPropTypes {
  data: number;
}

type InitialValues = [WritableAtom&lt;unknown, any[], any&gt;, unknown][];

const ProviderTest = ({ data }: ProviderTestPropTypes) =&gt; {
  const initialValues = [[countAtom, data]] as InitialValues;

  useHydrateAtoms(initialValues, { store: myStore });
  useHydrateAtoms([[countAtom, 2]] as InitialValues);

  const [count, setCount] = useAtom(countAtom, { store: myStore });
  const [count2, setCount2] = useAtom(countAtom);

  return (
    &lt;&gt;
      &lt;Provider store={myStore}&gt;
        &lt;div&gt;테스트 : {count}&lt;/div&gt;
        &lt;button onClick={() =&gt; setCount((p) =&gt; p + 1)}&gt;+1&lt;/button&gt;
      &lt;/Provider&gt;
      &lt;Provider&gt;
        &lt;div&gt;테스트2 : {count2}&lt;/div&gt;
        &lt;button onClick={() =&gt; setCount2((p) =&gt; p + 1)}&gt;+1&lt;/button&gt;
      &lt;/Provider&gt;
    &lt;/&gt;
  );
};

export default ProviderTest;

export const getServerSideProps = async (
  req: NextApiRequest,
  res: NextApiResponse
) =&gt; {
  const data = 77;
  return {
    props: {
      data,
    },
  };
};
</code></pre>
<blockquote>
<p>Jotai v2 기준으로 작성되었습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[매핑된 타입을 사용하여 값을 동기화]]></title>
            <link>https://velog.io/@dev_jazziron/%EB%A7%A4%ED%95%91%EB%90%9C-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B0%92%EC%9D%84-%EB%8F%99%EA%B8%B0%ED%99%94</link>
            <guid>https://velog.io/@dev_jazziron/%EB%A7%A4%ED%95%91%EB%90%9C-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B0%92%EC%9D%84-%EB%8F%99%EA%B8%B0%ED%99%94</guid>
            <pubDate>Tue, 04 Apr 2023 09:30:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>매핑된 타입은 한 객체가 또 다른 객체와 정확히 같은 속성을 가지게 할 때 이상적이다</p>
</blockquote>
<pre><code class="language-typescript">const REQUIRES_UPDATE: { [k in keyof ScatterProps]: boolean } = {
  xs: true,
  ys: true,
  xRange: true,
  yRange: true,
  color: true,
  onClick: false,
};

function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
  let k: keyof ScatterProps;
  for (k in oldProps) {
    if (oldProps[k] !== newProps[k] &amp;&amp; REQUIRES_UPDATE[k]) {
      return true;
    }
  }
  return false;
}</code></pre>
<p><code>typescript [k in keyof ScatterProps]</code> 은 타입 체커에게 <code>REQUIRES_UDPATE</code> 가 <code>ScatterProps</code> 와 동일한 속성을 가져야한다는 정보를 제공합니다.</p>
<p>나중에 <code>ScatterProps</code> 에 새로운 속성을 추가하는 경우 다음 코드와 같은 형태가 됩니다.</p>
<pre><code class="language-typescript">interace ScatterProps {
  // ... Error onDoubleClick 속성이 타입에 없습니다.
  onDoubleClick: () =&gt; void;
}</code></pre>
<p>이런 방식은 오류를 정확히 잡아 냅니다. 속성을 삭제하거나 이름을 바꾸어도 비슷한 오류가 발생합니다.</p>
<p>✅ <strong>매핑된 타입을 사용해서 관련된 값과 타입을 동기화하도록 합시다.</strong>
<strong>인터페이스에 새로운 속성을 추가할 때, 선택을 강제하도록 매핑된 타입을 고려해야 합니다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변경 관련된 오류 방지를 위해 readonly 사용하기]]></title>
            <link>https://velog.io/@dev_jazziron/%EB%B3%80%EA%B2%BD-%EA%B4%80%EB%A0%A8%EB%90%9C-%EC%98%A4%EB%A5%98-%EB%B0%A9%EC%A7%80%EB%A5%BC-%EC%9C%84%ED%95%B4-readonly-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/%EB%B3%80%EA%B2%BD-%EA%B4%80%EB%A0%A8%EB%90%9C-%EC%98%A4%EB%A5%98-%EB%B0%A9%EC%A7%80%EB%A5%BC-%EC%9C%84%ED%95%B4-readonly-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Apr 2023 08:03:04 GMT</pubDate>
            <description><![CDATA[<p>오류의 범위를 좁히기 위해 arraySum이 배열을 변경하지 않는다는 선언을 해보겠습니다.
<code>readonly</code> 접근 제어자를 사용하면 됩니다.</p>
<pre><code class="language-typescript">function arraySum (arr: readonly number[]) {
    let sum =0, num;
      while ((num = arr.pop()) !== undefined) {
        // readonly number[] 형식에 pop 속성이 없습니다.
          sum += num;
    }
      return sum;
}</code></pre>
<p><code>readonly</code> 는 몇가지 특성이 있습니다.</p>
<p>배열의 요소를 읽을 수 있지만, 쓸 수는 없습니다.
length를 읽을 수 있지만, 바꿀 수는 없습니다.(배열을 변경함)
배열을 변경하는 pop을 비롯한 다른 메서드를 호출할 수 없습니다.</p>
<p>number[]는 readonly number[]보다 기능이 많기 때문에, readonly number[]의 서브타입이 됩니다.
➡ 변경 가능한 배열을 readonly 배열에 할당할 수 있지만, 그 반대는 <strong>불가능</strong>합니다.</p>
<pre><code class="language-typescript">const a: number[] = [1,2,3];
const b: readonly number[] = a;
const c: number[] = b; // error 할당 불가능</code></pre>
<p>위의 arraySum 함수를 수정을 해보겠습니다.</p>
<pre><code class="language-typescript">function arraySum (arr: readonly number[]) {
    let sum =0, num;
      for (const num of arr) {
        sum += num;
    }
      return sum;
}</code></pre>
<blockquote>
<p>✅  만약 함수가 매개변수를 변경하지 않는다면, readonly로 선언하자. 어떤 함수를 readonly로 만들면, 그 함수를 호출하는 다른 함수도 모두 readonly로 만들어야 한다.</p>
</blockquote>
<h3 id="readonly는-얕게shallow-동작합니다">readonly는 얕게(shallow) 동작합니다.</h3>
<pre><code class="language-typescript">const dates: readonly Date[] = [new Date()];
dates.push(new Date());
// Error &#39;readonly Date[] 형식에 &#39;push&#39; 속성이 없습니다.

dates[0].setFullYear(2037);</code></pre>
<p>현재 시점에는 깊은(deep) readonly 타입이 기본으로 지원되지 않지만, 제네릭을 만들면 깊은 readonly 타입을 사용할 수 있습니다.
➡ 제네릭은 만들기 까다롭기 때문에 <a href="https://github.com/ts-essentials/ts-essentials">ts-essentials DeepReadonly 제네릭</a> 사용합니다.</p>
<h3 id="인덱스-시그니처에-readonly-사용하기">인덱스 시그니처에 readonly 사용하기</h3>
<p>인덱스 시그니처에도 readonly를 쓸 수 있습니다.
읽기는 허용하되 쓰기를 방지하는 효과가 있습니다.</p>
<pre><code class="language-typescript">let obj: {readonly [k:string]: number} = {};
// Readonly&lt;{[k: string]: number}&gt;

obj.hi =45; // Error ~ ...형식의 인덱스 시그니처는 읽기만 허용됩니다.
obj = {...obj, hi:12}; // 정상
obj = {...obj, bye: 34}; // 정상</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기]]></title>
            <link>https://velog.io/@dev_jazziron/number-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98%EB%B3%B4%EB%8B%A4%EB%8A%94-Array-%ED%8A%9C%ED%94%8C-ArrayLike%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/number-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98%EB%B3%B4%EB%8B%A4%EB%8A%94-Array-%ED%8A%9C%ED%94%8C-ArrayLike%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Apr 2023 04:55:12 GMT</pubDate>
            <description><![CDATA[<h2 id="자바스크립트의-객체">자바스크립트의 객체</h2>
<p>자바스크립트에서 객체란 키/값 쌍의 모음이며, 값은 어떤 것이든 될 수 있습니다.
자바스크립트 엔진에서 object의 키는 string, symbol 타입만 가능합니다.
➡ <strong>배열에서도 number 타입의 key로 접근이 불가능합니다.</strong></p>
<p>하지만, 자바스크립트 엔진에서 자동으로 형변환이 되기에 number 타입의 키로도 접근이 가능합니다</p>
<pre><code class="language-typescript">array[0] -&gt; array[&#39;0&#39;] // 내부적으로 형변환이 됩니다.

const arr = [1, 2, 3]
console.log(Object.keys(arr)) // [&#39;0&#39;, &#39;1&#39;, &#39;2&#39;]</code></pre>
<p>인덱싱은 <code>number</code> 를 사용하지만 실제 배열의 key는 <code>string</code> 입니다.</p>
<blockquote>
<p>타입스크립트는 일관성을 위해 number 타입의 키를 허용합니다.</p>
</blockquote>
<p>인덱스 시그니처를 만들 때, 키 타입이 <code>number</code> 라면 거의 대부분 경우 이미 정의된 <code>Array</code> 나 <code>튜플</code> , 또는 <code>ArrayLike</code> 타입을 통해 사용할 수 있습니다.
➡  ** <code>number</code> 인덱스 시그니처를 위해 새롭게 타입을 만들 일이 거의 없다는 뜻입니다.**</p>
<p>✅ 인덱스 시그니처에 <code>number</code> 를 사용하기 보다는 <code>Array</code> 나 <code>튜플</code> , 또는 <code>ArrayLike</code> 타입을 사용하는 것이 좋습니다.</p>
<pre><code class="language-typescript">function checkedAccess&lt;T&gt;(xs: ArrayLike&lt;T&gt;, i: number): T {
  if ( i &lt; xs.length) {
      return xs[i];
  }
  throw new Error(&#39;배열의 ...&#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[동적 데이터에 인덱스 시그니처 사용하기]]></title>
            <link>https://velog.io/@dev_jazziron/%EB%8F%99%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/%EB%8F%99%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%8B%9C%EA%B7%B8%EB%8B%88%EC%B2%98-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Apr 2023 04:39:07 GMT</pubDate>
            <description><![CDATA[<p>타입스크립트에서는 타입에 <code>인덱스 시그니처</code> 를 명시하여 유연하게 매핑을 표현할 수 있습니다.</p>
<pre><code class="language-typescript">type Rocket = {[property: string]: string};
const rocket: Rocket = {
  name: &#39;Falcon 9&#39;,
  variant: &#39;v1.0&#39;,
  thrust: &#39;4,940 kN&#39;,
}; </code></pre>
<h2 id="인덱스-시그니처">인덱스 시그니처</h2>
<pre><code class="language-typescript">[Property : string] : string</code></pre>
<ul>
<li>키의 이름 : 키의 위치만 표시, 타입 체커에서는 사용하지 않음</li>
<li>키의 타입 : string, number, symbol 조합, <strong>보통 string</strong>을 사용합니다.</li>
<li>값의 타입 : 어떤 것이든 될 수 있습니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>잘못된 키를 포함해 모든 키를 허용
🚨 name 대신 Name으로 작성해도 유효한 Rocket 타입이 됩니다.</li>
<li>특정 키가 필요하지 않습니다. {}도 유효한 Rocket 타입입니다.</li>
<li>키마다 다른 타입을 가질 수 없습니다.</li>
<li>타입스크립트 언어 서비스는 도움이 되지 못합니다.<ul>
<li>name: 을 입력할 때, 키는 무엇이든 가능하기 때문에 자동완성 기능이 동작하지 않습니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>string 타입이 너무 광범위해서 인덱스 시그니처를 사용하는데 문제가 있습니다.</p>
</blockquote>
<h3 id="대안">대안</h3>
<h4 id="record를-사용하는-방법이-있습니다">Record를 사용하는 방법이 있습니다.</h4>
<pre><code class="language-typescript">type Vec3D = Record&lt;&#39;x&#39; | &#39;y&#39; | &#39;z&#39;, number&gt;;
/* Type Vec3D = {
    x: number;
    y: number;
    z: number;
*/</code></pre>
<h4 id="매핑을-사용합니다">매핑을 사용합니다.</h4>
<p>매핑된 타입은 키마다 별도의 타입을 사용하게 해줍니다.</p>
<pre><code class="language-typescript">type Vec3D = {[k in &#39;x&#39; | &#39;y&#39; | &#39;z&#39;]: number};
type ABC = {[k in &#39;a&#39; | &#39;b&#39; | &#39;c&#39;]: k extends &#39;b&#39; ? string : number};
/* Type ABC = {
    a: number;
    b: string;
    c: number;
*/
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입과 인터페이스 차이점 알기]]></title>
            <link>https://velog.io/@dev_jazziron/%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EA%B8%B0</guid>
            <pubDate>Tue, 04 Apr 2023 00:51:39 GMT</pubDate>
            <description><![CDATA[<p>명명된 타입을 정의하는 방법은 두가지가 있습니다.</p>
<pre><code class="language-typescript">type Tstate = {
  name: string;
  capital: string;
}

interface IState {
  name: string;
  capital: string;
}</code></pre>
<p>대부분의 경우는 타입을 사용해도 되고 인터페이스를 사용해도 됩니다.
같은 상황에서는 동일한 방법으로 명명된타입을 정의해 <strong>일관성</strong>을 유지해야합니다.</p>
<blockquote>
<p>타입을 I(인터페이스) 또는 T(타입)로 시작해 어떤 형태로 정의되었는지 나타냈지만 <strong>지양</strong>해야 할 스타일입니다. (C# 관례)</p>
</blockquote>
<h2 id="타입과-인터페이스의-공통점">타입과 인터페이스의 공통점</h2>
<h4 id="추가-속성과-함께-할당하면-오류-발생합니다">추가 속성과 함께 할당하면 오류 발생합니다.</h4>
<pre><code class="language-typescript">const wyoming: TState = {
  name: &#39;Wyoming&#39;,
  capital: &#39;Cheyenne&#39;,
  population:5 50000
};
// ..형식은 &#39;TState&#39; 형식에 할당할 수 없습니다. 
// 개체 리터럴은 알려진 속성만 지정할 수 있으며, 
// TState 형식에는 population이 없습니다.</code></pre>
<h4 id="인덱스-시그니처-인터페이스-타입-모두-사용-가능합니다">인덱스 시그니처 인터페이스, 타입 모두 사용 가능합니다.</h4>
<pre><code class="language-typescript">type TDict = { [key: string]: string};

interface IDict {
  [key: string]: string;
}</code></pre>
<h4 id="함수타입을-인터페이스나-타입으로-지정할-수-있습니다">함수타입을 인터페이스나 타입으로 지정할 수 있습니다.</h4>
<pre><code class="language-typescript">type TFn = (x: number) =&gt; string;
interface IFn {
  (x: number) :string;
}

type TFnWithProperties = {
  (x: number): number;
  prop: string;
}
interface IFnWithProperties {
  (x: number): number;
  prop: string;
}</code></pre>
<h4 id="인터페이스는-타입을-확장할-수-있으며-타입은-인터페이스를-확장할-수-있습니다">인터페이스는 타입을 확장할 수 있으며, 타입은 인터페이스를 확장할 수 있습니다.</h4>
<pre><code class="language-typescript">interface IStateWithPop extends TState {
    population: number;
}

type TStateWithPop = IState &amp; { population: number }</code></pre>
<blockquote>
<p>🚨 인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지는 못한다는 것입니다.
➡ 복잡한 타입을 확장하고 싶다면 타입과 &amp;를 사용해야 합니다.</p>
</blockquote>
<h4 id="클래스-구현-시-사용가능합니다">클래스 구현 시 사용가능합니다.</h4>
<pre><code class="language-typescript">class StateT implements TState {
  name: string = &#39;&#39;;
  capital: string = &#39;&#39;;
}

class StateI implements IState {
  name: string = &#39;&#39;;
  capital: string = &#39;&#39;;
}</code></pre>
<h2 id="타입과-인터페이스의-차이점">타입과 인터페이스의 차이점</h2>
<h3 id="타입">타입</h3>
<p>유니온 타입은 있지만 유니온 인터페이스라는 개념은 없습니다.</p>
<pre><code class="language-typescript">type AorB = &#39;a&#39; | &#39;b&#39;;</code></pre>
<p>인터페이스는 타입을 확장할 수 있지만, 유니온은 할 수 없습니다.
✅ 유니온 타입을 확장하는 게 필요할 때가 있습니다.</p>
<pre><code class="language-typescript">type Input = {/* ... */};
type Output = {/* ... */};
interface VariableMap {
  [name: string]: Input | Output;
}</code></pre>
<p>이 타입은 인터페이스로 표현할 수 없습니다. <code>type</code> 키워드는 일반적으로 <code>interface</code> 보다 쓰임새가 많습니다.</p>
<pre><code class="language-typescript">type NamedVariable = (Input | Output) &amp; {name: string}</code></pre>
<blockquote>
<p>type 키워드는 유니온이 될 수 있고, 매핑된 타입 또는 조건부 타입 같은 고급 기능에 활용 될 수 있습니다.</p>
</blockquote>
<h4 id="튜플-배열-간결한-표현">튜플, 배열 간결한 표현</h4>
<p>튜플을 인터페이스로 구현해도, 튜플에서 사용할 수 있는 <code>const</code> 같은 매서드를 사용할 수 없기 때문에, <strong>튜플은 type으로 구현</strong>하는게 좋습니다.</p>
<pre><code class="language-typescript">type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];

interface Tuple {
  0: number;
  1: number;
  length: 2;
}</code></pre>
<h3 id="인터페이스">인터페이스</h3>
<p>보강(augment) 가능합니다.
➡ 속성을 확장하는 것을 <strong>선언 병합</strong>이라고 합니다.</p>
<pre><code class="language-typescript">interface IState {
  name: string;
  capital: string;
}
interface IState {
  population: number;
}
const wyoming: IState = {
  name: &#39;Wyoming&#39;,
  capital: &#39;Cheyenne&#39;,
  population: 500_000
};</code></pre>
<p>✅ 복잡한 타입이라면 타입 별칭을 사용하면 됩니다. 그러나 타입과 인터페이스, 두 가지 방법으로 모두 표현 할 수 있는 간단한 객체 타입이라면 일관성과 보강의 관점에서 고려해 봐야 합니다.
아직 스타일이 확립되지 않은 프로젝트라면, 향후 보강의 가능성이 있는지 확인이 필요합니다.</p>
<ul>
<li><p>어떤 API에 대한 타입 선언을 작성해야 한다면 인터페이스를 사용하는 게 좋습니다.
➡ API가 변경될 때 사용자가 인터페이스를 통해 새로운 필드를 병합할 수 있어 유용합니다.</p>
</li>
<li><p>프로젝트 내부적으로 사용되는 타입에 선언 병합이 발생하는 것은 잘못된 설계입니다.
➡ 이런 경우 타입을 사용해야합니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수 표현식에 타입 적용하기]]></title>
            <link>https://velog.io/@dev_jazziron/%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D%EC%97%90-%ED%83%80%EC%9E%85-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D%EC%97%90-%ED%83%80%EC%9E%85-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 03 Apr 2023 15:51:51 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트(타입스크립트)에서는 <code>함수 문장(statement)</code> 와 <code>함수 표현식(expression)</code> 을 다르게 인식합니다.</p>
<p>함수의 문장 표현</p>
<pre><code class="language-typescript">function rollDice1(sides: number): number { /* ... */ }</code></pre>
<p>함수 표현식</p>
<pre><code class="language-typescript">const rollDice2 = function(sides: number): number { /* ... */ }
const rollDice3 = (sides: number) : number =&gt; { /* ... */ }</code></pre>
<blockquote>
<p>타입스크립트에서는 함수 표현식을 사용하는 것이 좋습니다.</p>
</blockquote>
<p>그 이유는 아래와 같이 정리할 수 있습니다.</p>
<ul>
<li>함수의 매개변수, 반환값까지 전체를 함수 타입으로 선언하여 <strong>함수 표현식에 재사용</strong>할 수 있습니다.</li>
</ul>
<pre><code class="language-typescript">type DiceRollFn = (sides: number) =&gt; number;
const rollDice: DiceRollFn = sides =&gt; {/* ... */}</code></pre>
<ul>
<li>함수 타입 선언을 이용하면 타입 구문을 적게 사용할 수 있으며,불필요한 코드의 반복을 줄일 수 있습니다.</li>
<li>함수 구현부가 분리되어 있어 로직을 분명하게 표현할 수 있습니다.</li>
</ul>
<pre><code class="language-typescript">type BinaryFn = (a: number, b: number) =&gt; number;
const add: BinaryFn = (a, b) =&gt; a + b;
const sub: BinaryFn = (a, b) =&gt; a - b;
const mul: BinaryFn = (a, b) =&gt; a * b;</code></pre>
<p>fetch 함수 응답 예제를 확인해보자.</p>
<ul>
<li>정상적으로 데이터를 받는 경우 <code>json</code></li>
<li>비 정상적인 데이터를 받는 경우 <code>json</code> 이 아닌 오류 메세지를 담은 <code>rejected</code> Promise를 반환합니다.</li>
</ul>
<pre><code class="language-typescript">// lib.dom.d.ts에 다음과 같이 작성되어 있습니다.
declare function fetch(
 input: RequestInfo, init?: RequestInit
): Promise&lt;Response&gt;;

async function checkedFetc(input: RequestInfo, init?: RequestInit) {
  const response = await fetch(input, init);
  if (!response.ok) {
    // 비동기 함수 내에서 거절된 프로미스로 변환합니다.
    throw new Error(&#39;Request failed: &#39; + response.status);
  }
  return response;
}</code></pre>
<p>간결하게 작성해보기</p>
<ul>
<li>함수 표현식을 이용하여, 함수 전체에 타입을 적용
➡ 타입스크립트가 자동으로 <code>input</code> 과 <code>init</code> 을 추론할 수 있게 해 줍니다.</li>
<li>타입 구문 또한 <code>checkedFetch</code> 의 반환데이터를 보장합니다.</li>
</ul>
<pre><code class="language-typescript">const checkedFetch: typeof fetch = async (input, init) =&gt; {
  const response = await fetch(input, init);
  if (!response.ok) {
    throw new Error(&#39;Request failed: &#39; + response.status);
  }
  return response;
}</code></pre>
<p>throw 대신 return을 사용했다면, 타입스크립트는 그 실수를 잡아냅니다.</p>
<pre><code class="language-typescript">const checkedFetch: typeof fetch = async (input, init) =&gt; {
  // Error &#39;Promise&lt;Response | Error&gt;&#39; 형식은 Promise&lt;Response&gt; 형식에 할당할 수 없습니다.
  const response = await fetch(input, init);
  if (!response.ok) {
    return new Error(&#39;Request failed: &#39; + response.status);
  }
  return response;
}</code></pre>
<p>✅  함수의 매개변수에 타입 선언을 하는 것보다 함수 표현식 전체 타입을 정의하는 것이 코드도 간결하고 안전합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입 단언보다 타입 선언을 사용하기]]></title>
            <link>https://velog.io/@dev_jazziron/%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8%EB%B3%B4%EB%8B%A4-%ED%83%80%EC%9E%85-%EC%84%A0%EC%96%B8%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_jazziron/%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8%EB%B3%B4%EB%8B%A4-%ED%83%80%EC%9E%85-%EC%84%A0%EC%96%B8%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 03 Apr 2023 14:26:07 GMT</pubDate>
            <description><![CDATA[<h2 id="타입-단언과-선언">타입 단언과 선언</h2>
<p>타입스크립트에서는 변수에 값을 할당하고 타입을 부여하는 방법은 두가지입니다.</p>
<pre><code class="language-typescript">interface Person {name : string};
const alice : Person = {name : &#39;Alice&#39;} //타입은 Person
const bob = {name : &#39;Bob&#39;} as Person //타입은 Person</code></pre>
<ol>
<li>타입 선언 : alice:Person</li>
<li>타입 단언 : as Person </li>
</ol>
<p>✅ <strong>타입 단언보다 타입 선언을 사용하는 게 낫습니다.</strong></p>
<pre><code class="language-typescript">const alice : Person = {
    name : &#39;Alice&#39;,
    occupation : &#39;TypeScript developer&#39;
    // ~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있으며
    // &#39;Person&#39; 형식에 &#39;occupation&#39;이(가) 없습니다.
};
const bob = {
    name : &#39;Bob&#39;,
    occupation : &#39;JavaScript developer&#39;
} as Person; // 오류 없음
</code></pre>
<p>타입 선언문에는 <code>잉여 속성 체크</code> 가 동작했지만, 단언문에는 적용되지 않습니다.</p>
<p>화살표 함수의 타입 선언은 추론된 타입이 모호할 때가 있습니다.</p>
<pre><code class="language-typescript">const people = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(name =&gt; ({name}));
// Person[]을 원했지만 결과는 {name : string;}[]...</code></pre>
<p>타입 단언을 사용하면 문제가 해결되는 것처럼 보이지만, 런타임에 문제가 발생할 수 있습니다.</p>
<pre><code class="language-typescript">const people = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(name =&gt; ({name} as Person));
// 타입은 Person[]

// 런타임에서 문제가 발생할 수 있음
const people = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(name =&gt; ({} as Person));
// 오류없음</code></pre>
<p>단언문을 사용하지 않고 화살표 함수 안에 타입과 함께 변수를 선언하는 것이 가장 직관적입니다.</p>
<pre><code class="language-typescript">const people = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(name =&gt; (
    const person:Person = {name};
    return person
)); // 타입은 Person[]

// 간결한 코드를 위해 변수 대신 화살표 함수의 반환 타입을 선언
const people = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(
    (name):Person =&gt; ({name})
); // 타입은 Person[]&#39;
</code></pre>
<p><code>(name): Person</code> 은 name에 타입이 없고 반환 타입이  <code>Person</code> 임을 명시합니다.</p>
<p><code>(name: Person)</code> 은 name의 타입이 <code>Person</code> 임을 명시하고 반환 타입은 없기 때문에 오류가 발생합니다.</p>
<p>최종적으로 원하는 타입을 직접 명시하고, 타입스크립트가 할당문의 유효성 검사하게 합니다.</p>
<pre><code class="language-typescript">const people: Person[] = [&#39;alice&#39;, &#39;bob&#39;, &#39;jan&#39;].map(
    (name):Person =&gt; ({name})
); // 타입은 Person[]&#39;</code></pre>
<h2 id="타입-단언이-꼭-필요한-경우">타입 단언이 꼭 필요한 경우</h2>
<p>타입스크립트는 DOM에 접근할 수 없습니다.
자주쓰이는 특별한 문법(!)을 사용하여 null이 아님을 단언하는 경우도 있습니다.</p>
<pre><code class="language-typescript">const elNull = document.getElementById(&#39;foo&#39;); //타입은 HTMLElement | null
const el = document.getElementById(&#39;foo&#39;); //타입은 HTMLElement </code></pre>
<ul>
<li>변수의 접두사로 쓰인 !는 boolean의 부정문입니다.</li>
<li>접미사로 쓰인 !는 그 값이 null이 아니라는 단언문으로 해석됩니다.</li>
</ul>
<p>단언문은 컴파일 과정 중에 제거되므로, *<em>타입 체커는 알지 못하지만 그 값이 null이 아니라고 확신할 수 있을때 사용해야 합니다. *</em></p>
<p>타입 단언문으로 임의의 타입 간에 변환을 할 수는 없습니다.
A가 B의 부분집합인 경우에 타입 단언문을 사용해 변환할 수 있습니다.</p>
<p>➡ HTMLElement는 HTMLElement | null의 서브타입이기 때문에 이러한 타입 단언은 동작</p>
<pre><code class="language-typescript">interface Person { name: string; }
const body = document.body;
const el = body as Person;
// 형식이 충분히 겹치지 않기 때문에 실수로 판단, 의도적인 경우 먼저 식을 unknown으로 변환하세요.

const el = body as unknown as Person;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 구성 옵션]]></title>
            <link>https://velog.io/@dev_jazziron/typeScript-tsconfig</link>
            <guid>https://velog.io/@dev_jazziron/typeScript-tsconfig</guid>
            <pubDate>Mon, 13 Mar 2023 14:41:16 GMT</pubDate>
            <description><![CDATA[<h1 id="tsc-옵션">tsc 옵션</h1>
<p>자바스크립트에서 타입스크립트로 <code>index.ts</code> 파일을 컴파일 하기 위해 tsc index.ts 를 사용합니다.</p>
<p>tsc 명령은 타입스크립트의 대부분 옵션을 <code>--</code> 플래그로 사용할 수 있습니다.
➡ index.js 파일 생성을 건더뛰고 타입검사만 실행 (<code>--noEmit</code> 플래그 전달)</p>
<pre><code class="language-typescript">tsc index.ts --noEmit</code></pre>
<blockquote>
<p>tsc 구성 옵션 목록은 <code>tsc --all</code> 으로 확인 할 수 있습니다.</p>
</blockquote>
<h2 id="pretty-모드">pretty 모드</h2>
<p>tsc CLI는 색상과 간격의 스타일을 지정해 가독성을 높히는 <code>pretty 모드</code> 를 지원합니다.
➡ 기본적으로 pretty 모드로 설정됩니다.</p>
<blockquote>
<p><code>tsc index.ts --pretty false</code> 을 사용하여 타입스크립트에 더 간결하고 색상이 없는 형식을 사용하도록 지시할 수 있습니다.</p>
</blockquote>
<h2 id="watch-모드">watch 모드</h2>
<p><code>watch</code> 모드( <code>-w, --watch</code> )를 사용하면 종료하는 대신 타입스크립트를 무기한 실행 상태로 유지하고, 모든 오류의 실시간 목록을 가져와 터미널을 지속 업데이트 합니다.
➡ 여러 파일에 걸쳐서 리팩터링 같은 대규모 변경 작업을 할 때 유용함</p>
<blockquote>
<p><code>tsc index.ts -w</code></p>
</blockquote>
<hr>
<h1 id="tsconfig-파일-설정하기">TSConfig 파일 설정하기</h1>
<p>대부분의 구성 옵션을 디렉터리의 <code>tsconfig.json (TSConfig)</code> 파일에 구체적으로 명시할 수 있습니다.</p>
<ul>
<li>tsconfig.json는 해당 디렉터리가 타입스크립트 프로젝트 루트임을 나타냅니다.</li>
<li>디렉터리에서 <code>tsc</code> 를 실행하면 해당 <code>tsconfig.json</code> 파일의 모든 구성 옵션을 읽습니다.</li>
</ul>
<blockquote>
<p>tsc 명령에 <code>tsconfig.json</code> 파일이 있는 디렉터리 경로 또는 <code>tsc</code> 가 <code>tsconfig.json</code> 대신 사용할 경로를 <code>-p, --project</code> 플래그에 전달하여 사용합니다.</p>
</blockquote>
<p><code>tsc -p path/to/tsconfig.json</code></p>
<p>TSConfig 파일에서 사용 가능한 모든 구성옵션 목록은 [tsconfig.json] (<a href="https://www.typescriptlang.org/tsconfig">https://www.typescriptlang.org/tsconfig</a>) 참조하세요</p>
<h2 id="tsconfigjson-생성하기">tsconfig.json 생성하기</h2>
<p>tsc 명령의 <code>tsc --init</code> 명령어를 사용합니다.
➡ 타입스크립트 프로젝트에서는 구성 파일을 생성하기 위해 해당 명령어를 사용하는 것을 권장합니다.</p>
<p>완전히 주석 처리된 <code>tsconfig.json</code> 파일이 생성됩니다.</p>
<pre><code class="language-typescript">{
    &quot;compilerOptions&quot;: {
        //...
    }
}</code></pre>
<h2 id="compiler-options">Compiler Options</h2>
<p>CLI와 TSConfig 파일에서 사용 가능한 대부분의 옵션은 다음 두 가지 범주 중 하나로 분류됩니다.</p>
<ul>
<li>컴파일러 : 포함된 각 파일이 타입스크립트에 따라 컴파일 되거나 타입을 확인하는 방법</li>
<li>파일 : 타입스크립트가 실행될 파일과 실행되지 않은 파일</li>
</ul>
<hr>
<h1 id="파일-포함-옵션">파일 포함 옵션</h1>
<p>기본적으로 현재 디렉터리와 하위 디렉터리에 있는 숨겨지지 않은 모든 <code>.ts</code> 파일에서 실행되며, 숨겨진 디렉터리와 <code>node_modules</code> 디렉터리는 무시합니다.
✅ 타입스크립트 구성은 실행할 파일 목록을 수정할 수 있습니다.</p>
<h2 id="include">include</h2>
<p>파일을 포함하는 가장 흔한 방법으로 <code>tsconfig.json</code> 의 최상위 <code>include</code> 속성을 사용합니다. 
➡ <code>src/</code> 디렉터리 안의 모든 타입스크립트 소스 파일을 <strong>재귀적으로 포함</strong>합니다.</p>
<pre><code class="language-typescript">{
    &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p>포함할 파일을 더 세밀하게 제어하기 위해 <code>include</code> 문자열에 글로브(glob) 와일드 카드를 허용합니다.</p>
<ul>
<li>*: 0 개이상의 문자와 일치합니다. (디렉터리 구분자 제외)</li>
<li>?: 하나의 문자와 일치합니다. (디렉터리 구분자 제외)</li>
<li>**/ : 모든 레벨에 중첩된 모든 디렉터리와 일치합니다.</li>
</ul>
<pre><code class="language-typescript">{
    &quot;include&quot;: [
        &quot;typings/**/*.d.ts&quot;,
          &quot;src/**/*??.*&quot;
    ]
}</code></pre>
<p>다음 구성 파일은 <code>typings/</code> 하위의 중첩된 디렉터리의 <code>.d.ts</code> 파일과 화강자 앞의 파일명에 적어도 두 개 이상의 문자를 가진 <code>src/</code> 하위 파일만 허용합니다.</p>
<p>✅ 대부분 프로젝트는 <code>[&quot;src&quot;]</code> 와 같은 간단한 <code>include</code> 컴파일러 옵션만으로 충분합니다.</p>
<h2 id="exclude">exclude</h2>
<p>프로젝트의 <code>include</code> 파일 목록에 타입스크립트로 컴파일할 수 없는 파일이 포함되는 경우가 있습니다.
➡ TSConfig 파일의 최상위 <code>exclude</code> 속성에 경로를 지정하고 <code>include</code> 에서 경로를 생략합니다.</p>
<pre><code class="language-typescript">{
      &quot;exclude&quot; : [&quot;**/external&quot;, &quot;node_modules&quot;], 
    &quot;include&quot; : [&quot;src&quot;]
}</code></pre>
<p><code>exclude</code> 는 <code>include</code> 의 시작 목록에서 파일을 제거하는 작업만 수행합니다.
✅ 타입스크립트는 비록 가져온 파일이 <code>exclude</code> 목록에 명시적으로 나열되어 있더라도 포함된 파일에 따라 가져온 모든 파일에서 실행됩니다.</p>
<hr>
<h1 id="대체-확장자">대체 확장자</h1>
<p>타입스크립트는 기본적으로 확장자가 .ts인 모든 파일을 읽을 수 있습니다.
그러나, 일부 프로젝트는 <code>JSON 모듈</code> 또는 리액트와 같은 <code>JSX 구문</code> 처럼 확장자가 다른 파일을 읽을 수 있어야 합니다.</p>
<h2 id="jsx-구문">JSX 구문</h2>
<h3 id="jsx">jsx</h3>
<p>JSX 구문은 프리액트와 리액트 같은 UI 라이브러리에서 자주 사용합니다.
🟡 JSX 구문은 기술적으로 자바스크립트가 아닙니다.
🟢 자바스크립트로 컴파일 되는 자바스크립트 구문의 확장입니다.</p>
<p>JSX 구문을 사용하기 위해 두 가지를 수행합니다.</p>
<ul>
<li>구성 옵션에서 <code>jsx</code> 컴파일러 옵션을 활성화<ul>
<li>preserve (.jsx)</li>
<li>react (.js)</li>
<li>react-native (.js)</li>
</ul>
</li>
<li><code>.tsx</code> 확장자로 파일 이름을 지정</li>
</ul>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
        &quot;jsx&quot;: &quot;preserve&quot;
    }
}</code></pre>
<h3 id="tsx-파일의-제네릭-화살표-함수">.tsx 파일의 제네릭 화살표 함수</h3>
<p>제네릭 화살표 함수의 구문이 JSX 구문과 충돌합니다.
➡ <code>.tsx</code> 파일에서 화살표 함수에 대한 타입 인수 <code>&lt;T&gt;</code> 를 작성하면 T 요소의 시작 태그에 대한 종료 태그가 없기 때문에 구문 오류가 발생합니다.</p>
<pre><code class="language-typescript">const handleChange = &lt;T&gt;(input: T) =&gt; input;</code></pre>
<p>구문의 모호성을 해결하기 위해 <code>= unknown</code> 제약 조건을 추가할 수 있습니다.
➡ 타입 인수 기본 값이 <code>unknown</code> 타입이므로 코드 동작이 전혀 반영되지 않습니다.</p>
<pre><code class="language-typescript">const handleChange = &lt;T = unknown&gt;(input: T) =&gt; input;</code></pre>
<h2 id="resolvejsonmodule">resolveJsonModule</h2>
<p><code>resolveJsonModule</code> 컴파일러 옵션을 <code>true</code> 로 설정하면 <code>.json</code> 파일을 읽을 수 있습니다.
➡ <code>.json</code> 파일을 마치 객체를 내보내는 .ts 파일인 것처럼 가져오며 객체의 타입을 const 변수 인 것처럼 유추합니다.</p>
<pre><code class="language-typescript">    import { user } from &quot;./user.json&quot;;
    console.log(user);</code></pre>
<p><code>esModuleInterop</code> 컴파일러 옵션을 사용하면 기본 가져오기를 사용할 수 있습니다.</p>
<pre><code class="language-typescript">    import data from &quot;./actvist.json&quot;;</code></pre>
<p><code>array</code> 또는 <code>number</code> 같은 다른 리터널 타입을 포함한 JSON 파일이라면 <code>import</code> 구문으로 * 사용합니다.</p>
<pre><code class="language-typescript">    // user.json
    [
        &quot;SON&quot;,
          &quot;JON&quot;,
          &quot;LEE&quot;
    ]

    // user.ts
    import * as users from &quot;./actvist.json&quot;;
    console.log(users.length);</code></pre>
<hr>
<h1 id="자바스크립트로-내보내기">자바스크립트로 내보내기</h1>
<p>바벨 같은 전용 컴파일러 도구 등장으로 타입스크립트 역할이 타입 검사만으로 축소되었지만, 여전히 타입스크립트에 의존하는 프로젝트도 많습니다.</p>
<h2 id="outdir">outDir</h2>
<p>기본적으로 타입스크립트 출력 파일은 해당 소스 파일과 동일한 위치에 생성합니다.</p>
<pre><code class="language-typescript">    users/
     user.js
     user.ts</code></pre>
<p>🟢 경우에 따라 출력 파일을 다른 폴더에 생성하는 것이 더 나을 수 있습니다.
<code>tsc --outDir dist</code> 명령어를 실행하면 <code>dist/</code> 폴더에 출력 파일을 생성합니다.</p>
<pre><code class="language-typescript">    dist/
          users/
              user.js
    users/
     user.ts</code></pre>
<p>✅ <code>rootDir</code> 컴파일러 옵션은 해당 루트 디렉터리를 명시적으로 지정하기 위해 존재하지만 <code>.</code> 또는 <code>src</code> 이외의 값을 사용할 일은 거의 없습니다.</p>
<h2 id="target">target</h2>
<p>타입스크립트는 자바스크립트 코드 구문을 지원하기 위해 어느 버전까지 변환해야 하는지를 지정하는 <code>target</code> 컴파일러 옵션을 제공합니다.
지정하지 않으면 이전 버전과 호환성을 위해 기본적으로 <code>es3</code> 이 지정됩니다.</p>
<ul>
<li><code>tsc --init</code> 은 기본으로 <code>es2016</code> 을 지정하도록 설정되어 있습니다.</li>
</ul>
<p>🟡 오래된 환경에서 최신 자바스크립트 기능을 지원하려면 더 많은 자바스크립트 코드를 생성해야 하므로, 파일 크기가 조금 더 커지고 런타임 성능이 조금 저하 됩니다.</p>
<p>✅ <code>target</code> 컴파일러 옵션은 코드가 실행될 수 있는 가장 오래된 환경의 값으로 지정하면 코드가 구문 오류 없이 여전히 실행 가능한 현대적이로 간결한 구문으로 내보내집니다.</p>
<h2 id="내보내기-선언">내보내기 선언</h2>
<p><code>declaration</code> 컴파일러 옵션을 사용해 소스 파일에서 <code>.d.ts</code> 출력 파일을 내보냅니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
        &quot;declaration&quot;: true
    }
}</code></pre>
<p>``.d.ts<code>출력 파일은</code>outDir<code>옵션에 따라</code>.js``` 파일과 동일한 출력 규칙에 따라 내보내집니다.</p>
<pre><code class="language-typescript">    users/
     user.d.ts
     user.js
     user.ts</code></pre>
<h3 id="emitdeclarationonly">emitDeclarationOnly</h3>
<p>.js와 .jsx 파일 없이 선언 파일만 내보내도록 하는 <code>emitDeclarationOnly</code> 컴파일러 옵션이 존재합니다.
➡ 출력 선언 파일을 생성하려는 프로젝트에 유용합니다.</p>
<blockquote>
<p><code>tsc --emitDeclarationOnly</code></p>
</blockquote>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
        &quot;emitDeclarationOnly&quot;: true
    }
}</code></pre>
<blockquote>
</blockquote>
<pre><code class="language-typescript">    users/
     user.d.ts
     user.ts</code></pre>
<h2 id="소스-맵">소스 맵</h2>
<p>소스 맵은 출력 파일의 내용이 원본 소스 파일과 어떻게 일치하는지에 대한 설명입니다.
➡ 브라우저 개발자 도구와 IDE에서 디버깅하는 동안 원본 소스 파일 내용을 볼 수 있도록 하는 시각적인 디버거에 특히 소스 맵이 유용합니다.</p>
<h3 id="sourcemap">sourceMap</h3>
<p><code>sourceMap</code> 컴파일러 옵션을 사용하면 .js 또는 .jsx 출력 파일과 함께 .js.map 또는 .jsx.map 소스맵을 출력할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-typescript">tsc --sourceMap</code></pre>
<pre><code class="language-typescript">    users/
     user.js
     user.js.map
     user.ts</code></pre>
<h3 id="declarationmap">declarationMap</h3>
<p>.d.ts 선언 파일에 대한 소스 맵을 생성할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-typescript">tsc --declaration --declarationMap</code></pre>
<pre><code class="language-typescript">    users/
     user.d.ts
     user.d.ts.map
     user.js
     user.ts</code></pre>
<h3 id="noemit">noEmit</h3>
<p>파일 생성을 모두 건너뛰도록 지정할 수 있습니다.
➡ 어떤한 파일도 생성되지 않습니다. 타입스크립트는 발견한 구문 또는 타입 오류만을 보고합니다.</p>
<hr>
<h1 id="타입검사">타입검사</h1>
<p>타입스크립트 구성 옵션은 타입 검사기를 제어합니다.</p>
<h2 id="lib">lib</h2>
<p><code>lib</code> 컴파일러 옵션은 브라우저 타입 포함을 나타내는 <code>dom</code> 과 <code>target</code> 컴파일러 옵션을 기본값으로 하는 문자열 배열을 사용합니다.
➡ lib 설정을 변경하는 이유는 브라우저에서 실행되지 않는 프로젝트에서 기본으로 포함된 <code>dom</code>을 제거하기 위함입니다.</p>
<p>✅ 최신 자바스크립트 API를 지원하기 위해 <code>폴리필</code> 을 사용하는 프로젝트에서 lib 컴파일러 옵션을 사용해 dom과 특정 ECMA스크립트 특정 버전을 포함할 수 있습니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
        &quot;lib&quot;: [&quot;dom&quot;, &quot;es2020&quot;]
    }
}</code></pre>
<p>🔴 ES2020까지만 지원하는 플랫폼에서 실행되는 lib에서 String.replaceAll 과 같이 ES2021 이상에서 정의된 API를 사용하면 여전히 런타임 오류가 발생할 수 있습니다.</p>
<h2 id="skiplibcheck">skipLibCheck</h2>
<p>소스 코드에 명시적으로 포함되지 않은 선언 파일에서 타입 검사를 건너뛰도록 하는 <code>skipLibCheck</code> 컴파일러 옵션을 제공합니다.
➡ 공유된 라이브러리의 정의가 서로 다르고 충돌 할 수 있는 패키지 의존성을 많이 사용하는 애플리케이션에 유용합니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
        &quot;skipLibCheck&quot;: true
    }
}</code></pre>
<p>✅ 타입 검사 일부를 건너뛰는 작업으로 타입스크립트 성능을 개선합니다.
<code>skipLibCheck</code> 옵션을 활성화하는 것이 좋습니다.</p>
<h2 id="엄격-모드">엄격 모드</h2>
<p>타입 검사 컴파일러 옵션은 대부분 <code>strict mode</code> 로 그룹화 됩니다.
<code>defalut : false</code></p>
<p>엄격 옵션 중에 <code>noImplicitAny</code> 와 <code>stringNullChecks</code> 는 타입 안전 코드를 적용하는데 특히 유용합니다.</p>
<p>특정 검사를 제외한 모든 엄격 모드를 활성화하고 싶다면 <code>strict</code> 활성화
특정 검사를 명시적으로 비활성화 할 수있습니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;noImplicitAny&quot; : false,
        &quot;strict&quot;: true
    }
}</code></pre>
<h3 id="noimplicitany">noImplicitAny</h3>
<p>매개변수 또는 속성의 타입을 유추할 수 없는 경우는 any 타입으로 가정합니다.
🔴 대부분 우회할 수 있으므로 코드에서 이러한 암시적 타입은 허용하지 않는 것이 좋습니다.</p>
<pre><code class="language-typescript">const logMessage = (message) =&gt; {
    // Error : Parameter &#39;message&#39; implicitly has an &#39;any&#39; type
    console.log(&#39;Message: ${message}!&#39;);
}

const logMessage = (message:string) =&gt; {
    // OK
    console.log(&#39;Message: ${message}!&#39;);
}</code></pre>
<h3 id="strictbindcallapply">strictBindCallApply</h3>
<p>내장된 Function.apply, Function.bind, Function.call 함수 유틸리티를 나타낼 수 있을 만큼 충분한 타입 시스템 기능이 없었습니다.
➡ <code>any</code>를 사용해야 했음, 안정성과는 매우 거리가 먼 상태</p>
<pre><code class="language-typescript">function getLength(text:string, trim?: boolean) {
    return trim ? text.trim().length : text;
}
// (thisArg : typeof getLength, args: [text:string, trim?: boolean]) =&gt; number
getLength.apply;
// (trim?: boolean) =&gt; number
getLength.bind(undefined, &quot;abc123&quot;);
// number
getLength.call(undefined, &quot;abc123&quot;, true);
</code></pre>
<p>✅ <code>strictBindCallApply</code> 옵션을 활성화하는 것이 좋습니다.
프로젝트의 타입 안정성을 개선하는 데 도움이 됩니다.</p>
<h3 id="strictfunctiontypes">strictFunctionTypes</h3>
<p>함수 매개변수 타입을 약간 더 엄격하게 검사합니다.
매개변수가 다른 타입의 매개변수 하위 타입인 경우 함수 타입은 더 이상 다른 함수 타입에 할당 가능한 것으로 간주되지 않습니다.</p>
<pre><code class="language-typescript">function checkOnNumber(containsA: (input: number | string) =&gt; boolean) {
    return containsA(1337);
}

function stringContainsA(input:string) {
    return !!input.match(/a/i);
}

checkOnNumber(stringContainsA);
</code></pre>
<p>checkOnNumber 함수는 number | string 을 받는 함수를 사용해야 하지만, 오직 타입이 string인 매개변수만 받을 것 으로 예상하는 <code>stringContainsA</code> 함수 제공</p>
<p><code>stringContainsA</code> 함수가 제공되는 것을 허용하고, 프로그램은 매개 변수가 number인 경우 .match() 를 호출하려고 하면 문제가 발생합니다. </p>
<h3 id="strictnullchecks">strictNullChecks</h3>
<p><code>strictNullChecks</code> 플래그를 비활성화하면 코드의 모든 타입에 <code>null | undefined</code> 가 추가되고, 
➡ 모든 변수가 null 또는 undefined를 받을 수 있도록 허용합니다.</p>
<p><code>strictNullChecks</code> 활성화 한경우, string 타입인 value에 null을 할당하면 타입 오류가 발생합니다.</p>
<pre><code class="language-typescript">let value : string;

value = &quot;abc1234&quot;; // OK
value = null; // ERROR
</code></pre>
<p>✅ <code>strictNullChecks</code> 옵션을 활성화하는 것이 좋습니다.
충돌을 방지할 수 있습니다.</p>
<h3 id="strictpropertyinitialization">strictPropertyInitialization</h3>
<p>플래그는 초기화가 없고, 생성자에게 확실하게 할당되지 않은 클래스 속성에서 타입 오류를 발생합니다.</p>
<pre><code class="language-typescript">class Animal {
  private name: string; // error
  // &#39;name&#39; is declared but its value is never
}

class Animal {
  private name: string;
  constructor(name: string) { // OK
    this.name = name;
  }
}</code></pre>
<p>✅ <code>strictPropertyInitialization</code> 옵션을 활성화하는 것이 좋습니다.
클래스 초기화 로직 실수로 인한 충돌을 방지할 수 있습니다.</p>
<h3 id="useunknownincatchvariables">useUnknownInCatchVariables</h3>
<p>catch 문에 error 변수의 타입을 변경할 수 있는 옵션
true : unknown
false : any</p>
<pre><code class="language-typescript">try {
    someExternalFunction()
} catch (error) {
    error; // 기본 타입 : any
}</code></pre>
<p>any 사용과 마찬가지로 오류를 억지로 명시적 타입 어서션 또는 내로잉 하는 비용보다 <code>unknown</code> 으로 처리하는 것이 기술적으로 더 타당합니다.</p>
<pre><code class="language-typescript">try {
    someExternalFunction()
} catch (error : unknown) {
    error; // 기본 타입 : unknown
}</code></pre>
<hr>
<h1 id="모듈">모듈</h1>
<p>새로운 타입스크립트 프로젝트는 대부분 표준화된 ECMA스크립트 모듈 구문으로 작성됩니다.</p>
<pre><code class="language-typescript">import { value } from &quot;my-example-value&quot;;

export const logValue = () =&gt; console.log(value);</code></pre>
<h2 id="module">module</h2>
<p>어떤 모듈 시스템으로 변환된 코드를 사용할지 결정하기 위해 module 컴파일러 옵션을 제공합니다.
module 값에 따라 export와 import 문을 다른 모듈 시스템으로 변환할 수 있습니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;module&quot; : &quot;commonjs&quot;
    }
}</code></pre>
<pre><code class="language-typescript">const my_example_lib = require(&quot;my-example-value&quot;);

export const logValue = () =&gt; console.log(my-example-value);</code></pre>
<p>target 컴파일러 옵션이 <code>es3</code> 또는 <code>es5</code> 인 경우 <code>module</code> 컴파일러 옵션의 기본값은 <code>commonjs</code> 가 됩니다.</p>
<p>그렇지 않다면 ECMA스크립트 모듈로 출력하도록 지정하기 위해 <code>module</code> 컴파일러 옵션은 <code>es2015</code> 로 기본 설정됩니다.</p>
<h2 id="moduleresolution">moduleResolution</h2>
<p>모듈 해석은 import에서 가져온 경로가 <code>module</code>에 매핑되는 과정입니다.</p>
<ul>
<li>node : 기존 <code>Node.js</code> 와 같은 <code>CommonJS 리졸버</code> 에서 사용하는 동작</li>
<li>nodenext : ECMA스크립트 모듈에 대해 지정된 동작에 맞게 조정</li>
</ul>
<p>🟡 두 전략은 유사하며, 차이를 느끼지 못합니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;moduleResolution&quot; : &quot;nodenext&quot;
    }
}</code></pre>
<h2 id="commonjs와의-상호-운용성">CommonJs와의 상호 운용성</h2>
<p>자바스크립트 모듈로 작업할 때 모듈의 기본 내보내기와 네임스페이스 출력 간에는 차이점이 있습니다.</p>
<ul>
<li>모듈의 기본 내보내기 : 내보낸 객체의 <code>.default 속성</code></li>
<li>모듈의 네임스페이스 내보내기 : 내보낸 객체 자체</li>
</ul>
<table>
<thead>
<tr>
<th align="center">구분 영역</th>
<th align="center">CommonJS</th>
<th align="center">ECMA스크립트 모듈</th>
</tr>
</thead>
<tbody><tr>
<td align="center">기본 내보내기</td>
<td align="center">module.exports.default = value;</td>
<td align="center">export default value;</td>
</tr>
<tr>
<td align="center">기본 가져오기</td>
<td align="center">const { default: value } = require(&quot;...&quot;)</td>
<td align="center">import value from &quot;...&quot;;</td>
</tr>
<tr>
<td align="center">네임스페이스 내보내기</td>
<td align="center">module.exports = value</td>
<td align="center">지원 안함</td>
</tr>
<tr>
<td align="center">네임스페이스 가져오기</td>
<td align="center">const value =  require(&quot;...&quot;)</td>
<td align="center">import * as value from &quot;...&quot;</td>
</tr>
</tbody></table>
<p>🟡 대부분의 프로젝트 처럼 npm 패키지에 의존하는 경우 의존성 중 일부는 여전히 <code>CommonJS</code> 모듈로 배포됩니다.</p>
<h3 id="esmoduleinterop">esModuleInterop</h3>
<p><code>module</code> 이 <code>es2015</code> 또는 <code>esnext</code> 와 같은 ECMA스크립트 모듈 형식이 아닌 경우 타입스크립트에서 내보낸 자바스크립트 코드에 소량의 로직을 추가합니다.</p>
<p>✅ 기본 내보내기를 제공하지 않는 <code>react</code> 같은 패키지를 위해서입니다.</p>
<p>➡ <code>esModuleInterop</code> 은 내보낸 자바스크립트 코드가 가져오기로 작동하는 방식에 대해서만 직접 변경합니다.</p>
<h3 id="allowsyntheticdefaultimports">allowSyntheticDefaultImports</h3>
<p>ECMA스크립트 모듈이 호환되지 않는 <code>CommonJS</code> 네임스페이스 내보내기 파일에서 기본 가져오기를 할 수 있음을 타입 시스템에 알립니다.</p>
<p><code>allowSyntheticDefaultImports</code> 컴파일러 옵션은 다음 중 하나가 <code>true</code> 인 경우에만 <code>true</code> 로 기본적 설정됩니다.</p>
<ul>
<li>module이 system인 경우</li>
<li><code>esModuleInterop : true</code>, <code>module</code> 이 <code>es2015</code> 또는 <code>esnext</code> 와 같은 ECMA스크립트 모듈 형식이 아닌 경우</li>
</ul>
<p>➡ <code>esModuleInterop : true</code> 이지만 <code>module : esnext</code> 경우 타입스크립트는 컴파일된 출력 자바스크립트 코드가 가져오기 상호 운용성 지원을 사용하지 않는다고 가정합니다.</p>
<pre><code class="language-typescript">import React from &quot;react&quot;; 
// ERROR
// default-imported using the `allowSyntheticDefaultImports flag`</code></pre>
<h3 id="isolatedmodules">isolatedModules</h3>
<p>한 번에 하나의 파일에서만 작동하는 바벨과 같은 외부 트랜스파일러는 타입 시스템 정보를 사용해 자바스크립트를 내보낼 수 없습니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;isolatedModules&quot; : true
    }
}</code></pre>
<p>✅ 프로젝트에서 타입스크립트가 아닌 다른 도구를 사용해 자바스크립트로 변환하는 경우 <code>isolatedModules</code>를 활성화 하는 것이 좋습니다.</p>
<h2 id="자바스크립트">자바스크립트</h2>
<p>타입스크립트는 기본적으로 <code>.js</code> , <code>.jsx</code> 확장자를 가진 파일을 무시하지만 <code>allowJs</code> 와 <code>checkJs</code> 컴파일러 옵션을 사용하면 자바스크립트 파일을 읽고, 컴파일하고, 제한된 기능이지만 타입 검사도 할 수 있습니다.</p>
<h3 id="allowjs">allowJs</h3>
<p>자바스크립트 파일에 선언된 구문을 타입스크립트 파일에서 타입 검사를 하도록 허용합니다. <code>jsx</code> 컴파일러 옵션과 결합하면 <code>.jsx</code> 파일도 검사할 수 있습니다.</p>
<pre><code class="language-typescript">import { value } from &quot;./values&quot;;
console.log(&#39;Quote: &#39;${value.toUpperCase()}&#39;&#39;);</code></pre>
<p>🟡 <code>allowJs</code> 가 활성화 되지않으면<code>import</code> 문은 알려진 타입을 갖지 못합니다.
기본적으로 암시적 any가 되거나 타입오류가 발생합니다.</p>
<p><code>allowJs</code> 는 ECMA스크립트 target에 맞게 컴파일되고 자바스크립트로 내보내진 파일목록에 자바스크립트 파일을 추가합니다. 활성화 된 경우 소스 맵과 선언 파일도 생성됩니다.</p>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;allowJs&quot; : true
    }
}</code></pre>
<p>✅ <code>allowJs</code> 가 활성화되면 가져온 value는 string 타입이 되며 오류도 발생하지 않습니다.</p>
<h3 id="checkjs">checkJs</h3>
<p>자바스크립트 파일도 타입 검사가 가능합니다.</p>
<ul>
<li>allowJs 옵션이 아직 <code>true</code> 가 아니라면 기본값을 <code>true</code> 로 설정하기</li>
<li><code>.js</code> 와 <code>.jsx</code> 파일에서 타입 검사기 활성화 하기</li>
</ul>
<pre><code class="language-typescript">{
      &quot;compilerOptions&quot; : {
          &quot;checkJs&quot; : true
    }
}</code></pre>
<p>✅ <code>checkJs</code> 를 활성화하면 타입 스크립트가 자바스크립트 파일을 타입스크립트 관련 구문이 없는 타입스크립트 파일인 것 처럼 처리합니다.
타입 불일치, 철자가 틀린 변수명 등 타입스크립트 파일에서 일반적으로 발생하는 모든 오류를 발생시킬 수 있습니다.</p>
<pre><code class="language-typescript">// index.js
let myQuote = &quot;Each person...&quot;;
console.log(quote); // Error</code></pre>
<p>🔴 <code>checkJs</code> 가 활성화되지 않았다면 타입스크립트는 해당 버그에 대한 오류를 보고하지 않습니다.</p>
<h4 id="ts-check">@ts-check</h4>
<p>파일 상단에 <code>// @ts-check</code> 주석을 사용해 파일별로 <code>checkJs</code> 를 활성화 합니다.</p>
<pre><code class="language-typescript">// index.js
// @ts-check
let myQuote = &quot;Each person...&quot;;
console.log(quote); // Error</code></pre>
<h3 id="jsdoc-지원">JSDoc 지원</h3>
<p><code>allowJs</code> 와 <code>checkJs</code> 가 활성화되면 타입스크립트는 코드의 모든 <code>JSDoc</code> 정의를 인식합니다.</p>
<pre><code class="language-typescript">// index.js

/**
/* @param {string} text
*/
function sentenceCase(text) {
    return &#39;${text[0].toUpperCase()} ${text.slice(1)}.&#39;;
}

sentenceCase(&quot;hello world&quot;); // Ok
sentenceCase([&quot;hello&quot;, &quot;world&quot;]); // ERROR</code></pre>
<hr>
<h1 id="구성-확장">구성 확장</h1>
<p><code>TSConfig</code> 파일이 다른 구성 파일에서 구성 값을 <code>확장(extend)</code> 하거나 복사하도록 선택하는 메커니즘을 제공합니다.</p>
<h2 id="extends">extends</h2>
<p>extends 구성 옵션을 사용해 다른 TSConfig에서 확장할 수 있습니다.
extends는 다른 TSConfig 파일에 대한 경로를 가져오고 해당 파일의 모든 설정을 복사해야 함 을 나냅니다.</p>
<p>파생 또는 자식 구성에서 선언된 모든 옵션은 기본 또는 부모 구성에서 동일한 이름의 모든 옵션을 다시 정의합니다.</p>
<p>packages/* 디렉터리를 포함하는 <code>모노레포(monorepo)</code> 처럼 여러 개의 TSConfig가 있는 많은 저장소는 규칙에 따라 확장할 <code>tsconfig.json</code> 파일에 대한 <code>tsconfig.base.json</code> 파일을 생성합니다.</p>
<pre><code class="language-typescript">// tsconfig.base.json
{
      &quot;compilerOptions&quot; : {
          &quot;strict&quot; : true
    }
}

// packages/core/tsconfig.json
{
      &quot;extends&quot;: &quot;../../tsconfig.base.json&quot;,
    &quot;includes&quot;: [&quot;src&quot;]
}</code></pre>
<p><code>compilerOptions</code> 는 재귀적으로 고려됩니다.
파생된 TSConfig에서 특정 옵션을 재정의하지 않는 한 기본 TSConfig의 각 컴파일러 옵션은 파생된 TSConfig로 그대로 복사됩니다.</p>
<pre><code class="language-typescript">// packages/js/tsconfig.json
{
      &quot;extends&quot;: &quot;../../tsconfig.base.json&quot;,
    &quot;compilerOptions&quot; : {
        &quot;allowJs&quot; : true
    }
    &quot;includes&quot;: [&quot;src&quot;]
}</code></pre>
<h3 id="확장-모듈">확장 모듈</h3>
<p><code>extends</code> 속성은 다음 자바스크립트 가져오기 중 하나를 사용합니다.</p>
<ul>
<li>절대 경로 : @ 또는 알파벳 문자로 시작</li>
<li>상대 경로 : 마침표(.)로 시작하는 로컬 파일 경로</li>
</ul>
<p><code>extends</code> 값이 절대 경로라면 npm 모듈에서 TSConfig를 확장함을 나타냅니다.
타입스크립트는 이름과 일치하는 패키지를 찾기 위해 일반 노드 모듈 확인 시스템을 사용합니다.</p>
<p>✅ 많은 조직에서 npm 패키지를 사용해 여러 저장소 또는 모노레포 내에서 타입스크립트 컴파일 옵션을 표준화 합니다.</p>
<pre><code class="language-typescript">// packages/tsconfig.json
{
      &quot;compilerOptions&quot; : {
          &quot;strict&quot; : true
    }
}

// packages/js/tsconfig.json
{
      &quot;extends&quot;: &quot;@my-org/tsconfig&quot;,
    &quot;compilerOptions&quot; : {
          &quot;allowJs&quot; : true
    }
    &quot;includes&quot;: [&quot;src&quot;]
}

// packages/ts/tsconfig.json
{
      &quot;extends&quot;: &quot;@my-org/tsconfig&quot;,
    &quot;includes&quot;: [&quot;src&quot;]
}
</code></pre>
<h2 id="구성-베이스">구성 베이스</h2>
<p>처음 부터 고유한 구성을 생성하거나 <code>--init</code> 제안을 하는 대신, 특정 런타임 환경에 맞게 미리 만들어진 베이스 TSConfig 파일로 시작할 수 있습니다.</p>
<p>디노에서 권장되는 TSConfig 베이스를 설치하려면 다음과 같이 진행합니다.</p>
<pre><code>npm install --save-dev @tsconfig/deno
# or
yarn add --dev @tsconfig/deno
</code></pre><p>구성 패키지가 설치되고 나면 다른 npm 패키지 구성 확장처럼 참조할 수 있습니다.</p>
<pre><code class="language-typescript">// packages/js/tsconfig.json
{
      &quot;extends&quot;: &quot;@tsconfig/deno/tsconfig.json&quot;,
}</code></pre>
<hr>
<h1 id="프로젝트-레퍼런스">프로젝트 레퍼런스</h1>
<p>대규모 프로젝트에서 프로젝트의 서로 다른 영역에 서로 다른 구성 파일을 사용하는 것이 유용할 수 있습니다.
✅ 타입스크립트에서는 여러 개의 프로젝트를 함께 빌드하는 <code>프로젝트 레퍼런스</code> 시스템을 정의할 수 있습니다.</p>
<p>단일 TSConfig 파일을 사용하는 것보다 조금 더 작업이 많지만 몇 가지 핵심 이점이 있습니다.</p>
<ul>
<li>특정 코드 영역에 대해 다른 컴파일러 옵션을 지정할 수 있습니다.</li>
<li>개별 프로젝트에 대한 빌드 출력을 캐시할 수 있으므로 종종 대규모 프로젝트의 빌드 시간이 훨씬 빨라집니다.</li>
<li>프로젝트 레퍼런스는 코드의 개별 영역을 구조화하는데 유용한 의존성 트리(특정 프로젝트가 다른 프로젝트에서 파일을 가져오는 것만 허용)를 실행합니다.</li>
</ul>
<h2 id="composite">composite</h2>
<p><code>composite</code> 구성 옵션을 선택해 파일 시스템 입력과 출력이 제약 조건을 준수함을 나타냅니다.</p>
<p><code>composite : true</code> 일 때는 다음과 같습니다.</p>
<ul>
<li><code>rootDir</code> 설정이 아직 명시적으로 설정되지 않았다면 기본적으로 TSConfig 파일이 포함된 디렉토리로 설정됩니다.</li>
<li>모든 구현 파일은 포함된 패턴과 일치하거나 파일 배열에 나열되어야 합니다.</li>
<li><code>declaration</code> 컴파일 옵션은 반드시 <code>true</code> 여야 합ㄴ디ㅏ.</li>
</ul>
<pre><code class="language-typescript">// core/tsconfig.json
{
    &quot;compilerOptions&quot; : {
        &quot;declaration&quot; : true
    }
    &quot;composite&quot; : true
}</code></pre>
<p>✅ 타입스크립트가 프로젝트에 대한 모든 입력 파일과 일치하는 <code>.d.ts</code> 파일을 생성하도록 강제할 때 유용합니다.
<code>composite</code> 옵션은 다음 <code>refernces</code> 구성 옵션과 함께 사용할 때 가장 유용합니다.</p>
<h2 id="references">references</h2>
<p>참조된 프로젝트에서 모듈을 가져오는 것은 출력 <code>.d.ts</code> 선언 파일에서 가져오는 것으로 타입 시스템에 표시됩니다.</p>
<p>다음 구성 스니펫은 <code>core/</code> 디렉터리를 입력으로 참조하도록 <code>shell/</code> 디렉터리를 설정합니다.</p>
<pre><code class="language-typescript">// shell/tsconfig.json
{
   &quot;refernces&quot; : [
     { &quot;path&quot; : &quot;../core&quot; }
   ]
}</code></pre>
<p>💡 <code>references</code> 구성 옵션은 기본 TSConfig에서 extends를 통해 파생된 TSConfig로 복사되지 않습니다.</p>
<p><code>references</code> 옵션은 다음 빌드 모드와 함께 사용할 때 가장 유용합니다.</p>
<h2 id="빌드-모드">빌드 모드</h2>
<p>코드 영역이 프로젝트 레퍼런스를 사용하도록 한번 설정되면 <code>빌드(build)</code> 모드에서 -b 또는 --b CLI 플래그를 통해 <code>tsc</code>를 사용할 수 있습니다.</p>
<ul>
<li>TSConfig의 참조된 프로젝트를 찾습니다.</li>
<li>최신 상태를 감지합니다.</li>
<li>오래된 프로젝트를 올바른 순서로 빌드합니다.</li>
<li>재공된 TSConfig 또는 TSConfig의 의존성이 변경된 경우 빌드합니다.</li>
</ul>
<p>✅ 타입스크립트 빌드 모드 기능은 최신 프로젝트를 다시 빌드하는 것을 건너뛰도록 해 빌드 성능을 크게 향상시킵니다.</p>
<h3 id="코디네이터-구성">코디네이터 구성</h3>
<p>저장소에서 타입스크립트 프로젝트 레퍼런스를 설정하는 편리한 방법은 빈 파일 배열과 저장소의 모든 프로젝트 레퍼런스에 대한 레퍼런스를 사용해 최상위 레벨의 tsconfig.josn을 설정하는 것입니다.</p>
<p>최상위 TSConfig는 타입스크립트가 파일 자체를 빌드하도록 지시하지 않습니다.
➡ 필요에 따라 참조된 프로젝트를 빌드하도록 타입스크립트에 알리려는 역할만 합니다.</p>
<p>다음은 tsconfig.json은 저장소의 <code>packages/core</code> 와 <code>packages/shell</code> 프로젝트를 빌드하는 것을 나타냅니다.</p>
<pre><code class="language-typescript">// tsconfig.json
{
   &quot;files&quot;: [],
   &quot;refernces&quot; : [
     { &quot;path&quot; : &quot;./packages/core&quot; },
     { &quot;path&quot; : &quot;./pacakages/shell&quot; },
   ]
}</code></pre>
<h3 id="빌드-옵션-모드">빌드 옵션 모드</h3>
<p>빌드 모드는 몇 가지 빌드에 특화된 CLI 옵션을 지원합니다.</p>
<ul>
<li>--clean : 지정된 프로젝트의 출력을 삭제합니다 (--dry 함께 사용할 수 있습니다.)</li>
<li>--dry : 수행할 작업을 보여주지만 실제로 아무것도 빌드하지 않습니다.</li>
<li>--force : 모든 프로젝트가 오래된 것처럼 작동합니다.</li>
<li>-w or --watch : 일반적인 타입스크립트 watch 모드와 유사합니다.</li>
</ul>
<p>✅ 빌드 모드는 <code>watch</code> 모드를 지원하기 때문에 <code>tsc b -w</code> 같은 명령을 실행하면 대규모 프로젝트에서 모든 컴파일러 오류에 대한 최신 목록을 빠르게 확인 할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query Pagination]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-Pagination</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-Pagination</guid>
            <pubDate>Sun, 05 Mar 2023 15:33:55 GMT</pubDate>
            <description><![CDATA[<h1 id="react-query를-사용하여-pagination을-효율적으로-구현하기">React Query를 사용하여 Pagination을 효율적으로 구현하기</h1>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/867e90e5-2ce1-4715-9c69-7b8d96b14348/image.png" alt=""></p>
<p><code>Pagination</code> 은 <code>useQuery</code> 와 <code>prefetchQuery</code> 를 사용하면 다음 페이지로 이동할 때 이미 다음 페이지의 데이터를 캐시하고 있어, 사용자가 대기하는 시간 없이 바로 화면에 렌더링 할 수 있는 좋은 기법이다.</p>
<h2 id="keeppreviousdata-옵션-활성화">keepPreviousData 옵션 활성화</h2>
<p><code>keepPreviousData</code> 옵션을 <code>true</code> 로 설정한 경우, 새로운 데이터가 요청되는 동안 성공적으로 가져온 마지막 데이터를 화면에 유지시켜준다.
➡ 유저의 입장에서 화면이 일관성 있게 유지되며 데이터만 변경되기 때문에 자연스러운 UI를 볼 수 있다.</p>
<pre><code class="language-typescript">  const queryInfo = useQuery(
    queries.posts.getPosts(page, limit).queryKey,
    getPostsApi,
    {
      staleTime: 5000, // fetch된 데이터 5초간 fresh 상태
      keepPreviousData: true, // 쿼리 키가 변경되어도 이전 데이터를 유지
    },
  );</code></pre>
<h2 id="prefetchquery-사용하기">PrefetchQuery 사용하기</h2>
<blockquote>
<p><code>prefetchQuery</code> 은 이전 글을 참고하자
<a href="https://velog.io/@dev_jazziron/React-Query-Prefetching">React-Query PrefetchQuery</a></p>
</blockquote>
<pre><code class="language-typescript">useEffect(() =&gt; {
    if (data?.hasMore) {
      queryClient.prefetchQuery([&quot;projects&quot;, page + 1], () =&gt;
        fetchProjects(page + 1)
      );
    }
  }, [data, page, queryClient]);</code></pre>
<p>page가 변경될 때 다음 데이터가 존재하는 경우 다음 페이지의 데이터를 <code>prefetchQuery</code> 통해 프리 패칭하여 캐시에 저장한다.</p>
<blockquote>
<p>참고 : <a href="https://tanstack.com/query/v4/docs/react/examples/react/pagination">React-Query pagination</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query Prefetching]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-Prefetching</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-Prefetching</guid>
            <pubDate>Sun, 05 Mar 2023 05:11:09 GMT</pubDate>
            <description><![CDATA[<h1 id="prefetching">Prefetching?</h1>
<p><code>pre+fetch</code> 의 합성어
사용자가 데이터가 필요하기 전에 보여줄 데이터를 미리 가져올 수 있도록 알 수 있습니다.</p>
<p>주로 <code>server side</code> 에서 데이터를 미리 받아오거나, 화면 전환시 컴포넌트 마운트 전에 데이터를 미리 받아오기 위해 사용합니다.</p>
<p><code>prefetchQuery</code> method를 사용하여 캐시에 넣을 쿼리 결과를 미리 가져 올 수 있습니다.
➡ <code>loading</code> 없이 바로 임시 데이터를 보여줄 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/e8253170-c570-458f-9e85-9ac22037abc1/image.png" alt=""></p>
<blockquote>
<p>사용자에게 편리한 UX를 보여줄 수 있다!</p>
</blockquote>
<pre><code class="language-typescript">
const getPostsApi = async (
  ctx: QueryFunctionContext&lt;getPostListQuery[&quot;queryKey&quot;]&gt;,
): Promise&lt;Post[]&gt; =&gt; {
  const [, , { page, limit }] = ctx.queryKey;
  const response = await axiosClient.get&lt;Post[]&gt;(
    `${BASE_URL}/posts`,
    {
      params: { page: page, limit: limit },
    },
  );
  return response.data;
};


const prefetchTodos = async () =&gt; {
  const queryClient = useQueryClient();
  // ✅ The results of this query will be cached like a normal query
  await queryClient.prefetchQuery(
    queries.posts.getPosts(page, limit).queryKey
    getPostsApi,
  )
}</code></pre>
<ul>
<li>쿼리에 대한 데이터가 이미 캐시에 있으며, 무효화되지 않는 경우 데이터를 가져오지 않습니다.
➡ 지정된 <code>staleTime</code> 보다 오래된 경우 쿼리를 가져 옵니다.<pre><code class="language-typescript">const prefetchTodos = async () =&gt; {  
const queryClient = useQueryClient();
await queryClient.prefetchQuery(
  queries.posts.getPosts(page, limit).queryKey
  getPostsApi,
  {
      staleTime: 5000
  }
)
}</code></pre>
</li>
<li><code>useQuery</code> prefetch 된 쿼리에 대한 인스턴스가 없다면, 지정된 시간 이후 삭제되어 가비지에 수집됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query mutation]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-mutation</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-mutation</guid>
            <pubDate>Sat, 04 Mar 2023 13:26:32 GMT</pubDate>
            <description><![CDATA[<h1 id="what-are-mutations">What are mutations?</h1>
<p><code>mutations</code> 은 부과 효과(side effect) 함수 입니다.
react query로 서버 상태를 관리하기 때문에 <code>mutations</code> 은 서버에 부가 효과를 일으킬 수 있습니다.</p>
<h1 id="similarities-to-usequery">Similarities to useQuery</h1>
<p><code>useMutation</code> 은 <code>useQuery</code> 가 쿼리에 대해 수행하는 것처럼 <code>mutation</code> 의 상태를 추적합니다.</p>
<p><code>loading</code> , <code>error</code>, <code>status fields</code> 를 제공하여 무슨 상태인지 알 수 있습니다.</p>
<h1 id="differences-to-usequery">Differences to useQuery</h1>
<p>대부분의 쿼리는 자동으로 실행됩니다.</p>
<ul>
<li><code>mutations</code> 의 경우 자동으로 실행되지 않습니다.</li>
<li><code>mutations</code> 가 <code>useQuery</code> 처럼 상태를 공유하지 않습니다.</li>
<li>컴포넌트에서 <code>useQuery</code> 를 여러 번 호출 할 수 있으며 캐시된 결과를 동일하게 반환하지만 <code>mutations</code> 은 동작하지 않습니다.</li>
</ul>
<h1 id="tying-mutations-to-queries">Tying mutations to queries</h1>
<p><code>mutation</code> 은 대부분 쿼리에 직접 연결되지 않게 설계합니다.
<code>mutation</code> 이 쿼리에 대한 변경 사항을 반영하기 위해 두 가지 방법이 있습니다.</p>
<h2 id="invalidation-무효화">Invalidation (무효화)</h2>
<p>화면을 최신 상태로 만드는 가장 간단한 방법입니다.
react query가 현재 사용 중인 경우 해당 데이터를 다시 가져오며, 가져오기가 완료되면 화면이 자동으로 업데이트 됩니다.
➡ 무효화할 쿼리만 라이브러리에 알려주면 됩니다.</p>
<blockquote>
<p>💡 <code>queryClient.invalidateQueries(queryKey)</code> 를 통해 캐시를 무효화 할 수 있습니다.</p>
</blockquote>
<pre><code class="language-typescript">const useUpdatePostMutation = (postId: number) =&gt; {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({
      postId,
      title,
      body,
    }: {
      postId: number;
      title: string;
      body: string;
    }) =&gt; {
      return updatePostDetailApi(postId, title, body);
    },
    onSuccess: (newPost) =&gt; {
      // ✅ refetch the comments list for our blog post
      queryClient.invalidateQueries(queries.posts.detail(postId).queryKey);

    },
  });
};</code></pre>
<h2 id="direct-updates">Direct updates</h2>
<p><code>mutation</code> 가 알아야 할 모든 정보를 반환하는 경우 데이터를 다시 검색할 필요가 없습니다.</p>
<blockquote>
<p>💡 <code>setQueryData</code> 를 통해 직접 쿼리 캐시를 업데이트 할 수 있습니다.</p>
</blockquote>
<pre><code class="language-typescript">const useUpdatePostMutation = (postId: number) =&gt; {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({
      postId,
      title,
      body,
    }: {
      postId: number;
      title: string;
      body: string;
    }) =&gt; {
      return updatePostDetailApi(postId, title, body);
    },
    onSuccess: (newPost) =&gt; {
      // ✅ update detail view directly
      queryClient.setQueryData(queries.posts.detail(postId).queryKey, newPost);
    },
  });
};</code></pre>
<p><code>setQueryData</code> 를 통해 직접 캐시에 데이터를 주입하면 데이터가 백엔드에서 반환된 것 처럼 동작합니다.
➡ 해당 쿼리를 사용하는 모든 컴포넌트가 다시 렌더링되는 것을 뜻합니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/a59c076c-2b4d-46e4-b7d1-b21fcba95dd2/image.png" alt=""></p>
<blockquote>
<p>🤟 대부분 무효가 선호되는 것이 <strong>안전한 접근법</strong> 이라고 생각합니다.</p>
</blockquote>
<h2 id="optimistic-updates-낙관적-업데이트">Optimistic updates (낙관적 업데이트)</h2>
<p><code>useQuery</code> 캐시는 쿼리 간 전환 시, <code>prefetching</code> 과 결합할 때 데이터를 즉시 제공합니다.
➡ 전체적인 UI가 매우 빠르게 느껴집니다.
<code>mutations</code> 도 같은 이 점을 어떻게 얻을 수 있을까요?</p>
<blockquote>
<p>낙관적인 업데이트는 서버에 전송하기도 전에 <code>mutations</code> 의 성공을 가장합니다.</p>
</blockquote>
<ol>
<li>성공적인 응답을 받음</li>
<li>실제 데이터를 보기 위해 무효화 처리</li>
<li>요청이 실패하면 UI를 변환 전 상태로 롤백 처리</li>
</ol>
<h3 id="언제-사용-할까">언제 사용 할까?</h3>
<blockquote>
<p>구체적인 예시는 <a href="https://tanstack.com/query/latest/docs/react/guides/optimistic-updates?from=reactQueryV3&amp;original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fguides%2Foptimistic-updates">공식 문서</a>에 정리되어 있습니다. ( <a href="https://tanstack.com/query/latest/docs/react/examples/react/optimistic-updates-typescript?from=reactQueryV3&amp;original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fexamples%2Freact%2Foptimistic-updates-typescript">typescript example</a> )</p>
</blockquote>
<p>낙관적 업데이트는 정말 필요할 때 사용해 주세요
➡ UX를 실제로 나쁘게 만들 수 있으며, 로딩 애니메이션을 보여주는 것이 더 효율적일 수 있습니다.</p>
<h1 id="common-gotchas">Common Gotchas</h1>
<p>확실하지 않을 수 있는 <code>mutations</code> 을 사용할 때 몇 가지 좋은 사항에 대해서 알아봅시다.</p>
<h2 id="awaited-promises">awaited Promises</h2>
<p><code>mutation</code> 콜백에서 반환된 Promise는 react query에 의해 대기(awaited)되며, 실제로 <code>invalidateQueries</code> 는 Promise를 반환합니다.
➡ 관련 쿼리가 업데이트되는 동안 변환을 <code>loading</code> 상태로 유지하려면 콜백에서 <code>invalidateQueries</code> 의 결과를 반환해야 합니다.</p>
<pre><code class="language-typescript">{
  // 🎉 will wait for query invalidation to finish
  onSuccess: () =&gt; {
    return queryClient.invalidateQueries([&#39;posts&#39;, id, &#39;comments&#39;])
  }
}
{
  // 🚀 fire and forget - will not wait
  onSuccess: () =&gt; {
    queryClient.invalidateQueries([&#39;posts&#39;, id, &#39;comments&#39;])
  }
}</code></pre>
<h2 id="mutate-or-mutateasync">Mutate or MutateAsync</h2>
<ul>
<li><code>mutate</code> 는 아무것도 반환하지 않습니다.</li>
<li><code>mutateAsync</code> 는 돌연변이의 결과를 포함하는 Promise를 반환합니다.</li>
</ul>
<blockquote>
<p><code>mutation reponse</code> 에 대한 접근이 필요할 때 <code>mutateAsync</code> 를 사용 할 수 있습니다.</p>
</blockquote>
<h3 id="항상-mutate-을-해야한다고-생각합니다-왜">항상 <code>mutate</code> 을 해야한다고 생각합니다! 왜?</h3>
<ul>
<li><code>mutate</code> 는 콜백을 통해 데이터나 오류에 액세스할 수 있으며 오류 처리 걱정이 없음
➡ react query는 내부적으로 오류를 캐치하거나 무시하기에 <code>mutate</code> 를 사용할 때 오류 처리가 필요 없습니다. 🤟</li>
<li><code>mutateAsync</code> 는 Promise를 제어할 수 있기 때문에 오류를 직접 처리해야함</li>
</ul>
<h3 id="그럼-mutateasync-언제-쓰나요">그럼 MutateAsync 언제 쓰나요?</h3>
<ul>
<li>Promise가 꼭 필요할 경우</li>
<li>여러 <code>mutations</code>를 동시에 시작하며, 모든 <code>mutations</code> 이 끝나기를 기다리는 작업</li>
<li>콜백 지옥에 빠질 수 있는 의존적 <code>mutations</code> 이 있는 경우 필요함</li>
</ul>
<h2 id="mutations-only-take-argument-for-variables">Mutations only take argument for variables</h2>
<p>반환하는 마지막 인수가 옵션 객체이므로 <code>useMutation</code> 은 현재 변수에 대해 하나의 인수만 사!
용할 수 있습니다.
➡ 제한적이지만, 객체를 사용하여 쉽게 해결
<img src="https://velog.velcdn.com/images/dev_jazziron/post/4c955f51-639c-45b3-b049-030bb93aab8d/image.png" alt=""></p>
<pre><code class="language-typescript">// 🚨 this is invalid syntax and will NOT work
const mutation = useMutation((title, body) =&gt; updateTodo(title, body))
mutation.mutate(&#39;hello&#39;, &#39;world&#39;)

// ✅ use an object for multiple variables
const mutation = useMutation(({ title, body }) =&gt; updateTodo(title, body))
mutation.mutate({ title: &#39;hello&#39;, body: &#39;world&#39; })</code></pre>
<h2 id="some-callbacks-might-not-fire">Some callbacks might not fire</h2>
<p><code>useMutation</code> 에 대한 콜백뿐만 아닌 <code>mutate</code> 에 대한 콜백을 가질 수 있습니다.
➡ <code>mutate</code> 의 콜백 전에 <code>useMutation</code> 이 호출되는 것을 알아야 합니다.</p>
<p>🚨 변환이 완료되기 전에 컴포넌트가 마운트 해제 된 경우 <code>mutate</code> 콜백이 전혀 실행되지 않을 수 있습니다.
➡ 콜백에서 문제를 분리하는 것이 좋다고 생각합니다.</p>
<p>** <code>useMutation</code> 콜백 수행 **
절대적으로 필요한 작업 및 로직 관련 작업(쿼리 무효화)
➡ 무효화 논리는 항상 동일하기 때문에 Custom hook의 재사용성이 높습니다.</p>
<p>** 리디렉션, 토스트 알림을 콜백으로 표시하는 것과 같은 UI관련 작업을 수행 **
➡ 사용자가 변환이 완료되기 전에 현재 화면에서 멀리 탐색한 경우, 의도적으로 작동하지 않음</p>
<pre><code class="language-typescript">const useUpdateTodo = () =&gt;
  useMutation(updateTodo, {
    // ✅ always invalidate the todo list
    onSuccess: () =&gt; {
      queryClient.invalidateQueries([&#39;todos&#39;, &#39;list&#39;])
    },
  })

// in the component

const updateTodo = useUpdateTodo()
updateTodo.mutate(
  { title: &#39;newTitle&#39; },
  // ✅ only redirect if we&#39;re still on the detail page
  // when the mutation finishes
  { onSuccess: () =&gt; history.push(&#39;/todos&#39;) }
)
</code></pre>
<blockquote>
<p>참조 링크</p>
</blockquote>
<ul>
<li><a href="https://tkdodo.eu/blog/mastering-mutations-in-react-query">TkDodo&#39;s blog - Mastering Mutations in React Query</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query Placeholder와 initData
]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-Placeholder-initData</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-Placeholder-initData</guid>
            <pubDate>Thu, 02 Mar 2023 14:37:53 GMT</pubDate>
            <description><![CDATA[<h1 id="placeholder-and-initial-data-in-react-query">Placeholder and initial Data in React Query</h1>
<p>react query를 사용하여 사용자 경험을 개선하는 방법에 대해서 알아보자.
 ➡ 대부분은 사람(유저)들은 <code>Loading Spinner</code> 를 싫어 합니다. 🤬 </p>
<p>react query는 서로 다르지만 유사한 방법으로 <a href="https://tanstack.com/query/latest/docs/react/guides/placeholder-query-data?from=reactQueryV3&amp;original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fguides%2Fplaceholder-query-data">Placeholder Query Data</a> 와 <a href="https://tanstack.com/query/latest/docs/react/guides/initial-query-data?from=reactQueryV3&amp;original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fguides%2Finitial-query-data">Initial Query Data</a> 의 두 가지 방법을 제공합니다.</p>
<h1 id="similarities">Similarities</h1>
<p>데이터를 캐시에 동기적으로 미리 채울 수 있는 방법을 제공합니다.
 ➡ 쿼리가 로딩 상태가 아닌 바로 성공 상태로 전환됩니다.</p>
<pre><code class="language-typescript">function Component() {
  // ✅ status will be success even if we have not yet fetched data
  const { data, status } = useQuery([&#39;number&#39;], fetchNumber, {
    placeholderData: 23,
  })

  // ✅ same goes for initialData
  const { data, status } = useQuery([&#39;number&#39;], fetchNumber, {
    initialData: () =&gt; 42,
  })
}</code></pre>
<p><strong>캐시에 데이터가 이미 있는 경우</strong> 둘 다 영향을 미치지 않습니다.</p>
<h1 id="on-cache-level">On cache level</h1>
<p>각 쿼리 키에는 캐시 항목이 하나만 있습니다.
 ➡ react query가 애플리케이션에서 동일한 데이터를 <strong>전역적으로</strong> 공유할 수 있기 때문입니다.</p>
<p><code>staleTime</code> 과 <code>cacheTime</code> 에 영향을 미치며 옵션에 따라 <code>GC</code> 될 수 있는 시기를 설정 할 수 있습니다.</p>
<h1 id="on-observer-level">On observer level</h1>
<p>react query의 <code>observer</code> 는 하나의 캐시 엔트리를 위해 생성된 <code>subscription</code> 입니다.
 ➡ <code>observer</code> 는 캐시 항목이 변경되었는지 확인하고 변경 사항이 있는 경우 알림을 받습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/69903807-f5d7-4278-bd43-7c15ffb1a6f8/image.png" alt="React Query Devtools"></p>
<p><code>React Query Devtools</code> 에서 쿼리 키 왼쪽의 숫자는 (1) 쿼리에 포함된 <code>observer</code> 수를 확인 할 수 있습니다.</p>
<ul>
<li><code>observer</code> 는 useQuery를 호출하면 생성됩니다.
➡ 호출 할 때마다 <code>observer</code> 을 만들고 데이터가 변경되면 컴포넌트가 다시 렌더링됩니다.
💡 동일한 캐시 항목을 보는 여러 <code>observer</code> 가 있을 수 있다.</li>
</ul>
<h1 id="differences">Differences</h1>
<blockquote>
</blockquote>
<ul>
<li>InitialData : <code>캐시</code> 수준에서 동작</li>
<li>placeholderData : <code>observer level</code> 에서 동작</li>
</ul>
<h2 id="persistence-영속성">Persistence (영속성)</h2>
<h3 id="initialdata">initialData</h3>
<p><strong><code>initialData</code></strong> 는 캐시에 유지됩니다.</p>
<p>캐시 수준에서 작동하기 때문에 하나의 <code>initialData</code> 만 있을 수 있으며, 캐시 항목이 생성되는 즉시(첫 번재 관찰자가 마운트될 때) 캐시에 저장됩니다.</p>
<p> 🚨 다른 <code>initialData</code>로 두 번째 <code>observer</code> 를 마운트하려고 하면 _아무 작업도 수행하지 않습니다. _</p>
<hr>
<h3 id="placeholderdata">PlaceholderData</h3>
<p><strong><code>PlaceholderData</code></strong> 는 캐시에 유지되지 않습니다.</p>
<p>해당 데이터는 서버 데이터와 관련 없는 <strong>보여주기용 가짜 데이터</strong> 입니다.
react query는 실제 데이터를 가져오는 동안 해당 데이터를 보여줍니다.</p>
<p><code>observer</code> 수준에서 작동하기 때문에 이론적으로 다른 컴포넌트에 대해 다른 <code>placehoderData</code> 를 가질 수 있습니다.</p>
<h2 id="background-refetches">Background refetches</h2>
<h3 id="initialdata-1">initialData</h3>
<p><strong><code>initialData</code></strong> 는 캐시에 유효한 데이터로 간주되므로 실제로 저장되어 오랜 시간 남아 있습니다.
만약 <code>staleTime</code> 이 0(기본값) 이라면 <code>background-refetch</code> 를 확인할 수 있습니다.</p>
<blockquote>
<p>쿼리에 <code>staleTime(ex 30초)</code> 를 설정한 경우 초기 데이터를 동기적으로 얻을 수 있으며, 해당 데이터는 30초간 유효하기 때문에 backend에 요청할 필요가 없습니다.</p>
</blockquote>
<p>✅ <code>initialDataUpdatedAt</code> 을 사용하여 해결 할 수 있습니다.
 ➡ <code>initailData</code> 가 생성되었을 때 react query에 알리며 고려하여 백그라운드 다시 가져오기가 트리거 됩니다.</p>
<p> 기존 캐시 항목에서 <code>dataUpdatedAt</code> 타임스탬프와 <code>initialData</code> 를 사용할 때 매우 유용합니다.</p>
<pre><code class="language-typescript">const useTodo = (id) =&gt; {
  const queryClient = useQueryClient()

  return useQuery([&#39;todo&#39;, id], () =&gt; fetchTodo(id), {
    staleTime: 30 * 1000,
    initialData: () =&gt;
      queryClient
        .getQueryData([&#39;todo&#39;, &#39;list&#39;])
        ?.find((todo) =&gt; todo.id === id),
    initialDataUpdatedAt: () =&gt;
    // ✅ initial data를 채우는데 사용한 쿼리 데이터가 
    // staleTime(30초) 더 오래된 경우 백그라운드에서 다시 가져옵니다.
      queryClient.getQueryState([&#39;todo&#39;, &#39;list&#39;])?.dataUpdatedAt,
  })
}</code></pre>
<hr>
<h3 id="placeholderdata-1">PlaceholderData</h3>
<p><strong><code>PlaceholderData</code></strong> 를 사용하면 처음으로 관찰자를 탑재할 때 항상 백그라운드에서 다시 가져옵니다.
➡ 데이터가 <strong>실제 데이터</strong>가 아니므로 react query가 실제 데이터를 가져옵니다.</p>
<p>데이터를 가저오는 동안 react query에서 반환된 <code>isPlaceholderData</code> 플래그를 얻을 수 있습니다.
✅ 이 플레그를 사용하여 사용자가 보고 있는 데이터가 실제로는 <code>PlaceholderData</code> 임을 시각적으로 알 수 있습니다.
➡ 실제 데이터가 들어오는 즉시 <code>false</code> 로 전환됩니다.</p>
<h2 id="error-transitions">Error transitions</h2>
<p><code>initailData</code> 또는 <code>PlaceholderData</code> 사용 시, 백그라운드 리페치가 트리거된 다음, 쿼리가 실패한다는 가정을 새워봅니다. <em>어떤 상황이 발생할까요?</em></p>
<h3 id="initialdata-2">initialData</h3>
<p><code>initailData</code> 는 캐시에 유지되므로 refetch 오류는 다른 백그라운드 오류와 같이 처리됩니다.
➡ 쿼리는 오류 상태이지만 데이터는 여전히 존재함</p>
<hr>
<h3 id="placeholderdata-2">PlaceholderData</h3>
<p>쿼리는 <code>error</code> 상태가 되며 데이터는 <code>undefined</code> 가 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/c3b19a86-f0d5-41ca-a370-4c8e120ebace/image.png" alt=""></p>
<h1 id="언제-무엇을-사용해야-할까">언제 무엇을 사용해야 할까?</h1>
<ul>
<li>다른 쿼리로 부터 쿼리를 미리 채울 때 <code>initialData</code></li>
<li>그 외 <code>placeholderData</code></li>
</ul>
<blockquote>
<p>참조 링크</p>
</blockquote>
<ul>
<li><a href="https://tkdodo.eu/blog/placeholder-and-initial-data-in-react-query">TkDodo&#39;s blog - placeholder-and-initial-data-in-react-query</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query 이펙티브 리액트 쿼리 키]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-effective-react-query-keys</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-effective-react-query-keys</guid>
            <pubDate>Thu, 02 Mar 2023 13:49:52 GMT</pubDate>
            <description><![CDATA[<h1 id="caching-data">Caching Data</h1>
<h2 id="query-key-특징">query key 특징</h2>
<ul>
<li>쿼리 키는 고유해야 합니다.</li>
<li>react query가 캐시에서 키에 대한 항목을 찾으면 사용합니다.</li>
<li><code>useQuery</code> 와 <code>useInfiniteQuery</code>에는 동일한 키를 사용할 수 없습니다.</li>
</ul>
<pre><code class="language-typescript">useQuery([&#39;todos&#39;], fetchTodos)

// 🚨 this won&#39;t work
useInfiniteQuery([&#39;todos&#39;], fetchInfiniteTodos)

// ✅ choose something else instead
useInfiniteQuery([&#39;infiniteTodos&#39;], fetchInfiniteTodos)</code></pre>
<h1 id="automatic-refetching">Automatic Refetching</h1>
<blockquote>
<p>쿼리는 선언형입니다.</p>
</blockquote>
<pre><code class="language-typescript">function Component() {
  const { data, refetch } = useQuery([&#39;todos&#39;], fetchTodos)

  // ❓ how do I pass parameters to refetch ❓
  return &lt;Filters onApply={() =&gt; refetch(???)} /&gt;
}</code></pre>
<p>쿼리, <code>refetching</code> 을 명령형으로 생각하지만, &quot;클릭&quot;하는 데도 시간이 걸릴 수 있습니다.</p>
<p><code>refetch</code> 의 뜻은 동일한 매개 변수를 사용하여 <code>refetching</code> 을 하는 것입니다.</p>
<pre><code class="language-typescript">function Component() {
  const [filters, setFilters] = React.useState()
  const { data } = useQuery([&#39;todos&#39;, filters], () =&gt; fetchTodos(filters))

  // ✅ set local state and let it &quot;drive&quot; the query
  return &lt;Filters onApply={setFilters} /&gt;
}</code></pre>
<h1 id="always-use-array-keys">Always use Array Keys</h1>
<blockquote>
<p>참고 : <a href="https://velog.io/@dev_jazziron/react-query-querykey">React Query queryKey 관리하기</a></p>
</blockquote>
<p>정확한 키를 알면 하나의 특정 목록을 대상으로 지정할 수 있습니다.
필요한 경우 모든 목록을 대상으로 지정할 수 있으므로 <a href="https://tanstack.com/query/latest/docs/react/guides/updates-from-mutation-responses?from=reactQueryV3&amp;original=https%3A%2F%2Ftanstack.com%2Fquery%2Fv3%2Fdocs%2Fguides%2Fupdates-from-mutation-responses">Updates from Mutation Responses</a> 는 훨씬 더 유연해집니다.</p>
<pre><code class="language-typescript">function useUpdateTitle() {
  return useMutation(updateTitle, {
    onSuccess: (newTodo) =&gt; {
      // ✅ update the todo detail
      queryClient.setQueryData([&#39;todos&#39;, &#39;detail&#39;, newTodo.id], newTodo)

      // ✅ update all the lists that contain this todo
      queryClient.setQueriesData([&#39;todos&#39;, &#39;list&#39;], (previous) =&gt;
        previous.map((todo) =&gt; (todo.id === newTodo.id ? newtodo : todo))
      )
    },
  })
}</code></pre>
<p><em>만약 list와 detail 구조가 많이 다르다면</em> 이 기능이 작동하지 않을 수 있기 때문에 모든 list를 <strong>무효화</strong> 할 수 있습니다.</p>
<pre><code class="language-typescript">function useUpdateTitle() {
  return useMutation(updateTitle, {
    onSuccess: (newTodo) =&gt; {
      queryClient.setQueryData([&#39;todos&#39;, &#39;detail&#39;, newTodo.id], newTodo)

      // ✅ just invalidate all the lists
      queryClient.invalidateQueries([&#39;todos&#39;, &#39;list&#39;])
    },
  })
}</code></pre>
<p>URL에서 필터를 읽어 어떤 목록에 있는 데이터를 알고 있으므로 정확한 <code>queryKey</code> 를 구성할 수 있다면, 이 두가지 방법을 결합하여 <code>list</code> 에서 <code>setQueryData</code> 를 호출하고 다른 모든 것을 <strong>무효화</strong> 할 수 있습니다.</p>
<pre><code class="language-typescript">function useUpdateTitle() {
  // imagine a custom hook that returns the current filters,
  // stored in the url
  const { filters } = useFilterParams()

  return useMutation(updateTitle, {
    onSuccess: (newTodo) =&gt; {
      queryClient.setQueryData([&#39;todos&#39;, &#39;detail&#39;, newTodo.id], newTodo)

      // ✅ update the list we are currently on instantly
      queryClient.setQueryData([&#39;todos&#39;, &#39;list&#39;, { filters }], (previous) =&gt;
        previous.map((todo) =&gt; (todo.id === newTodo.id ? newtodo : todo))
      )

      // 🥳 invalidate all the lists, but don&#39;t refetch the active one
      queryClient.invalidateQueries({
        queryKey: [&#39;todos&#39;, &#39;list&#39;],
        refetchActive: false,
      })
    },
  })
}</code></pre>
<blockquote>
<p>참조링크</p>
</blockquote>
<ul>
<li><a href="https://tkdodo.eu/blog/effective-react-query-keys">TkDodo&#39;s blog - effective-react-query-keys</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query 함수 컨텍스트 활용하기]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-QueryFunctionContext</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-QueryFunctionContext</guid>
            <pubDate>Wed, 01 Mar 2023 08:19:51 GMT</pubDate>
            <description><![CDATA[<h1 id="hot-take">Hot take</h1>
<p>인라인 함수는 Custom Hook에서 사용할 수 있는 다른 변수를 닫을 수 있기에 <code>queryFn</code>에 전달하는 가장 쉬운 방법입니다.</p>
<pre><code class="language-typescript">type State = &#39;all&#39; | &#39;open&#39; | &#39;done&#39;
type Todo = {
  id: number
  state: TodoState
}
type Todos = ReadonlyArray&lt;Todo&gt;

const fetchTodos = async (state: State): Promise&lt;Todos&gt; =&gt; {
  const response = await axios.get(`todos/${state}`)
  return response.data
}

export const useTodos = () =&gt; {
  // imagine this grabs the current user selection
  // from somewhere, e.g. the url
  const { state } = useTodoParams()

  // ✅ The queryFn is an inline function that
  // closures over the passed state
  return useQuery([&#39;todos&#39;, state], () =&gt; fetchTodos(state))
}</code></pre>
<p>🚨 매개변수가 많은 경우 상당한 문제를 발생할 수 있습니다.</p>
<pre><code class="language-typescript">type Sorting = &#39;dateCreated&#39; | &#39;name&#39;
const fetchTodos = async (
  state: State,
  sorting: Sorting
): Promise&lt;Todos&gt; =&gt; {
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.data
}</code></pre>
<p>쿼리에 정렬을 추가하려고합니다.
 ➡ fetchTodos에서 Custom Hook에서 오류가 발생할 수 있습니다. 
 쿼리 키가 실제 종속성과 다를 수 있는 복잡한 문제가 발생할 수 있습니다.😡</p>
<pre><code class="language-typescript">export const useTodos = () =&gt; {
  const { state, sorting } = useTodoParams()

  // 🚨 can you spot the mistake ⬇️
  return useQuery([&#39;todos&#39;, state], () =&gt; fetchTodos(state, sorting))
}</code></pre>
<p><strong>해결방법?</strong>
모든 종속성을 포함하여 쿼리 키를 생성하여 이 문제를 해결하는 <a href="https://github.com/dominictwlee/babel-plugin-react-query-key-gen">babel-plugin-react-query-key-gen </a> 있습니다.</p>
<p>💡 react query에는 종속성을 처리하는 다른 기본 메서드가 포함되어 있습니다. 
<code>QueryFunctionContext</code> 사용하는 것입니다.</p>
<h1 id="queryfunctioncontext">QueryFunctionContext</h1>
<p><code>QueryFunctionContext</code> 는 <code>queryFn</code> 에 인수로 전달되는 객체입니다.</p>
<pre><code class="language-typescript">const fetchTodos = async ({ queryKey }) =&gt; {
  // 🚀 we can get all params from the queryKey
  const [, state, sorting] = queryKey
  const response = await axios.get(`todos/${state}?sorting=${sorting}`)
  return response.data
}

export const useTodos = () =&gt; {
  const { state, sorting } = useTodoParams()

  // ✅ no need to pass parameters manually
  return useQuery([&#39;todos&#39;, state, sorting], fetchTodos)
}</code></pre>
<h1 id="how-to-type-the-queryfunctioncontext">How to type the QueryFunctionContext</h1>
<p>전체 타입의 안정성을 확보하고 useQuery에 전달된 queryKey에서 <code>QueryFunctionContext</code> 의 타입을 추론하는 것입니다.</p>
<pre><code class="language-typescript">export const useTodos = () =&gt; {
  const { state, sorting } = useTodoParams()

  return useQuery(
    [&#39;todos&#39;, state, sorting] as const,
    async ({ queryKey }) =&gt; {
      const response = await axios.get(
        // ✅ this is safe because the queryKey is a tuple
        `todos/${queryKey[1]}?sorting=${queryKey[2]}`
      )
      return response.data
    }
  )
}
</code></pre>
<p>🚨 여전히 많은 단점이 있습니다.</p>
<ul>
<li>무언가를 사용하기 위해 클로저 내부에 쿼리를 생성해야 합니다.</li>
<li><code>queryKey</code> 를 사용하여 위의 방법으로 URL을 작성하는 것은 모든 문자열을 지정할 수 있으므로 여전히 안전하지 않습니다.</li>
</ul>
<h1 id="query-key-factories">Query Key Factories</h1>
<p>키를 빌드하기 위한 <code>typesafe query key factory</code> 가 있는 경우 반환 타입을 사용하여 <code>QueryFunctionContext</code> 를 입력할 수 있도록 합니다.</p>
<blockquote>
<p>참고 : <a href="https://velog.io/@dev_jazziron/react-query-querykey">react-query-querykey</a></p>
</blockquote>
<pre><code class="language-typescript">// index.ts
import {
  mergeQueryKeys,
  inferQueryKeyStore,
} from &quot;@lukemorales/query-key-factory&quot;;

export const postQueryKeys = createQueryKeys(&quot;posts&quot;, {
  getPosts: (page?: number, limit?: number) =&gt; ({
    queryKey: [{ page, limit }],
  }),
});

// postTypes.ts
export const queries = mergeQueryKeys(postQueryKeys);
export type QueryKeys = inferQueryKeyStore&lt;typeof queries&gt;;
export type GetPostListQuery = QueryKeys[&quot;posts&quot;][&quot;getPosts&quot;];

export interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

// queries.ts
const getPostsApi = async (
  ctx: QueryFunctionContext&lt;GetPostListQuery[&quot;queryKey&quot;]&gt;,
): Promise&lt;Post[]&gt; =&gt; {
  const [, , { page, limit }] = ctx.queryKey;
  const response = await axiosClient.get&lt;Post[]&gt;(
    &quot;https://jsonplaceholder.typicode.com/posts&quot;,
    {
      params: { _page: page, _limit: limit },
    },
  );
  return response.data;
};

const useGetPosts = (page: number, limit: number) =&gt; {
  return useQuery(
    queries.posts.getPosts(page, limit).queryKey,
    getPostsApi,
  );
};
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_jazziron/post/d870e812-8a9f-475e-9b20-deb4c05d56b2/image.png" alt=""></p>
<p><code>QueryFunctionContext</code> 타입은 react query에 의해 export 됩니다.
queryKey의 타입을 정의하는 하나의 <code>제네릭</code>을 사용합니다.
해당 구조에 맞지 않는 키를 사용하면 타입 오류가 발생하게 됩니다.</p>
<blockquote>
<p>참조</p>
</blockquote>
<ul>
<li><a href="https://tkdodo.eu/blog/leveraging-the-query-function-context">tkdodo blog - query-function-context</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query 렌더링 최적화]]></title>
            <link>https://velog.io/@dev_jazziron/React-Query-Render-Optimizations</link>
            <guid>https://velog.io/@dev_jazziron/React-Query-Render-Optimizations</guid>
            <pubDate>Wed, 01 Mar 2023 07:37:33 GMT</pubDate>
            <description><![CDATA[<h1 id="isfetching-transition">isFetching transition</h1>
<p>이전 섹션에서 <a href="https://velog.io/@dev_jazziron/react-query-data-transformation">React Query의 데이터 변환</a> 에서 
데이터의 길이가 변경될 경우에만 다시 렌더링된다고 정의했지만, 기술적으로 사실이 아니다. 🤦</p>
<p><code>background-fetch</code> 를 다시 실행할 때마다 구성 요소는 다음 쿼리 정보를 두번 re-render 처리합니다.</p>
<pre><code class="language-typescript">{ status: &#39;success&#39;, data: 2, isFetching: true, isLoading: true, ... }
{ status: &#39;success&#39;, data: 2, isFetching: false, isLoading: false, ... }</code></pre>
<h2 id="isfetching과-isloading-무슨-차이">isFetching과 isLoading 무슨 차이?</h2>
<h2 id="정의">정의</h2>
<h3 id="isfetching">isFetching</h3>
<p><code>isFetching</code> 은 어떠한 react query 요청 내부의 <strong>비동기 함수 처리여부</strong>에 따라 <code>true/false</code> 상태로 처리됩니다.
 ➡ 캐시 유무와 상관없이 <strong>데이터를 가져오는 유무</strong>만 확인하여 <code>true/false</code> 상태로 처리합니다.</p>
<h3 id="isloading">isLoading</h3>
<p><code>isLoading</code> 은 <strong>캐시 데이터조차 없이, 처음 실행된 쿼리</strong>일 때 <code>loading</code> 에 따라 <code>true/false</code> 상태로 처리됩니다.
 ➡ 어떤 데이터를 호출하여 웹 브라우저 상에 캐시가 존재한 상태에서 <code>같은 쿼리 키의 데이터</code> 를 중복으로 호출한다면 <code>isLoading</code> 은 무조건 <code>false</code> 상태입니다.</p>
<blockquote>
</blockquote>
<p><code>isFetching</code> 와 <code>isLoading</code>은 비슷하게 loading 개념을 사용하지만 기존 <strong>캐시 데이터</strong> 여부에 따라 다르게 동작합니다.</p>
<h2 id="언제-구분해서-사용할까">언제 구분해서 사용할까?</h2>
<ul>
<li><code>isFetching</code> : 서버에 데이터 요청을 다시 할 경우 (캐시 데이터가 존재할 때)</li>
<li><code>isLoading</code> : 서버에 데이터 요청을 처음 할 경우</li>
</ul>
<hr>
<h1 id="notifyonchangeprops">notifyOnChangeProps</h1>
<p>react query는 <code>notifyOnChangeProps</code> 옵션이 있습니다.
props 중 하나가 변경된 경우에만 해당 <code>observer</code> 에게 변경 사항을 알려주도록 <code>observer</code> 수준에서 설정할 수 있습니다.</p>
<pre><code class="language-typescript">export const useTodosQuery = (select, notifyOnChangeProps) =&gt;
  useQuery([&#39;todos&#39;], fetchTodos, { select, notifyOnChangeProps })
export const useTodosCount = () =&gt;
  useTodosQuery((data) =&gt; data.length, [&#39;data&#39;])</code></pre>
<p>이렇게 적용하면 useQuery의 response 값의 <code>data</code> 만 변경 된 경우 리렌더링이 됩니다.</p>
<h1 id="staying-in-sync">Staying in Sync</h1>
<pre><code class="language-typescript">export const useTodosCount = () =&gt;
  useTodosQuery((data) =&gt; data.length, [&#39;data&#39;])

function TodosCount() {
  // 🚨 we are using error, but we are not getting notified if error changes!
  const { error, data } = useTodosCount()

  return (
    &lt;div&gt;
      {error ? error : null}
      {data ? data : null}
    &lt;/div&gt;
  )
}
</code></pre>
<p>🚨 status를 이용하여 조건부 렌더링을 하는 경우 문제가 발생함 
 ➡ status 의 변경여부를 감지할 수 없음</p>
<ul>
<li>플래그 값을 사용하는 것이 아닌, <code>notifyOnChangeProps</code> 목록을 컴포넌트에서 실제로 사용하고 있는 필드와 동기화하면 됩니다.</li>
</ul>
<p>🚨 Custom Hook에서 하드코딩한 경우 실제로 무엇을 사용할지 모르기 때문에 문제가 발생
 ➡ 동기화 작업을 하지 않고 데이터 속성만 관찰하는 경우 오류가 표시되면, 컴포넌트가 다시 렌더링되지 않으므로 오래된 값 입니다.</p>
<h1 id="tracked-queries">Tracked Queries</h1>
<p>💡 <code>notifyOnChangeProps</code> 를 <strong><code>tracked</code></strong> 로 설정하면 react query는 렌더링 중에 사용중인 필드를 추적하며, 이 필드를 사용하여 목록을 계산합니다.
 ➡ 목록을 수동으로 지정하는 것과 정확히 동일한 방식으로 최적화되며, 사용자가 고려할 필요가 없습니다. </p>
<ul>
<li>해당 옵션은 모든 쿼리에 전역적으로 설정할 수 있습니다.</li>
</ul>
<blockquote>
<p>v4 부터는 기본적으로 notifyOnChangeProps: &#39;tracked&#39; 옵션이 설정되어 있습니다.
  ➡ notifyOnChangeProps: &#39;all&#39; 설정하여 옵트아웃 할 수 있습니다.</p>
</blockquote>
<pre><code class="language-typescript">const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
         notifyOnChangeProps: &#39;tracked&#39;,
     },
   },
})</code></pre>
<h2 id="고려사항">고려사항</h2>
<h3 id="구조분해destructuring를-쓰지마세요">구조분해(destructuring)를 쓰지마세요</h3>
<p>일반적인 분해 할당은 괜찮지만, 그 외는 하지마세요.</p>
<pre><code class="language-typescript">// 🚨 will track all fields
const { isLoading, ...queryInfo } = useQuery(...)

// ✅ this is totally fine
const { isLoading, data } = useQuery(...)</code></pre>
<h3 id="추적된-쿼리는-render-중에만-작동합니다">추적된 쿼리는 <code>render</code> 중에만 작동합니다.</h3>
<p>작동 중인 필드에만 엑세스하면 추적되지 않습니다.
 🚨  종속성 배열로 인해 매우 어려운 경우</p>
<pre><code class="language-typescript">const queryInfo = useQuery(...)

// 🚨 will not corectly track data
React.useEffect(() =&gt; {
    console.log(queryInfo.data)
})

// ✅ fine because the dependency array is accessed during render
React.useEffect(() =&gt; {
    console.log(queryInfo.data)
}, [queryInfo.data])</code></pre>
<h3 id="필드를-한번-추적하면-컴포넌트-생명동안-추적합니다">필드를 한번 추적하면 컴포넌트 생명동안 추적합니다.</h3>
<p>추적된 쿼리는 각 render에서 재설정되지 않으므로 필드를 한 번 추적하면 관찰자(Observer)의 수명동안 추적됩니다.</p>
<pre><code class="language-typescript">const queryInfo = useQuery(...)

if (someCondition()) {
// 🟡 we will track the data field if someCondition was true in any previous render cycle
    return &lt;div&gt;{queryInfo.data}&lt;/div&gt;
}</code></pre>
<h1 id="structural-sharing">Structural sharing</h1>
<p>이 기능을 사용한다면 모든 수준에서 데이터 참조 ID를 유지할 수 있습니다.</p>
<pre><code class="language-typescript">[
  { &quot;id&quot;: 1, &quot;name&quot;: &quot;Learn React&quot;, &quot;status&quot;: &quot;active&quot; },
  { &quot;id&quot;: 2, &quot;name&quot;: &quot;Learn React Query&quot;, &quot;status&quot;: &quot;todo&quot; }
]
</code></pre>
<p>다음과 같은 데이터 구조에서 첫 번째 todo를 &#39;done&#39; 상태로 전환하고 re-fetch를 한다면, backend에서 새로운 jsonData 값을 얻을 수 있습니다.</p>
<pre><code class="language-typescript">[
-  { &quot;id&quot;: 1, &quot;name&quot;: &quot;Learn React&quot;, &quot;status&quot;: &quot;active&quot; },
+  { &quot;id&quot;: 1, &quot;name&quot;: &quot;Learn React&quot;, &quot;status&quot;: &quot;done&quot; },
  { &quot;id&quot;: 2, &quot;name&quot;: &quot;Learn React Query&quot;, &quot;status&quot;: &quot;todo&quot; }
]</code></pre>
<p>react query는 이전 상태와 새 상태를 비교하며, 가능한 많은 이전 상태를 유지합니다.
todo를 업데이트했기 때문에 todos 배열 데이터가 새로워집니다.
 ➡ <code>id:2</code> 에 대한 개체는 이전 상태의 개체와 동일한 참조가 됩니다.</p>
<blockquote>
<p>참조</p>
</blockquote>
<ul>
<li><a href="https://tkdodo.eu/blog/react-query-render-optimizations">https://tkdodo.eu/blog/react-query-render-optimizations</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>