text: /* @flow */
  
  import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
  import { extend } from '../util/misc'
  import { normalizeLocation } from '../util/location'
  import { warn } from '../util/warn'
  
  // work around weird flow bug
  const toTypes: Array<Function> = [String, Object]
  const eventTypes: Array<Function> = [String, Array]
  
  const noop = () => {}
  
  let warnedCustomSlot
  let warnedTagProp
  let warnedEventProp
  
  export default {
    name: 'RouterLink',
    props: {
      to: {
        type: toTypes,
        required: true
      },
      tag: {
        type: String,
        default: 'a'
      },
      custom: Boolean,
      exact: Boolean,
      exactPath: Boolean,
      append: Boolean,
      replace: Boolean,
      activeClass: String,
      exactActiveClass: String,
      ariaCurrentValue: {
        type: String,
        default: 'page'
      },
      event: {
        type: eventTypes,
        default: 'click'
      }
    },
    render (h: Function) {
      const router = this.$router
      const current = this.$route
      // 根据目标路径以及当前路由，获取目标路由信息
      const { location, route, href } = router.resolve(
        this.to,
        current,
        this.append
      )
  
      // 处理元素配置的样式/common,single
      const classes = {}
      const globalActiveClass = router.options.linkActiveClass
      const globalExactActiveClass = router.options.linkExactActiveClass
      // Support global empty active class
      const activeClassFallback =
        globalActiveClass == null ? 'router-link-active' : globalActiveClass
      const exactActiveClassFallback =
        globalExactActiveClass == null
          ? 'router-link-exact-active'
          : globalExactActiveClass
      const activeClass =
        this.activeClass == null ? activeClassFallback : this.activeClass
      const exactActiveClass =
        this.exactActiveClass == null
          ? exactActiveClassFallback
          : this.exactActiveClass
  
      const compareTarget = route.redirectedFrom
        ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
        : route
  
      classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath)
      classes[activeClass] = this.exact || this.exactPath
        ? classes[exactActiveClass]
        : isIncludedRoute(current, compareTarget)
  
      const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null
  
      const handler = e => {
        if (guardEvent(e)) {
          if (this.replace) {
            router.replace(location, noop)
          } else {
            router.push(location, noop)
          }
        }
      }
  
      const on = { click: guardEvent }
      if (Array.isArray(this.event)) {
        this.event.forEach(e => {
          on[e] = handler
        })
      } else {
        on[this.event] = handler
      }
  
      const data: any = { class: classes }
  
      const scopedSlot =
        !this.$scopedSlots.$hasNormal &&
        this.$scopedSlots.default &&
        this.$scopedSlots.default({
          href,
          route,
          navigate: handler,
          isActive: classes[activeClass],
          isExactActive: classes[exactActiveClass]
        })
        // 插槽编译之后会变成函数，这里传参使用，所以作用域插槽可以拿到传参
  
      if (scopedSlot) {
        if (process.env.NODE_ENV !== 'production' && !this.custom) {
          !warnedCustomSlot && warn(false, 'In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link v-slot="{ navigate, href }" custom></router-link>\n')
          warnedCustomSlot = true
        }
        if (scopedSlot.length === 1) {
          return scopedSlot[0]
        } else if (scopedSlot.length > 1 || !scopedSlot.length) {
          if (process.env.NODE_ENV !== 'production') {
            warn(
              false,
              `<router-link> with to="${
                this.to
              }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
            )
          }
          return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
        }
      }
  
      if (process.env.NODE_ENV !== 'production') {
        if ('tag' in this.$options.propsData && !warnedTagProp) {
          warn(
            false,
            `<router-link>'s tag prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
          )
          warnedTagProp = true
        }
        if ('event' in this.$options.propsData && !warnedEventProp) {
          warn(
            false,
            `<router-link>'s event prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
          )
          warnedEventProp = true
        }
      }
  
      if (this.tag === 'a') {
        data.on = on
        data.attrs = { href, 'aria-current': ariaCurrentValue }
      } else {
        // find the first <a> child and apply listener and href
        const a = findAnchor(this.$slots.default)
        if (a) {
          // in case the <a> is a static node
          a.isStatic = false
          const aData = (a.data = extend({}, a.data))
          aData.on = aData.on || {}
          // transform existing events in both objects into arrays so we can push later
          for (const event in aData.on) {
            const handler = aData.on[event]
            if (event in on) {
              aData.on[event] = Array.isArray(handler) ? handler : [handler]
            }
          }
          // append new listeners for router-link
          for (const event in on) {
            if (event in aData.on) {
              // on[event] is always a function
              aData.on[event].push(on[event])
            } else {
              aData.on[event] = handler
            }
          }
  
          const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
          aAttrs.href = href
          aAttrs['aria-current'] = ariaCurrentValue
        } else {
          // doesn't have <a> child, apply listener to self
          data.on = on
        }
      }
  
      return h(this.tag, data, this.$slots.default)
    }
  }
  
  function guardEvent (e) {
    // don't redirect with control keys
    if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
    // don't redirect when preventDefault called
    if (e.defaultPrevented) return
    // don't redirect on right click
    if (e.button !== undefined && e.button !== 0) return
    // don't redirect if `target="_blank"`
    if (e.currentTarget && e.currentTarget.getAttribute) {
      const target = e.currentTarget.getAttribute('target')
      if (/\b_blank\b/i.test(target)) return
    }
    // this may be a Weex event which doesn't have this method
    if (e.preventDefault) {
      e.preventDefault()
    }
    return true
  }
  
  function findAnchor (children) {
    if (children) {
      let child
      for (let i = 0; i < children.length; i++) {
        child = children[i]
        if (child.tag === 'a') {
          return child
        }
        if (child.children && (child = findAnchor(child.children))) {
          return child
        }
      }
    }
  }
  