blog

10分でシングルページアプリケーションのルーティングを完全に理解する

"前提:"Webページでは、サイト内の他のページにジャンプするためにクリックすることができるボタンがあります。 "シングルページアプリケーション:" ボタンをクリックすると、新たなhtmlリクエストは...

Sep 26, 2020 · 8 min. read
シェア

シングルページアプリケーションの特徴

" あるウェブページに、クリックするとサイト内の他のページにジャンプできるボタンがあるとします

"マルチページアプリケーション:" ボタンをクリックすると、新しいhtmlリソースがロードされ、ページ全体が更新されます;

「単一ページアプリ: ボタンをクリックすると、新しいhtmlリクエストがなく、部分的なリフレッシュのみで、ネイティブに近い、シルクのように滑らかなエクスペリエンスが生まれます。

SPAのシングルページのアプリケーションは、なぜほとんどリフレッシュすることができますか?そのSP -シングルページのため、アプリケーションへの最初の時間では、つまり、唯一のhtmlページとそのパブリック静的リソースのリターンは、その後のいわゆる "ジャンプ "は、もはやサーバーからhtmlファイルを取得するには、単にDOMの置換操作をシミュレートされます。DOMの置換操作がシミュレートされます。

では、jsはどのようにしてコンポーネントが切り替わるタイミングをキャッチし、リフレッシュせずにブラウザのURLを変更するのでしょうか?ハッシュとHTML5Historyに依存します。

hash

特徴

  1. www.xiaoming.html#bar ハッシュルートと同様に、#の後のハッシュ値が変更された場合、データはサーバーから要求されません。
  2. サーバー側の協力は不要
  3. SEOに不利

原則

HTML5History

特徴

  1. www.xiaoming.html/bar history.pushState(state, title, url) 履歴モードは、HTML5の新機能であり、ハッシュルーティングよりも直感的な、長いこのようなものに、ページのジャンプをシミュレートするブラウザのルーティングを更新するために、ルーティングの変更は、DOMを操作するためにpopstateイベントをリッスンしています!
  2. リダイレクトにはバックエンドの協力が必要
  3. 比較的SEOに強い

原則

vue-routerソースコード解釈

Vueのルートvue-routerを例にして、そのソースコードを読み解いてみましょう。

Tips:この記事の焦点はシングル・ページ・ルーティングの2つのモードを説明することなので、主な説明のためにいくつかのキーコードのみを以下に列挙します:

  1. 登録プラグイン
  2. ルーティングパターンを区別するための VueRouter コンストラクタ
  3. グローバル登録コンポーネント
  4. hash / HTML5History モードのプッシュおよびリッスン メソッド
  5. transitionTo メソッド

登録プラグイン

まず第一に、プラグインとして、Daddy Vueが使用するインストール方法を公開する自己認識を持つことが重要です。

ソースコードのinstall.jsファイルは、プラグインのインストールを登録するメソッドinstallを定義し、各コンポーネントのフック関数のメソッドをミックスし、beforeCreateフックが実行されたときにルートを初期化します:

Vue.mixin({
 beforeCreate () {
 if (isDef(this.$options.router)) {
 this._routerRoot = this
 this._router = this.$options.router
 this._router.init(this)
 Vue.util.defineReactive(this, '_route', this._router.history.current)
 } else {
 this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
 }
 registerInstance(this, this)
 },
 // 全文...メッシュの表示は省略した。
 ...
});

mode

次に、index.jsからプラグイン全体の基本クラスであるVueRouterを探します。コンストラクタの中にあるのを見るのは難しくありません。

