成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

vue:路由實(shí)現(xiàn)原理

mikasa / 3127人閱讀

摘要:方法與方法不同之處在于,它并不是將新路由添加到瀏覽器訪問(wèn)歷史棧頂,而是替換掉當(dāng)前的路由可以看出,它與的實(shí)現(xiàn)結(jié)構(gòu)基本相似,不同點(diǎn)它不是直接對(duì)進(jìn)行賦值,而是調(diào)用方法將路由進(jìn)行替換。

隨著前端應(yīng)用的業(yè)務(wù)功能起來(lái)越復(fù)雜,用戶(hù)對(duì)于使用體驗(yàn)的要求越來(lái)越高,單面(SPA)成為前端應(yīng)用的主流形式。大型單頁(yè)應(yīng)用最顯著特點(diǎn)之一就是采用的前端路由系統(tǒng),通過(guò)改變URL,在不重新請(qǐng)求頁(yè)面的情況下,更新頁(yè)面視圖。

更新視圖但不重新請(qǐng)求頁(yè)面,是前端路由原理的核心之一,目前在瀏覽器環(huán)境中這一功能的實(shí)現(xiàn)主要有2種方式:

利用URL中的hash("#");

利用History interfaceHTML5中新增的方法;

vue-routerVue.js框架的路由插件,它是通過(guò)mode這一參數(shù)控制路由的實(shí)現(xiàn)模式的:

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

創(chuàng)建VueRouter的實(shí)例對(duì)象時(shí),mode以構(gòu)造參數(shù)的形式傳入。

src/index.js

export default class VueRouter{
    mode: string; // 傳入的字符串參數(shù),指示history類(lèi)別
  history: HashHistory | HTML5History | AbstractHistory; // 實(shí)際起作用的對(duì)象屬性,必須是以上三個(gè)類(lèi)的枚舉
  fallback: boolean; // 如瀏覽器不支持,"history"模式需回滾為"hash"模式
  
