Wenn Leute TanStack Router zum ersten Mal verwenden, haben sie oft viele Fragen, die sich um die folgenden Themen drehen
Warum muss ich Dinge auf diese Weise tun?
Warum wird es so und nicht anders gemacht?
Ich bin es gewohnt, es auf diese Weise zu tun, warum sollte ich das ändern?
Und das sind alles berechtigte Fragen. Meistens sind die Leute es gewohnt, Routing-Bibliotheken zu verwenden, die sich sehr ähnlich sind. Sie alle haben eine ähnliche API, ähnliche Konzepte und ähnliche Vorgehensweisen.
Aber TanStack Router ist anders. Es ist keine gewöhnliche Routing-Bibliothek. Es ist keine gewöhnliche State-Management-Bibliothek. Es ist nichts Gewöhnliches.
Es ist wichtig zu bedenken, dass die Ursprünge von TanStack Router aus dem Bedarf von Nozzle.io nach einer Client-Side-Routing-Lösung stammen, die eine erstklassige Erfahrung mit URL-Suchparametern bot, ohne Kompromisse bei der Typsicherheit einzugehen, die für die Stromversorgung seiner komplexen Dashboards erforderlich war.
Und so wurde von Anfang an jeder Aspekt des Designs von TanStack Router sorgfältig durchdacht, um sicherzustellen, dass seine Typsicherheit und Entwicklererfahrung unübertroffen waren.
TypeScript! TypeScript! TypeScript!
Jeder Aspekt von TanStack Router ist darauf ausgelegt, so typsicher wie möglich zu sein, und dies wird erreicht, indem das Typsystem von TypeScript in vollem Umfang genutzt wird. Dies beinhaltet die Verwendung einiger sehr fortgeschrittener und komplexer Typen, Typinferenz und anderer Funktionen, um sicherzustellen, dass die Entwicklererfahrung so reibungslos wie möglich ist.
Aber um dies zu erreichen, mussten wir einige Entscheidungen treffen, die von den Normen in der Routing-Welt abweichen.
TLDR; Alle Designentscheidungen bei der Entwicklererfahrung mit TanStack Router werden getroffen, damit Sie eine erstklassige Typsicherheit erleben können, ohne Kompromisse bei der Kontrolle, Flexibilität und Wartbarkeit Ihrer Routenkonfigurationen einzugehen.
Wenn Sie die Inferenzfunktionen von TypeScript in vollem Umfang nutzen möchten, werden Sie schnell feststellen, dass Generics Ihr bester Freund sind. Daher verwendet TanStack Router überall Generics, um sicherzustellen, dass die Typen Ihrer Routen so weit wie möglich abgeleitet werden.
Das bedeutet, dass Sie Ihre Routen so definieren müssen, dass TypeScript die Typen Ihrer Routen so weit wie möglich ableiten kann.
Kann ich JSX zur Definition meiner Routen verwenden?
Die Verwendung von JSX zur Definition Ihrer Routen ist ausgeschlossen, da TypeScript die Routenkonfigurationstypen Ihres Routers nicht ableiten kann.
// ⛔️ This is not possible
function App() {
return (
<Router>
<Route path="/posts" component={PostsPage} />
<Route path="/posts/$postId" component={PostIdPage} />
{/* ... */}
</Router>
// ^? TypeScript cannot infer the routes in this configuration
)
}
// ⛔️ This is not possible
function App() {
return (
<Router>
<Route path="/posts" component={PostsPage} />
<Route path="/posts/$postId" component={PostIdPage} />
{/* ... */}
</Router>
// ^? TypeScript cannot infer the routes in this configuration
)
}
Und da dies bedeuten würde, dass Sie die to-Prop des <Link>-Components manuell typisieren müssten und keine Fehler bis zur Laufzeit erkennen würden, ist dies keine praktikable Option.
Vielleicht könnte ich meine Routen als Baum verschachtelter Objekte definieren?
// ⛔️ This file will just keep growing and growing...
const router = createRouter({
routes: {
posts: {
component: PostsPage, // /posts
children: {
$postId: {
component: PostIdPage, // /posts/$postId
},
},
},
// ...
},
})
// ⛔️ This file will just keep growing and growing...
const router = createRouter({
routes: {
posts: {
component: PostsPage, // /posts
children: {
$postId: {
component: PostIdPage, // /posts/$postId
},
},
},
// ...
},
})
Auf den ersten Blick scheint das eine gute Idee zu sein. Es ist einfach, die gesamte Routenhierarchie auf einmal zu visualisieren. Aber dieser Ansatz hat ein paar große Nachteile, die ihn für große Anwendungen nicht ideal machen.
Dies wird nur schlimmer, wenn Sie weitere Funktionen des Routers nutzen, wie z. B. verschachtelten Kontext, Loader, Validierung von Suchparametern usw.
Was ist also der beste Weg, meine Routen zu definieren?
Was wir als den besten Weg zur Definition Ihrer Routen herausgefunden haben, ist die Abstraktion der Definition der Routenkonfiguration außerhalb des Routenbaums. Dann werden Ihre Routenkonfigurationen zu einem einzigen, zusammenhängenden Routenbaum zusammengefügt, der dann an die createRouter-Funktion übergeben wird.
Sie können mehr über Code-basiertes Routing lesen, um zu sehen, wie Sie Ihre Routen auf diese Weise definieren.
Tipp
Finden Sie Code-basiertes Routing etwas zu umständlich? Sehen Sie, warum File-Based Routing die bevorzugte Methode zur Definition Ihrer Routen ist.
Warum muss ich den Router deklarieren?
Diese Deklarationssachen sind zu kompliziert für mich...
Sobald Sie Ihre Routen zu einem Baum zusammengestellt und ihn an Ihre Router-Instanz übergeben haben (mit createRouter) und alle Generics korrekt funktionieren, müssen Sie diese Informationen irgendwie an den Rest Ihrer Anwendung weitergeben.
Wir hatten zwei Ansätze dafür in Betracht gezogen
import { router } from '@/src/app'
export const PostsIdLink = () => {
return (
<Link<typeof router> to="/posts/$postId" params={{ postId: '123' }}>
Go to post 123
</Link>
)
}
import { router } from '@/src/app'
export const PostsIdLink = () => {
return (
<Link<typeof router> to="/posts/$postId" params={{ postId: '123' }}>
Go to post 123
</Link>
)
}
Ein Nachteil dieses Ansatzes ist, dass Sie die gesamte Router-Instanz in jede Datei importieren müssten, in der Sie sie verwenden möchten. Dies kann zu erhöhten Bundle-Größen führen und umständlich zu verwalten sein, und wird nur schlimmer, wenn Ihre Anwendung wächst und Sie mehr Funktionen des Routers nutzen.
Dies werden Sie einmal in Ihrer Anwendung tun.
// src/app.tsx
declare module '@tanstack/solid-router' {
interface Register {
router: typeof router
}
}
// src/app.tsx
declare module '@tanstack/solid-router' {
interface Register {
router: typeof router
}
}
Und dann können Sie von der Autovervollständigung überall in Ihrer App profitieren, ohne sie importieren zu müssen.
export const PostsIdLink = () => {
return (
<Link
to="/posts/$postId"
// ^? TypeScript will auto-complete this for you
params={{ postId: '123' }} // and this too!
>
Go to post 123
</Link>
)
}
export const PostsIdLink = () => {
return (
<Link
to="/posts/$postId"
// ^? TypeScript will auto-complete this for you
params={{ postId: '123' }} // and this too!
>
Go to post 123
</Link>
)
}
Wir haben uns für die Moduldeklaration entschieden, da wir dies als den skalierbarsten und wartbarsten Ansatz mit dem geringsten Overhead und der geringsten Boilerplate empfanden.
Warum drängen die Docs auf File-Based Routing?
Ich bin es gewohnt, meine Routen in einer einzigen Datei zu definieren, warum sollte ich das ändern?
Etwas, das Ihnen (ziemlich bald) in der Dokumentation von TanStack Router auffallen wird, ist, dass wir uns für File-Based Routing als bevorzugte Methode zur Definition Ihrer Routen einsetzen. Dies liegt daran, dass wir festgestellt haben, dass File-Based Routing der skalierbarste und wartbarste Weg ist, Ihre Routen zu definieren.
Tipp
Bevor Sie fortfahren, ist es wichtig, dass Sie ein gutes Verständnis von Code-basiertem Routing und File-Based Routing haben.
Wie eingangs erwähnt, wurde TanStack Router für komplexe Anwendungen entwickelt, die ein hohes Maß an Typsicherheit und Wartbarkeit erfordern. Und um dies zu erreichen, wurde die Konfiguration des Routers auf präzise Weise vorgenommen, die es TypeScript ermöglicht, die Typen Ihrer Routen so weit wie möglich abzuleiten.
Ein wichtiger Unterschied beim Einrichten einer einfachen Anwendung mit TanStack Router ist, dass Ihre Routenkonfigurationen eine Funktion erfordern, die an getParentRoute übergeben wird und die die Elternroute der aktuellen Route zurückgibt.
import { createRoute } from '@tanstack/solid-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
import { createRoute } from '@tanstack/solid-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
Zu diesem Zeitpunkt geschieht dies, damit die Definition von postsIndexRoute seinen Platz im Routenbaum kennt und damit die Typen des context, der path params und der search params, die von der Elternroute zurückgegeben werden, korrekt abgeleitet werden können. Eine falsche Definition der getParentRoute-Funktion führt dazu, dass die Eigenschaften der Elternroute von der Kindroute nicht korrekt abgeleitet werden.
Daher ist dies ein kritischer Teil der Routenkonfiguration und ein Fehlerpunkt, wenn er nicht korrekt ausgeführt wird.
Aber dies ist nur ein Teil der Einrichtung einer einfachen Anwendung. TanStack Router erfordert, dass alle Routen (einschließlich der Root-Route) zu einem Routenbaum zusammengestellt werden, damit dieser an die createRouter-Funktion übergeben werden kann, bevor die Router-Instanz auf dem Modul zur Typinferenz deklariert wird. Dies ist ein weiterer kritischer Teil der Routenkonfiguration und ein Fehlerpunkt, wenn er nicht korrekt ausgeführt wird.
🤯 Wenn dieser Routenbaum für eine Anwendung mit ca. 40-50 Routen in einer eigenen Datei wäre, könnte er leicht über 700 Zeilen lang werden.
const routeTree = rootRoute.addChildren([
postsRoute.addChildren([postsIndexRoute, postsIdRoute]),
])
const routeTree = rootRoute.addChildren([
postsRoute.addChildren([postsIndexRoute, postsIdRoute]),
])
Diese Komplexität nimmt nur zu, wenn Sie weitere Funktionen des Routers nutzen, wie z. B. verschachtelten Kontext, Loader, Validierung von Suchparametern usw. Daher wird es nicht mehr praktikabel, Ihre Routen in einer einzigen Datei zu definieren. Und so bauen Benutzer ihre eigenen, halb konsistenten Methoden auf, um ihre Routen über mehrere Dateien hinweg zu definieren. Dies kann zu Inkonsistenzen und Fehlern in der Routenkonfiguration führen.
Schließlich kommt das Problem des Code-Splittings. Wenn Ihre Anwendung wächst, möchten Sie Ihre Komponenten aufteilen, um die anfängliche Bundle-Größe Ihrer Anwendung zu reduzieren. Dies kann ein ziemlicher Aufwand sein, wenn Sie Ihre Routen in einer einzigen Datei oder sogar über mehrere Dateien hinweg definieren.
import { createRoute, lazyRouteComponent } from '@tanstack/solid-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
component: lazyRouteComponent(() => import('../page-components/posts/index')),
})
import { createRoute, lazyRouteComponent } from '@tanstack/solid-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
component: lazyRouteComponent(() => import('../page-components/posts/index')),
})
All diese Boilerplate, egal wie wichtig sie für die Bereitstellung einer erstklassigen Typinferenzerfahrung ist, kann überwältigend sein und zu Inkonsistenzen und Fehlern in der Routenkonfiguration führen.
... und dieses Beispiel für eine Konfiguration dient nur dem Rendern einer einzigen Code-Splitting-Route. Stellen Sie sich vor, Sie müssten dies für 40-50 Routen tun. Denken Sie daran, dass Sie immer noch nicht den context, die loaders, die search param validation und andere Funktionen des Routers berührt haben 🤕.
Warum also ist File-Based Routing der bevorzugte Weg?
TanStack Routers File-Based Routing wurde entwickelt, um all diese Probleme zu lösen. Es ermöglicht Ihnen, Ihre Routen auf eine vorhersehbare Weise zu definieren, die einfach zu verwalten und zu warten ist und mit wachsender Anwendung skaliert.
Der File-Based Routing-Ansatz wird durch das TanStack Router Bundler Plugin angetrieben. Es führt 3 wesentliche Aufgaben aus, die die Schmerzpunkte bei der Routenkonfiguration bei Verwendung von Code-basiertem Routing lösen.
Schauen wir uns an, wie die Routenkonfiguration für das vorherige Beispiel mit File-Based Routing aussehen würde.
// src/routes/posts/index.ts
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/')({
component: () => 'Posts index component goes here!!!',
})
// src/routes/posts/index.ts
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/')({
component: () => 'Posts index component goes here!!!',
})
Das war's! Sie müssen sich nicht um die Definition der getParentRoute-Funktion, das Zusammenfügen des Routenbaums oder das Aufteilen Ihrer Komponenten kümmern. Das TanStack Router Bundler Plugin erledigt all dies für Sie.
Zu keinem Zeitpunkt nimmt das TanStack Router Bundler Plugin Ihnen die Kontrolle über Ihre Routenkonfigurationen ab. Es wurde entwickelt, um so flexibel wie möglich zu sein und Ihnen zu ermöglichen, Ihre Routen so zu definieren, dass sie zu Ihrer Anwendung passen, während die Boilerplate und die Komplexität der Routenkonfiguration reduziert werden.
Schauen Sie sich die Anleitungen für File-Based Routing und Code-Splitting für eine detailliertere Erklärung an, wie sie in TanStack Router funktionieren.
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.