前言
在之前我们已经讲过了如何手撕本文RouterLink,深入讲解了RouterLink的基本原理
手撕Vue中的RouterLink和RouterView,深入理解其底层原理(一) - 掘金 (juejin.cn)
接下来我们就继续手撕RouterView吧!!!
RouterView
RouterView是根据url地址去显示对应的页面
所以RouterView实现的核心就是动态组件<component :is="component"> </component>
那我们渲染页面的流程应该是怎么样的?
- 拿到url
- 看看配置,/、/homr对应的谁
- 加载
接下来我们需要修改@/router/index.js中的代码,为其添加router的配置数组
import { createRouter } from '@/router/grouter/index';
const router = createRouter();
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/about',
name: 'About',
component: () => import('@/views/AboutView.vue'),
},
];
export default router;
修改好这个以后,我们就想,既然是创建router,那createRouter()方法是不是一定需要接收配置参数
既然是这样,我们就需要继续修改代码
- @/router/index
import { createRouter, createWebHistory } from "@/router/grouter/index";
const routes = [
{
path: "/",
name: "Home",
component: () => import("@/views/HomeView.vue"),
},
{
path: "/about",
name: "About",
component: () => import("@/views/AboutView.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
可以看到我们在构建router的时候传入了两个数据,一个是配置参数和规则
并且规则是在@/router/grouter/index里面的
显然我们就需要对应的去修改@/router/grouter/index的代码了
- @/router/grouter/index
import RouterLink from "./RouterLink.vue";
import RouterView from "./RouterView.vue";
class Router {
constructor(options) {
console.log(options,"/");
}
install(app) {
console.log(app);
console.log("vue对接router");
app.component("RouterLink", RouterLink);
app.component("RouterView", RouterView);
}
}
const createWebHistory = () => {};
const createRouter = function (options) {
return new Router(options);
};
export { createRouter,createWebHistory };
这里面我们在构造函数constructor中添加了一个参数,并且定义了一个createWebHistory规则
同时我们还修改了对应的createRouter方法,让他将接收的参数传递给构造函数
接下来我们可以看到页面的打印效果为
可以看到我们已经在构造函数中拿到了数据了
接下来我们就去修改构造函数,给这个类赋值
constructor(options) {
console.log(options, "/");
this.routes = options.routes;
this.history = options.history;
// 当前的url状态 我们只要改变this.history.url的值,current就会改变,并更新页面
this.current = ref(this.history.url);
}
接下来我们继续修改一下createWebHistory函数
const createWebHistory = () => {
const bindEvents = (fn) => {
window.addEventListener("hashchange", fn);
};
return {
url: window.location.hash.slice(1) || "/",
bindEvents,
};
};
函数内部定义了一个 bindEvents
函数,用于给 window
对象的 hashchange
事件添加一个事件处理函数 fn
。
当调用 createWebHistory
函数时,它返回一个对象,这个对象有两个属性:
url
:获取当前window.location.hash
去除#
符号后的内容,如果没有hash
部分则默认为'/'
。bindEvents
:用于添加hashchange
事件处理函数的方法。
接下来我们修改构造函数
constructor(options) {
this.routes = options.routes;
this.history = options.history;
console.log(options, "/");
// 当前的url状态 我们只要改变this.history.url的值,current就会改变,并更新页面
this.current = ref(this.history.url);
this.history.bindEvents(() => {
this.current.value = window.location.hash.slice(1) || "/";
console.log(this.current.value);
});
}
接收一个 options
对象作为参数,并从这个对象中提取 router
和 history
属性进行赋值。
然后,使用 ref
创建了一个响应式数据 current
,并初始化为 history.url
的值。
接着,通过调用 history
对象的 bindEvents
方法,为 hashchange
事件添加了一个事件处理函数。当 hashchange
事件触发时,会更新 current
的值为新的 URL 的 hash
部分(去除 #
符号),如果没有 hash
部分则设置为 '/'
,并打印出更新后的 current
值。
我们可以看到这段代码的执行结果为
接下来我们就要在编写一个useRouter方法,在任何地方,使用 useRouter() 获取路由对象
const ROUTER_KEY = "__router__";
// 在任何地方,使用 useRouter() 获取路由对象
const useRouter = () =>{
return inject(ROUTER_KEY);
}
然后就需要在install方法里面全局提供
install(app) {
console.log(app);
console.log("vue对接router");
// 全局提供
app.provide(ROUTER_KEY, this);
app.component("RouterLink", RouterLink);
app.component("RouterView", RouterView);
}
在一个应用程序(app
)中,使用 provide
方法来提供一个具有键 ROUTER_KEY
且值为 this
的内容。
然后我们来编写一下RouterView.vue的代码
<template>
<component :is="component"> </component>
</template>
<script setup>
import { computed } from "vue";
import { useRouter } from "./index";
let router = useRouter(); // router对象
console.log(router);
const component = computed(() => {
const route = router.routes.find(
route => route.path === router.current.value
);
return route ? route.component : null;
});
</script>
<style lang="scss" scoped></style>
在模板 <template>
部分,使用了动态组件 <component :is="component">
,意味着要根据 component
的值来决定渲染的具体组件。
在脚本 <script setup>
部分,从自定义的 ./index
中导入了 useRouter
来获取路由对象 router
。通过计算属性 component
,根据当前路由路径在 router.routes
中查找匹配的路由,并返回对应的组件,如果没有找到匹配的路由则返回 null
。
接下来运行项目就可以看到效果
我们的router对象已经成功打印出来了
之所以可以打印结果就是因为在@/router/grouter/index中将useRouter抛出,useRouter是可以获得__router__的
const ROUTER_KEY = "__router__";
// 在任何地方,使用 useRouter() 获取路由对象
const useRouter = () =>{
return inject(ROUTER_KEY);
}
此时的组件树为
此时我们还没有办法渲染页面
这是由于我们之前采用的按需导入组件
- @/router/index
import { createRouter, createWebHistory } from "@/router/grouter/index";
const routes = [
{
path: "/",
name: "Home",
component: () => import("@/views/HomeView.vue"),
},
{
path: "/about",
name: "About",
component: () => import("@/views/AboutView.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
将其修改为直接导入组件之后我们就能看到效果了
import { createRouter, createWebHistory } from "@/router/grouter/index";
import HomeView from "@/views/HomeView.vue";
import AboutView from "@/views/AboutView.vue";
const routes = [
{
path: "/",
name: "Home",
component: HomeView,
},
{
path: "/about",
name: "About",
component: AboutView,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
效果如下
自此我们就成功的实现了手搓一个RouterView
总结
本文讲解了如何手搓一个RouterView,结合代码一步一步自己手动实现RouterView可以让我们更加透彻的理解其底层的原理,以及其设计思想,希望对看到这里的你能够有所帮助!!