Wenn Leute anfangen, TanStack Router zu verwenden, haben sie oft viele Fragen, die sich um die folgenden Themen drehen
Warum muss ich Dinge auf diese Weise tun?
Warum ist es so und nicht anders?
Ich bin es gewohnt, es so zu tun, warum sollte ich es ändern?
Und das sind alles berechtigte Fragen. Meistens sind die Leute es gewohnt, Routing-Bibliotheken zu verwenden, die sich sehr ähnlich sind. Sie haben alle eine ähnliche API, ähnliche Konzepte und ähnliche Vorgehensweisen.
Aber TanStack Router ist anders. Es ist keine durchschnittliche Routing-Bibliothek. Es ist keine durchschnittliche State-Management-Bibliothek. Es ist nichts Durchschnittliches.
Es ist wichtig zu bedenken, dass die Ursprünge von TanStack Router in der Notwendigkeit von Nozzle.io für eine Client-Side-Routing-Lösung liegen, die eine erstklassige URL Search Parameters-Erfahrung bot, ohne Kompromisse bei der Typsicherheit einzugehen, die für die Unterstützung seiner komplexen Dashboards erforderlich war.
Daher wurde von Beginn an jeder Aspekt seines Designs sorgfältig durchdacht, um sicherzustellen, dass seine Typsicherheit und Entwicklererfahrung unübertroffen sind.
TypeScript! TypeScript! TypeScript!
Jeder Aspekt von TanStack Router ist darauf ausgelegt, so typsicher wie möglich zu sein, und dies wird durch die vollständige Nutzung des Typsystems von TypeScript erreicht. 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.
Um dies zu erreichen, mussten wir jedoch einige Entscheidungen treffen, die von den Konventionen 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 Generics überall, 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 Typen der Routenkonfiguration 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 der <Link>-Komponente manuell typisieren müssten und Fehler erst zur Laufzeit entdeckt 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 dies eine gute Idee zu sein. Es ist einfach, die gesamte Routenhierarchie auf einen Blick zu visualisieren. Aber dieser Ansatz hat ein paar große Nachteile, die ihn für große Anwendungen ungeeignet machen
Dies verschlimmert sich nur noch, wenn Sie mehr 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 gefunden haben, ist, die Definition der Routenkonfiguration außerhalb des Routenbaums zu abstrahieren. 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-based Routing lesen, um zu sehen, wie Sie Ihre Routen auf diese Weise definieren.
Tipp
Code-based Routing ist Ihnen 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 viel zu kompliziert für mich...
Sobald Sie Ihre Routen zu einem Baum zusammengestellt und mit allen Generics korrekt an Ihre Router-Instanz übergeben haben (mittels createRouter), müssen Sie diese Informationen irgendwie an den Rest Ihrer Anwendung weitergeben.
Wir haben 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 ist umständlich zu verwalten, und es wird nur noch schlimmer, wenn Ihre Anwendung wächst und Sie mehr Funktionen des Routers nutzen.
Dies tun Sie einmal in Ihrer Anwendung.
// src/app.tsx
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
// src/app.tsx
declare module '@tanstack/react-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 Boilerplate-Code gefunden haben.
Warum drängen die Docs auf File-based Routing?
Ich bin es gewohnt, meine Routen in einer einzigen Datei zu definieren, warum sollte ich es ändern?
Etwas, das Sie (sehr bald) in der TanStack Router-Dokumentation bemerken werden, ist, dass wir File-based Routing als bevorzugte Methode zur Definition Ihrer Routen empfehlen. Das 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-based 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. Um dies zu erreichen, wurde die Konfiguration des Routers präzise vorgenommen, sodass TypeScript die Typen Ihrer Routen so weit wie möglich ableiten kann.
Ein wesentlicher Unterschied bei der Einrichtung einer grundlegenden Anwendung mit TanStack Router ist, dass Ihre Routenkonfigurationen eine Funktion erfordern, die an getParentRoute übergeben wird und die Elternroute der aktuellen Route zurückgibt.
import { createRoute } from '@tanstack/react-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
import { createRoute } from '@tanstack/react-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
})
Zu diesem Zeitpunkt geschieht dies, damit die Definition von postsIndexRoute über ihren Standort im Routenbaum informiert ist und die Typen des context, der path params und der search params, die von der Elternroute zurückgegeben werden, korrekt ableiten kann. 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 durchgeführt wird.
Aber dies ist nur ein Teil der Einrichtung einer grundlegenden Anwendung. TanStack Router erfordert, dass alle Routen (einschließlich der Root-Route) zu einem Routenbaum zusammengefügt werden, damit er an die createRouter-Funktion übergeben werden kann, bevor die Router-Instanz für die Typinferenz im Modul deklariert wird. Dies ist ein weiterer kritischer Teil der Routenkonfiguration und ein Fehlerpunkt, wenn er nicht korrekt durchgefü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 noch zu, wenn Sie mehr 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. Folglich entwickeln Benutzer ihre eigenen semi-konsistenten Wege, 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 etwas mühsam zu verwalten sein, wenn Sie Ihre Routen in einer einzigen Datei oder sogar über mehrere Dateien hinweg definieren.
import { createRoute, lazyRouteComponent } from '@tanstack/react-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
component: lazyRouteComponent(() => import('../page-components/posts/index')),
})
import { createRoute, lazyRouteComponent } from '@tanstack/react-router'
import { postsRoute } from './postsRoute'
export const postsIndexRoute = createRoute({
getParentRoute: () => postsRoute,
path: '/',
component: lazyRouteComponent(() => import('../page-components/posts/index')),
})
All diese Boilerplate, egal wie unerlässlich sie für eine erstklassige Typinferenzerfahrung ist, kann etwas überwältigend sein und zu Inkonsistenzen und Fehlern in der Routenkonfiguration führen.
... und diese Beispielkonfiguration dient nur zum Rendern einer einzelnen Code-Splitting-Route. Stellen Sie sich vor, Sie müssten dies für 40-50 Routen tun. Denken Sie jetzt daran, dass Sie immer noch nichts mit dem context, den loaders, der search param validation und anderen Funktionen des Routers zu tun haben 🤕.
Warum ist File-based Routing also die bevorzugte Methode?
Das File-based Routing von TanStack Router 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 dem Wachstum Ihrer Anwendung skaliert.
Der File-based Routing-Ansatz wird vom TanStack Router Bundler Plugin unterstützt. Es führt 3 wesentliche Aufgaben aus, die die Schmerzpunkte bei der Routenkonfiguration bei der Verwendung von Code-based Routing lösen
Schauen wir uns an, wie die Routenkonfiguration des vorherigen Beispiels mit File-based Routing aussehen würde.
// src/routes/posts/index.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: () => 'Posts index component goes here!!!',
})
// src/routes/posts/index.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: () => 'Posts index component goes here!!!',
})
Das war's! Keine Notwendigkeit, sich um die Definition der getParentRoute-Funktion, das Zusammenfügen des Routenbaums oder das Aufteilen Ihrer Komponenten zu kümmern. Das TanStack Router Bundler Plugin erledigt all dies für Sie.
Das TanStack Router Bundler Plugin entzieht Ihnen zu keinem Zeitpunkt die Kontrolle über Ihre Routenkonfigurationen. Es ist so konzipiert, dass es so flexibel wie möglich ist, sodass Sie Ihre Routen so definieren können, wie es Ihrer Anwendung entspricht, während gleichzeitig der Boilerplate-Code und die Komplexität der Routenkonfiguration reduziert werden.
Schauen Sie sich die Anleitungen für File-based Routing und Code-splitting an, um eine ausführlichere Erklärung zu erhalten, wie sie in TanStack Router funktionieren.
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.