手撕router-view/router-link,vue-router底层到底是怎么实现的?(一)

前言

使用vue时总会使用到路由,路由的跳转..等等,对于对代码充满好奇的我们,自然不会放过每一个手撕源码的机会,因此今天我们一起来看看router-link、router-view的底层到底是怎么实现的

vue-router中的router-link的使用

首先我们来玩一下vue封装好的vue-router

  1. npm i vue-router 安装vue-router
  2. 在router文件夹下创建index.js

index.js

js 复制代码
import { createRouter, createWebHashHistory } from 'vue-router';

const routes = [
    {
        path: '/',
        name: 'home',
        component: () => import('../views/Home.vue')
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('../views/About.vue')
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
})

export default router;

这里我们就创建了一个router,抛出之后,在全局引入(main.js),并使用use。

注意: 这里我们使用的是带Hash的createWebHashHistory,它与不带Hash的路由创建模式存在一些区别:

URL 形式 :使用 createWebHashHistory 创建的路由模式,其 URL 中会带有 # 号,例如 /#/home/#/about 等;而 createWebHistory 创建的路由模式,其 URL 更加直观,不会在 URL 中添加 # 号,例如 /home/about 等。

服务器配置要求createWebHashHistory 不需要在服务器端进行特殊配置,因为 # 后面的部分不会被发送到服务器。而使用 createWebHistory 时,由于直接使用了 HTML5 中的 history.pushStatehistory.replaceState 方法来实现路由跳转,如果直接在浏览器中刷新或直接访问某个路由,服务器将无法识别该路由,并返回 404 错误。因此,需要在服务器端进行配置,将所有的路由请求都返回首页,再由前端代码进行路由的匹配和处理。

浏览器兼容性createWebHashHistory 支持所有浏览器,包括老版本的浏览器。而 createWebHistory 只支持 HTML5 标准浏览器。

SEO 优化createWebHashHistory 因为 URL 中带有 # 号,在搜索引擎的 SEO 优化方面存在一些问题。而 createWebHistory 的 URL 形式更有利于 SEO。

开发环境设置 :在开发环境下,使用 createWebHistory 需要将 webpack 的 historyApiFallback 属性设置为 true,以便在开发环境下正常使用路由。

main.js

js 复制代码
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
    .mount('#app')

引入之后我们就可以在App.js中加入路由入口 <router-view />,启动项目,这样一个基本的路由就建立起来了。

App.vue

js 复制代码
<template>
  <router-link to="/">首页</router-link>
  <router-link to="/about">about</router-link>
  <router-view />
</template>

<script setup>

</script>

<style lang="scss" scoped></style>
  • router-link点击可以跳转。
  • 此时启动项目,就能直接访问到/路径下的Home.vue了,更改路径为/about就能访问到About.vue。

走到这一步,我们就自然而然会思考:这个routerlink是如何进入到组件树,它的底层又是如何担起跳转页面的功能的?

组件都是开发出来的,这个routerlink也一样,它来自于我们安装的vue-router,底层源码到底做了什么事情?

手撕router-link

  1. routerlink能实现页面跳转的功能,那么必然他的底层一定存在a标签
  2. 标签中的to,具体到哪个路径需要做绑定,用到defineProps,并且这是一个必填required属性,就像href一样。
  3. 我们要使用自己写的routerlink,那必然在main.js中不要再use已经写好的router
  4. 我们学习html时标签中间的文字部分(显示不同的超链接,如:about超链接,home超链接),每一个超链接标签中间的文字都是不一样的,此时我们把自己写好的RouterLink组件引入到App.js中使用时,如何把组件中间写的文字搬运到a标签中?----><slot></slot>插槽占位符提升组件的定制性,可读性更好

RouterLink.vue

js 复制代码
<template>
    <a :href="'#' + props.to">
        <slot></slot>
    </a>
</template>

<script setup>
import { defineProps } from "vue";

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

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

App.vue

js 复制代码
<template>
  <RouterLink to="/">首页</RouterLink>
  <RouterLink to="/about">about</RouterLink>
</template>

<script setup>
import RouterLink from './router/grouter/RouterLink.vue'
</script>

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

