面试只是起点,能力才是终局。本期着重讨论vue-router
。
router-view
组件我们平时写vue项目的时候,遇到路由的时候习惯上直接使用router-view
组件,但是这个组件时谁提供的呢?我们似乎很少考虑这个问题,其实<router-view/>
, <link />
是vue-router
提供的两个组件。
router-view
组件中的使用父组件的$createElement
方法,对定义的路由进行匹配,如果匹配到路由对应的组件,则对这个组件执行createElement
,否则就创建一个空的Dom。示例代码如下:
// 定义routerView组件
export default {
name: 'RouterView',
render (_, { props, children, parent, data }) {
const h = parent.$createElement
const route = parent.$route
const matched = route.matched[depth]
const component = matched && matched.components[name]
...
// 没匹配到组件 则渲染空节点
if (!matched || !component) {
cache[name] = null
return h()
}
...
// 匹配到 则渲染该组件
return h(component, data, children)
}
}
link
组件
了解了router-view
组件后,思考一下link
组件,link组件接收一个to
属性标识路由地址,然后有一个click
事件,用来进行路由跳转,嗯,大致就是这个样子。另外它在界面上会渲染为一个a
标签,是因为它有个默认的Tag
属性,默认值是a
,然后渲染的时候根据这个tag属性进行渲染。
export default {
name: 'RouterLink',
props: {
to: {
type: toTypes,
required: true
},
tag: {
type: String,
default: 'a'
},
}
render (h: Function) {
const router = this.$router
const current = this.$route
...
// 渲染
return h(this.tag, data, this.$slots.default)
}
}
这个问题我们通常会说通过this.route.params获取,当然这些都是文档上写的正确的内容。但是假如我再问你,这个this.route.params具体是怎么获取的路由参数呢?怎么回答?
同理,this.$route.query
呢? 是怎么实现的参数捕获?
对于路由参数,可以理解为有两种,一种是动态路由的参数,一种是常见的路径中的查询字符串。
动态路由参数
也就是文档中说的如下的配置:
// 动态路由配置
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
动态路由参数的获取通过this.$route.params
来获取。其实现逻辑大致是:
现根据当前路由进行匹配,匹配时会根据route参数中的
name
和path
进行判断,然后创建route的时候将query
和params
挂载成为route的两个参数
const normalizeLocation = () => {
//...
return {
_normalized: true,
path,
query,
hash
}
}
// match方法
export const match(rawLocation,currentRoute,redirectedFrom){
// 定义location
const location = normalizeLocation(rawLocation,currentRoute,false,router)
const { name } = location
if(name){
...
return _createRoute(record, location, redirectedFrom)
}else if(location.path){
...
return _createRoute(record, location, redirectedFrom)
}
// 什么都没有
return _createRoute(null, location)
}
// createRoute
const _createRoute = (recordRoute,location,router){
const route = {
name:location.name || recordRoute.name,
meta:recordRoute.meta || {},
path:location.path || '/',
hash:location.hash || '',
query:,
params:location.params || {},
...
}
return Object.freeze(route)
}
文档上的写法是路由守卫
。然后提供了几个router的方法:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
const router = new VueRouter({ ... })
router.afterEach((to, from) => {
// ...
})
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
我们的重点不在于文档提供的这些方法怎么用,而是要思考下面这个问题:
什么是钩子函数? 如何实现这些钩子函数?
对于经常写react代码的同学,我们通常会用到诸如useState,useEffect
之类的钩子方法。同样可以思考这个问题。
钩子方法
是一种非常简单有效的隔离变化的一种手段。我们可以把vueRouter
看成是一个模板,钩子方法就是模板内的一个方方法,在初始化的时候根据钩子方法的结果去执行不同的逻辑。比如:
// 模板
class VueRouter {
read(){
console.log('ready')
}
push(){
console.log('push')
}
go(){
console.log('go')
}
testHook(){
return window.confirm( '执行ready?' );
}
init(){
if(this.testHook()){
this.read()
}
}
}
let testRouter = new VueRouter();
testRouter.init()
钩子函数最简单的写法大概就是这个样子。
这个是说从服务端获取数据的时机,也就是我们平时请求接口的时机。
一般来说我们通常是在created
或mounted
中进行请求,这个其实是在路由导航完成之后进行的数据获取。
还有一种方法是在导航完成之前进行请求,比如:
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}
这种方法有一个问题是,在为后面的视图获取数据时,用户会停留在当前的界面,需要对这个问题进行单独处理。
VueRouter还提供了一个支持页面滚动的方法scrollBehavior
。这个功能只在支持 history.pushState 的浏览器中可用。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
我们可以理解这个方法是对window.scrollTo
方法的封装。因为在代码的最后你会发现这样的一段:
function scrollToPosition(shouldScroll, position){
// ...略
if (position) {
// $flow-disable-line
if ('scrollBehavior' in document.documentElement.style) {
window.scrollTo({
left: position.x,
top: position.y,
// $flow-disable-line
behavior: shouldScroll.behavior
})
} else {
window.scrollTo(position.x, position.y)
}
}
}
在vue-router的实例方法中addRoute
,addRoutes
有时候可以用来处理一些复杂的业务逻辑,必须权限控制之类的,当让这个需要根据实际情况进行调整。
本文分享自 JavaScript高级程序设计 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!