vue-router 源码:组件

开头

最后来了解了解 vue-router 的两个组件 <router-link><router-view>

这两个组件的使用方式如下:

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>
复制代码

在打开源码之前,先来简单思考一下,组件可以怎样去实现。

<router-link> 可以是一个 <a> 标签,to 是它的 props,然后绑定一个点击事件。

还记得 编程式的导航 吗?在之前的源码中,我们也看到 this.history 实现了 push、replace 等方法,通过点击触发这些方法即可触发路由的改变。

// 伪代码
Vue.component('router-link', {
  props: {
    to: String
  },
  template: '<a v-on="handleClick"><slot></slot></a>',
  methods: {
    handleClick () {
      this.$router.push(this.to)
    }
  }
})
复制代码

路由的改变,会修改到 this.$route,利用 Vue 的响应式,在 <router-view> 里使用到 this.$route,便能够及时的获取到相应的组件并渲染出来。

// 伪代码
Vue.component('router-view', {
  render (h, { parent }) {
    const route = parent.$route

    // route 里不一定有 component
    // 表示通过 route 可以找到 component 的意思
    const component = route.component

    return h(component);
  }
})
复制代码

这只是最简单的猜想,实现实现的代码远比上面的要复杂得多。下面就来真正的看看它们的具体实现吧。

router-link

router-link 组件的写法是 渲染函数 的写法。

主要实现了几块功能:

  1. 定义正确的跳转 url。
  2. 定义切换的高亮样式。
  3. 定义点击事件。
  4. 设置 data 对象

router-link 的大体框架是这样的:

export default {
  name: 'router-link',
  props: {
    to: {
      type: toTypes,
      required: true
    },
    tag: {
      type: String,
      default: 'a'
    }
    // ...
  },
  render (h: Function) {
    // ...

    return h(this.tag, data, this.$slots.default)
  }
}
复制代码

render 里就是实现的几块功能。

定义正确的跳转 url

const router = this.$router
const current = this.$route
const to = normalizeLocation(this.to, current, this.append)
const resolved = router.match(to)
const fullPath = resolved.redirectedFrom || resolved.fullPath
const base = router.history.base
const href = base ? cleanPath(base + fullPath) : fullPath
复制代码

最后得到 href 作为 <a> 标签的跳转属性,具体细节不展开了。

定义切换的高亮样式

const classes = {}
const activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active'

// 省略一堆代码
classes[activeClass] = true // or false
复制代码

这里可以看到,高亮样式默认就是 'router-link-active'。

定义点击事件

const on = {
  click: (e) => {
    // ...
    e.preventDefault()
    if (this.replace) {
      router.replace(to)
    } else {
      router.push(to)
    }
  }
}
复制代码

点击事件里其实就是调用触发 router 的 push 或 replace,跟编程式导航类似。

设置 data 对象

const data: any = {
  class: classes
}

if (this.tag === 'a') {
  data.on = on
  data.attrs = { href }
} else {
  // find the first <a> child and apply listener and href
  const a = findAnchor(this.$slots.default)
  if (a) {
    const aData = a.data || (a.data = {})
    aData.on = on
    const aAttrs = aData.attrs || (aData.attrs = {})
    aAttrs.href = href
  } else {
    // doesn't have <a> child, apply listener to self
    data.on = on
  }
}
复制代码

这里有一个判断,如果 tag 不是 <a> 标签的话,就会递归使用 findAnchor 函数去找 slots 里面的 <a> 标签。最后还是没有的话,就只能给 router-link 自己赋值 on 点击事件。

最后,将 createElement 函数返回,即完成 router-link 组件的实现。

return h(this.tag, data, this.$slots.default)
复制代码

router-view

router-view 是一个 函数式组件,专门只做渲染的工作。

最主要就是找到要显示的组件,期间会优先取已经缓存的组件,其次是取嵌套了的组件。

router-view 的大体框架是这样的:

export default {
  name: 'router-view',
  functional: true,
  props: {
    name: {
      type: String,
      default: 'default'
    }
  },
  render (h, { props, children, parent, data }) {
    // ...

    return h(component, data, children)
  }
}
复制代码

路由触发

其中使用到了 $route。

const route = parent.$route

// ...

const matched = route.matched[depth]
复制代码

回到 install,当时使用了 Vue 将 $route 进行了响应式处理:

Object.defineProperty(Vue.prototype, '$route', {
  get () { return this.$root._route }
})
复制代码

然后还记得路由初始化时的监听:

this.history.listen(route => {
  this.app._route = route
})
复制代码

一旦修改了 _route,即修改到了 $route,也会同时触发到 route-view 的 render,从而实现了路由的跳转(即路由的切换)。

嵌套路由处理

route-view 通过 depth 和 route.matched 来找到嵌套的路由,从而实现嵌套路由的渲染。

let depth = 0

while (parent) {
  if (parent.$vnode && parent.$vnode.data.routerView) {
    depth++
  }
  parent = parent.$parent
}

data.routerViewDepth = depth
const matched = route.matched[depth]
复制代码

keep-alive 处理

使用到了 keep-alive,那就会有 cache 缓存,那么组件就直接用缓存的即可。

const cache = parent._routerViewCache || (parent._routerViewCache = {})
let inactive = false

while (parent) {
  // ...

  if (parent._inactive) {
    inactive = true
  }
  parent = parent.$parent
}

const matched = route.matched[depth]

const name = props.name
const component = inactive
  ? cache[name]
  : (cache[name] = matched.components[name])
复制代码

最后,找到对应的要渲染的组件,将 createElement 函数返回,即完成 router-view 组件的实现。

return h(component, data, children)
复制代码

最后

以上只是简略得对 <router-link><route-view> 的解读,想更加深入了解代码实现,可以移动到 vue-router源码分析-整体流程

至此,vue-router 的源码解读告一段落,鼓掌。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值