initComponents
initComponents
是用于创建 router-link
和 router-view
组件的方法,她接收 Vue
参数,虽然 Vue
已经被我们记录到了全局变量中的 _Vue
变量上,但是这里当作参数传入,就是为了减少这个方法和外部的依赖。
js
initComponent(Vue) {
Vue.component("router-link", { ... })
Vue.component("router-view", { ... })
}
再回顾一下 router-link
和 router-view
组件的使用:
js
<div id="nav">
<router-link to="/">Index</router-link> |
<router-link to="/blog">Blog</router-link> |
...
</div>
<router-view/>
创建 router-link
- 最终要渲染成一个超链接 --
<a>
- 接收一个字符串类型的参数
to
作为超链接的地址 --props
- 将标签之间的内容作为超链接的内容渲染出来 --
<slot>
js
Vue.component("router-link", {
props: {
to: String
},
template:"<a :href='to'><slot></slot></a>"
})
试一试
导出一下我们写到现在的 VueRouter
(export default class VueRouter
),然后在我们的 VueCli
项目中修改导入 router
的地址,将 import VueRouter from 'vue-router'
中的 vue-router
改为我们 VueRouter
文件的地址,试着看看我们的 router-link
组件是否能正确显示。
当我们启动项目后,可以在控制台看到以下报错:
这里提示我们使用的是 runtime-only
版本的 vue
,其中是不包含编译 template
的模块的,那我们想要避免这个错误有两种方法:
- 使用完整版本的
vue
VueCli
默认使用的是运行时版本的 Vue
,因为这样效率更高,如果我们需要使用完整版本的 Vue,就需要在项目根目录下添加 vue.config.js
文件,然后添加配置 runtimeCompiler
:
js
// vue.config.js
module.exports = {
runtimeCompiler: true
}
再次运行项目就会发现报错消失,页面正常显示了 router-link
组件。
- 将
template
改为render
函数的写法render
函数就是渲染函数,它接收一个参数createElement
方法(通常简写成h
),通过调用createElement
可以创建虚拟DOM
。
js
Vue.component("router-link", {
props: {
to: String
},
render(h) {
// `createElement` 方法可以接收3个参数,分别是要创建元素的`选择器`、`属性`、`子元素`。
return h("a", {
attrs: {
href: this.to
}
}, [this.$slots.default]) // 默认插槽
},
})
创建 router-view
router-view
是根据当前路由地址(this.data.current
)在 routeMap
中找到对应显示组件的。需要注意的有2点:
- 写在
render
函数中的this
不是VueRouter
的实例而是routerView
组件,所以需要在initComponent
方法内先获取this
,此时的this
是VueRouter
的实例。 h
可以接收一个组件,然后将组件转换为虚拟DOM
。
js
const self = this
Vue.component("router-view", {
render(h) {
const cm = self.routeMap[self.data.current]
return h(cm)
}
})
切换 router-link
时更新 router-view
我们已经完成了 router-link
和 router-view
两个组件,可以在浏览器上看下效果,没有报错,但是当我们点击 router-link
切换路由时,我们可以看到页面的 url
改变了,并且向服务端发起请求重新刷新了页面。
但在单页面应用中,我们其实不希望这样,那我们就需要做出以下修改:
1. 注册 click
事件覆盖超链接默认向服务器发起请求的行为
这里如果不加 e.preventDefault()
,则点击事件会让整个页面向服务器发起请求并刷新页面,也不要删除 href
属性,删掉这个属性会导致超链接的样式变得和普通文本一样。
2. 使用 history.pushState
方法改变地址栏的路径,但是不会向服务器发起请求
pushState
接收三个参数,分别是 data
, title
(网页的标题), url
(要转跳的路径),暂时只设置 url
即可。
3. 修改 current
路由地址,触发 router-view
的更新
VueRouter
的属性 data
是响应式的,在 router-view
中我们根据当前地址 data.current
来决定显示的组件。那么我们只需要改变 data.current
的值,就可以同步更新 router-view
的组件了。
那么怎么在 click
方法里,修改 data.current
的值呢?首先明确 click
方法中的 this
就是 router-view
组件,它是一个 vue
实例,对于所有的 vue
实例都具有 $router
属性,其中的包含 current
表示当前路由地址。
js
render(h){
return h("a",{
attrs:{ href:'' },
on:{
// 不要加小括号,否则会变成立即调用
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
history.pushState({},"",this.to)
this.$router.data.current=this.to
e.preventDefault()
}
}
完整代码
js
initComponent(Vue){
Vue.component("router-link",{
props:{
to:String
},
render(h){
return h("a",{
attrs:{
href:''
},
on:{
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
history.pushState({},"",this.to)
this.$router.data.current=this.to
e.preventDefault()
}
}
})
const self = this
Vue.component("router-view",{
render(h){
const cm=self.routeMap[self.data.current]
return h(cm)
}
})
}