シングルページアプリケーションの特徴
" あるウェブページに、クリックするとサイト内の他のページにジャンプできるボタンがあるとします 。
"マルチページアプリケーション:" ボタンをクリックすると、新しいhtmlリソースがロードされ、ページ全体が更新されます;
「単一ページアプリ:「 ボタンをクリックすると、新しいhtmlリクエストがなく、部分的なリフレッシュのみで、ネイティブに近い、シルクのように滑らかなエクスペリエンスが生まれます。
SPAのシングルページのアプリケーションは、なぜほとんどリフレッシュすることができますか?そのSP -シングルページのため、アプリケーションへの最初の時間では、つまり、唯一のhtmlページとそのパブリック静的リソースのリターンは、その後のいわゆる "ジャンプ "は、もはやサーバーからhtmlファイルを取得するには、単にDOMの置換操作をシミュレートされます。DOMの置換操作がシミュレートされます。
では、jsはどのようにしてコンポーネントが切り替わるタイミングをキャッチし、リフレッシュせずにブラウザのURLを変更するのでしょうか?ハッシュとHTML5Historyに依存します。
hash
特徴
www.xiaoming.html#barハッシュルートと同様に、#の後のハッシュ値が変更された場合、データはサーバーから要求されません。- サーバー側の協力は不要
- SEOに不利
原則
HTML5History
特徴
www.xiaoming.html/barhistory.pushState(state, title, url)履歴モードは、HTML5の新機能であり、ハッシュルーティングよりも直感的な、長いこのようなものに、ページのジャンプをシミュレートするブラウザのルーティングを更新するために、ルーティングの変更は、DOMを操作するためにpopstateイベントをリッスンしています!- リダイレクトにはバックエンドの協力が必要
- 比較的SEOに強い
原則
vue-routerソースコード解釈
Vueのルートvue-routerを例にして、そのソースコードを読み解いてみましょう。
Tips:この記事の焦点はシングル・ページ・ルーティングの2つのモードを説明することなので、主な説明のためにいくつかのキーコードのみを以下に列挙します:
- 登録プラグイン
- ルーティングパターンを区別するための VueRouter コンストラクタ
- グローバル登録コンポーネント
- hash / HTML5History モードのプッシュおよびリッスン メソッド
- 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)
})
}
}
}
)
}
...
}





