반응형 컨트롤러와 훅 비교
React Query 예제에서는 훅을 사용합니다. Lit Query는 반응형 컨트롤러를 사용합니다.
작업은 비슷합니다. 컴포넌트를 QueryClient에 구독하고, 최신 결과를 읽으며, 캐시가 변경될 때 컴포넌트를 업데이트합니다. Lit 컴포넌트는 React의 렌더링 및 훅 시스템 대신 ReactiveControllerHost 인터페이스를 사용하므로 통합 지점이 다릅니다.
개념 매핑
| React Query | Lit Query |
|---|---|
useQuery(options) | createQueryController(this, options) |
useQueries(options) | createQueriesController(this, options) |
useMutation(options) | createMutationController(this, options) |
useInfiniteQuery(options) | createInfiniteQueryController(this, options) |
useIsFetching(options) | useIsFetching(this, options) |
useIsMutating(options) | useIsMutating(this, options) |
useMutationState(options) | useMutationState(this, options) |
| 훅 결과 객체 | 호출 가능한 결과 접근자 |
| React 컨텍스트 제공자 | QueryClientProvider 사용자 정의 요소 |
| 컴포넌트 렌더링 재실행 | host.requestUpdate() |
호스트에 바인딩된 API
컴포넌트를 쿼리 또는 뮤테이션 상태에 구독시키는 Lit API는 첫 번째 인수로 host를 받습니다:
class TodosView extends LitElement {
private readonly todos = createQueryController(this, {
queryKey: ['todos'],
queryFn: fetchTodos,
})
}
this는 LitElement가 ReactiveControllerHost를 구현하므로 유효합니다. 컨트롤러는 호스트에 연결되고, 호스트가 연결될 때 구독하며, 쿼리 결과가 변경될 때 업데이트를 요청하고, 호스트의 연결이 끊길 때 구독을 해제합니다.
호스트에 바인딩된 API는 createQueryController, createQueriesController, createInfiniteQueryController, createMutationController, useIsFetching, useIsMutating 및 useMutationState입니다.
useQueryClient는 다릅니다. 반응형 컨트롤러가 아니며, host를 받지 않고, 구독하지 않으며, 사용할 수 있는 단일 기본 client가 없으면 동기적으로 오류를 발생시킵니다. 정확히 하나의 QueryClientProvider가 연결되어 있는 동안 실행되는 명령형 코드에만 사용합니다. host에 바인딩된 API 내부에서는 provider context를 우선 사용하거나 명시적인 QueryClient를 전달합니다.
결과 읽기
Lit Query controller 생성자는 current 속성이 있는 호출 가능한 accessor를 반환합니다:
const query = this.todos()
const sameQuery = this.todos.current
렌더링 메서드는 일반적으로 accessor를 호출합니다:
render() {
const query = this.todos()
if (query.isPending) return html`Loading...`
if (query.isError) return html`Error: ${query.error.message}`
return html`<todo-list .items=${query.data}></todo-list>`
}
반응형 옵션
쿼리 옵션이 host 상태에 의존하는 경우 함수를 전달하세요. Lit Query는 host 업데이트 중에 함수 접근자를 다시 읽습니다:
class ProjectView extends LitElement {
static properties = {
projectId: { type: Number },
}
projectId = 1
private readonly project = createQueryController(this, () => ({
queryKey: ['project', this.projectId],
queryFn: () => fetchProject(this.projectId),
}))
}
옵션이 정적이면 객체를 전달합니다. 정적 옵션 객체를 직접 변경하는 경우 refetch처럼 observer가 새 옵션을 인식하게 하는 controller 헬퍼를 호출하거나, 반응형 상태에는 함수 접근자를 사용하는 편이 좋습니다.
Provider 컨텍스트
호스트에 바인딩된 API는 명시적인 QueryClient를 받을 수 있지만, 대부분의 앱은 QueryClientProvider 아래에서 렌더링됩니다. provider는 Lit context를 사용해 하위 controller에 클라이언트를 전달합니다.
customElements.define('query-client-provider', QueryClientProvider)
html`
<query-client-provider .client=${queryClient}>
<todos-view></todos-view>
</query-client-provider>
`
커스텀 엘리먼트 등록은 항상 애플리케이션의 책임입니다.
QueryClientProvider는 useQueryClient 및 resolveQueryClient를 위해 프로세스 로컬 대체 저장소에도 클라이언트를 등록합니다. 이 대체 저장소는 의도적으로 보수적으로 동작합니다:
- 연결된 provider가 없으면
useQueryClient()에서 오류를 발생시킵니다. - 서로 다른 클라이언트가 정확히 하나 연결되어 있으면
useQueryClient()는 해당 클라이언트를 반환합니다. - 동일한 JavaScript 컨텍스트에 서로 다른 클라이언트가 여러 개 연결되어 있으면 대체 동작이 모호해지므로
useQueryClient()및resolveQueryClient()에서 오류가 발생합니다.
여러 루트, 마이크로 프런트엔드, 공유 모듈이 있는 테스트 스위트 및 중첩된 앱은 프로세스 로컬 폴백에 의존하지 않아야 합니다. 호스트에 바인딩된 컨트롤러를 올바른 provider 아래에서 렌더링하거나, 명시적인 QueryClient를 컨트롤러에 전달하거나, 테스트 사이에 provider의 연결을 깔끔하게 해제합니다.