vue-router源码实现1

2021-08-28 19:55:23 Vue 大约 4 分钟

Vue Router 是 Vue.js (opens new window) (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

# 基本使用

  • 安装:

    vue add router
    
    1
  • 使用vue-router插件,router.js

    import Router from 'vue-router'
    Vue.use(Router)
    
    // 创建Router实例 并导出
    export default new Router({...})
    
    1
    2
    3
    4
    5
  • 在根组件上添加该实例,main.js

    import router from './router'
    new Vue({
    	router,
    }).$mount("#app");
    
    1
    2
    3
    4
  • 添加路由视图,App.vue

    <router-view></router-view>
    
    1
  • 导航

    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
    
    1
    2

# 源码实现

要做的事情就是防着vue-router官方的路由,写一个简单的自己的路由;

分析:

  • 根据官方路由的使用和配置,我们需要实现一个插件;
  • 它可以监听#后面值的变化,然后匹配不同的组件从而显示不同的页面;
  • 需要考虑嵌套路由;
  • 需要实现<router-view></router-view><router-link></router-link>组件;

# vue-router

创建一个vue-router类并导出;

class Router {
    constructor(options) {
        this.$options = options;
    }

    // vue默认会执行这个Install方法
    static install (Vue) {
        Vue.mixin({
            beforeCreate() {
                console.log(this.$options)
                // 只有vue根示例有 router 选项;
                if (this.$options.router) {
                    // 把vue根示例的选项挂载到vue的原型上;
                    Vue.prototype.$router = this.$options.router
                }
            }
        })
    }
}

export default Router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

实现全局组件router-link,用来导航。

// vue默认会执行这个Install方法
static install (Vue) {
   ...省略
    // 创建<router-link>全局组件
    // 注意这里不可以使用template选项,因为没有编译器
    Vue.component('router-link', {
        props: {
            to: {}
        },
        render(createElement) {
            return createElement('a', {
                attrs: {
                    href: `#/${this.to}`,
                },
            }, this.$slots.default,)
        }
    })
}

// 使用
<router-link to="/about"></router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 创建router-view

实现全局组件router-view,用作容器书写子路由。

// vue默认会执行这个Install方法
static install (Vue) {
   ...省略
   // 创建<router-view>全局组件
   Vue.component('router-view', {
       render(createElement) {
           return createElement('div', '123')
       }
   })
}

// 使用
<router-view></router-view>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 监听路由变化

前面实现的内容目前达不到切换路由匹配不同组件的能力,接下载就是监听路由更新组件;


class Router {
    constructor(options) {
        this.$options = options;

        this.currentRouter = '/';

        // 监听路由的变化
        window.addEventListener('hashchange', () => {
            this.currentRouter = window.location.hash.slice(1)
        })
    }

	... 省略
    // 创建<router-view>全局组件
    Vue.component('router-view', {
        render(createElement) {
            let component = null;
            // 查找当前的路由并获取组件
            this.$router.$options.routers.forEach((router) => {
                if (router.path === this.$router.currentRouter) {
                    component = router.component
                }
            })
            return createElement(component)
        }
    })
    }
}

export default Router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

以上代码就可以在哈希路由变化的时候,匹配不同的组件;

::: wanging

但是还有一个问题就是,路由变化了,但是render函数没有重新执行;这里需要考虑响应式;

:::

# 实时渲染

这里做的目的就是,当匹配到路由变化时,重新执行render函数,达到重新渲染的目的;


let Vue;

class Router {
    constructor(options) {

        this.currentRouter = '/';
        // 响应式 currentRouter
        Vue.util.defineReactive(this, 'currentRouter', '/');
    }
}

export default Router

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 解决刷新视图消失


let Vue;

class Router {
    constructor(options) {
        this.$options = options;

        this.currentRouter = '/';

        // 响应式 currentRouter
        Vue.util.defineReactive(this, 'currentRouter', '/');

        // 监听路由的变化
        window.addEventListener('hashchange', this.onhashchange.bind(this))

        // 解决刷新时,不渲染render的问题;
        window.addEventListener('load', this.onhashchange.bind(this))
    }

    onhashchange() {
        this.currentRouter = window.location.hash.slice(1)
    }
}

export default Router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 完整代码


let Vue;

class Router {
    constructor(options) {
        this.$options = options;

        this.currentRouter = '/';

        // 响应式 currentRouter
        Vue.util.defineReactive(this, 'currentRouter', '/');

        // 监听路由的变化
        window.addEventListener('hashchange', () => {
            this.currentRouter = window.location.hash.slice(1)
        })
    }

    // vue默认会执行这个Install方法
    static install (_Vue) {
        Vue = _Vue
        Vue.mixin({
            beforeCreate() {
                // 只有vue根示例有 router 选项;
                if (this.$options.router) {
                    Vue.prototype.$router = this.$options.router
                }
            }
        })

        // 创建<router-link>全局组件
        // 注意这里不可以使用template选项,因为没有编译器
        Vue.component('router-link', {
            props: {
                to: {}
            },
            render(createElement) {
                return createElement('a', {
                    attrs: {
                        href: `#${this.to}`,
                    },
                }, this.$slots.default,)
            }
        })

        // 创建<router-view>全局组件
        Vue.component('router-view', {
            render(createElement) {
                let component = null;
                this.$router.$options.routers.forEach((router) => {
                    if (router.path === this.$router.currentRouter) {
                        component = router.component
                    }
                })
                return createElement(component)
            }
        })
    }
}

export default Router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

好了,至此router的初始版本就这样了,目的也是用于学习,所有实现一个简单的router;

提示

使用方式和官方的 vue-router一样,不过这里只是实现了路由和容器,有很多细节并没有考虑,比如嵌套路由;

# 嵌套路由

上次编辑于: 2023年7月4日 09:36