Wichtig
Diese Anleitung konzentriert sich auf externe Zustandsverwaltungsbibliotheken und deren Integration mit TanStack Router für Datenladen, SSR, Hydrierung/Dehydrierung und Streaming. Wenn Sie die Standardanleitung Datenladen noch nicht gelesen haben, tun Sie dies bitte zuerst.
Während der Router ausgereift genug ist, um die meisten Datenbedürfnisse Out-of-the-Box zu speichern und zu verwalten, möchten Sie manchmal einfach etwas Robusteres haben!
Der Router ist dafür konzipiert, ein perfekter Koordinator für externe Datenlade- und Caching-Bibliotheken zu sein. Das bedeutet, dass Sie jede beliebige Datenlade-/Caching-Bibliothek verwenden können, und der Router wird das Laden Ihrer Daten so koordinieren, dass es mit der Navigation Ihrer Benutzer und deren Erwartungen an Aktualität übereinstimmt.
Jede Datenladebibliothek, die asynchrone Promises unterstützt, kann mit TanStack Router verwendet werden. Dazu gehören
Oder sogar...
Buchstäblich jede Bibliothek, die einen Promise zurückgeben und Daten lesen/schreiben kann, kann integriert werden.
Der einfachste Weg, externe Caching-/Datenbibliotheken in den Router zu integrieren, ist die Verwendung von route.loaders, um sicherzustellen, dass die für eine Route benötigten Daten geladen wurden und zur Anzeige bereit sind.
⚠️ ABER WARUM? Es ist sehr wichtig, Ihre kritischen Render-Daten im Loader vorzuladen, aus mehreren Gründen
- Keine "Flash of loading" Zustände
- Kein Waterfall-Datenladen, verursacht durch komponentenbasierte Datenladung
- Besser für SEO. Wenn Ihre Daten zum Zeitpunkt des Renderings verfügbar sind, werden sie von Suchmaschinen indiziert.
Hier ist eine naive Illustration (tun Sie dies nicht) der Verwendung der loader Option einer Route, um den Cache für einige Daten zu füllen.
// src/routes/posts.tsx
let postsCache = []
export const Route = createFileRoute('/posts')({
loader: async () => {
postsCache = await fetchPosts()
},
component: () => {
return (
<div>
{postsCache.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
// src/routes/posts.tsx
let postsCache = []
export const Route = createFileRoute('/posts')({
loader: async () => {
postsCache = await fetchPosts()
},
component: () => {
return (
<div>
{postsCache.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
Dieses Beispiel ist offensichtlich fehlerhaft, veranschaulicht aber den Punkt, dass Sie die loader Option einer Route verwenden können, um Ihren Cache mit Daten zu füllen. Schauen wir uns ein realistischeres Beispiel mit TanStack Query an.
Schauen wir uns ein realistischeres Beispiel mit TanStack Query an.
// src/routes/posts.tsx
const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})
export const Route = createFileRoute('/posts')({
// Use the `loader` option to ensure that the data is loaded
loader: () => queryClient.ensureQueryData(postsQueryOptions),
component: () => {
// Read the data from the cache and subscribe to updates
const {
data: { posts },
} = useSuspenseQuery(postsQueryOptions)
return (
<div>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
// src/routes/posts.tsx
const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})
export const Route = createFileRoute('/posts')({
// Use the `loader` option to ensure that the data is loaded
loader: () => queryClient.ensureQueryData(postsQueryOptions),
component: () => {
// Read the data from the cache and subscribe to updates
const {
data: { posts },
} = useSuspenseQuery(postsQueryOptions)
return (
<div>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
Wenn ein Fehler auftritt, während suspense mit TanStack Query verwendet wird, müssen Sie die Queries darauf hinweisen, dass Sie versuchen möchten, sie beim erneuten Rendern erneut zu laden. Dies kann durch die Verwendung der reset Funktion geschehen, die vom useQueryErrorResetBoundary Hook bereitgestellt wird. Sie können diese Funktion in einem Effekt aufrufen, sobald die Fehlerkomponente gemountet ist. Dies stellt sicher, dass die Query zurückgesetzt wird und versucht, die Daten erneut abzurufen, wenn die Routenkomponente erneut gerendert wird. Dies deckt auch Fälle ab, in denen Benutzer von der Route weg navigieren, anstatt auf den retry Button zu klicken.
export const Route = createFileRoute('/')({
loader: () => queryClient.ensureQueryData(postsQueryOptions),
errorComponent: ({ error, reset }) => {
const router = useRouter()
const queryErrorResetBoundary = useQueryErrorResetBoundary()
useEffect(() => {
// Reset the query error boundary
queryErrorResetBoundary.reset()
}, [queryErrorResetBoundary])
return (
<div>
{error.message}
<button
onClick={() => {
// Invalidate the route to reload the loader, and reset any router error boundaries
router.invalidate()
}}
>
retry
</button>
</div>
)
},
})
export const Route = createFileRoute('/')({
loader: () => queryClient.ensureQueryData(postsQueryOptions),
errorComponent: ({ error, reset }) => {
const router = useRouter()
const queryErrorResetBoundary = useQueryErrorResetBoundary()
useEffect(() => {
// Reset the query error boundary
queryErrorResetBoundary.reset()
}, [queryErrorResetBoundary])
return (
<div>
{error.message}
<button
onClick={() => {
// Invalidate the route to reload the loader, and reset any router error boundaries
router.invalidate()
}}
>
retry
</button>
</div>
)
},
})
Werkzeuge, die dazu in der Lage sind, können mit den praktischen Dehydrierungs-/Hydrierungs-APIs von TanStack Router integriert werden, um dehydrierte Daten zwischen Server und Client zu übermitteln und sie bei Bedarf neu zu hydrieren. Lassen Sie uns durchgehen, wie dies sowohl mit kritischen 3rd-Party-Daten als auch mit verzögerten 3rd-Party-Daten geschieht.
Für kritische Daten, die für das erste Rendering/Painting benötigt werden, unterstützt TanStack Router die Optionen dehydrate und hydrate bei der Konfiguration des Router. Diese Callbacks sind Funktionen, die automatisch auf dem Server und Client aufgerufen werden, wenn der Router normal dehydriert und hydriert, und es Ihnen ermöglichen, die dehydrierten Daten mit Ihren eigenen Daten zu erweitern.
Die dehydrate Funktion kann beliebige serialisierbare JSON-Daten zurückgeben, die zusammengeführt und in die an den Client gesendete dehydrierte Nutzlast eingefügt werden.
Lassen Sie uns zum Beispiel einen TanStack Query QueryClient dehydrieren und hydrieren, damit die auf dem Server abgerufenen Daten für die Hydrierung auf dem Client verfügbar sind.
// src/router.tsx
export function createRouter() {
// Make sure you create your loader client or similar data
// stores inside of your `createRouter` function. This ensures
// that your data stores are unique to each request and
// always present on both server and client.
const queryClient = new QueryClient()
return createRouter({
routeTree,
// Optionally provide your loaderClient to the router context for
// convenience (you can provide anything you want to the router
// context!)
context: {
queryClient,
},
// On the server, dehydrate the loader client so the router
// can serialize it and send it to the client for us
dehydrate: () => {
return {
queryClientState: dehydrate(queryClient),
}
},
// On the client, hydrate the loader client with the data
// we dehydrated on the server
hydrate: (dehydrated) => {
hydrate(queryClient, dehydrated.queryClientState)
},
// Optionally, we can use `Wrap` to wrap our router in the loader client provider
Wrap: ({ children }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
},
})
}
// src/router.tsx
export function createRouter() {
// Make sure you create your loader client or similar data
// stores inside of your `createRouter` function. This ensures
// that your data stores are unique to each request and
// always present on both server and client.
const queryClient = new QueryClient()
return createRouter({
routeTree,
// Optionally provide your loaderClient to the router context for
// convenience (you can provide anything you want to the router
// context!)
context: {
queryClient,
},
// On the server, dehydrate the loader client so the router
// can serialize it and send it to the client for us
dehydrate: () => {
return {
queryClientState: dehydrate(queryClient),
}
},
// On the client, hydrate the loader client with the data
// we dehydrated on the server
hydrate: (dehydrated) => {
hydrate(queryClient, dehydrated.queryClientState)
},
// Optionally, we can use `Wrap` to wrap our router in the loader client provider
Wrap: ({ children }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
},
})
}
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.