手写实现vue-router(超详细思路+代码)

前言

今天来手写实现vue-router简化版,主要以hash模式来实现网址的跳转改变。

涉及到router-link,router-view,路由配置
主要目的是为了深刻的理解vue-router在路由加载背后到底做了些什么?话不多说开始进入今天的主题。

这是App.vue文件,感兴趣的小伙伴可以一起来实现

App.vue

<template>
  <div>
    <header>
      <nav>
        <router-link to="/">首页</router-link>
        <router-link to="/about">About</router-link>
      </nav>
    </header>
    <main>
      <router-view></router-view>
    </main>
    <footer></footer>
  </div>
</template>

<script setup>
</script>

<style lang="css" scoped>
</style>

router/index.js完整代码

import { createRouter, createWebHashHistory } from './grouter/index.js';
import Home from '../pages/Home.vue';
import About from '../pages/About.vue';
const router = createRouter({
    history: createWebHashHistory(),
    routes: [
        {
            path: '/',
            name: 'Home',
            component: Home
        }, {
            path: '/about',
            name: 'About',
            component: About
        }
    ]
})
export default router;

理解app.use

一般我们在使用路由插件时是先进行npm i vue-router下载路由,然后在main.js中使用app.use导入router插件。想要手写vue-router,必须先搞懂app.use()干了一件什么事情。我们去官方文档下面看看。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
看完我们知道插件中自带一个install()方法,在app.use方法内会自动执行。所以我们只需要关注插件的实现即可。app.use()传送门 应用实例 API | Vue.js (vuejs.org)

创建Router对象来管理路由

useRouter方法和createRouter中我们可以明白,在vue-router中是有一个Router对象对着全局的路由进行管理。所以首先我们需要进行Router对象的创建,而对象的功能具备install方法,将Router对象挂载在app上。然后constructor(构造方法)需要进行history和routes的加载配置。让我们来逐一实现。

  1. 首先是install方法内,我们需要将整个路由对象暴露出去,然后使用createRouter方法来接受暴露的Router对象在默认导出,这里我们使用provideinject方法来对Router对象的暴露与接受。
  2. app将组件变为全局组件的方法,app.component来注册router-link和router-view

完整Router和useRouter代码,路径:router/grouter/index.js

// 标记一下 router 要向所有人暴露
const ROUTER_KEY = "__router__";

export const useRouter = () => {
    //通过关键字获取暴露的Router对象
    return inject(ROUTER_KEY);
}
class Router {
    constructor(options) {
        //history对象就是createHashWebHistroy返回的history对象
        this.history = options.history;
        // 路由对象数组,path,component,name等配置对象
        this.routes = options.routes;
        //获取当前路由地址
        this.current = ref(this.history.url);
       // 这里的方法是监听hashchange事件来更新current,切换路由
       //,可以先不看这一段等会会讲
        this.history.bindEvent(() => {
            this.current.value = window.location.hash.slice(1);
        })
    }
    //use 会调用 插件的install方法
    install(app) {
        // 全局声明 有一个router 全局使用的对象
        app.provide(ROUTER_KEY, this);
        console.log("准备与vue对接");
        //注册全局组件
        app.component('router-link', RouterLink);
        app.component('router-view', RouterView);
    }
}

router-link和router-view的实现

首先创建两个对应的vue文件,然后让我们来分别实现其中的功能。

router-link

<router-link to="/">首页</router-link>

router-link本质是使用了a标签,所以我们在进行router-link组件封装时, 直接使用<a></a>标签进行地址栏的跳转,to可以用defineProps来接受,但是router-link中的文字如何添加进入a标签中呢?这里我们使用到插槽Slots语法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
通过图片我们可以看到,只需要在<a></a>标签内添加插槽即可实现文字的接收,这里是Slots传送门,感兴趣的小伙伴可以自行查看:插槽 Slots | Vue.js (vuejs.org)

完成之后我们只需要注意我们使用的是hash模式,所以地址栏需要出现#

router-link完整代码 路径:router/grouter/RouterLink.vue

<template>
  <a :href="'#' + props.to">
    <slot></slot>
  </a>
</template>

<script setup>
const props = defineProps({
  to: {
    type: String,
    required: true,
  },
});
</script>

<style lang="css" scoped>
</style>

测试了一下发现没啥问题,地址可以改变。那么接下来就是router-view该如何实现

router-view

router-view需要的就相对繁琐一些,因为需要动态的根据地址hash的改变来进行组件的渲染,所以这里我们主要需要两个功能的实现。

  1. 组件的动态渲染
  2. hash地址的寻找正确的组件

首先思考功能1,v-if虽然可以动态渲染组件但是太多组件的话这要多少个v-else-if,所以排除,那有没有一种
可以让我们只需要写一个组件然后让组件自动渲染不同的组件呢?你别说哦~~还真有。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
传送门:内置特殊元素 | Vue.js (vuejs.org)
这个component组件还真可以满足我们的需求进行组件的动态渲染。那么我们接下来只需要考虑组件的寻找了。

