vue-router中 push方法 name 和 path 路由跳轉的區別(從源碼的角度講)附帶性能測試

背景:

新公司入職 首次進行代碼評審,在看到 this.$router.push('/path1'), 有同事建議使用 name進行路由跳轉,給出的理由是更美觀,個人雖然有代碼潔癖,但是這個理由不是很能接受,於是查看了下vue-router的源碼,想從根本上看下下他倆的區別。

在看源碼之前我所瞭解的name 和 path的區別是從vue-router 官方文檔中獲得的,下面貼下文檔的說明

 

源碼解讀

push 方法執行的順序

  1. transitionTo
    1. match
      1. 格式化參數:normalizeLocation (name:params做淺拷貝,path:對path進行格式化, 得到基本路由、url上參數merge到query、處理hash 返回{ path, query, hash } })
      2. 匹配路由 name: nameMap[name] 並將params 加在route中; path: pathMap[path]
      3. 創建跳轉路由: _createRoute
    2. 執行跳轉(confirmTransition)等等。。。 後續操作都一致

後面貼一下源碼

首先是transitionTo

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(
      location,
      route => {
        pushHash(route.fullPath)
        handleScroll(this.router, route, fromRoute, false)
        onComplete && onComplete(route)
      },
      onAbort
    )
  }



 transitionTo (
    location: RawLocation,
    onComplete?: Function,
    onAbort?: Function
  ) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(
      route,
      () => {
        const prev = this.current
        this.updateRoute(route)
        onComplete && onComplete(route)
        this.ensureURL()
        this.router.afterHooks.forEach(hook => {
          hook && hook(route, prev)
        })

        // fire ready cbs once
        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach(cb => {
            cb(route)
          })
        }
      },
...

匹配路由

function match (
    raw,
    currentRoute,
    redirectedFrom
  ) {
    var location = normalizeLocation(raw, currentRoute, false, router);
    var name = location.name;

    if (name) {
      var record = nameMap[name];
      if (process.env.NODE_ENV !== 'production') {
        warn(record, ("Route with name '" + name + "' does not exist"));
      }
      if (!record) { return _createRoute(null, location) }
      var paramNames = record.regex.keys
        .filter(function (key) { return !key.optional; })
        .map(function (key) { return key.name; });

      if (typeof location.params !== 'object') {
        location.params = {};
      }

      if (currentRoute && typeof currentRoute.params === 'object') {
        for (var key in currentRoute.params) {
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key];
          }
        }
      }

      location.path = fillParams(record.path, location.params, ("named route \"" + name + "\""));
      return _createRoute(record, location, redirectedFrom)
    } else if (location.path) {
      location.params = {};
      for (var i = 0; i < pathList.length; i++) {
        var path = pathList[i];
        var record$1 = pathMap[path];
        if (matchRoute(record$1.regex, location.path, location.params)) {
          return _createRoute(record$1, location, redirectedFrom)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }

normalizeLocation

function normalizeLocation (
  raw,
  current,
  append,
  router
) {
  var next = typeof raw === 'string' ? { path: raw } : raw;
  // named target
  if (next._normalized) {
    return next
  } else if (next.name) {
    next = extend({}, raw);
    var params = next.params;
    if (params && typeof params === 'object') {
      next.params = extend({}, params);
    }
    return next
  }

  // relative params
  if (!next.path && next.params && current) {
    next = extend({}, next);
    next._normalized = true;
    var params$1 = extend(extend({}, current.params), next.params);
    if (current.name) {
      next.name = current.name;
      next.params = params$1;
    } else if (current.matched.length) {
      var rawPath = current.matched[current.matched.length - 1].path;
      next.path = fillParams(rawPath, params$1, ("path " + (current.path)));
    } else if (process.env.NODE_ENV !== 'production') {
      warn(false, "relative params navigation requires a current route.");
    }
    return next
  }

  var parsedPath = parsePath(next.path || '');
  var basePath = (current && current.path) || '/';
  var path = parsedPath.path
    ? resolvePath(parsedPath.path, basePath, append || next.append)
    : basePath;

  var query = resolveQuery(
    parsedPath.query,
    next.query,
    router && router.options.parseQuery
  );

  var hash = next.hash || parsedPath.hash;
  if (hash && hash.charAt(0) !== '#') {
    hash = "#" + hash;
  }

  return {
    _normalized: true,
    path: path,
    query: query,
    hash: hash
  }
}

性能測試

創建兩個vue組件測試下性能的區別

<!-- 組件A -->
<template>
  <div></div>
</template>

<script>
import Vue from 'vue'

export default {
  name: 'RouteA',
  created () {
    Vue.count === 0 && console.time('push--path性能測試')
    if (Vue.count < 50) {
      Vue.count++
      this.$router.push({ path: '/routeB' })
      // this.$router.push({ name: 'routeB' })
    } else {
      console.timeEnd('push--path性能測試')
    }
  }
}
</script>

<style scoped>

</style>

<!-- 組件B -->
<template>
  <div></div>
</template>

<script>
export default {
  name: 'RouteB',
  created () {
    this.$router.push({ path: '/routeA' })
  }
}
</script>

<style scoped>

</style>

看下控制檯的打印,性能上並沒有什麼區別。

push--path性能測試: 59.458984375ms
push--path性能測試: 65.81689453125ms
push--path性能測試: 61.808837890625ms
push--path性能測試: 67.8359375ms
push--path性能測試: 63.342041015625ms
push--path性能測試: 63.43017578125ms

push--name性能測試: 63.48193359375ms
push--name性能測試: 61.52392578125ms
push--name性能測試: 69.216064453125ms
push--name性能測試: 63.93017578125ms
push--name性能測試: 66.28515625ms
push--name性能測試: 76.2060546875ms
push--name性能測試: 58.2890625ms

總結

在不傳params的情況下(一般很少場景會用到這種傳參,因爲刷新路由參數會丟失),性能是並沒有什麼卻別,參數傳遞方式沒有優劣之分,項目中定一個規範就好。值得注意的是,本來想大數字測下性能的,但是在超過50之後vue會報錯誤,這個後續可以研究一下,知道的小夥伴歡迎留言。

[Vue warn]: You may have an infinite update loop in a component render function

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章