TypeScript 4.9 adds the satifies
-Operator to the Typing System. This enables us to combine as const
(for the specific type of an expression) and satisfies
(to make the expression also match some type) to create new typings with a very specific base type.
We can now use this combination to create a better typing for the vue router. Suppose we have the following routes defined:
const routes = [
{
path: "/",
name: "Home",
component: HomeView,
},
{
name: "About",
path: "/about",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: HomeView,
children: [
{
path: "/test",
name: "test",
component: HomeView,
},
],
},
{
name: "Login",
path: "/login",
component: HomeView,
},
]
We can now add as const
aswell as satifies
to make the routes match our predefined type ReadonlyRouteRecordRaw
...
{
name: "Login",
path: "/login",
component: HomeView,
},
] as const satisfies readonly ReadonlyRouteRecordRaw[];
With that in place we can now try to make the router routes typesafe.
type RouteNames = ExtractRotuteNames<typeof router> // "Home" | "About" | "Login"
export function useTypedRouter() {
const router = useRouter();
return router as Omit<Router, 'push'> & {
push: (
to: ExtractRouteNames<typeof routes> | Exclude<RouteLocationRaw, string>
) => Promise<NavigationFailure | void | undefined>;
};
}
// Use composable
const Header = defineComponent({
setup() {
const router = useTypedRouter();
router.push('Home');
},
});
const APP_ROUTES: ExtractRoutePaths<typeof routes> = {
HOME: '/',
ABOUT: { ROOT: '/about', TEST: '/test' },
LOGIN: '/login',
};
// Later somewhere in the app:
router.push({ path: APP_ROUTES.HOME }}
When using Approach 2, we check if the path is a dynamic route => if so, expect a function that takes the param and returns the full path
// Extends the routes from above
...
children: [
{
path: '/test',
name: 'test',
component: HomeView,
},
{
path: '/:testId',
name: 'TestDetails',
component: HomeView,
},
],
...
const APP_ROUTES: ExtractRoutePathsWithParams<typeof routes> = {
HOME: '/',
ABOUT: {
ROOT: '/about',
TEST: '/test',
TESTDETAILS: (test) => {
return `/test/${test}`;
},
},
LOGIN: '/login',
};
Last note: The routes const need to be casted to Readonly<RouteRecordRaw[]>, otherwise the router won't accept it
export const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes as Readonly<RouteRecordRaw[]>,
...