skipTokenTanStack Query ist jetzt in TypeScript geschrieben, um sicherzustellen, dass die Bibliothek und Ihre Projekte typsicher sind!
Zu beachtende Punkte
Typen in TanStack Query fließen im Allgemeinen sehr gut, so dass Sie keine Typannotationen selbst angeben müssen
@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(),
}))
}
Dies funktioniert am besten, wenn Ihre queryFn einen gut definierten Rückgabetyp hat. Beachten Sie, dass die meisten Datenabrufbibliotheken standardmäßig any zurückgeben. Stellen Sie daher sicher, dass Sie diese in eine ordnungsgemäß typisierte Funktion extrahieren.
In diesem Beispiel übergeben wir Group[] an den Typparameter der get-Methode von HttpClient.
@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 verwendet einen diskriminierten Union-Typ für das Query-Ergebnis, diskriminiert durch das status-Feld und die abgeleiteten booleschen Statusflags. Dies ermöglicht es Ihnen, z. B. den isSuccess()-Status zu prüfen, um data als definiert zu behandeln
@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 unterstützt derzeit keine diskriminierten Unions bei Objektmethoden. Die Verengung auf Signalfelder in Objekten wie Query-Ergebnissen funktioniert nur bei Signalen, die einen booleschen Wert zurückgeben. Bevorzugen Sie die Verwendung von isSuccess() und ähnlichen booleschen Statusignalen gegenüber status() === 'success'.
Der Typ für Fehler ist standardmäßig Error, da dies das ist, was die meisten Benutzer erwarten.
@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
}))
}
Wenn Sie einen benutzerdefinierten Fehler oder etwas werfen möchten, das kein Error ist, können Sie den Typ des Fehlerfelds angeben
@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,
}))
}
Dies hat jedoch den Nachteil, dass die Typinferenz für alle anderen Generika von injectQuery nicht mehr funktioniert. Es gilt im Allgemeinen nicht als gute Praxis, etwas zu werfen, das kein Error ist. Wenn Sie also eine Unterklasse wie AxiosError haben, können Sie die *Typ-Verengung* verwenden, um das Fehlerfeld spezifischer zu machen
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 ermöglicht es, einen globalen Fehlertyp für alles festzulegen, ohne Generika auf der Aufruferseite angeben zu müssen, indem das Register-Interface ergänzt wird. Dies stellt sicher, dass die Inferenz weiterhin funktioniert, aber das Fehlerfeld den angegebenen Typ hat. Wenn Sie erzwingen möchten, dass Aufrufer explizite Typ-Verengung durchführen müssen, setzen Sie defaultError auf unknown
import '@tanstack/angular-query-experimental'
declare module '@tanstack/angular-query-experimental' {
interface Register {
// Use unknown so call sites must narrow explicitly.
defaultError: unknown
}
}
const query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
computed(() => {
const error = query.error()
// ^? error: unknown | null
})
import '@tanstack/angular-query-experimental'
declare module '@tanstack/angular-query-experimental' {
interface Register {
// Use unknown so call sites must narrow explicitly.
defaultError: unknown
}
}
const query = injectQuery(() => ({
queryKey: ['groups'],
queryFn: fetchGroups,
}))
computed(() => {
const error = query.error()
// ^? error: unknown | null
})
Ähnlich wie bei der Registrierung eines globalen Fehlertyps können Sie auch einen globalen Meta-Typ registrieren. Dies stellt sicher, dass das optionale meta-Feld auf Queries und Mutationen konsistent bleibt und typsicher ist. Beachten Sie, dass der registrierte Typ von Record<string, unknown> erben muss, damit meta ein Objekt bleibt.
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
}
}
Ebenso wie bei der Registrierung eines globalen Fehlertyps können Sie auch einen globalen QueryKey- und MutationKey-Typ registrieren. Dies ermöglicht es Ihnen, Ihren Schlüsseln mehr Struktur zu geben, die der Hierarchie Ihrer Anwendung entspricht, und sie über die gesamte Oberfläche der Bibliothek typsicher zu machen. Beachten Sie, dass der registrierte Typ von Array erben muss, damit Ihre Schlüssel ein Array bleiben.
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
}
}
Wenn Sie Query-Optionen in injectQuery inline verwenden, erhalten Sie automatische Typinferenz. Möglicherweise möchten Sie die Query-Optionen jedoch in eine separate Funktion extrahieren, um sie zwischen injectQuery und z. B. prefetchQuery zu teilen oder sie in einem Service zu verwalten. In diesem Fall würden Sie die Typinferenz verlieren. Um sie wiederzugewinnen, können Sie die Hilfsfunktion queryOptions verwenden
@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))
}
}
Darüber hinaus kennt der von queryOptions zurückgegebene queryKey die zugehörige queryFn, und wir können diese Typinformationen nutzen, um Funktionen wie queryClient.getQueryData ebenfalls typsicher zu machen
data = this.queryClient.getQueryData(groupOptions().queryKey)
// ^? data: Post | undefined
data = this.queryClient.getQueryData(groupOptions().queryKey)
// ^? data: Post | undefined
Ohne queryOptions wäre der Datentyp unbekannt, es sei denn, wir würden einen Typparameter übergeben
data = queryClient.getQueryData<Post>(['post', 1])
data = queryClient.getQueryData<Post>(['post', 1])
Ähnlich wie bei queryOptions können Sie mutationOptions verwenden, um Mutationsoptionen in eine separate Funktion zu extrahieren
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)
},
})
}
}
Wenn Sie TypeScript verwenden, können Sie skipToken verwenden, um eine Query zu deaktivieren. Dies ist nützlich, wenn Sie eine Query basierend auf einer Bedingung deaktivieren möchten, aber die Query trotzdem typsicher halten möchten. Lesen Sie mehr dazu im Leitfaden Disabling Queries.