响应式控制器 vs Hook
React Query 的示例使用 Hook。Lit Query 则使用响应式控制器。
二者职责相似:让组件订阅一个 QueryClient、读取最新结果,并在缓存变更时更新组件。但集成方式不同,因为 Lit 组件使用 ReactiveControllerHost 接口,而非 React 的渲染与 Hook 系统。
概念映射
| 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) |
| Hook 返回的对象 | 可调用的结果访问器 |
| React 上下文提供器 | QueryClientProvider 自定义元素 |
| 组件渲染重新执行 | host.requestUpdate() |
与宿主绑定的 API
Lit 中用于让组件订阅查询或修改状态的 API,其第一个参数接收一个 host:
1class TodosView extends LitElement {2 private readonly todos = createQueryController(this, {3 queryKey: ['todos'],4 queryFn: fetchTodos,5 })6}this 是合法的,因为 LitElement 实现了 ReactiveControllerHost。该控制器会挂载到宿主上,在宿主连接时订阅,在查询结果变更时请求更新,并在宿主断开连接时取消订阅。
与宿主绑定的 API 包括:createQueryController、createQueriesController、createInfiniteQueryController、createMutationController、useIsFetching、useIsMutating 和 useMutationState。
useQueryClient 则不同:它不是响应式控制器,不接受宿主参数,不进行订阅,且若未配置唯一默认客户端,则同步抛出错误。仅在恰好有一个 QueryClientProvider 连接时,才应在命令式代码中使用它。在与宿主绑定的 API 内部,请优先使用提供器上下文,或显式传入一个 QueryClient。
读取结果
Lit Query 控制器创建器返回一个可调用的访问器,该访问器具有一个 current 属性:
1const query = this.todos()2const sameQuery = this.todos.current渲染方法通常直接调用该访问器:
1render() {2 const query = this.todos()3 4 if (query.isPending) return html`Loading...`5 if (query.isError) return html`Error: ${query.error.message}`6 7 return html`<todo-list .items=${query.data}></todo-list>`8}响应式选项
若查询选项依赖于宿主状态,请传入一个函数。Lit Query 会在宿主更新期间重新读取函数类型的访问器:
1class ProjectView extends LitElement {2 static properties = {3 projectId: { type: Number },4 }5 6 projectId = 17 8 private readonly project = createQueryController(this, () => ({9 queryKey: ['project', this.projectId],10 queryFn: () => fetchProject(this.projectId),11 }))12}若选项是静态的,请传入一个对象。若你自行修改了静态选项对象,请调用控制器辅助方法(例如 refetch),使观察者感知到新选项;或者,对于响应式状态,更推荐使用函数访问器。
提供器上下文
与宿主绑定的 API 可接收显式的 QueryClient,但大多数应用会在 QueryClientProvider 下渲染。该提供器利用 Lit 上下文机制,将客户端传递给后代控制器。
1customElements.define('query-client-provider', QueryClientProvider)1html`2 <query-client-provider .client=${queryClient}>3 <todos-view></todos-view>4 </query-client-provider>5`自定义元素注册始终由应用自身负责。
QueryClientProvider 还会在进程级回退存储中注册其客户端,以供 useQueryClient 和 resolveQueryClient 使用。该回退机制有意设计为保守策略:
- 若无提供器连接,
useQueryClient()将抛出错误。 - 若恰好有一个独立客户端连接,
useQueryClient()返回该客户端。 - 若在同一 JavaScript 上下文中连接了多个独立客户端,
useQueryClient()和resolveQueryClient()将抛出错误,因为此时回退行为存在歧义。
多根节点、微前端、模块共享的测试套件以及嵌套应用,均应避免依赖进程级回退机制。请确保将与宿主绑定的控制器渲染在正确的提供器之下,向控制器显式传入 QueryClient,或在测试间干净地断开提供器连接。