본문으로 건너뛰기

반응형 컨트롤러와 훅 비교

React Query 예제에서는 훅을 사용합니다. Lit Query는 반응형 컨트롤러를 사용합니다.

작업은 비슷합니다. 컴포넌트를 QueryClient에 구독하고, 최신 결과를 읽으며, 캐시가 변경될 때 컴포넌트를 업데이트합니다. Lit 컴포넌트는 React의 렌더링 및 훅 시스템 대신 ReactiveControllerHost 인터페이스를 사용하므로 통합 지점이 다릅니다.

개념 매핑​

React QueryLit 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의 연결을 깔끔하게 해제합니다.