但是问题来了:我们使用vue-router中的routerlink组件的时候,想让其生效,其实就是在main.js中用app给use了一下我们在路由中创建的router,它压根就不需要像上方我们的做法一样(引入这个组件import RouterLink ......),那么这个use方法到底做了什么事情呢?

  • vue-router中的routerlink组件不需要声明就能使用,这是:全局组件,想让一个组件成为全局组件:app.component('组件名',类在哪里),如routerlink组件声明:app.component('router-link',RouterLink)
  • main.js中我们原来是在router文件夹中的index.js中引入vue-router 然后去createrouter..createWebHashHistory,然后定义一个routes,通过createrouter方法创造一个router最终抛出这个router,现在我们手撕一下这个index.js

index.js

js 复制代码
import { createRouter } from './grouter/index'

const router = createRouter()


export default router

我们还封装了一层index.js在grouter文件夹下:

index.js

js 复制代码
// VueRouter封装在这里

import RouterLink from "./RouterLink.vue"
const createRouter = () => {
    return new Router()
}

class Router {
    constructor() {

    }
    install(app) {
        // console.log(app);
        // console.log('vue-router install');
        // 完成全局组件的声明
        app.component('RouterLink', RouterLink);

    }

}

export {
    createRouter
}

那么至此,在App.vue中我们就再也不需要引入RouterLink组件才能去使用RouterLink了

App.vue

js 复制代码
<template>
  <RouterLink to="/">首页</RouterLink>
  <RouterLink to="/about">about</RouterLink>
</template>

<script setup>
// import RouterLink from './router/grouter/RouterLink.vue'

</script>

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

效果:

到这里,可以看到,vue分为vue的本体以及vue的生态,不得不说尤雨溪是真的厉害。在做vue框架时做好边界(mvvm,组件,VDOM...)的同时还有自己的生态(install)app只需要use一下拿到这个实例,把app传过去就能调用install方法,

手撕router-view

既然已经写到这里了,不如把router-view也给写了

  • 还是一样,在刚才封装的index.js中再将RouterView也给声明成全局组件,虽然这个组件还没写
  • 动态组件 <component :is="component"> </component>这个component是响应式的我们需要拿到url才知道需要去哪里,这里就需要对index.js中写的路由routes数组里的path与当前url做对比,比对成功就跳转

RouterView.vue

js 复制代码
<template>
    <component :is="component"> </component>
</template>

<script setup>
import { computed, ref } from 'vue'

let router = useRouter()

const component = computed(() => {
    const route = router.routes.find(
        (route) => route.path === router.url
    )
    return route ? route.component : null
})
</script>

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

grouter文件夹下的index.js

js 复制代码
// VueRouter封装在这里
import { ref } from "vue";
import RouterLink from "./RouterLink.vue"
import RouterView from "./RouterView.vue"
const createRouter = (options) => {
    return new Router(options)
}

const createWebHashHistory = () => {
    return {
        url: window.location.hash.slice(1) || '/'
    }
}

class Router {
    constructor(options) {
        console.log(options, '/////////');
        this.history = options.history
        this.routes = options.routes;
        // 当前的url 状态 它是router-view computed计算属性的依赖
        this.current = ref(this.history.url)
    }
    install(app) {
        // console.log(app);
        // console.log('vue-router install');
        // 完成全局组件的声明
        app.component('RouterLink', RouterLink);
        app.component('RouterView', RouterView);

    }

}

export {
    createRouter,
    createWebHashHistory
}
  • url: window.location.hash.slice(1) || '/' 由于是带Hash的,需要将井号去除
  • 接下来一起来看看option打印出来的效果吧,createRouter接收两个值,一个history,一个routes,现在我们已经把history实现了,看看他是否真的接收到了我们的url。

至此,routerlink介绍完毕,routerview未完待续...

相关推荐
惜.己1 分钟前
Jmeter中的配置原件(四)
java·前端·功能测试·jmeter·1024程序员节
EasyNTS2 分钟前
无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍
前端·javascript·vue.js
guokanglun26 分钟前
Vue.js动态组件使用
前端·javascript·vue.js
Go4doom29 分钟前
vue-cli3+qiankun迁移至rsbuild
前端
-seventy-38 分钟前
Ajax 与 Vue 框架应用点——随笔谈
前端
我认不到你1 小时前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript
集成显卡1 小时前
axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)
前端·ajax·json
焚琴煮鹤的熊熊野火1 小时前
前端垂直居中的多种实现方式及应用分析
前端
我是苏苏1 小时前
C# Main函数中调用异步方法
前端·javascript·c#
转角羊儿2 小时前
uni-app文章列表制作⑧
前端·javascript·uni-app