  constructor (options: RouterOptions = {}) {
    
    let mode = options.mode || "hash" // 默認(rèn)為"hash"模式
    this.fallback = mode === "history" && !supportsPushState // 通過(guò)supportsPushState判斷瀏覽器是否支持"history"模式
    if (this.fallback) {
      mode = "hash"
    }
    if (!inBrowser) {
      mode = "abstract" // 不在瀏覽器環(huán)境下運(yùn)行需強(qiáng)制為"abstract"模式
    }
    this.mode = mode

    // 根據(jù)mode確定history實(shí)際的類(lèi)并實(shí)例化
    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 (app: any /* Vue component instance */) {
    
    const history = this.history

    // 根據(jù)history的類(lèi)別執(zhí)行相應(yīng)的初始化操作和監(jiān)聽(tīng)
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  // VueRouter類(lèi)暴露的以下方法實(shí)際是調(diào)用具體history對(duì)象的方法
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.push(location, onComplete, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    this.history.replace(location, onComplete, onAbort)
  }
}

作為參數(shù)傳入的字符串屬性mode只是一個(gè)標(biāo)記,用來(lái)指示實(shí)際起作用的對(duì)象屬性history的實(shí)現(xiàn)類(lèi),兩者對(duì)應(yīng)關(guān)系:

    modehistory:
        "history":HTML5History;
        "hash":HashHistory;
        "abstract":AbstractHistory;

在初始化對(duì)應(yīng)的history之前,會(huì)對(duì)mode做一些校驗(yàn):若瀏覽器不支持HTML5History方式(通過(guò)supportsPushState變量判斷),則mode設(shè)為hash;若不是在瀏覽器環(huán)境下運(yùn)行,則mode設(shè)為abstract;

VueRouter類(lèi)中的onReady(),push()等方法只是一個(gè)代理,實(shí)際是調(diào)用的具體history對(duì)象的對(duì)應(yīng)方法,在init()方法中初始化時(shí),也是根據(jù)history對(duì)象具體的類(lèi)別執(zhí)行不同操作

HashHistory

hash("#")符號(hào)的本來(lái)作用是加在URL指示網(wǎng)頁(yè)中的位置:

http://www.example.com/index.html#print

#本身以及它后面的字符稱(chēng)之為hash可通過(guò)window.location.hash屬性讀取.

hash雖然出現(xiàn)在url中,但不會(huì)被包括在http請(qǐng)求中,它是用來(lái)指導(dǎo)瀏覽器動(dòng)作的,對(duì)服務(wù)器端完全無(wú)用,因此,改變hash不會(huì)重新加載頁(yè)面。

可以為hash的改變添加監(jiān)聽(tīng)事件:

window.addEventListener("hashchange",funcRef,false)

每一次改變hash(window.location.hash),都會(huì)在瀏覽器訪問(wèn)歷史中增加一個(gè)記錄。

利用hash的以上特點(diǎn),就可以來(lái)實(shí)現(xiàn)前端路由"更新視圖但不重新請(qǐng)求頁(yè)面"的功能了。

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
}

transitionTo()方法是父類(lèi)中定義的是用來(lái)處理路由變化中的基礎(chǔ)邏輯的,push()方法最主要的是對(duì)windowhash進(jìn)行了直接賦值:

window.location.hash=route.fullPath

hash的改變會(huì)自動(dòng)添加到瀏覽器的訪問(wèn)歷史記錄中。
那么視圖的更新是怎么實(shí)現(xiàn)的呢,我們來(lái)看看父類(lèi)History中的transitionTo()方法:

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

updateRoute (route: Route) {
  
  this.cb && this.cb(route)
  
}

listen (cb: Function) {
  this.cb = cb
}

可以看到,當(dāng)路由變化時(shí),調(diào)用了Hitory中的this.cb方法,而this.cb方法是通過(guò)History.listen(cb)進(jìn)行設(shè)置的,回到VueRouter類(lèi)定義中,找到了在init()中對(duì)其進(jìn)行了設(shè)置:

init (app: any /* Vue component instance */) {
    
  this.apps.push(app)

  history.listen(route => {
    this.apps.forEach((app) => {
      app._route = route
    })
  })
}

appVue組件實(shí)例,但是Vue作為漸進(jìn)式的前端框架,本身的組件定義中應(yīng)該是沒(méi)有有關(guān)路由內(nèi)置屬性_route,如果組件中要有這個(gè)屬性,應(yīng)該是在插件加載的地方,即VueRouterinstall()方法中混入Vue對(duì)象的,install.js的源碼:

export function install (Vue) {
  
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, "_route", this._router.history.current)
      }
      registerInstance(this, this)
    },
  })
}

通過(guò)Vue.mixin()方法,全局注冊(cè)一個(gè)混合,影響注冊(cè)之后所有創(chuàng)建的每個(gè)Vue實(shí)例,該混合在beforeCreate鉤子中通過(guò)Vue.util.defineReactive()定義了響應(yīng)式的_route屬性。所謂響應(yīng)式屬性,即當(dāng)_route值改變時(shí),會(huì)自動(dòng)調(diào)用Vue實(shí)例的render()方法,更新視圖。

$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()
HashHistory.replace()

replace()方法與push()方法不同之處在于,它并不是將新路由添加到瀏覽器訪問(wèn)歷史棧頂,而是替換掉當(dāng)前的路由:

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  this.transitionTo(location, route => {
    replaceHash(route.fullPath)
    onComplete && onComplete(route)
  }, onAbort)
}
  
function replaceHash (path) {
  const i = window.location.href.indexOf("#")
  window.location.replace(
    window.location.href.slice(0, i >= 0 ? i : 0) + "#" + path
  )
}

可以看出,它與push()的實(shí)現(xiàn)結(jié)構(gòu)基本相似,不同點(diǎn)它不是直接對(duì)window.location.hash進(jìn)行賦值,而是調(diào)用window.location.replace方法將路由進(jìn)行替換。

監(jiān)聽(tīng)地址欄

