fangchaolong
文章57
标签24
分类10
vue 路由模式

vue 路由模式

原理:通过改变url,在不重新请求页面的情况下,更新页面视图

实现方式

1、Hash方式利用URL地址追加”#”的方式
2、History方式,基于浏览器的内置历史对象暴露的方法

Vue 中是通过 mode 这一参数控制路由的实现模式


const router=new VueRouter({
    mode:'history',
    routes:[...]
})

vue-router 源码解析
源码地址:当前vue项目/node_modules/vue-router/src/index.js


export default class VueRouter {
  mode: string // 传入的字符串参数,指示history类别
  history: HashHistory | HTML5History | AbstractHistory // 实际起作用的对象属性,必须是以上三个类的枚举
  matcher: Matcher
  fallback: boolean // 如浏览器不支持,'history'模式需回滚为'hash'模式
  beforeHooks: Array
  resolveHooks: Array
  afterHooks: Array

  constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'
    this.fallback =
      mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    } // 通过supportsPushState判断浏览器是否支持'history'模式
    if (!inBrowser) {
      mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式
    }
    this.mode = 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}`)
        }
    }
  }

  match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
    return this.matcher.match(raw, current, redirectedFrom)
  }

  get currentRoute (): ?Route {
    return this.history && this.history.current
  }

  init (app: any /* Vue component instance */) {
    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)

    // set up app destroyed handler
    // https://github.com/vuejs/vue-router/issues/2639
    app.$once('hook:destroyed', () => {
      // clean out app from this.apps array once destroyed
      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

      if (!this.app) this.history.teardown()
    })

    // main app previously initialized
    // return as we don't need to set up new history listener
    if (this.app) {
      return
    }

    this.app = app

    const history = this.history

    if (history instanceof HTML5History || history instanceof HashHistory) {
      const handleInitialScroll = routeOrError => {
        const from = history.current
        const expectScroll = this.options.scrollBehavior
        const supportsScroll = supportsPushState && expectScroll

        if (supportsScroll && 'fullPath' in routeOrError) {
          handleScroll(this, routeOrError, from, false)
        }
      }
      const setupListeners = routeOrError => {
        history.setupListeners()
        handleInitialScroll(routeOrError)
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupListeners,
        setupListeners
      )
    }

    history.listen(route => {
      this.apps.forEach(app => {
        app._route = route
      })
    })
  }
  // 路由的全局前置钩子函数,跳转之前的回调
  beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }

  beforeResolve (fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }
  // 路由的全局后置钩子函数,跳转之后的回调
  afterEach (fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

  onReady (cb: Function, errorCb?: Function) {
    this.history.onReady(cb, errorCb)
  }

  onError (errorCb: Function) {
    this.history.onError(errorCb)
  }
  // 路由对象暴露的切换路由的方法
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
  }
  // 路由对象暴露的切换路由的方法
  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.replace(location, resolve, reject)
      })
    } else {
      this.history.replace(location, onComplete, onAbort)
    }
  }

  go (n: number) {
    this.history.go(n)
  }

  back () {
    this.go(-1)
  }

  forward () {
    this.go(1)
  }

  getMatchedComponents (to?: RawLocation | Route): Array {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if (!route) {
      return []
    }
    return [].concat.apply(
      [],
      route.matched.map(m => {
        return Object.keys(m.components).map(key => {
          return m.components[key]
        })
      })
    )
  }

  resolve (
    to: RawLocation,
    current?: Route,
    append?: boolean
  ): {
    location: Location,
    route: Route,
    href: string,
    // for backwards compat
    normalizedTo: Location,
    resolved: Route
  } {
    current = current || this.history.current
    const location = normalizeLocation(to, current, append, this)
    const route = this.match(location, current)
    const fullPath = route.redirectedFrom || route.fullPath
    const base = this.history.base
    const href = createHref(base, fullPath, this.mode)
    return {
      location,
      route,
      href,
      // for backwards compat
      normalizedTo: location,
      resolved: route
    }
  }

  getRoutes () {
    return this.matcher.getRoutes()
  }

  addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
    this.matcher.addRoute(parentOrRoute, route)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
  // 动态添加权限路由
  addRoutes (routes: Array) {
    if (process.env.NODE_ENV !== 'production') {
      warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')
    }
    this.matcher.addRoutes(routes)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
}


mode参数

1.默认 hash
2. history。如果浏览器不支持 history 新特性,则采用 hash
3. 如果不在浏览器环境下,就采用 abstract(Node环境下)


mode 区别

  1. mode:”hash” 多了 “#”
http://localhost:8080/#/login

    

2.mode:”history”

http://localhost:8080/recommend

使用history模式时使用注意,项目正式上线的时候,需要进行配置一下nginx指向,只要访问这个域名就指向当前打包好的index.html。
原因:如果不指定的话,默认是访问当前项目的recommend文件夹,“hash”不需要指定

hashHistory

hash(“#”)的作用是加载 URL 中指示网页中的位置。
“#”本身以及它后面的字符称职位 hash,可通过 window.location.hash 获取


特点:

  1. hash 虽然出现在 url 中,但不会被包括在 http 请求中,它是用来指导浏览器动作的,对服务器端完全无用,因此,改变 hash 不会重新加载页面。
  2. 可以为 hash 的改变添加监听事件:
    window.addEventListener("hashchange",funcRef,false)
  3. 每一次改变 hash(window.localtion.hash),都会在浏览器访问历史中增加一个记录。
    利用 hash 的以上特点,就可以来实现前端路由”更新视图但不重新请求页面”的功能了。

HashHistory 拥有两个方法,一个是 push, 一个是 replace

两个方法:HashHistory.push() 和 HashHistory.replace()
  
HashHistory.push() 将新路由添加到浏览器访问历史的栈顶


push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  this.transitionTo(location, route => {
    pushHash(route.fullPath)
    onComplete && onComplete(route)
  }, onAbort)
}

function pushHash (path) {
  window.location.hash = path
}

HashHisttory.push()

遵循堆栈理念:先进先出

从设置路由改变到视图更新的流程:
$router.push() –> HashHistory.push() –> History.transitionTo() –> History.updateRoute() –> {app._route = route} –> vm.render()

解析:


$router.push() //调用方法
HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
History.transitionTo() //监测更新,更新则调用History.updateRoute()
History.updateRoute() //更新路由
{app._route= route} //替换当前app路由
vm.render() //更新视图

##HTML5History:##
  History interface 是浏览器历史记录栈提供的接口,通过back()、forward()、go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。

  从 HTML5开始,History interface 提供了2个新的方法:pushState()、replaceState() 使得我们可以对浏览器历史记录栈进行修改:

  window.history.pushState(stateObject,title,url)
  window.history,replaceState(stateObject,title,url)     

  stateObject:当浏览器跳转到新的状态时,将触发 Popstate 事件,该事件将携带这个 stateObject 参数的副本

  title:所添加记录的标题

  url:所添加记录的 url

    
  这2个方法有个共同的特点:当调用他们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会立即发送请求该url,这就为单页应用前端路由,更新视图但不重新请求页面提供了基础

    
  1.push与hash模式类似,只是将window.hash改为history.pushState

  2.replace与hash模式类似,只是将window.replace改为history.replaceState

  3.监听地址变化在HTML5History的构造函数中监听popState(window.onpopstate)

两种模式比较
1.pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
2.pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
3.pushState可额外设置title属性供后续使用
4.history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误

无以生计,卖文苟延

微信
支付宝