likes
comments
collection
share

手写Router,解析Router在Vue底层到底干了什么?

作者站长头像
站长
· 阅读数 19

今天我们就来彻底地剖析Router的底层究竟是如何封装实现的,同时我们也手写完成一个自己的handRouter,能够完成Router的基本功能。Router中的主要四个功能有:

  1. 全局注册 RouterView 和 RouterLink 组件。
  2. 添加全局 $router 和 $route 属性。
  3. 启用 useRouter() 和 useRoute() 组合式函数。
  4. 触发路由器解析初始路由。

手写

新建目录结构如下 在index.js文件中实现核心业务

手写Router,解析Router在Vue底层到底干了什么?

index.js

这里我们实现的history模式是hashHistory 实现思路: ①创建Router实例(包括属性history,routes,current),并在创建实例的方法上绑定事件监听,监听url的hash值改变,当hash值改变时将current的值改为此时的hash值。 ②将RouterLink和RouterView注册为全局组件 ③利用privide,inject将实例对象暴露给RouterView 源码如下

import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
import { ref,inject,provide } from 'vue'
export const createRouter = (options) => {
    return new Router(options)
}


//单例的职责
export const createHashRouter = () => {
    // history 对象
    function bindEvents(fn){
        window.addEventListener('hashchange', fn)
    }
    return {
        url: window.location.hash.slice(1) || '/',
        bindEvents
    }
}
// 标记一下   router 要向全世界暴露
const ROUTER_KEY = '__router__'
export const useRouter = () => {
    return inject(ROUTER_KEY)
}


class Router {
    // use(Router) 会调用插件的install方法
    constructor(options){
        this.history = options.history
        this.routes = options.routes
        this.current = ref(this.history.url)
        this.history.bindEvents(()=>{
            // console.log('////');
            this.current.value = window.location.hash.slice(1)
        })
    }
    install(app) {
        // 全局声明由一个router 全局使用的对象
        app.provide(ROUTER_KEY, this)
        console.log('准备与vue 对接', this);
        app.component('router-link', RouterLink)
        app.component('router-view', RouterView)
    }
}

Router-Link

实现思路: ①Router-Link组件主要实现原理是a标签跳转路由,利用a标签的锚点链接**href="#"**再加上to这个参数传递给a标签 ②a标签的内容由插槽slot接收 源码如下

<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="less" scoped>

</style>

Router-view

实现思路: ①利用vue中内置组件动态地渲染组件,它允许你在运行时根据表达式的值来渲染哪个组件,基于组件的name或者component对象动态地切换所显示的组件,component标签通常v-bind:is结合使用,v-bind:is绑定一个动态组件名或者组件对象。 ②通过index.js中useRouter()方法暴露出来的实例Router对象,拿到current当前路由的值,与routes数组中的各个数据进行比较,若有相同则记录下当前路由route并返回给component组件绑定的is。 源码如下

<template>
    <component :is="component"></component>
</template>

<script setup>
import { computed } from 'vue';
import Home from '../../pages/Home.vue'
import About from '../../pages/About.vue'
import { useRouter } from './index.js';
const router = useRouter()
console.log(router);
// computed是基于一些基础属性的变化来计算,也就是根据ref和props或者computed类型的值的改变而变化
// 响应式 router.current 设置为ref
// ref是私有响应式变量, 只能依靠自身函数进行修改。无法通过变量的变化实时监视并修改。
const component = computed(()=>{
    const route = router.routes.find(
        (route)=>route.path == router.current.value
    )

    console.log(route);
    return route?route.component:null;
})
</script>

<style lang="scss" scoped>

</style>

配置Router

这样完成了这三个模块的编写,router就可以直接到路由配置文件中使用了,别忘记了引入你的三个文件。

import {createRouter,createHashRouter} from './grouter/index'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
import { ref } from 'vue'


const routes = [
  {
    path:'/',
    name:'Home',
    component:Home
  },
  {
    path:'/About',
    name:'About',
    component:About
  }
]

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

export default router

实测App根组件,能够正常跳转路由

<template>
  <header>
    <nav>
      <router-link to="/">主页</router-link>
      <router-link to="/About">关于</router-link>
    </nav>
  </header>
  <main>
    <router-view></router-view>
  </main>
</template>

<script setup>

</script>

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

手写完工!

转载自:https://juejin.cn/post/7393740927154864164
评论
请登录