好家伙,
今天来手写我们的老伙计vue-router,
1.替换router
新开一个项目,并使用我们手写的router

2.大致结构
- let Vue; // 保存vue的构造函数
- class VueRouter {
- constructor(options) {
-
- }
- }
- VueRouter.install = (_Vue) => {
- Vue = _Vue; //备份Vue
- Vue.mixin({
- beforeCreate() {
- if (this.$options.router) {
- Vue.prototype.$router = this.$options.router;
- }
- }
- })
- Vue.component("router-link", {});
- //实现思路,找到对应的组件并将它渲染出来
- Vue.component("router-view", {});
- }
- export default VueRouter;
2.1.这里使用Vue.mixin(),使任何组件都能调用到router
2.2.Vue = _Vue,一会要用到Vue的方法,将某个变量变为响应式的
3.router-link实现
3.1.组件的使用

3.2.实现
- Vue.component("router-link", {
- props: {
- to: {
- type: String,
- required: true,
- },
- },
- render(h) {
- return h("a", {
- attrs: {
- href: `#${this.to}`
- }
- }, this.$slots.default);
- }
- });
重点来了,为什么要用个#?
在这段代码中,使用 #
的目的是为了在单页面应用(SPA)中实现基于 hash 的路由。在传统的单页面应用中,通过改变 URL 中的 hash 部分来切换页面内容,而不会导致整个页面重新加载。这种方式被称为 hash 模式路由。
具体来说,当用户点击带有 #
的链接时,浏览器会更新 URL 中的 hash 部分,但不会触发整个页面的重新加载,而是根据新的 hash 值来 更新页面内容,从而实现页面的切换和路由导航。
在 Vue 中,使用 #
可以帮助我们正确地处理 hash 模式路由。
4.实现router-view
- Vue.component("router-view", {
- render(h) {
- let component = null;
- //获取当前路由所对应的组件并将它渲染出来
- const current = this.$router.current;
- const route = this.$router.$options.routes.find((route) =>
- route.path === current
- )
- // const route = this.$router.$options.routes.find((route) =>
- // {route.path === current}
- // )
- //!!错误
- //若使用箭头函数块{},必须要有返回值
-
- console.log(route, current)
- if (route) {
- component = route.component
- }
- return h(component);
- }
- });
总体上看,代码逻辑非常简单,在router中找到匹配的组件,然后返回相应的组件就好了,但问题来了,我怎么知道当前页面current是什么?
5.实现VueRouter
- class VueRouter {
- constructor(options) {
- this.$options = options;
- this.current = "/";
- let initial = window.location.hash.slice(1) || "/"
- Vue.util.defineReactive(this, "current", initial)
- window.addEventListener("hashchange", () => {
- this.current = window.location.hash.slice(1) || "/"
- console.log(this.current)
- })
- }
- }
第一步:开始我们默认this.current = "/"; 即首页,
第二步:将current变为响应式数据,
第三步:让current动态获取当前路由的值
问:为什么要将current变为响应式数据?
答:render的更新依赖于响应式数据curren,若current不为响应式数据,current变化,render不会重新渲染
搞定
6.源码
- let Vue; // 保存vue的构造函数
- class VueRouter {
- constructor(options) {
- this.$options = options;
- this.current = "/";
- let initial = window.location.hash.slice(1) || "/"
- Vue.util.defineReactive(this, "current", initial)
- window.addEventListener("hashchange", () => {
- this.current = window.location.hash.slice(1) || "/"
- console.log(this.current)
- })
- }
- }
- VueRouter.install = (_Vue) => {
- Vue = _Vue; //备份Vue
- Vue.mixin({
- beforeCreate() {
- if (this.$options.router) {
- Vue.prototype.$router = this.$options.router;
- }
- }
- })
- Vue.component("router-link", {
- props: {
- to: {
- type: String,
- required: true,
- },
- },
- render(h) {
- return h("a", {
- attrs: {
- href: `#${this.to}`
- }
- }, this.$slots.default);
- }
- });
- //实现思路,找到对应的组件并将它渲染出来
- Vue.component("router-view", {
- render(h) {
- let component = null;
- //获取当前路由所对应的组件并将它渲染出来
- const current = this.$router.current;
- // const route = this.$router.$options.routes.find((route) =>
- // route.path === current
- // )
- const route = this.$router.$options.routes.find((route) =>
- {return route.path === current}
- )
- //!!错误
- //若使用箭头函数块{},必须要有返回值
- console.log(route, current)
- if (route) {
- component = route.component
- }
- return h(component);
- }
- });
- }
- export default VueRouter;
7.补充
一个小小bug
- const route = this.$router.$options.routes.find((route) =>
- route.path === current
- )
不能写成
- const route = this.$router.$options.routes.find((route) =>
- {route.path === current}
- )
第一段代码使用了简洁的箭头函数写法,直接返回了 route.path === current
的结果。这种写法适用于只有一行代码的情况,箭头函数会自动将这一行代码的结果作为返回值。因此,第一段代码会返回第一个满足条件 route.path === current
的 route
对象。
第二段代码使用了代码块 {}
包裹起来,但在代码块中没有显式返回值。这种情况下,箭头函数不会自动返回代码块中的结果,需要手动添加 return
关键字来返回值。因此,第二段代码中的箭头函数没有正确返回值,会导致代码出错。
所以,若要使用代码块 {}
- const route = this.$router.$options.routes.find((route) =>
- {return route.path === current}
- )