上面的VueRouter.push()VueRouter.replace()是可以在vue組件的邏輯代碼中直接調(diào)用的,除此之外在瀏覽器中,用戶(hù)還可以直接在瀏覽器地址欄中輸入改變路由,因此還需要監(jiān)聽(tīng)瀏覽器地址欄中路由的變化 ,并具有與通過(guò)代碼調(diào)用相同的響應(yīng)行為,在HashHistory中這一功能通過(guò)setupListeners監(jiān)聽(tīng)hashchange實(shí)現(xiàn):

setupListeners () {
  window.addEventListener("hashchange", () => {
    if (!ensureSlash()) {
      return
    }
    this.transitionTo(getHash(), route => {
      replaceHash(route.fullPath)
    })
  })
}

該方法設(shè)置監(jiān)聽(tīng)了瀏覽器事件hashchange,調(diào)用的函數(shù)為replaceHash,即在瀏覽器地址欄中直接輸入路由相當(dāng)于代碼調(diào)用了replace()方法。

HTML5History

History interface是瀏覽器歷史記錄棧提供的接口,通過(guò)back(),forward(),go()等方法,我們可以讀取瀏覽器歷史記錄棧的信息,進(jìn)行各種跳轉(zhuǎn)操作。
HTML5開(kāi)始,History interface提供了2個(gè)新的方法:pushState(),replaceState()使得我們可以對(duì)瀏覽器歷史記錄棧進(jìn)行修改:

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

stateObject:當(dāng)瀏覽器跳轉(zhuǎn)到新的狀態(tài)時(shí),將觸發(fā)popState事件,該事件將攜帶這個(gè)stateObject參數(shù)的副本

title:所添加記錄的標(biāo)題

url:所添加記錄的url

2個(gè)方法有個(gè)共同的特點(diǎn):當(dāng)調(diào)用他們修改瀏覽器歷史棧后,雖然當(dāng)前url改變了,但瀏覽器不會(huì)立即發(fā)送請(qǐng)求該url,這就為單頁(yè)應(yīng)用前端路由,更新視圖但不重新請(qǐng)求頁(yè)面提供了基礎(chǔ)。

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

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  const { current: fromRoute } = this
  this.transitionTo(location, route => {
    replaceState(cleanPath(this.base + route.fullPath))
    handleScroll(this.router, route, fromRoute, false)
    onComplete && onComplete(route)
  }, onAbort)
}

// src/util/push-state.js
export function pushState (url?: string, replace?: boolean) {
  saveScrollPosition()
  // try...catch the pushState call to get around Safari
  // DOM Exception 18 where it limits to 100 pushState calls
  const history = window.history
  try {
    if (replace) {
      history.replaceState({ key: _key }, "", url)
    } else {
      _key = genKey()
      history.pushState({ key: _key }, "", url)
    }
  } catch (e) {
    window.location[replace ? "replace" : "assign"](url)
  }
}

export function replaceState (url?: string) {
  pushState(url, true)
}

代碼結(jié)構(gòu)以及更新視圖的邏輯與hash模式基本類(lèi)似,只不過(guò)將對(duì)window.location.hash()直接進(jìn)行賦值window.location.replace()改為了調(diào)用history.pushState()history.replaceState()方法。

HTML5History中添加對(duì)修改瀏覽器地址欄URL的監(jiān)聽(tīng)popstate是直接在構(gòu)造函數(shù)中執(zhí)行的:

constructor (router: Router, base: ?string) {
  
  window.addEventListener("popstate", e => {
    const current = this.current
    this.transitionTo(getLocation(this.base), route => {
      if (expectScroll) {
        handleScroll(router, route, current, true)
      }
    })
  })
}

HTML5History用到了HTML5的新特性,需要瀏版本的支持,通過(guò)supportsPushState來(lái)檢查:

src/util/push-state.js

export const supportsPushState = inBrowser && (function () {
  const ua = window.navigator.userAgent

  if (
    (ua.indexOf("Android 2.") !== -1 || ua.indexOf("Android 4.0") !== -1) &&
    ua.indexOf("Mobile Safari") !== -1 &&
    ua.indexOf("Chrome") === -1 &&
    ua.indexOf("Windows Phone") === -1
  ) {
    return false
  }

  return window.history && "pushState" in window.history
})()

