Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue-router 源码:组件 #21

Open
cobish opened this issue Sep 21, 2018 · 0 comments
Open

vue-router 源码:组件 #21

cobish opened this issue Sep 21, 2018 · 0 comments

Comments

@cobish
Copy link
Owner

cobish commented Sep 21, 2018

开头

最后来了解了解 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 的源码解读告一段落,鼓掌。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant