TanStack Query 现在使用 TypeScript 编写,以确保库和您的项目是类型安全的!
需要记住的事情
TanStack Query 中的类型通常会很好地流动,因此您不必为自己提供类型注释
@Component({
// ...
template: `@let data = query.data();`,
// ^? data: number | undefined
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
}))
}
@Component({
// ...
template: `@let data = query.data();`,
// ^? data: number | undefined
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
}))
}
@Component({
// ...
template: `@let data = query.data();`,
// ^? data: string | undefined
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
select: (data) => data.toString(),
}))
}
@Component({
// ...
template: `@let data = query.data();`,
// ^? data: string | undefined
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
select: (data) => data.toString(),
}))
}
如果您的 queryFn 具有明确定义的返回类型,则效果最佳。请记住,大多数数据获取库默认返回 any,因此请务必将其提取到正确类型的函数中。
在此示例中,我们将 Group[] 传递给 HttpClient 的 get 方法的类型参数。
@Component({
template: `@let data = query.data();`,
// ^? data: Group[] | undefined
})
class MyComponent {
http = inject(HttpClient)
query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: () => lastValueFrom(this.http.get<Group[]>('/groups')),
}))
}
@Component({
template: `@let data = query.data();`,
// ^? data: Group[] | undefined
})
class MyComponent {
http = inject(HttpClient)
query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: () => lastValueFrom(this.http.get<Group[]>('/groups')),
}))
}
TanStack Query 对 query 结果使用 可辨识联合类型,由 status 字段和派生的状态布尔标志区分。这将允许您检查例如 isSuccess() 状态以使 data 被定义
@Component({
// ...
template: `
@if (query.isSuccess()) {
@let data = query.data();
// ^? data: number
}
`,
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
}))
}
@Component({
// ...
template: `
@if (query.isSuccess()) {
@let data = query.data();
// ^? data: number
}
`,
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
}))
}
TypeScript 目前不支持对象方法上的可辨识联合类型。对对象(例如 query 结果)上的信号字段进行缩小仅适用于返回布尔值的信号。 优先使用 isSuccess() 和类似的布尔状态信号,而不是 status() === 'success'。
error 的类型默认为 Error,因为这是大多数用户期望的。
@Component({
// ...
template: `@let error = query.error();`,
// ^? error: Error | null
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups
}))
}
@Component({
// ...
template: `@let error = query.error();`,
// ^? error: Error | null
})
class MyComponent {
query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups
}))
}
如果您想抛出自定义错误,或者根本不是 Error 的东西,您可以指定 error 字段的类型
@Component({
// ...
template: `@let error = query.error();`,
// ^? error: string | null
})
class MyComponent {
query = injectQuery<Group[], string>(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
}
@Component({
// ...
template: `@let error = query.error();`,
// ^? error: string | null
})
class MyComponent {
query = injectQuery<Group[], string>(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
}
但是,这样做有一个缺点,即 injectQuery 的所有其他泛型的类型推断将不再起作用。通常不建议抛出不是 Error 的东西,因此如果您有像 AxiosError 这样的子类,则可以使用类型缩小来使 error 字段更具体
import axios from 'axios'
query = injectQuery(() => ({ queryKey: ['groups'], queryFn: fetchGroups }))
computed(() => {
const error = query.error()
// ^? error: Error | null
if (axios.isAxiosError(error)) {
error
// ^? const error: AxiosError
}
})
import axios from 'axios'
query = injectQuery(() => ({ queryKey: ['groups'], queryFn: fetchGroups }))
computed(() => {
const error = query.error()
// ^? error: Error | null
if (axios.isAxiosError(error)) {
error
// ^? const error: AxiosError
}
})
TanStack Query v5 允许为所有内容设置全局 Error 类型,而无需在调用端指定泛型,方法是修改 Register 接口。这将确保推断仍然有效,但 error 字段将是指定的类型
import '@tanstack/angular-query-experimental'
declare module '@tanstack/angular-query-experimental' {
interface Register {
defaultError: AxiosError
}
}
const query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
computed(() => {
const error = query.error()
// ^? error: AxiosError | null
})
import '@tanstack/angular-query-experimental'
declare module '@tanstack/angular-query-experimental' {
interface Register {
defaultError: AxiosError
}
}
const query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
computed(() => {
const error = query.error()
// ^? error: AxiosError | null
})
与注册全局错误类型类似,您还可以注册全局 Meta 类型。这确保了 queries 和 mutations 上的可选 meta 字段保持一致且类型安全。请注意,注册的类型必须扩展 Record<string, unknown>,以便 meta 仍然是一个对象。
import '@tanstack/angular-query-experimental'
interface MyMeta extends Record<string, unknown> {
// Your meta type definition.
}
declare module '@tanstack/angular-query-experimental' {
interface Register {
queryMeta: MyMeta
mutationMeta: MyMeta
}
}
import '@tanstack/angular-query-experimental'
interface MyMeta extends Record<string, unknown> {
// Your meta type definition.
}
declare module '@tanstack/angular-query-experimental' {
interface Register {
queryMeta: MyMeta
mutationMeta: MyMeta
}
}
同样与注册全局错误类型类似,您还可以注册全局 QueryKey 和 MutationKey 类型。这允许您为您的 keys 提供更多结构,使其与应用程序的层次结构匹配,并在库的所有表面区域中对其进行类型化。请注意,注册的类型必须扩展 Array 类型,以便您的 keys 仍然是一个数组。
import '@tanstack/angular-query-experimental'
type QueryKey = ['dashboard' | 'marketing', ...ReadonlyArray<unknown>]
declare module '@tanstack/angular-query-experimental' {
interface Register {
queryKey: QueryKey
mutationKey: QueryKey
}
}
import '@tanstack/angular-query-experimental'
type QueryKey = ['dashboard' | 'marketing', ...ReadonlyArray<unknown>]
declare module '@tanstack/angular-query-experimental' {
interface Register {
queryKey: QueryKey
mutationKey: QueryKey
}
}
如果您将 query options 内联到 injectQuery 中,您将获得自动类型推断。但是,您可能希望将 query options 提取到单独的函数中,以便在 injectQuery 和例如 prefetchQuery 之间共享它们,或者在服务中管理它们。 在这种情况下,您将失去类型推断。要恢复它,您可以使用 queryOptions 助手
@Injectable({
providedIn: 'root',
})
export class QueriesService {
private http = inject(HttpClient)
post(postId: number) {
return queryOptions({
queryKey: ['post', postId],
queryFn: () => {
return lastValueFrom(
this.http.get<Post>(
`https://jsonplaceholder.typicode.com/posts/${postId}`,
),
)
},
})
}
}
@Component({
// ...
})
export class Component {
queryClient = inject(QueryClient)
postId = signal(1)
queries = inject(QueriesService)
optionsSignal = computed(() => this.queries.post(this.postId()))
postQuery = injectQuery(() => this.queries.post(1))
postQuery = injectQuery(() => this.queries.post(this.postId()))
// You can also pass a signal which returns query options
postQuery = injectQuery(this.optionsSignal)
someMethod() {
this.queryClient.prefetchQuery(this.queries.post(23))
}
}
@Injectable({
providedIn: 'root',
})
export class QueriesService {
private http = inject(HttpClient)
post(postId: number) {
return queryOptions({
queryKey: ['post', postId],
queryFn: () => {
return lastValueFrom(
this.http.get<Post>(
`https://jsonplaceholder.typicode.com/posts/${postId}`,
),
)
},
})
}
}
@Component({
// ...
})
export class Component {
queryClient = inject(QueryClient)
postId = signal(1)
queries = inject(QueriesService)
optionsSignal = computed(() => this.queries.post(this.postId()))
postQuery = injectQuery(() => this.queries.post(1))
postQuery = injectQuery(() => this.queries.post(this.postId()))
// You can also pass a signal which returns query options
postQuery = injectQuery(this.optionsSignal)
someMethod() {
this.queryClient.prefetchQuery(this.queries.post(23))
}
}
此外,从 queryOptions 返回的 queryKey 知道与其关联的 queryFn,我们可以利用该类型信息使 queryClient.getQueryData 等函数也意识到这些类型
data = this.queryClient.getQueryData(groupOptions().queryKey)
// ^? data: Post | undefined
data = this.queryClient.getQueryData(groupOptions().queryKey)
// ^? data: Post | undefined
如果没有 queryOptions,数据的类型将是 unknown,除非我们传递类型参数
data = queryClient.getQueryData<Post>(['post', 1])
data = queryClient.getQueryData<Post>(['post', 1])
与 queryOptions 类似,您可以使用 mutationOptions 将 mutation options 提取到单独的函数中
export class QueriesService {
private http = inject(HttpClient)
updatePost(id: number) {
return mutationOptions({
mutationFn: (post: Post) => Promise.resolve(post),
mutationKey: ['updatePost', id],
onSuccess: (newPost) => {
// ^? newPost: Post
this.queryClient.setQueryData(['posts', id], newPost)
},
})
}
}
export class QueriesService {
private http = inject(HttpClient)
updatePost(id: number) {
return mutationOptions({
mutationFn: (post: Post) => Promise.resolve(post),
mutationKey: ['updatePost', id],
onSuccess: (newPost) => {
// ^? newPost: Post
this.queryClient.setQueryData(['posts', id], newPost)
},
})
}
}
如果您正在使用 TypeScript,您可以使用 skipToken 来禁用 query。当您想根据条件禁用 query,但仍然希望保持 query 的类型安全时,这非常有用。 在禁用 Queries 指南中阅读更多相关信息。