手撕Vue中的RouterLink和RouterView,深入理解其底层原理(二)

前言

在之前我们已经讲过了如何手撕本文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 对象作为参数,并从这个对象中提取 routerhistory 属性进行赋值。

然后,使用 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可以让我们更加透彻的理解其底层的原理,以及其设计思想,希望对看到这里的你能够有所帮助!!

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值