从0到0.8实现Vue-Router4-Router-view和link实现(第三章)

本篇文章将借助基于vue3讲解vue-router4是如何实现的,简化了很多源码逻辑,只关注核心原理去实现基本的路由系统。

课程目录

核心路由系统的实现(第一章)

响应式路由实现(第二章)

Router-view和link实现(第三章)

完整代码gitee地址

写在前面

上一章,第一章讲解了如何通过history内置对象实现url更新,并且存储了为后续开发提供了数据的支持。第二章讲解了如何给用户提供路由的方法和属性,这些属性记录了当前路由的状态,调用方法我们可以获取到路由需要渲染的组件。

接下来,将讲解vue-routerrouterView、routerLink组件,在路由跳转的时候如何渲染对应的页面。

实现思路

routerView、routerLink没有想象的那么复杂,根据前面的铺垫,已经拿到了路由对应的组件,在routerView中只需要将其渲染出来即可,routerLink就更简单了,调用前面封装好的push方法进行跳转,通过插槽渲染组件内容即可,废话少说直接干活。

具体实现

新建两个组件,分别是router-view.js、router-link.js,最终目录结构如下

text 复制代码
.
├── README.md
├── index.html
├── jsconfig.json
├── package.json
├── src
│   ├── App.vue
│   ├── components
│   │   ├── A.vue
│   │   └── B.vue
│   ├── main.js
│   ├── router
│   │   └── index.js
│   ├── views
│   │   ├── AboutView.vue
│   │   └── HomeView.vue
│   └── vue-router
│       ├── history
│       │   ├── hash.js
│       │   └── html5.js
│       ├── index.js
│       ├── router-link.js
│       └── router-view.js
└── vite.config.js

vue-router中,router-view、router-link是全局注册的,并不需要用户手动引入注册,所以我们直接修改vue-router/index.js引入这两个组件

js 复制代码
import { RouterLink } from './router-link';
import { RouterView } from './router-view';
...代码省略
function createRouter(options) {
    const router = {
        install(app) {
            ...代码省略
            app.component('RouterLink', RouterLink);
            app.component('RouterView', RouterView);
            ...代码省略
        }
    }
    return router;
}

先看下router-link的实现

js 复制代码
import { h, inject } from 'vue';

function useLink(props) {
    const router = inject('router location');
    function navigate() {
        router.push(props.to);
    }
    return {
        navigate
    };
}

export const RouterLink = {
    name: 'RouterLink',
    props: {
        to: {
            type: String,
            default() {
                return '/';
            }
        }
    },
    setup(props, { slots }) {
        const link = useLink(props);
        return () =>
            h(
                'a',
                {
                    onClick: link.navigate
                },
                slots.default && slots.default()
            );
    }
};
  • 代码23行,setup函数,利用h函数返回一个渲染函数
  • 代码26行,官方vue-routerrouter-link组件也是渲染成a标签的,我们这里不作修改,获取当前插槽对象,如果有插槽则直接渲染插槽内容,给a标签绑定link.navigate方法。
  • 代码4行,inject依赖注入,就是在上一章provide注入的router location,调用其push方法,跳转路由。

router-view的实现

js 复制代码
import { h, inject, provide, computed } from 'vue';

export const RouterView = {
    name: 'RouterView',
    setup(props, { slots }) {
        const depth = inject('depth', 0);
        // 注入route
        const injectRoute = inject('route')
        const matchedRouteRef = computed(() => injectRoute.matched[depth]);
        provide('depth', depth + 1);
        return () => {
            const matchRoute = matchedRouteRef.value;
            const viewComponents = matchRoute && matchRoute.components.default;

            if (!viewComponents) {
                // 没有组件,则渲染插槽内容
                return slots.default && slots.default();
            }
            return h(viewComponents);
        }
    }
}
  • 代码8行,inject依赖注入,就是上一张通过provide注入的route,里面包含了路由的所有属性。
  • 先来回想一下路由匹配器返回的matched数据结构,也就是当前路由匹配到的组件数组
    • 举个例子: 路由/a需要渲染HomeView组件包含A组件,匹配到的matched结构是[HomeViewRecord, ARecord],在数组前一项往往包含着后一项,也就是说HomeViewRecord渲染位置是App.vue文件下的router-view,而数组第二项渲染位置是HomeView.vue组件内的router-view组件。
  • 代码6行,获取一个depth依赖,默认值是0,其定义在代码10行,也就是说,当第一次渲染HomeViewRecord时,depth依赖是0,通过他可以直接读取matched[depth]项,执行完后在代码10行,将depth+1在下一次的router-view组件中,拿到的值就是depth+1,所以无论router-view嵌套多少层,通过matched[depth]总能拿到对应的组件进行渲染。
  • 代码9行,用computed获取需要渲染的组件。
  • 代码15行,如果需要渲染的路由组件不存在,则渲染插槽内容。
  • 代码19行,利用h函数生成渲染函数。

测试代码

js 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import About from '../views/AboutView.vue'
import A from '../components/A.vue';
import B from '../components/B.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
      children: [
        {
          path: '/a',
          name: 'a',
          component: A
        },
        {
          path: '/b',
          name: 'b',
          component: B
        }
      ]
    },
    {
      path: '/about',
      name: 'about',=
      component: About
    }
  ]
})

export default router
vue 复制代码
// App.vue
<template>
    <RouterLink to="/">Home</RouterLink>
    <RouterLink to="/about">About</RouterLink>
    <RouterView />
</template>

// HomeView
<template>
  <RouterLink @click="clickPath('/a')">A组件</RouterLink>
  <RouterLink @click="clickPath('/b')">B组件</RouterLink>
  <RouterView></RouterView>
</template>

<script setup>
import { inject } from 'vue';

const router = inject('router location');
function clickPath(path) {
  router.push(path);
}
</script>


// AboutView.vue
<template>
  <div class="about">
    about
  </div>
</template>

AB组件随便写,看看最终效果

相关推荐
神夜大侠3 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱3 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
杨荧6 小时前
【JAVA毕业设计】基于Vue和SpringBoot的宠物咖啡馆平台
java·开发语言·jvm·vue.js·spring boot·spring cloud·开源
NoloveisGod7 小时前
Vue的基础使用
前端·javascript·vue.js
GISer_Jing7 小时前
前端系统设计面试题(二)Javascript\Vue
前端·javascript·vue.js
理想不理想v8 小时前
使用JS实现文件流转换excel?
java·前端·javascript·css·vue.js·spring·面试
EasyNTS8 小时前
无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍
前端·javascript·vue.js
guokanglun9 小时前
Vue.js动态组件使用
前端·javascript·vue.js
糊涂涂是个小盆友11 小时前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app
开心工作室_kaic14 小时前
ssm111基于MVC的舞蹈网站的设计与实现+vue(论文+源码)_kaic
前端·vue.js·mvc