기록을 불러오는 중입니다...
Powerful asynchronous state management for TS/JS, React, Solid, Vue, Svelte and Angular
💡 과정을 표현하는 절차형이 아니라, 원하는 결과를 표현하는 선언형 패러다임과 더불어 상태 관리에 필요한 각종 부수작업들을 알아서 처리해줍니다.
💡 비동기를 처리할 수 있는 최신 JS 문법에 잘 어우러지고, 무겁고 복잡한 설정 코드를 작성할 필요가 없으므로 코드 표현이 간결해집니다.
💡 격리된 블랙박스로 인해 다른 영역에 영향을 끼치지 않으므로 모듈성이 좋으며, 비동기 상태 관리를 위한 여러가지 세부 설정이 가능해서 다양한 경우에 맞춰 유연하게 사용할 수 있습니다.
TanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze.
Hook을 통해 서로 비슷한 것을 하는 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용할 수 있습니다.
useQuery 내부 구현을 보면, 쿼리 클라이언트와 옵저버를 이용하는 커스텀 훅임을 알 수 있습니다.스스로 상태를 관리하는 캡슐화된 컴포넌트를 만드세요. 그리고 이를 조합해 복잡한 UI를 만들어보세요.
// 여기서는 글 목록만!
function PostList() {
const { data: postList } = useQuery({
queryKey: ['post'],
queryFn: fetchPostList,
});
}
// 여기서는 글만!
function Post(props) {
const { id, img } = props;
const { data: post } = useQuery({
queryKey: ['Post', id],
queryFn: fetchPost,
});
}function useBaseQuery(...) {
// Handle suspense
if (shouldSuspend(defaultedOptions, result)) {
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
}
}❓ <a href="https://tanstack.com/query/latest/docs/framework/react/guides/render-optimizations#structural-sharing">구조적 공유</a>란? 낡은 서버 상태와 새로운 서버 상태를 비교할 때, 이전 상태를 최대한 많이 유지하려고 하는 전략입니다. JSON 형태로 직렬화 가능한 데이터에 대해, 변경이 없는 부분은 얕은 복사를 통해 레퍼런스를 유지하여 리렌더링을 방지합니다.
QueryClientProvider 를 통해, 리액트 컴포넌트 트리 내에서 손쉽게 QueryClient 를 공유할 수 있습니다. 물론, 이 또한 React 내장 Context API를 이용해 만들어져있습니다.🚨 QueryClient 는 참조-안전하게 사용하길 권장합니다. 가장 좋은 방법은 App 외부에 쿼리 클라이언트 인스턴스를 생성하는 것입니다. (<a href="https://tkdodo.eu/blog/react-query-fa-qs#2-the-queryclient-is-not-stable">참고</a>)
❗ 공식 문서에 나와있는 예제는 여기서 설명하지 않도록 하겠습니다.
Treat the query key like a dependency array
initialData 내부에서 getQueryData 를 사용하여, 화면의 깜빡거림을 방지하는 기술을 보여주고 있습니다.type State = 'all' | 'open' | 'done';
type Todo = {
id: number
state: State
};
type Todos = ReadonlyArray<Todo>;
const fetchTodos = async (state: State): Promise<Todos> => {
const response = await axios.get(`todos/${state}`)
return response.data
};
const useTodosQuery = (state: State) =>
useQuery({
queryKey: ['todos', state],
queryFn: () => fetchTodos(state),
initialData: () => {
const allTodos = queryClient.getQueryData<Todos>([
'todos',
'all',
])
const filteredData =
allTodos?.filter((todo) => todo.state === state) ?? []
return filteredData.length > 0 ? filteredData : undefined
},
}); const App = () => {
const { data } = useQuery({
queryKey: ['key'],
queryFn,
staleTime: Infinity,
})
return data ? <MyForm initialData={data} /> : null
}
const MyForm = ({ initialData }) => {
const [data, setData] = React.useState(initialData)
...
}staleTime 을 Infinity 로 설정해주는 것을 잊지 마세요!setQueryData 를 이용해 쿼리 데이터를 변경하는 것은 일반적으로 낙관적 업데이트 혹은 서버 상태를 받아온 직후에만 사용하기를 권장하고 있습니다.🚨 추가로, setQueryData 를 사용하는 경우 쿼리 키가 반드시 정확히 일치해야 합니다. (<a href="https://tkdodo.eu/blog/react-query-fa-qs#1-query-keys-are-not-matching">참고</a>)
// 2번에 나온 예제 코드
const useTodosQuery = (state: State) =>
useQuery({
queryKey: ['todos', state],
queryFn: () => fetchTodos(state),
});// 패치 함수 내에서 서버 상태를 변형하는 경우
const fetchTodos = async (): Promise<Todos> => {
const response = await axios.get('todos')
const data: Todos = response.data
return data.map((todo) => todo.name.toUpperCase())
};
const useTodosQuery = () =>
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});select 옵션이 좋은 해결책이 됩니다. 이는 온전히 클라이언트 사이드에서 변형 로직을 최적화하기 좋은 곳입니다.useCallback 을 이용해서 메모이징을 하는 것을 권장합니다.// select 옵션을 이용해 서버 상태를 변형하는 경우
const useTodosQuery = (select) =>
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select,
});
const useTodosCount = () => useTodosQuery((data) => data.length);
const useTodo = (id) => useTodosQuery((data) => data.find((todo) => todo.id === id));💡 저자는 “불필요한 리렌더링” 보다, “필요한 리렌더링을 놓치는 것”을 더 경계라하고 조언합니다.