VueRouter 원리 조금 만 배 워 볼 게 요.

25642 단어 전단vue.jsvue-router
블 로그
두 가지 경로 모델 의 기본 원리
vue - router 를 사용 해 보면 두 가지 모드 를 제공 한 다 는 것 을 알 수 있 습 니 다. hashhistory 라 우 터 를 통 해 옵션 mode 를 구축 할 수 있 습 니 다.SPA 의 전단 경 로 를 간단하게 이해 하 는 것 은:
  • 기 존의 API 를 이용 하여 url 의 변 화 를 실현 하지만 브 라 우 저가 새로운 url 을 자발적으로 불 러 오 는 것 을 촉발 하지 않 습 니 다. 새로운 페이지 전시 논 리 는 모두 js 제어 에 맡 깁 니 다.
  • history 에 기록 을 추가 하여 페이지 의 후퇴 를 실현 합 니 다.

  • 전단 경로 아래 에서 두 가지 예 를 통 해 이 두 가지 경로 의 가장 기본 적 인 원 리 를 알 아 보 자.
    hash 모드
    hash 모드 는 URL. hash (즉 url 에서 # 식별 자 를 수정 한 내용) 를 통 해 이 루어 집 니 다.URL. hash 의 변경 사항 은 브 라 우 저 로 페이지 를 불 러 오지 않 지만 history 기록 을 주동 적 으로 수정 합 니 다.
    
    
    
    
      
      router-hash
    
    
    
      //        hash
      Home
      About
      
    // hash window.addEventListener('load', () => { app.innerHTML = location.hash.slice(1) }) // hash window.addEventListener('hashchange', () => { app.innerHTML = location.hash.slice(1) })

    history 模式

    history 模式 主要原理是使用了浏览器的 history API,主要是 history.pushState()history.replaceState() 两个方法。

    通过这两种方法修改 history 的 url 记录时,浏览器不会检查并加载新的 url 。

    这两个方法都是接受三个参数:

    1. 状态对象 -- 可以用来暂存一些数据
    2. 标题 -- 暂无效 一般写空字符串
    3. url -- 新的历史 url 记录

    两个方法的区别是 replaceState() 仅修改当前记录而非新建。

    
    
    
    
      
      router-history
    
    
    
      //       go       
      Home
      About
      
    // history function go(pathname) { history.pushState(null, '', pathname) app.innerHTML = pathname } // window.addEventListener('popstate', () => { app.innerHTML = location.pathname })

    手写一个超简易的 VueRouter

    看源码之前,先通过一个简易的 VueRouter 了解一下整体的结构和逻辑。

    class HistoryRoute {
      constructor() {
        this.current = null
      }
    }
    
    class VueRouter {
      constructor(opts) {
        this.mode = opts.mode || 'hash';
        this.routes = opts.routes || [];
        //        
        this.routesMap = this.creatMap(this.routes);
        //          
        this.history = new HistoryRoute();
        this.init();
      }
      //          history.current
      init() {
        if (this.mode === 'hash') {
          location.hash ? '' : location.hash = '/';
          window.addEventListener('load', () => {
            this.history.current = location.hash.slice(1);
          });
          window.addEventListener('hashchange', () => {
            this.history.current = location.hash.slice(1);
          });
        } else {
          location.pathname ? '' : location.pathname = '/';
          window.addEventListener('load', () => {
            this.history.current = location.pathname;
          });
          window.addEventListener('popstate', () => {
            this.history.current = location.pathname;
          })
        }
      }
      //        
      // {
      //   '/': HomeComponent,
      //   '/about': AboutCompontent
      // }
      creatMap(routes) {
        return routes.reduce((memo, current) => {
          memo[current.path] = current.component;
          return memo;
        }, {})
      }
    }
    // Vue.use(Router)    
    VueRouter.install = function (Vue) {
      //    $router $route   
      Object.defineProperty(Vue.prototype, '$router', {
        get() {
          return this.$root._router;
        }
      });
      Object.defineProperty(Vue.prototype, '$route', {
        get() {
          return this.$root._route;
        }
      });
      //      beforeCreate     
      Vue.mixin({
        beforeCreate() {
          //    this.$options.router       
          if (this.$options && this.$options.router) {
            this._router = this.$options.router;
            //   this             
            // https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js
            Vue.util.defineReactive(this, '_route', this._router.history);
          }
        },
      });
      //      & JSX  https://cn.vuejs.org/v2/guide/render-function.html
      //        router-link
      //       a   
      Vue.component('router-link', {
        props: {
          to: String,
          tag: String
        },
        methods: {
          handleClick() {
            const mode = this._self.$root._router.mode;
            location.href = mode === 'hash' ? `#${this.to}` : this.to;
          }
        },
        render: function (h) {
          const mode = this._self.$root._router.mode;
          const tag = this.tag || 'a';
          return (
            
              { this.$slots.default }
            
          );
        }
      });
      //        router-view
      //    history.current                   
      Vue.component('router-view', {
        render: function (h) {
          const current = this._self.$root._route.current;
          const routeMap = this._self.$root._router.routesMap;
          return h(routeMap[current]);
        }
      });
    }
    
    export default VueRouter;

    120 줄 코드 는 가장 기본 적 인 VueRouter 를 실현 하고 전체적인 구 조 를 정리 합 니 다.
  • 먼저 VueRouter 류 이 고 인 스타 그램 방법 이 있 습 니 다. 인 스타 그램 방법 은 Vue.use(VueRouter) 을 사용 할 때 호출 됩 니 다.
  • install 방법 에 Vue 원형 대상 의 두 가지 속성 $router $routerouter-view router-link 두 개의 전역 구성 요 소 를 추가 했다.
  • VueRouter 류 에서 구조 함수 처 리 를 통 해 들 어 오 는 매개 변 수 를 생 성하 고 루트 맵 표를 생 성하 고 init 방법 을 호출 합 니 다.
  • init 방법 에서 경로 변 화 를 감청 하고 history. current 를 변경 합 니 다.
  • history. current 는 현재 경 로 를 표시 합 니 다. install 에서 응답 식 속성 _route 으로 정의 되 었 습 니 다. 이 속성 이 바 뀌 면 의존 하 는 응답 이 렌 더 링 router-view 의 구성 요 소 를 촉발 합 니 다.

  • 지금 은 VueRouter 에 대해 기본 적 인 인식 을 가지 게 되 었 고 소스 코드 를 보 러 갈 때 좀 쉬 워 졌 다.
    소스 코드 를 맛보다.
    다음은 제 가 VueRouter 소스 코드 를 보고 글 과 결합 한 학습 노트 입 니 다.
    원본 코드 를 읽 는 과정 에서 이해 하기 쉬 운 주석 을 썼 습 니 다. 원본 코드 를 읽 는 데 도움 이 되 기 를 바 랍 니 다. github: vue - router 소스 코드.
    vue - router 의 src 디 렉 터 리 는 다음 과 같 습 니 다. 다음은 이 주요 파일 의 역할 을 순서대로 분석 하 겠 습 니 다.
    index.js
    VueRouter 의 입구 파일 은 VueRouter 클래스 를 정의 하고 내 보 내 는 역할 을 합 니 다.다음은 index.js 소스 코드 로 flow 와 관련 된 유형 정의 와 함수 의 구체 적 인 실현 을 삭 제 했 습 니 다. 먼저 전체적인 구조 와 각 부분의 기능 을 살 펴 보 겠 습 니 다.
    // ...
    //    VueRouter  
    export default class VueRouter {
      //            
      // install    vue      ,Vue.use        install   
      static install: () => void;
      static version: string;
    
      //                   
      constructor (options) {}
      //               
      match ( raw, current, redirectedFrom ) {}
      //    history.current       
      get currentRoute () {}
    
      //        ,        
      init (app) {}
    
      //           
      beforeEach (fn) {} //       
      beforeResolve (fn) {} //       
      afterEach (fn) {} //       
      onReady (cb, errorCb) {} //            
      onError (errorCb) {} //              
    
      //      history     
      push (location, onComplete, onAbort) {}
      replace (location, onComplete, onAbort) {}
      go (n) {}
      back () {}
      forward () {}
    
      //          
      getMatchedComponents (to) {}
      //      
      resolve (to, current, append) {}
      //                
      addRoutes (routes) {}
    }
    
    //       ,push     
    function registerHook (list, fn) {}
    //     (hash / history)   location.href
    function createHref (base, fullPath, mode) {}
    
    //          
    VueRouter.install = install
    VueRouter.version = '__VERSION__'
    
    //         window.Vue         Vue.use        
    if (inBrowser && window.Vue) {
      window.Vue.use(VueRouter)
    }

    다음은 주요 방법의 구체 적 인 실현 을 살 펴 보 자.
    constructor
    VueRouter 구조 함수, 주로 세 가지 일 을 했 습 니 다.
  • 들 어 오 는 매개 변수 options, 즉 호출 new VueRouter() 시 들 어 오 는 매개 변 수 를 초기 화 합 니 다.
  • match 일치 함 수 를 만 들 고 현재 path 나 name 에 대응 하 는 경로 구성 요소 와 일치 합 니 다.
  • 서로 다른 모델 에 따라 history 인 스 턴 스 를 생 성하 고 history 인 스 턴 스 는 점프, 감청 등 방법 을 제공 합 니 다.

  • history 인 스 턴 스 및 match 일치 함수 에 대해 서 는 나중에 말씀 드 리 겠 습 니 다.
    constructor(options = {}) {
      this.app = null //      ,  init       
      this.apps = [] //          ,  init     
      this.options = options //        
      this.beforeHooks = [] //          
      this.resolveHooks = [] //          
      this.afterHooks = [] //          
      this.matcher = createMatcher(options.routes || [], this) //    match     
    
      let mode = options.mode || 'hash' //    hash   
      // history                 hash   
      this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
      if (this.fallback) {
        mode = 'hash'
      }
      //            abstract   
      if (!inBrowser) {
        mode = 'abstract'
      }
      this.mode = mode
    
      //          history   
      switch (mode) {
        case 'history':
          this.history = new HTML5History(this, options.base)
          break
        case 'hash':
          this.history = new HashHistory(this, options.base, this.fallback)
          break
        case 'abstract':
          this.history = new AbstractHistory(this, options.base)
          break
        default:
          if (process.env.NODE_ENV !== 'production') {
            assert(false, `invalid mode: ${mode}`)
          }
      }
    }

    init install.js 에서 init 함 수 는 루트 구성 요소 인 스 턴 스 의 beforeCreate 수명 주기 함수 에서 호출 되 어 루트 구성 요소 인 스 턴 스 로 전 달 됩 니 다.
    //        
    init(app) {
      //                    
      process.env.NODE_ENV !== 'production' && assert(
        install.installed,
        `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
        `before creating root instance.`
      )
      //         
      this.apps.push(app)
    
      //    app     
      // https://github.com/vuejs/vue-router/issues/2639
      app.$once('hook:destroyed', () => {
        //     ,  app   this.apps      ,      
        const index = this.apps.indexOf(app)
        if (index > -1) this.apps.splice(index, 1)
        // ensure we still have a main app or null if no apps
        // we do not release the router so it can be reused
        if (this.app === app) this.app = this.apps[0] || null
      })
    
      // app          
      if (this.app) {
        return
      }
    
      this.app = app
    
      //        
      const history = this.history
    
      if (history instanceof HTML5History) {
        history.transitionTo(history.getCurrentLocation())
      } else if (history instanceof HashHistory) {
        const setupHashListener = () => {
          history.setupListeners()
        }
        history.transitionTo(
          history.getCurrentLocation(),
          setupHashListener,
          setupHashListener
        )
      }
      //       ,        _route   ,      
      history.listen(route => {
        this.apps.forEach((app) => {
          app._route = route
        })
      })
    }

    install.js
    이 파일 은 주로 인 스 톨 방법 을 정의 하고 내 보 냅 니 다. Vue.use(VueRouter) 에서 호출 됩 니 다.인 스타 그램 방법 은 주로 이 몇 가지 일 을 했다.
  • 전역 적 으로 beforeCreate 갈고리 함수 에 섞 여 모든 vue 구성 요소 인 스 턴 스 에 같은 경로 인 스 턴 스 를 가리 키 는 _routerRoot 속성 을 추가 하여 모든 구성 요소 에서 경로 정보 와 방법 을 얻 을 수 있 도록 합 니 다.
  • Vue. prototype 에 마 운 트 $router $route 두 가지 속성 은 각각 경로 인 스 턴 스 와 현재 경로 임 을 나타 낸다.
  • 전역 등록 router - link router - view 구성 요소.
  • import View from './components/view'
    import Link from './components/link'
    
    export let _Vue
    
    export function install (Vue) {
      //           
      if (install.installed && _Vue === Vue) return
      install.installed = true
      // install      Vue     _Vue 
      //                 Vue   
      _Vue = Vue
    
      const isDef = v => v !== undefined
    
      const registerInstance = (vm, callVal) => {
        let i = vm.$options._parentVnode
        if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
          i(vm, callVal)
        }
      }
      //        beforeCreate        
      Vue.mixin({
        beforeCreate () {
          //        router   ,        
          if (isDef(this.$options.router)) {
            //      
            this._routerRoot = this
            this._router = this.$options.router
            //      ,       VueRouter   init   
            this._router.init(this)
            // _router       
            Vue.util.defineReactive(this, '_route', this._router.history.current)
          } else {
            //         $parent       _routerRoot   ,       
            this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
          }
          registerInstance(this, this)
        },
        destroyed () {
          registerInstance(this)
        }
      })
      //    $router $route
      Object.defineProperty(Vue.prototype, '$router', {
        get () { return this._routerRoot._router }
      })
    
      Object.defineProperty(Vue.prototype, '$route', {
        get () { return this._routerRoot._route }
      })
      //      router-link router-view  
      Vue.component('RouterView', View)
      Vue.component('RouterLink', Link)
    
      const strats = Vue.config.optionMergeStrategies
      // use the same hook merging strategy for route hooks
      strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
    }

    create-route-map.js create-route-map.js 파일 은 path 와 name 에 따 른 맵 을 만 드 는 데 사용 되 는 createRouteMap 방법 을 내 보 냅 니 다.
    // ...
    //      map
    export function createRouteMap(routes, oldPathList, oldPathMap, oldNameMap) {
      // pathList            
      const pathList = oldPathList || []
      //    path       
      const pathMap = oldPathMap || Object.create(null)
      //    name       
      const nameMap = oldNameMap || Object.create(null)
      //           pathList pathMap nameMap  
      routes.forEach(route => {
        addRouteRecord(pathList, pathMap, nameMap, route)
      })
      //        *       ,            
      for (let i = 0, l = pathList.length; i < l; i++) {
        if (pathList[i] === '*') {
          pathList.push(pathList.splice(i, 1)[0])
          l--
          i--
        }
      }
      return {
        pathList,
        pathMap,
        nameMap
      }
    }
    
    function addRouteRecord(pathList, pathMap, nameMap, route, parent, matchAs) {}
    
    function compileRouteRegex(path, pathToRegexpOptions) {}
    
    function normalizePath(path, parent, strict) {}

    addRouteRecord
    createRouteMap 함수 에서 가장 중요 한 단 계 는 경로 설정 을 옮 겨 다 니 며 맵 에 추 가 된 addRouteRecord 함수 입 니 다.addRoute Record 함수 역할 은 두 개의 맵 표를 생 성 하 는 것 입 니 다. PathMap 과 NameMap 은 각각 path 와 name 을 통 해 해당 하 는 경로 기록 대상 을 조회 할 수 있 습 니 다. 경로 기록 대상 은 meta, props, 그리고 가장 중요 한 components 보기 구성 요소 인 스 턴 스 를 포함 하여 router-view 그룹 에 렌 더 링 할 수 있 습 니 다.
    //           
    function addRouteRecord(pathList, pathMap, nameMap, route, parent, matchAs) {
      //    path, name
      const { path, name } = route
      //        
      const pathToRegexpOptions = route.pathToRegexpOptions || {}
      //     path
      //    pathToRegexpOptions.strict            /
      //         /                  
      const normalizedPath = normalizePath(
        path,
        parent,
        pathToRegexpOptions.strict //            (default: false)
      )
      //            ?(   :false)
      //       caseSensitive   pathToRegexpOptions.sensitive     
      if (typeof route.caseSensitive === 'boolean') {
        pathToRegexpOptions.sensitive = route.caseSensitive
      }
    
      //        
      const record = {
        path: normalizedPath,
        regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
        //         ,         
        components: route.components || {
          default: route.component
        },
        instances: {},
        name,
        parent,
        matchAs, // alias         path    ,    matchAs   
        redirect: route.redirect,
        beforeEnter: route.beforeEnter,
        meta: route.meta || {},
        props: route.props == null ? {} : route.components ?
          route.props : {
            default: route.props
          }
      }
      if (route.children) {
        //        ,     ,        ,     。
        //        name                  
        // https://github.com/vuejs/vue-router/issues/629
        if (process.env.NODE_ENV !== 'production') {
          if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
            warn(
              false,
              `Named Route '${route.name}' has a default child route. ` +
              `When navigating to this named route (:to="{name: '${route.name}'"), ` +
              `the default child route will not be rendered. Remove the name from ` +
              `this route and use the name of the default child route for named ` +
              `links instead.`
            )
          }
        }
        //         children   ,      
        route.children.forEach(child => {
          //          path   matchAs
          const childMatchAs = matchAs ?
            cleanPath(`${matchAs}/${child.path}`) :
            undefined
          addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
        })
      }
      //      alias            
      if (route.alias !== undefined) {
        const aliases = Array.isArray(route.alias) ?
          route.alias : [route.alias]
    
        aliases.forEach(alias => {
          const aliasRoute = {
            path: alias,
            children: route.children
          }
          addRouteRecord(
            pathList,
            pathMap,
            nameMap,
            aliasRoute,
            parent,
            record.path || '/' // matchAs
          )
        })
      }
      //    path map
      if (!pathMap[record.path]) {
        pathList.push(record.path)
        pathMap[record.path] = record
      }
      //         
      if (name) {
        if (!nameMap[name]) {
          nameMap[name] = record
        } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
          warn(
            false,
            `Duplicate named routes definition: ` +
            `{ name: "${name}", path: "${record.path}" }`
          )
        }
      }
    }

    재 귀적 인 하위 경로 에는 경로 이름 이 있 고 기본 적 인 하위 경로 가 있 을 때 개발 환경 에 알림 이 있 습 니 다.이 힌트 는 bug 를 피 하 는 데 사 용 됩 니 다. 구체 적 으로 대응 하 는 issue 를 볼 수 있 습 니 다. 쉽게 말 하면 이름 경로 에 기본 하위 경로 가 있 을 때 입 니 다.
    routes: [{ 
      path: '/home', 
      name: 'home',
      component: Home,
      children: [{
        path: '',
        name: 'home.index',
        component: HomeIndex
      }]
    }]

    사용 to="/home" 은 HomeIndex 기본 하위 경로 로 이동 하고 사용 :to="{ name: 'home' }" 은 Home 으로 만 이동 하 며 HomeIndex 기본 하위 경로 가 표시 되 지 않 습 니 다.위의 addRoute Record 함수 원본 코드 를 통 해 이 두 가지 점프 방식 path 와 name 이 서로 다른 이 유 를 알 수 있 습 니 다. path 와 name 을 통 해 각각 두 개의 맵 표 에서 해당 하 는 경로 기록 을 찾 기 때 문 입 니 다. pathMap 생 성 과정 에서 먼저 하위 경로 로 돌아 갑 니 다. 예 를 들 어 이 하위 경로 의 경로 기록 을 추가 할 때 key 는 /home 입 니 다.하위 경로 추가 후 부모 경로 추가 시 판단 /home 이 존재 하면 pathMap 에 추가 되 지 않 습 니 다.한편, nameMap 의 key 는 name 이 고 home 대응 하 는 것 은 Home 구성 요소 이 며 home.index 대응 하 는 HomeIndex 입 니 다.
    create-matcher.js
    createmacher 함 수 는 경로 설정 에 따라 createRouteMap 방법 으로 맵 표를 만 들 고 일치 하 는 경로 기록 match 와 경로 기록 addRoutes 두 가지 방법 을 제공 합 니 다.
    addRoutes 는 루트 설정 을 동적 으로 추가 하 는 데 사 용 됩 니 다.match 는 들 어 오 는 location 와 경로 대상 에 따라 새로운 경로 대상 을 되 돌려 줍 니 다.
    //    routes      VueRouter     routes     
    // router    VueRouter   
    export function createMatcher(routes, router) {
      //        
      const { pathList, pathMap, nameMap } = createRouteMap(routes)
    
      function addRoutes(routes) {
        createRouteMap(routes, pathList, pathMap, nameMap)
      }
      //     
      function match(raw, currentRoute, redirectedFrom) {
        const location = normalizeLocation(raw, currentRoute, false, router)
        const { name } = location
    
        if (name) {
          //       
          //    location   record               
        } else if (location.path) {
          //       
          //    location   record               
        }
        //                     
        return _createRoute(null, location)
      }
    
      function redirect(record, location) {
        // ...
      }
    
      function alias(record, location, matchAs) {
        // ...
      }
    
      //            
      function _createRoute(record, location, redirectedFrom) {
        //        redirect
        if (record && record.redirect) {
          return redirect(record, redirectedFrom || location)
        }
        //       alias
        if (record && record.matchAs) {
          return alias(record, location, record.matchAs)
        }
        return createRoute(record, location, redirectedFrom, router)
      }
    
      return {
        match,
        addRoutes
      }
    }

    history/base.js
    history / base. js 에서 History 류 를 정 의 했 는데 주요 한 역할 은 경로 가 변화 할 때 transition To 방법 을 호출 하여 해당 하 는 경로 기록 을 얻 고 일련의 수비 갈고리 함 수 를 순서대로 집행 하 는 것 이다.
    export class History {
      constructor (router, base) {
        this.router = router
        this.base = normalizeBase(base)
        // start with a route object that stands for "nowhere"
        this.current = START
        this.pending = null
        this.ready = false
        this.readyCbs = []
        this.readyErrorCbs = []
        this.errorCbs = []
      }
      listen (cb) {
        this.cb = cb
      }
      onReady (cb, errorCb) {
        if (this.ready) {
          cb()
        } else {
          this.readyCbs.push(cb)
          if (errorCb) {
            this.readyErrorCbs.push(errorCb)
          }
        }
      }
      onError (errorCb) {
        this.errorCbs.push(errorCb)
      }
      //     ,  VueRouter               
      transitionTo (location, onComplete, onAbort) {
        //          
        const route = this.router.match(location, this.current)
        //       
        this.confirmTransition(route, () => {
          //                
          //       ,     _route       ,      
          //    afterHooks       
          this.updateRoute(route)
          //    hashchange   
          onComplete && onComplete(route)
          //    URL
          this.ensureURL()
          //       ready   
          if (!this.ready) {
            this.ready = true
            this.readyCbs.forEach(cb => { cb(route) })
          }
        }, err => {
          if (onAbort) {
            onAbort(err)
          }
          if (err && !this.ready) {
            this.ready = true
            this.readyErrorCbs.forEach(cb => { cb(err) })
          }
        })
      }
      //       
      confirmTransition (route, onComplete, onAbort) {
        const current = this.current
        //         
        const abort = err => {
          if (isError(err)) {
            if (this.errorCbs.length) {
              this.errorCbs.forEach(cb => { cb(err) })
            } else {
              warn(false, 'uncaught error during route navigation:')
              console.error(err)
            }
          }
          onAbort && onAbort(err)
        }
        //             
        if (
          isSameRoute(route, current) &&
          // in the case the route map has been dynamically appended to
          route.matched.length === current.matched.length
        ) {
          this.ensureURL()
          return abort()
        }
    
        //                ,       ,     
        const { updated, deactivated, activated } = resolveQueue(this.current.matched, route.matched)
        //       
        const queue = [].concat(
          //        
          extractLeaveGuards(deactivated),
          //    beforeEach   
          this.router.beforeHooks,
          //        ,           
          extractUpdateHooks(updated),
          //        enter     
          activated.map(m => m.beforeEnter),
          //         
          resolveAsyncComponents(activated)
        )
        //     
        this.pending = route
        //    ,     queue         
        const iterator = (hook, next) => {
          //            
          if (this.pending !== route) {
            return abort()
          }
          try {
            //     
            hook(route, current, (to) => {
              //             next,             
              //        
              //          next()     
              if (to === false || isError(to)) {
                // next(false) -> abort navigation, ensure current URL
                this.ensureURL(true)
                abort(to)
              } else if (
                typeof to === 'string' ||
                (typeof to === 'object' && (
                  typeof to.path === 'string' ||
                  typeof to.name === 'string'
                ))
              ) {
                // next('/')    next({ path: '/' }) ->    
                abort()
                if (typeof to === 'object' && to.replace) {
                  this.replace(to)
                } else {
                  this.push(to)
                }
              } else {
                //      next
                //           runQueue    step(index + 1)
                // confirm transition and pass on the value
                next(to)
              }
            })
          } catch (e) {
            abort(e)
          }
        }
        //         
        runQueue(queue, iterator, () => {
          const postEnterCbs = []
          const isValid = () => this.current === route
          //             ,        ,    runQueue    cb()
          //                    
          const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
          const queue = enterGuards.concat(this.router.resolveHooks)
          //            ,       
          runQueue(queue, iterator, () => {
            //     
            if (this.pending !== route) {
              return abort()
            }
            this.pending = null
            onComplete(route)
            if (this.router.app) {
              this.router.app.$nextTick(() => {
                postEnterCbs.forEach(cb => { cb() })
              })
            }
          })
        })
      }
      updateRoute (route) {
        const prev = this.current
        this.current = route
        this.cb && this.cb(route)
        this.router.afterHooks.forEach(hook => {
          hook && hook(route, prev)
        })
      }
    }

    레 퍼 런 스
  • vue - router 소스 코드 분석 - 전체 절차
  • 전단 진급 의 길 - VueRouter 소스 코드 분석
  • MDN History_API
  • 한 장의 사고 지도 가 Vue | Vue - Outer | Vuex 소스 구조
  • 를 깊이 이해 하도록 도와 준다.
  • vue - router 소스 코드 읽 기 학습
  • 좋은 웹페이지 즐겨찾기