以上就是hash模式與history模式源碼導(dǎo)讀,這2種模式都是通過(guò)瀏覽器接口實(shí)現(xiàn)的,除此之外,vue-router還為非瀏覽器環(huán)境準(zhǔn)備了一個(gè)abstract模式,其原理為用一個(gè)數(shù)組stack模擬出瀏覽器歷史記錄棧的功能。

兩種模式比較

一般的需求場(chǎng)景中,hash模式與history模式是差不多的,根據(jù)MDN的介紹,調(diào)用history.pushState()相比于直接修改hash主要有以下優(yōu)勢(shì):

pushState設(shè)置的新url可以是與當(dāng)前url同源的任意url,而hash只可修改#后面的部分,故只可設(shè)置與當(dāng)前同文檔的url

pushState設(shè)置的新url可以與當(dāng)前url一模一樣,這樣也會(huì)把記錄添加到棧中,而hash設(shè)置的新值必須與原來(lái)不一樣才會(huì)觸發(fā)記錄添加到棧中

pushState通過(guò)stateObject可以添加任意類(lèi)型的數(shù)據(jù)記錄中,而hash只可添加短字符串

pushState可額外設(shè)置title屬性供后續(xù)使用

history模式的問(wèn)題

對(duì)于單頁(yè)應(yīng)用來(lái)說(shuō),理想的使用場(chǎng)景是僅在進(jìn)入應(yīng)用時(shí)加載index.html,后續(xù)在的網(wǎng)絡(luò)操作通過(guò)ajax完成,不會(huì)根據(jù)url重新請(qǐng)求頁(yè)面,但是如果用戶(hù)直接在地址欄中輸入并回車(chē),瀏覽器重啟重新加載等特殊情況。

hash模式僅改變hash部分的內(nèi)容,而hash部分是不會(huì)包含在http請(qǐng)求中的(hash#):

http://oursite.com/#/user/id //如請(qǐng)求,只會(huì)發(fā)送http://oursite.com/

所以hash模式下遇到根據(jù)url請(qǐng)求頁(yè)面不會(huì)有問(wèn)題

history模式則將url修改的就和正常請(qǐng)求后端的url一樣(history不帶#)

http://oursite.com/user/id

如果這種向后端發(fā)送請(qǐng)求的話(huà),后端沒(méi)有配置對(duì)應(yīng)/user/idget路由處理,會(huì)返回404錯(cuò)誤。

官方推薦的解決辦法是在服務(wù)端增加一個(gè)覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個(gè) index.html 頁(yè)面,這個(gè)頁(yè)面就是你 app 依賴(lài)的頁(yè)面。同時(shí)這么做以后,服務(wù)器就不再返回 404 錯(cuò)誤頁(yè)面,因?yàn)閷?duì)于所有路徑都會(huì)返回 index.html 文件。為了避免這種情況,在 Vue 應(yīng)用里面覆蓋所有的路由情況,然后在給出一個(gè) 404 頁(yè)面?;蛘撸绻怯?Node.js 作后臺(tái),可以使用服務(wù)端的路由來(lái)匹配 URL,當(dāng)沒(méi)有匹配到路由的時(shí)候返回 404,從而實(shí)現(xiàn) fallback

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94890.html

