0.핵심
React Query의 hydrate와 dehydrate는 서버에서 미리 가져온 데이터를 클라이언트 사이드에서
재사용할 수 있게 해줍니다.
1. Hydration
Hydrate는 ‘수화(水化) 시키다’ 라는 뜻을 가지고 있습니다. 여기서 ‘수화(水化’는 어떤 물질이 물과 결합하여 수화물(물을 포함하는 화합물)이 되는 현상을 말합니다.
서버의 데이터가 클라이언트의 DOM과 결합하는 과정을 빗대어 hydrate라는 단어로 정의했다라고 생각하시면 될 것 같습니다.
React는 클라이언트 렌더링만 있어서 유저에게 보여줄 HTML,CSS 자바스크립트 모두 render() 함수를 이용해 생성하여, React가 어떤 DOM을 렌더하는지 알려줍니다.
반면에 Next는 서버에서 보여줄 HTML 컨텐츠를 가져오기 때문에 재차 render() 함수로 HTML을 생성하여 DOM을 그리는 일은 비효율적입니다. 따라서,
hydrate() 함수로 서버에서 받아온 HTML에 유저가 상호작용할 수 있는 이벤트 리스너만 연결하는 것입니다.
2. Hydration API 사용하기
React query에서는 dehydrate와 hydrate함수를 제공해줍니다. 각각의 용어가 하는 일은 다음과 같습니다.
- dehydrate: 서버에서 React Query의 상태를 클라이언트로 전송할 수 있는 형태로 만들기위해 사용됨
- 서버에서 데이터를 가져와 직렬화(serialization) 하여 클라이언트로 전송됨
- hydrate: 클라이언트 측에서 직렬화된(serialization) 상태를 받아 이를 React Query의 상태로 변환함.
- 서버에서 미리 가져온 데이터를 클라이언트의 쿼리 캐시에 적용하여, 네트워크 없이 데이터를 사용할 수 있게 함
직렬화(serialization)란?서버에서 React Query의 상태 데이터를 클라이언트로 전송할수 있는 형태로 만든 상태를 DehydratedState라고하는데, 이 과정에서 React Query의 캐시 데이터를 클라이언트로 전송하기위해 하는것을 직렬화라고 합니다. 즉 여기서 말하는 직렬화란, JSON형식으로 만드는것을 의미합니다.
3. 요약
🌟 Hydration의 진행 과정 살펴보기
즉 사용자는 SSR을 통해 UI를 미리 보고, Hydration을 통해 페이지 조작이 가능해집니다.
폴더 구조
저희 프로젝트에서 사용할 react-query, hydration의 폴더구조는 다음과 같습니다.
- 루트 디렉토리에 service 폴더가 있습니다.
- 예시구조는 다음과 같습니다.
service/
└── admin-members/
├── queries.ts // react-query key와 react-query 함수를 작성하고 관리하는 파일입니다.
└── admin-members-service.ts // supabase의 쿼리문을 모아놓은 파일입니다.
|___ Service.ts // 컴포넌트 실행환경에 따른 supabase client를 class로 만들어놓은 파일입니다.
리액트 쿼리와 관련된 파일만을 예시로 들겠습니다.
사용 예시
- *queries.ts
import AdminMembersService from './admin-members-service'
// querites.ts
const queryKeys = {
all: ['admin/members'] as const,
members: ['admin/members'] as const
// 아래는 key 작성 예시입니다.
// detail: (memberId: number) => [...queryKeys.all, memberId] as const
// 위랑 같음 detail: (memberId: number) => ['admin/members', memberId] as const
}
const queryOptions = {
all: () => ({
queryKey: queryKeys.all,
queryFn: () => {
return AdminMembersService.all()
}
}),
members: () => ({
queryKey: queryKeys.members,
queryFn: () => {
return AdminMembersService.getMembers(pageParam)
}
})
}
export default queryOptions
- 위의 코드를 서버 컴포넌트에서 Hydrate화 시켜 사용해줄 수 있습니다.
import queryOptions from '@/service/admin-members/queries'
import { getDehydratedQuery, Hydrate } from '@/utils/react-query'
import { CardList } from './components'
export default async function InstructorPage() {
const { queryKey, queryFn } = queryOptions.all()
const query = await getDehydratedQuery({ queryKey, queryFn })
return (
<div className="not-prose mt-8 flex flex-col gap-4">
<div className="flex items-center gap-2"></div>
<Hydrate state={{ queries: [query] }}>
{JSON.stringify(query?.state.data)}
<CardList />
</Hydrate>
</div>
)
}
결과
위의 코드에서 Hydrate화 시켜준 데이터를 화면에 렌더링 시켜준 모습입니다.
다음 이미지는받아온 query 데이터를 콘솔로 찍어본 모습은 다음과 같습니다.
`console.log(query)`
여기서 중요한점은, 리액트 쿼리 데브툴스의 모습입니다.
위의 코드를 사용하여 SSR을 수행하고 있는 page.tsx 파일에서,
서버에서 prefeching 한 데이터를 hydration 시켰을 때 아래와 같이 inactive 상태로 보이는 것을 볼 수 있습니다.
이것은 , Client 컴포넌트에서 동일한 쿼리를 요청했을 때 active 상태로 바뀝니다.
상태 값이 바뀌는게 어떤 의미인가?를 생각해봤을때 다음과 같은 중요포인트가 있습니다.
Inactive 상태: 서버에서 데이터를 직렬화하여 클라이언트로 보낼 때는 데이터가 이미 존재하므로 쿼리가 비활성화된(inactive) 상태로 남아 있있습니다.
Active 상태: 클라이언트 컴포넌트에서 동일한 쿼리를 사용하면, 클라이언트 사이드에서 데이터를 다시 가져오거나 확인하려고 하기 때문에 쿼리가 활성화(active) 상태로 변환됩니다.
즉, 데이터를 다시 확인하여 최신 상태로 유지하려는 목적으로 쿼리가 활성화되는 것입니다.
이러한것을 종합해봤을때 클라이언트에서 이미 있는 데이터(하이드레이션된 데이터)를 먼저 보여주고, 그 데이터가 오래됐을(혹은 최신이 아닐) 가능성이 있으면 백그라운드에서 새로 API 요청을 보내 최신 데이터를 가져오는 방식입니다.
이 덕분에 필요한 경우에만 새 데이터를 가져오니까, 네트워크 요청이 줄고 성능도 최적화될 수 있습니다.
클라이언트 컴포넌트에서 사용예시
- 폴더 구조는 똑같습니다.
service/
└── admin-members/
├── queries.ts
└── admin-members-service.ts
|___ Service.ts
- AdminMembers.tsx
'use client'
import { useQuery } from '@tanstack/react-query'
import queryOptions from '@/service/admin-members/queries'
export function CardList() {
const { data: members } = useQuery(queryOptions.all())
return <div>테스트용..</div>
}
참고 자료
- nextjs에서 하이드레이션 사용하기
- https://tanstack.com/query/latest/docs/framework/react/guides/ssr
- 관련 한국어 블로그 글
- 코드 깃허브(아래)
https://github.com/soobing/next-14-react-query
'개인 공부 > nextjs' 카테고리의 다른 글
react-query,ISR (0) | 2024.12.02 |
---|---|
dev 환경에서 페이지 느려짐과 router.push로 페이지 이동은 안좋다. (0) | 2024.11.12 |
캐시 재검증 revalidatePath, revalidateTag (0) | 2024.09.20 |
fetch 데이터 캐시 설정 (0) | 2024.09.20 |
인터셉트 라우트 (0) | 2024.09.17 |