前言
还是那样,懂得如何使用一个常用库,还得了解其原理或者怎么模拟实现,今天实现一下 vue-router 。
有一些知识我这篇文章提到了,这里就不详细一步步写,请看我 手写一个简易的 Vuex
基本骨架
-
Vue 里面使用插件的方式是
Vue.use(plugin)
,这里贴出它的用法:
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
- 全局混入
使用 Vue.mixin(mixin)
全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。可以使用混入向组件注入自定义的行为,它将影响每一个之后创建的 Vue 实例。
- 路由用法
比如简单的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 路由数组 const routes = [ { path: '/' , name: 'Page1' , component: Page1, }, { path: '/page2' , name: 'Page2' , component: Page2, }, ] const router = new VueRouter({ mode: 'history' , // 模式 routes, }) |
它是传入了mode
和routes
,我们实现的时候需要在VueRouter
构造函数中接收。
在使用路由标题的时候是这样:
1
2
3
4
5
6
7
8
9
10
|
<p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to= "/page1" >Go to Foo</router-link> <router-link to= "/page2" >Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> |
故我们需要使用Vue.component( id, [definition] )
注册一个全局组件。
了解了大概,我们就可以写出一个基本骨架
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
|
let Vue = null class VueRouter { constructor(options) { this .mode = options.mode || 'hash' this .routes = options.routes || [] } } VueRouter.install = function (_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { // 根组件 if ( this .$options && this .$options.router) { this ._root = this // 把当前vue实例保存到_root上 this ._router = this .$options.router // 把router的实例挂载在_router上 } else if ( this .$parent && this .$parent._root) { // 子组件的话就去继承父组件的实例,让所有组件共享一个router实例 this ._root = this .$parent && this .$parent._root } }, }) Vue.component( 'router-link' , { props: { to: { type: [String, Object], required: true , }, tag: { type: String, default : 'a' , // router-link 默认渲染成 a 标签 }, }, render(h) { let tag = this .tag || 'a' return <tag href={ this .to}>{ this .$slots. default }</tag> }, }) Vue.component( 'router-view' , { render(h) { return h( 'h1' , {}, '视图显示的地方' ) // 暂时置为h1标签,下面会改 }, }) } export default VueRouter |
mode
vue-router
有两种模式,默认为 hash 模式。
history 模式
通过window.history.pushStateAPI
来添加浏览器历史记录,然后通过监听popState
事件,也就是监听历史记录的改变,来加载相应的内容。
- popstate 事件
当活动历史记录条目更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创建的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。
- History.pushState()方法
window.history.pushState(state, title, url)
该方法用于在历史中添加一条记录,接收三个参数,依次为:
- state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。
- title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
- url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
hash 模式
使用 URL 的 hash 来模拟一个完整的 URL。,通过监听hashchange事件,然后根据hash值(可通过 window.location.hash 属性读取)去加载对应的内容的。
继续增加代码,
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
let Vue = null class HistoryRoute { constructor() { this .current = null // 当前路径 } } class VueRouter { constructor(options) { this .mode = options.mode || 'hash' this .routes = options.routes || [] this .routesMap = this .createMap( this .routes) this .history = new HistoryRoute() // 当前路由 this .initRoute() // 初始化路由函数 } createMap(routes) { return routes.reduce((pre, current) => { pre[current.path] = current.component return pre }, {}) } initRoute() { if ( this .mode === 'hash' ) { // 先判断用户打开时有没有hash值,没有的话跳转到 #/ location.hash ? '' : (location.hash = '/' ) window.addEventListener( 'load' , () => { this .history.current = location.hash.slice(1) }) window.addEventListener( 'hashchange' , () => { this .history.current = location.hash.slice(1) }) } else { // history模式 location.pathname ? '' : (location.pathname = '/' ) window.addEventListener( 'load' , () => { this .history.current = location.pathname }) window.addEventListener( 'popstate' , () => { this .history.current = location.pathname }) } } } VueRouter.install = function (_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { if ( this .$options && this .$options.router) { this ._root = this this ._router = this .$options.router Vue.util.defineReactive( this , '_route' , this ._router.history) // 监听history路径变化 } else if ( this .$parent && this .$parent._root) { this ._root = this .$parent && this .$parent._root } // 当访问this.$router时即返回router实例 Object.defineProperty( this , '$router' , { get() { return this ._root._router }, }) // 当访问this.$route时即返回当前页面路由信息 Object.defineProperty( this , '$route' , { get() { return this ._root._router.history.current }, }) }, }) } export default VueRouter |
router-link 和 router-view 组件
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
|
VueRouter.install = function (_Vue) { Vue = _Vue Vue.component( 'router-link' , { props: { to: { type: [String, Object], required: true , }, tag: { type: String, default : 'a' , }, }, methods: { handleClick(event) { // 阻止a标签默认跳转 event && event.preventDefault && event.preventDefault() let mode = this ._self._root._router.mode let path = this .to this ._self._root._router.history.current = path if (mode === 'hash' ) { window.history.pushState( null , '' , '#/' + path.slice(1)) } else { window.history.pushState( null , '' , path.slice(1)) } }, }, render(h) { let mode = this ._self._root._router.mode let tag = this .tag || 'a' let to = mode === 'hash' ? '#' + this .to : this .to console.log( 'render' , this .to) return ( <tag on-click={ this .handleClick} href={to}> { this .$slots. default } </tag> ) // return h(tag, { attrs: { href: to }, on: { click: this.handleClick } }, this.$slots.default) }, }) Vue.component( 'router-view' , { render(h) { let current = this ._self._root._router.history.current // current已经是动态响应 let routesMap = this ._self._root._router.routesMap return h(routesMap[current]) // 动态渲染对应组件 }, }) } |
至此,一个简易的vue-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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
let Vue = null class HistoryRoute { constructor() { this .current = null } } class VueRouter { constructor(options) { this .mode = options.mode || 'hash' this .routes = options.routes || [] this .routesMap = this .createMap( this .routes) this .history = new HistoryRoute() // 当前路由 // 初始化路由函数 this .initRoute() } createMap(routes) { return routes.reduce((pre, current) => { pre[current.path] = current.component return pre }, {}) } initRoute() { if ( this .mode === 'hash' ) { // 先判断用户打开时有没有hash值,没有的话跳转到 #/ location.hash ? '' : (location.hash = '/' ) window.addEventListener( 'load' , () => { this .history.current = location.hash.slice(1) }) window.addEventListener( 'hashchange' , () => { this .history.current = location.hash.slice(1) }) } else { // history模式 location.pathname ? '' : (location.pathname = '/' ) window.addEventListener( 'load' , () => { this .history.current = location.pathname }) window.addEventListener( 'popstate' , () => { this .history.current = location.pathname }) } } } VueRouter.install = function (_Vue) { Vue = _Vue Vue.mixin({ beforeCreate() { // 根组件 if ( this .$options && this .$options.router) { this ._root = this // 把当前vue实例保存到_root上 this ._router = this .$options.router // 把router的实例挂载在_router上 Vue.util.defineReactive( this , '_route' , this ._router.history) // 监听history路径变化 } else if ( this .$parent && this .$parent._root) { // 子组件的话就去继承父组件的实例,让所有组件共享一个router实例 this ._root = this .$parent && this .$parent._root } // 当访问this.$router时即返回router实例 Object.defineProperty( this , '$router' , { get() { return this ._root._router }, }) // 当访问this.$route时即返回当前页面路由信息 Object.defineProperty( this , '$route' , { get() { return this ._root._router.history.current }, }) }, }) Vue.component( 'router-link' , { props: { to: { type: [String, Object], required: true , }, tag: { type: String, default : 'a' , }, }, methods: { handleClick(event) { // 阻止a标签默认跳转 event && event.preventDefault && event.preventDefault() // 阻止a标签默认跳转 let mode = this ._self._root._router.mode let path = this .to this ._self._root._router.history.current = path if (mode === 'hash' ) { window.history.pushState( null , '' , '#/' + path.slice(1)) } else { window.history.pushState( null , '' , path.slice(0)) } }, }, render(h) { let mode = this ._self._root._router.mode let tag = this .tag || 'a' let to = mode === 'hash' ? '#' + this .to : this .to return ( <tag on-click={ this .handleClick} href={to}> { this .$slots. default } </tag> ) // return h(tag, { attrs: { href: to }, on: { click: this.handleClick } }, this.$slots.default) }, }) Vue.component( 'router-view' , { render(h) { let current = this ._self._root._router.history.current // current已经是动态 let routesMap = this ._self._root._router.routesMap return h(routesMap[current]) // 动态渲染对应组件 }, }) } export default VueRouter |
ps: 个人技术博文 Github 仓库,觉得不错的话欢迎 star,给我一点鼓励继续写作吧~
以上就是如何手写简易的 Vue Router的详细内容,更多关于手写简易的 Vue Router的资料请关注服务器之家其它相关文章!
原文链接:https://segmentfault.com/a/1190000037403885?utm_source=tuicool&utm_medium=referral