相關(guān)文章

  • 前端路由簡(jiǎn)介以及vue-router實(shí)現(xiàn)原理

    摘要:后端路由簡(jiǎn)介路由這個(gè)概念最先是后端出現(xiàn)的。前端路由模式隨著的流行,異步數(shù)據(jù)請(qǐng)求交互運(yùn)行在不刷新瀏覽器的情況下進(jìn)行。通過(guò)這些就能用另一種方式來(lái)實(shí)現(xiàn)前端路由了,但原理都是跟實(shí)現(xiàn)相同的。 后端路由簡(jiǎn)介 路由這個(gè)概念最先是后端出現(xiàn)的。在以前用模板引擎開(kāi)發(fā)頁(yè)面時(shí),經(jīng)常會(huì)看到這樣 http://www.xxx.com/login 大致流程可以看成這樣: 瀏覽器發(fā)出請(qǐng)求 服務(wù)器監(jiān)聽(tīng)到80端口(或4...

    tuomao 評(píng)論0 收藏0
  • 前端路由原理解析和實(shí)現(xiàn)

    摘要:如何實(shí)現(xiàn)前端路由要實(shí)現(xiàn)前端路由,需要解決兩個(gè)核心如何改變卻不引起頁(yè)面刷新如何檢測(cè)變化了下面分別使用和兩種實(shí)現(xiàn)方式回答上面的兩個(gè)核心問(wèn)題。 原文鏈接:github.com/whinc/blog/… 在單頁(yè)應(yīng)用如此流行的今天,曾經(jīng)令人驚嘆的前端路由已經(jīng)成為各大框架的基礎(chǔ)標(biāo)配,每個(gè)框架都提供了強(qiáng)大的路由功能,導(dǎo)致路由實(shí)現(xiàn)變的復(fù)雜。想要搞懂路由內(nèi)部實(shí)現(xiàn)還是有些困難的,但是如果只想了解路由實(shí)現(xiàn)基本...

    lavor 評(píng)論0 收藏0
  • vue-router實(shí)現(xiàn)原理

    摘要:我們知道是的核心插件,而當(dāng)前項(xiàng)目一般都是單頁(yè)面應(yīng)用,也就是說(shuō)是應(yīng)用在單頁(yè)面應(yīng)用中的。原理是傳統(tǒng)的頁(yè)面應(yīng)用,是用一些超鏈接來(lái)實(shí)現(xiàn)頁(yè)面切換和跳轉(zhuǎn)的其實(shí)剛才單頁(yè)面應(yīng)用跳轉(zhuǎn)原理即實(shí)現(xiàn)原理實(shí)現(xiàn)原理原理核心就是更新視圖但不重新請(qǐng)求頁(yè)面。 近期面試,遇到關(guān)于vue-router實(shí)現(xiàn)原理的問(wèn)題,在查閱了相關(guān)資料后,根據(jù)自己理解,來(lái)記錄下。我們知道vue-router是vue的核心插件,而當(dāng)前vue項(xiàng)目...

    vibiu 評(píng)論0 收藏0
  • 面試官常問(wèn)——vue

    摘要:如果要相應(yīng)狀態(tài)改變,通常最好使用計(jì)算屬性或取而代之。那解決問(wèn)題的思路便是在改變的情況下,保證頁(yè)面的不刷新。后面值的變化,并不會(huì)導(dǎo)致瀏覽器向服務(wù)器發(fā)出請(qǐng)求,瀏覽器不發(fā)出請(qǐng)求,也就不會(huì)刷新頁(yè)面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動(dòng)態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...

    BlackMass 評(píng)論0 收藏0
  • 面試官常問(wèn)——vue

    摘要:如果要相應(yīng)狀態(tài)改變,通常最好使用計(jì)算屬性或取而代之。那解決問(wèn)題的思路便是在改變的情況下,保證頁(yè)面的不刷新。后面值的變化,并不會(huì)導(dǎo)致瀏覽器向服務(wù)器發(fā)出請(qǐng)求,瀏覽器不發(fā)出請(qǐng)求,也就不會(huì)刷新頁(yè)面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動(dòng)態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...

    xingqiba 評(píng)論0 收藏0
  • 面試官常問(wèn)——vue

    摘要:如果要相應(yīng)狀態(tài)改變,通常最好使用計(jì)算屬性或取而代之。那解決問(wèn)題的思路便是在改變的情況下,保證頁(yè)面的不刷新。后面值的變化,并不會(huì)導(dǎo)致瀏覽器向服務(wù)器發(fā)出請(qǐng)求,瀏覽器不發(fā)出請(qǐng)求,也就不會(huì)刷新頁(yè)面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動(dòng)態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...

    quietin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

mikasa

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<