我们可以创建一个mapRoutermap对象,然后在组件被加载时,进行当前router-view对象中的组件信息进行注册(set(path,component)),然后更改地址,只需要进行get方法就可以拿到对应的组件,但是注意有可能用户会输入错误代码所以需要进行校验

router-view完整代码 路径:router/grouter/RouterView.vue

<template>
  <!-- 动态组件 -->
  <component :is="component"></component>
</template>

<script setup>
import { useRouter } from "./index.js";
import { computed } from "vue";
const router = useRouter();
// router-view 动态组件 展示 依赖于url的变化
const mapRouter = new Map();
const reflectMapRouter = (router) => {
  router.routes.forEach((route) => {
     // 更具路径进行组件查询,保证O(1)时间
    mapRouter.set(route.path, route.component);
  });
};
//方法执行进行组件注册
reflectMapRouter(router);
// 响应式 router.current 设置为ref
//设置为计算属性响应式更新
const component = computed(() => {
  const nowRoute = mapRouter.get(router.current.value);
  //这里注意,需要进行校验,因为用户可能输入错误网址
  return nowRoute ? nowRoute : null;
});
</script>
<style lang="css" scoped>
</style>

createWebHashHistory&createRouter

createWebHashHistory
两个事,1-监听网址的改变,2-返回地址中的hash值

createRouter就更简单了返回new Router(),

代码 路径 router/grouter/index.js

export const createRouter = (options) => {
    return new Router(options);
}
export const createWebHashHistory = () => {
    function bindEvent(fn) {
        window.addEventListener('hashchange', fn);
    }
    // history 对象
    return {
        url: window.location.hash.slice(1) || '/',
        bindEvent
    }
}

结语

本篇文章到此就基本全部完成了,文章全部为手写,所以可能会有一些打错字的情况,欢迎在评论区指正。又或者有小伙伴和大佬对本篇文章有别的看法和指导也欢迎在评论区指出,本人非常欢迎大佬和伙伴的建议和指正。- ̗̀(๑ᵔ⌔ᵔ๑)

好了本篇文章就到此为止了,喜欢的话就点个赞和关注吧!

好的,下面是搭建后台管理系统的简单步骤: 1. 创建项目 使用 Vue CLI 创建项目,选择 vue3 preset,安装完成后进入项目目录。 ``` vue create my-project cd my-project ``` 2. 安装依赖包 安装 vite、vue-router 和 element-plus。 ``` npm install vite vue-router@4 element-plus --save ``` 安装 pinia 和 echarts。 ``` npm install pinia echarts@5 --save ``` 3. 配置 vite 在根目录下创建 `vite.config.js` 文件,配置 alias 和 server。 ```js import path from 'path' import { defineConfig } from 'vite' export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, 'src'), }, }, server: { port: 3000, open: true, }, }) ``` 4. 配置路由 在 `src` 目录下创建 `router` 文件夹,并创建 `index.js` 文件,配置路由。 ```js import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home, }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router ``` 在 `src` 目录下的 `main.js` 中引入路由。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app') ``` 5. 配置状态管理 在 `src` 目录下创建 `store` 文件夹,并创建 `index.js` 文件,配置状态管理。 ```js import { createPinia } from 'pinia' const store = createPinia() export default store ``` 在 `src` 目录下的 `main.js` 中引入状态管理。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' const app = createApp(App) app.use(router) app.use(store) app.mount('#app') ``` 6. 配置 UI 框架 在 `src` 目录下的 `main.js` 中引入 element-plus。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' const app = createApp(App) app.use(router) app.use(store) app.use(ElementPlus) app.mount('#app') ``` 7. 配置 echarts 在 `src` 目录下的 `main.js` 中引入 echarts。 ```js import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' import * as echarts from 'echarts' const app = createApp(App) app.use(router) app.use(store) app.use(ElementPlus) app.config.globalProperties.$echarts = echarts app.mount('#app') ``` 8. 创建页面 在 `src` 目录下创建 `views` 文件夹,并创建页面组件。 9. 创建布局 在 `src` 目录下创建 `layouts` 文件夹,并创建布局组件。 10. 配置路由和布局 在 `router/index.js` 中配置路由和布局。 ```js import { createRouter, createWebHistory } from 'vue-router' import Layout from '@/layouts/Layout.vue' import Home from '@/views/Home.vue' const routes = [ { path: '/', component: Layout, children: [ { path: '', name: 'Home', component: Home, }, ], }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router ``` 11. 运行项目 在项目根目录下运行 `npm run dev`,打开浏览器访问 `http://localhost:3000` 查看效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绯雨934

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值