JohannesSchwegler / vue-typed-routes

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Vue-Typed-Routes ✨

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.

1.Approach: Extract all route names and add custom composable

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');
	},
});

2.Approach: Create type for routes-object and create an object based on the type

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 }}

Handling Params

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[]>,
	...
	

About


Languages

Language:TypeScript 100.0%