Vue-Router简版手写实现

发布于:2025-06-02 ⋅ 阅读:(27) ⋅ 点赞:(0)

1. 路由库工程设计

首先,我们需要创建几个核心文件来组织我们的路由库:

src/
  router/
    index.ts
    RouterView.ts
    RouterLink.ts
    useRouter.ts
    injectionsymbols.ts
    history.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


网站公告

今日签到

点亮在社区的每一天
去签到