1. 路由库工程设计
首先,我们需要创建几个核心文件来组织我们的路由库:
src/router/index.tsRouterView.tsRouterLink.tsuseRouter.tsinjectionsymbols.tshistory.ts
2. injectionSymbols.ts
定义一些注入符号来在应用中共享状态:
import { inject, provide, InjectionKey, Ref } from 'vue';export interface Router {currentRoute: Ref<Route>;push: (to: string) => void;replace: (to: string) => void;resolve: (to: string) => string;
}export interface Route {path: string;component: any;
}export const routerKey: InjectionKey<Router> = Symbol('router');
export const matchedRouteKey: InjectionKey<Ref<Route>> = Symbol('matchedRoute');
export const viewDepthKey: InjectionKey<number> = Symbol('viewDepth');export function provideRouter(router: Router) {provide(routerKey, router);
}export function useRouter(): Router {return inject(routerKey);
}export function useMatchedRoute(): Ref<Route> {return inject(matchedRouteKey);
}export function provideMatchedRoute(route: Ref<Route>) {provide(matchedRouteKey, route);
}export function useViewDepth(): number {return inject(viewDepthKey, 0);
}export function provideViewDepth(depth: number) {provide(viewDepthKey, depth);
}
3. RouterView.ts
实现RouterView组件:
import { defineComponent, h, computed } from 'vue';
import { useMatchedRoute, useViewController, provideViewController } from './injectionsymbols';export const RouterView = defineComponent({name: 'RouterView',setup() {const depth = useViewController();const matchedRoute = useMatchedRoute();const route = computed(() => matchedRoute.value[depth]);provideViewController(depth + 1);return () => {const Component = route.value && route.value.component;return Component ? h(Component) : null;};}
});
4. RouterLink.ts
实现RouterLink组件:
import { defineComponent, h } from 'vue';
import { useRouter } from './injectionsymbols';export const RouterLink = defineComponent({name: 'RouterLink',props: {to: {type: [String, Object],required: true},replace: Boolean},setup(props, { slots }) {const router = useRouter();const navigate = (event: Event) => {event.preventDefault();if (props.replace) {router.replace(props.to as string);} else {router.push(props.to as string);}};return () => {return h('a', {href: router.resolve(props.to as string),onClick: navigate}, slots.default ? slots.default() : '');};}
});
5. useRouter.ts
实现useRouter函数:
import { inject } from 'vue';
import { routerKey, Router } from './injectionSymbols';export function useRouter(): Router {return inject(routerKey);
}
6. index.ts
实现createRouter函数:
import { ref, reactive, watch, Ref } from 'vue';
import { provideRouter, provideMatchedRoute, Route, Router } from './injectionsymbols';export function createRouter({ history, routes }: { history: any, routes: Route[] }): Router {const currentRoute: Ref<Route> = ref({ path: '/', component: null });function createMatcher(routes: Route[]) {const matchers = routes.map(route => ({...route,regex: new RegExp(`^${route.path}$`)}));return (path: string) => matchers.find(route => route.regex.test(path));}const matcher = createMatcher(routes);function push(to: string) {const route = matcher(to);if (route) {currentRoute.value = route;history.push(to);}}function replace(to: string) {const route = matcher(to);if (route) {currentRoute.value = route;history.replace(to);}}const router: Router = {currentRoute,push,replace,resolve: (to: string) => to};watch(currentRoute, (route) => {provideMatchedRoute(ref(route));});provideRouter(router);return router;
}
7. history.ts
实现createWebHistory函数:
export function createWebHistory() {const listeners: ((path: string) => void)[] = [];window.addEventListener('popstate', () => {listeners.forEach(listener => listener(window.location.pathname));});function push(path: string) {window.history.pushState({}, '', path);listeners.forEach(listener => listener(path));}function replace(path: string) {window.history.replaceState({}, '', path);listeners.forEach(listener => listener(path));}function listen(callback: (path: string) => void) {listeners.push(callback);}return {push,replace,listen};
}
8. 示例应用
最后,我们可以在一个简单的 Vue 应用中使用我们的自定义路由库:
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { createRouter } from './router';
import { createWebHistory } from './router/history';const routes = [{ path: '/', component: () => import('./components/Home.vue') },{ path: '/about', component: () => import('./components/About.vue') }
];const history = createWebHistory();
const router = createRouter({ history, routes });createApp(App).use(router).mount('#app');
以下是在App.vue中
// App.vue
<template><div id="app"><router-link to="/">Home</router-link><router-link to="/about">About</router-link><router-view></router-view></div>
</template><script lang="ts">
import { defineComponent } from 'vue';export default defineComponent({name: 'App'
});
</script>
这个简化版的 Vue Router 库的 TypeScript 版本包含了核心组件 RouterView、RouterLink 以及核心 API createRouter 和 useRouter,实现了基本的路由功能。通过这个实现,你可以了解 Vue Router 的基本工作原理和核心概念。
9. 补充资料
vue-router 官方文档:https://router.vuejs.org/zh/introduction.html
vue-router 相关 api 速查:https://router.vuejs.org/zh/api/
源码:https://github.com/vuejs/router
浏览器历史记录协议:https://developer.mozilla.org/en-US/docs/Web/API/History_API
路由库实现:https://github.com/vuejs/router/blob/v4.3.3/packages/router/src/history/html5.ts