本篇文章将借助基于vue3讲解vue-router4是如何实现的,简化了很多源码逻辑,只关注核心原理去实现基本的路由系统。
课程目录
写在前面
接上一章,第一章讲解了如何通过history
内置对象实现url
更新,并且存储了为后续开发提供了数据的支持。第二章讲解了如何给用户提供路由的方法和属性,这些属性记录了当前路由的状态,调用方法我们可以获取到路由需要渲染的组件。
接下来,将讲解vue-router
的routerView、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-router
的router-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
组件。
- 举个例子: 路由/a需要渲染HomeView组件包含A组件,匹配到的
- 代码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>
A
和B
组件随便写,看看最终效果