... import {install} from './install'; import {HashHistory} from './history/hash'; import {HTML5History} from './history/html5'; ... export default class VueRouter { static install: () => void; constructor (options: RouterOptions = {}) { if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' } this.mode = mode 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}`) } } } }

グローバル登録router-link

<router-link/> <router-view/>ここで、「vue-routerを使用する場合、どこに一般的なものが導入されているのか?

install.jsファイルに戻ると、router-viewとrouter-linkコンポーネントが導入され、グローバルに登録されています:

import View from './components/view'; import Link from './components/link'; ... Vue.component('RouterView', View); Vue.component('RouterLink', Link);

./components/link.js<router-link/>では、クリックイベントはデフォルトでコンポーネントにバインドされ、クリックするとハンドラメソッドが起動し、適切なルーティングアクションが実行されます。

const handler = e => {
 if (guardEvent(e)) {
 if (this.replace) {
 router.replace(location, noop)
 } else {
 router.push(location, noop)
 }
 }
};

router.replace router.push冒頭で述べたように、VueRouterのコンストラクタはモードごとにHistoryインスタンスを初期化するため、初期化方法も異なります。次に、それぞれのモードのソースコードを見てみましょう。

hash

HTML5Historyモードをサポートするブラウザ環境では、ブラウザのアドレスを変更するためにhistory.pushStateが呼び出されます。その他のブラウザ環境では、アドレスを新しいハッシュに置き換えるためにlocation.hash = pathが直接使用されます。

実際には、ここで最初に読んだいくつかの疑問は、すでにハッシュモードなので、なぜまだsupportsPushStateを判断するのですか? もともと、scrollBehaviorサポートするために、history.pushStateは、各URLの履歴は、各ルートの位置情報を保存するためのキーを持つように、キーに渡すことができます。keyは各ルートの位置情報を保存するために使われます。

一方、プロトタイプにバインドされた setupListeners メソッドは、ハッシュの変更をリッスンする役割を果たします。HTML5History モードをサポートするブラウザでは、popstate イベントをリッスンし、その他のブラウザでは、ハッシュの変更をリッスンします。変更を聞くと、handleRoutingEvent メソッドがトリガーされ、親の transitionTo ジャンプ ロジックを呼び出して DOM の置換を実行します。変更をリスニングした後、handleRoutingEvent メソッドがトリガーされ、親クラスの transitionTo ジャンプロジックを呼び出して DOM 置換を実行します。

import { pushState, replaceState, supportsPushState } from '../util/push-state' ... export class HashHistory extends History { setupListeners () { ... const handleRoutingEvent = () => { const current = this.current if (!ensureSlash()) { return } // transitionTo親クラスへの呼び出し 歴史 ジャンプメソッドの下で、ジャンプパスの後にハッシュ化される this.transitionTo(getHash(), route => { if (supportsScroll) { handleScroll(this.router, route, current, true) } if (!supportsPushState) { replaceHash(route.fullPath) } }) } const eventType = supportsPushState ? 'popstate' : 'hashchange' window.addEventListener( eventType, handleRoutingEvent ) this.listeners.push(() => { window.removeEventListener(eventType, handleRoutingEvent) }) } 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 ) } } ... // 入ってくるパスをハッシュとして扱うURL function getUrl (path) { const href = window.location.href const i = href.indexOf('#') const base = i >= 0 ? href.slice(0, i) : href return `${base}#${path}` } ... // hash function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)) } else { window.location.hash = path } } // 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 && typeof window.history.pushState === 'function' })()

HTML5History

同様に、HTML5History クラスは history/html5.js で定義されています。

ブラウザのパスを変更するためにhistory.pusheStateを呼び出すpushプロトタイプメソッドを定義します。

同時に、プロトタイプの setupListeners メソッドは popstate のイベントをリッスンし、必要に応じて DOM の置換を行います。

import {pushState, replaceState, supportsPushState} from '../util/push-state';
...
export class HTML5History extends History {
 setupListeners () {
 const handleRoutingEvent = () => {
 const current = this.current;
 const location = getLocation(this.base);
 if (this.current === START && location === this._startLocation) {
 return
 }
 this.transitionTo(location, route => {
 if (supportsScroll) {
 handleScroll(router, route, current, true)
 }
 })
 }
 window.addEventListener('popstate', handleRoutingEvent)
 this.listeners.push(() => {
 window.removeEventListener('popstate', handleRoutingEvent)
 })
 }
 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)
 }
}
...
// util/push-state.jsファイル内のメソッド
export function pushState (url?: string, replace?: boolean) {
 saveScrollPosition()
 const history = window.history
 try {
 if (replace) {
 const stateCopy = extend({}, history.state)
 stateCopy.key = getStateKey()
 history.replaceState(stateCopy, '', url)
 } else {
 history.pushState({ key: setStateKey(genStateKey()) }, '', url)
 }
 } catch (e) {
 window.location[replace ? 'replace' : 'assign'](url)
 }
}

transitionTo はルート変更ロジックを処理します。

上記の2つのルーティングパターンは、どちらもリスニング時にthis.transitionToをトリガーします。実はこれはhistory/base.jsの基底クラスで定義されたプロトタイプメソッドで、ルーティングの変更ロジックを処理します。const route = this.router.match(location, this.current)this.confirmTransitionまず、入力された値を現在の値と比較し、対応するルートオブジェクトを返します。次に、新しいルートが現在のルートと同じかどうかを判断し、同じであればそのまま返します。同じでなければ、コールバックを実行してルートオブジェクトを更新し、ビューに関連するDOMを置き換えます。

export class History {
 ...
 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)
 })
 if (!this.ready) {
 this.ready = true
 this.readyCbs.forEach(cb => {
 cb(route)
 })
 }
 },
 err => {
 if (onAbort) {
 onAbort(err)
 }
 if (err && !this.ready) {
 this.ready = true
 // https://.//-//25
 if (!isRouterError(err, NavigationFailureType.redirected)) {
 this.readyErrorCbs.forEach(cb => {
 cb(err)
 })
 } else {
 this.readyCbs.forEach(cb => {
 cb(route)
 })
 }
 }
 }
 )
 }
 ...
}
Read next

CSS3の新機能を学ぶフロントエンド

CSS3の現状\nCSS3の新機能には互換性の問題があります。\nモバイル対応はPCより良い\n継続的な改善\n応用範囲は比較的広い\n現段階での主な学習:新セレクタ、ボックスモデルなど\nCSS3 新しいセレクタ\n\nプロパティ

Sep 26, 2020 · 7 min read