フロントエンド アプリケーションのビジネス機能がますます複雑になり、ユーザー エクスペリエンスに対するユーザーの要件がますます高くなるにつれて、シングル ページ アプリケーション (SPA) がフロントエンド アプリケーションの主流の形式になりました。大規模なシングルページ アプリケーションの最も注目すべき機能の 1 つは、ページを再要求せずに URL を変更することでページ ビューを更新するフロントエンド ルーティング システムの使用です。 「ページを再リクエストせずにビューを更新する」ことは、フロントエンド ルーティングのコア原則の 1 つです。現在、ブラウザ環境でこの機能を実装するには、主に次の 2 つの方法があります。
vue-router は、Vue.js フレームワークのルーティング プラグインです。まずはソース コードから始めて、コードと原理を読み、vue-router がこれら 2 つの方法を通じてフロントエンド ルーティングを実装する方法を浅いところから深いところまで学んでいきましょう。 モードパラメータvue-router では、mode パラメータを使用してルーティング実装モードを制御します。 const ルーター = 新しい VueRouter({ モード: '履歴'、 ルート: [...] }) VueRouter のインスタンス オブジェクトを作成するときに、モードがコンストラクター パラメーターとして渡されます。疑問を念頭に置きながらソースコードを読んでいくと、VueRouter クラスの定義から始めることができます。通常、プラグインによって公開されるクラスは、ソースコード src のルート ディレクトリにある index.js ファイルで定義されます。ファイルを開くと、VueRouter クラスの定義を確認できます。mode パラメータに関連する抜粋は次のとおりです。 デフォルトクラスVueRouterをエクスポートします。 mode: string; // 渡された文字列パラメータは履歴カテゴリを示しますhistory: HashHistory | HTML5History | AbstractHistory; // 実際に機能するオブジェクトプロパティは、上記の 3 つのクラスの列挙である必要がありますfallback: boolean; // ブラウザがサポートしていない場合は、「履歴」モードを「ハッシュ」モードにロールバックする必要がありますconstructor (options: RouterOptions = {}) { let mode = options.mode || 'hash' // デフォルトは 'hash' モードです this.fallback = mode === 'history' && !supportsPushState // supportsPushState を使用して、ブラウザーが 'history' モードをサポートしているかどうかを判断します if (this.fallback) { モード = 'ハッシュ' } ブラウザ内で mode = 'abstract' // ブラウザ環境で実行されていない場合は、強制的に 'abstract' モードにします} this.mode = モード // モードに基づいて実際の履歴クラスを決定し、switch (mode) をインスタンス化します。 ケース「履歴」: this.history = 新しい HTML5History(this、options.base) 壊す ケース 'ハッシュ': this.history = 新しい HashHistory(this、options.base、this.fallback) 壊す ケース「抽象」: this.history = 新しい AbstractHistory(this、options.base) 壊す デフォルト: process.env.NODE_ENV !== 'production' の場合 { assert(false, `無効なモード: ${mode}`) } } } init (app: any /* Vueコンポーネントインスタンス */) { 定数履歴 = this.history // 履歴カテゴリに応じて対応する初期化操作と監視を実行します。if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } そうでない場合 (history instanceof HashHistory) { const セットアップハッシュリスナー = () => { 履歴.setupListeners() } 履歴の遷移先( 履歴.getCurrentLocation()、 セットアップハッシュリスナー、 セットアップハッシュリスナー ) } history.listen(ルート => { this.apps.forEach((アプリ) => { app._route = ルート }) }) } // VueRouter クラスによって公開される次のメソッドは、実際には特定の履歴オブジェクトのメソッドを呼び出します push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.push(場所、onComplete、onAbort) } 置換 (場所: RawLocation、onComplete?: Function、onAbort?: Function) { this.history.replace(場所、onComplete、onAbort) } } 次のことがわかります。 パラメータとして渡される文字列属性モードは、実際に動作するオブジェクト属性履歴の実装クラスを示すための単なるマーカーです。両者の対応関係は次のとおりです。
対応する履歴を初期化する前に、モードのチェックが行われます。ブラウザが HTML5History メソッドをサポートしていない場合 (supportsPushState 変数によって判断)、モードは強制的に 'hash' に設定されます。ブラウザ環境で実行されていない場合、モードは強制的に 'abstract' に設定されます。 VueRouter クラスの onReady()、push() などのメソッドは単なるプロキシです。実際には、特定の履歴オブジェクトの対応するメソッドを呼び出します。init() メソッドで初期化されると、履歴オブジェクトの特定のカテゴリに応じて異なる操作が実行されます。 ブラウザ環境の 2 つのメソッドは、それぞれ HTML5History クラスと HashHistory クラスに実装されています。これらはすべて src/history フォルダーに定義されており、同じディレクトリ内の base.js ファイルで定義されている History クラスから継承されます。 History は一般的な基本メソッドを定義しますが、直接読むと混乱する可能性があります。まずは、HTML5History クラスと HashHistory クラスのよく知られた push() メソッドと replace() メソッドから始めましょう。 ハッシュ履歴ソースコードを見る前に原則を確認しましょう。 ハッシュ ("#") 記号は、もともと Web ページの場所を示すために URL に追加されます。 https://www.example.com/index.html#… を参照してください。 # 記号自体とそれに続く文字はハッシュと呼ばれ、window.location.hash プロパティを通じて読み取ることができます。以下の機能があります:
window.addEventListener("hashchange", funcRef, false) ハッシュ (window.location.hash) が変更されるたびに、ブラウザのアクセス履歴に記録が追加されます。 ハッシュの上記特性を利用することで、フロントエンドルーティングにおいて「ビューを更新するがページを再リクエストしない」という機能を実装することができます。 ハッシュ履歴.push()HashHistory の push() メソッドを見てみましょう。 プッシュ (場所: RawLocation、onComplete?: Function、onAbort?: Function) { this.transitionTo(場所、ルート => { pushHash(ルート.fullPath) onComplete && onComplete(ルート) }, 中止時) } 関数pushHash(パス){ window.location.hash = パス } transitionTo() メソッドは、ルート変更の基本ロジックを処理するために親クラスで定義されています。push() メソッドは主に、ウィンドウのハッシュを直接割り当てるために使用されます。 window.location.hash = ルート.fullPath ハッシュの変更はブラウザのアクセス履歴に自動的に追加されます。 では、ビューはどのように更新されるのでしょうか? 親クラス History の transitionTo() メソッドを見てみましょう。 transitionTo (場所: RawLocation、onComplete?: Function、onAbort?: Function) { const ルート = this.router.match(場所、this.current) this.confirmTransition(ルート、() => { this.updateRoute(ルート) ... }) } updateRoute (ルート: ルート) { this.cb && this.cb(ルート) } listen (cb: 関数) { this.cb = cb } ご覧のとおり、ルートが変更されると、History の this.cb メソッドが呼び出され、History.listen(cb) を通じて this.cb メソッドが設定されます。 VueRouter クラスの定義に戻ると、init() メソッドで設定されていることがわかりました。 init (app: any /* Vueコンポーネントインスタンス */) { this.apps.push(アプリ) history.listen(ルート => { this.apps.forEach((アプリ) => { app._route = ルート }) }) } コメントによると、app は Vue コンポーネント インスタンスですが、プログレッシブ フロントエンド フレームワークである Vue は、コンポーネント定義に組み込みルート属性 _route を持つべきではないことがわかっています。コンポーネントにこの属性が必要な場合は、プラグインがロードされる場所、つまり VueRouter の install() メソッドで、Vue オブジェクトにミックスする必要があります。install.js ソース コードを確認すると、次の段落があります。 エクスポート関数インストール (Vue) { Vue.mixin({ 作成前() { if (isDef(this.$options.router)) { this._router = this.$options.router this._router.init(これ) Vue.util.defineReactive(this, '_route', this._router.history.current) } インスタンスを登録します(これ、これ) }, }) } Vue.mixin() メソッドを通じて、ミックスインはグローバルに登録され、登録後に作成されるすべての Vue インスタンスに影響します。ミックスインは、beforeCreate フックの Vue.util.defineReactive() を通じて、レスポンシブな _route 属性を定義します。いわゆる responsive プロパティは、_route 値が変更されると、Vue インスタンスの render() メソッドが自動的に呼び出され、ビューが更新されることを意味します。 まとめると、ルート変更の設定からビューの更新までのプロセスは次のようになります。 $router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> {app._route = route} --> vm.render() ハッシュ履歴.replace()replace() メソッドは、新しいルートをブラウザのアクセス履歴スタックの先頭に追加するのではなく、現在のルートを置き換えるという点で push() メソッドとは異なります。 置換 (場所: RawLocation、onComplete?: Function、onAbort?: Function) { this.transitionTo(場所、ルート => { ハッシュを置換(ルート.fullPath) onComplete && onComplete(ルート) }, 中止時) } 関数 replaceHash (パス) { 定数 i = window.location.href.indexOf('#') ウィンドウの場所を置き換える( window.location.href.slice(0, i >= 0 ? i : 0) + '#' + パス ) } 実装構造は基本的にpush()と似ていることがわかります。違いは、window.location.hashに直接値を割り当てるのではなく、window.location.replaceメソッドを呼び出してルートを置き換えることです。 アドレスバーを聞く上で説明した VueRouter.push() と VueRouter.replace() は、vue コンポーネントのロジック コード内で直接呼び出すことができます。また、ブラウザでは、ユーザーがブラウザのアドレス バーにルートの変更を直接入力することもできます。そのため、VueRouter はブラウザのアドレス バーでルートの変更を監視し、コードを介して呼び出す場合と同じ応答動作を実現できる必要があります。 HashHistory では、この関数は setupListeners を通じて実装されます。 セットアップリスナー() { window.addEventListener('ハッシュ変更', () => { if (!ensureSlash()) { 戻る } this.transitionTo(getHash(), ルート => { ハッシュを置換(ルート.fullPath) }) }) } このメソッドは、ブラウザ イベント hashchange をリスナーとして設定し、呼び出される関数は replaceHash です。つまり、ブラウザのアドレス バーにルートを直接入力することは、コード内で replace() メソッドを呼び出すことと同じです。 HTML5の歴史履歴インターフェースは、ブラウザの履歴スタックによって提供されるインターフェースです。back()、forward()、go() などのメソッドを通じて、ブラウザの履歴スタックの情報を読み取り、さまざまなジャンプ操作を実行できます。 HTML5 以降、History インターフェースには pushState() と replaceState() という 2 つの新しいメソッドが用意されており、ブラウザの履歴スタックを変更できるようになります。 window.history.pushState(状態オブジェクト、タイトル、URL) window.history.replaceState(状態オブジェクト、タイトル、URL)
これら 2 つのメソッドには共通の機能があります。ブラウザの履歴スタックを変更するために呼び出されると、現在の URL が変更されても、ブラウザは URL にすぐにリクエストを送信しません (ブラウザは pushState() の呼び出し後にこの URL の読み込みを試行しません)。これにより、シングル ページ アプリケーションのフロントエンド ルーティングが「ビューを更新するがページを再リクエストしない」という基礎が提供されます。 vue-router のソースコードを見てみましょう。 プッシュ (場所: RawLocation、onComplete?: Function、onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(場所、ルート => { pushState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, ルート, fromRoute, false) onComplete && onComplete(ルート) }, 中止時) } 置換 (場所: RawLocation、onComplete?: Function、onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(場所、ルート => { replaceState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, ルート, fromRoute, false) onComplete && onComplete(ルート) }, 中止時) } // src/util/push-state.js エクスポート関数pushState(url?:文字列、replace?:ブール値){ スクロール位置を保存します。 // Safari を回避するために pushState 呼び出しをキャッチしてみてください // DOM 例外 18 では、pushState 呼び出しが 100 回に制限されます 定数履歴 = window.history 試す { (置換)の場合{ history.replaceState({ キー: _key }, '', url) } それ以外 { _key = genKey() history.pushState({ キー: _key }, '', url) } } キャッチ (e) { window.location[replace ? 'replace' : 'assign'](url) } } エクスポート関数replaceState(url?: string) { プッシュステート(url, true) } ビューを更新するコード構造とロジックは基本的にハッシュ モードと同様ですが、window.location.hash を window.location.replace() に直接割り当てる代わりに、history.pushState() メソッドと history.replaceState() メソッドを呼び出すように変更されています。 HTML5History のブラウザ アドレス バーの URL を変更するためのリスナーを追加すると、コンストラクター内で直接実行されます。 コンストラクタ (ルータ: Router、ベース: ?string) { window.addEventListener('popstate', e => { 定数 current = this.current this.transitionTo(getLocation(this.base)、ルート => { if (expectScroll) { handleScroll(ルーター、ルート、現在、true) } }) }) } もちろん、HTML5History は HTML5 の新機能を使用するため、特定のブラウザ バージョンのサポートが必要です。すでに説明したように、ブラウザがサポートしているかどうかは、変数 supportsPushState を通じてチェックされます。 // src/util/push-state.js エクスポートconst supportsPushState = inBrowser && (function () { 定数 ua = window.navigator.userAgent もし ( (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('モバイルSafari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1 ){ 偽を返す } window.history && 'pushState' を window.history で返す })() 以上がハッシュモードと履歴モードのソースコードの紹介です。両モードともブラウザインターフェースを通じて実装されています。また、vue-router はブラウザ以外の環境向けに抽象モードも用意しています。原理は配列スタックを使用してブラウザ履歴スタックの機能をシミュレートすることです。もちろん、上記はコアロジックの一部にすぎません。システムの堅牢性を確保するために、ソースコードには多くの補助ロジックがあり、それらも学習する価値があります。さらに、vue-routerにはルートマッチングやルータービューのビューコンポーネントなどの重要な部分があります。 2つのモードの比較一般的な需要シナリオでは、ハッシュ モードと履歴モードは似ていますが、ほとんどすべての記事では履歴モードの使用を推奨しています。その理由は、「#」記号が見苦しすぎるためです...0_0 "
もちろん、厳格な人間として、私たちは技術の質を外見だけで判断すべきではありません。 MDN によると、history.pushState() を呼び出すと、ハッシュを直接変更するよりも次の利点があります。
履歴モードの問題シングルページ アプリケーションの場合、理想的な使用シナリオは、アプリケーションに入るときにのみ index.html を読み込み、その後のネットワーク操作は Ajax を通じて完了し、URL に従ってページを再要求しないことです。ただし、ユーザーがアドレス バーに直接入力して Enter キーを押したり、ブラウザーが再起動してアプリケーションを再読み込みしたりするなど、特殊な状況に遭遇することは避けられません。 ハッシュ モードではハッシュ部分の内容のみが変更され、ハッシュ部分は HTTP リクエストに含まれません。 http://oursite.com/#/user/id // 再度リクエストすると、http://oursite.com/ のみが送信されます したがって、URL に基づいてページをリクエストする場合、ハッシュ モードでは問題は発生しません。 履歴モードでは、URL が通常のリクエスト バックエンドの URL と同じになるように変更されます。 http://oursite.com/user/id この場合、バックエンドにリクエストを再送信します。バックエンドに、対応する /user/id ルーティング処理が設定されていない場合は、404 エラーが返されます。公式に推奨されている解決策は、すべての状況をカバーするためにサーバー側に候補リソースを追加することです。URL がどの静的リソースとも一致しない場合は、アプリが依存する同じ index.html ページが返される必要があります。また、これを行うと、すべてのパスに対して index.html ファイルが返されるため、サーバーは 404 エラー ページを返さなくなります。これを回避するには、Vue アプリケーション内のすべてのルーティング状況をカバーしてから、404 ページを表示します。あるいは、バックエンドとして Node.js を使用する場合は、サーバー側のルーティングを使用して URL を照合し、一致するルートがない場合は 404 を返すことで、フォールバックを実装できます。 アプリケーションファイルを直接読み込む
Vue プロジェクトが vue-cli の webpack を通じてパッケージ化された後、コマンド ラインに次のプロンプトが表示されます。通常、開発中であろうとオンライン中であろうと、フロントエンド プロジェクトはサーバーを介してアクセスされ、「file:// で index.html を開く」ことはありません。ただし、プログラマーは皆、要件とシナリオが常に奇妙であることを知っています。製品マネージャーが考えられないことは何もありません。考えられないことだけです。 この記事を書いた当初の目的は、次のような問題に遭遇することでした。モバイル ディスプレイ プロジェクトを迅速に開発する必要があり、WebView を使用して Vue シングル ページ アプリケーションをロードすることにしましたが、バックエンド サーバーが提供されていなかったため、すべてのリソースをローカル ファイル システムからロードする必要がありました。 //Androidアプリラッパー パブリッククラスMainActivityはAppCompatActivityを拡張します{ プライベート WebView webView; @オーバーライド 保護されたvoid onCreate(バンドルsavedInstanceState) { super.onCreate(保存されたインスタンス状態); webView = 新しい WebView(これ); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl("file:///android_asset/index.html"); コンテンツビューを設定します。 } @オーバーライド パブリックブールonKeyDown(int keyCode, KeyEventイベント) { ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) の場合 { webView に戻る(); true を返します。 } false を返します。 } } この場合は「index.htmlをfile://で開く」必要があるようですので、まずは設定を行う必要があります。
これは明らかに必要な変更ですが、変更後も正常にロードできません。調査を重ねた結果、プロジェクトの開発時にルーターが履歴モード(美観のため...0_0")に設定されていたことが判明しました。ハッシュモードに変更すると、正常にロードできるようになりました。 なぜこのようなことが起こるのでしょうか?私はその理由を次のように分析しました。 ファイル システムから index.html を直接読み込む場合、URL は次のようになります。 ファイル:///android_asset/index.html ホームページビューが一致する必要があるパスは、パス: '/' です。 デフォルトの新しいルーターをエクスポートします({ モード: '履歴'、 ルート: [ { パス: '/'、 名前: 'インデックス', コンポーネント: IndexView } ] }) まず、HTML5History の履歴モードを見てみましょう。 確実にURLをプッシュする(ブール値) getLocation(this.base) !== this.current.fullPath の場合 { const current = cleanPath(this.base + this.current.fullPath) プッシュ? pushState(現在) : replaceState(現在) } } エクスポート関数 getLocation (base: string): string { パス = window.location.pathname とします ベース && path.indexOf(base) === 0 の場合 パス = パス.スライス(ベース.長さ) } 戻り値 (パス || '/') + window.location.search + window.location.hash } このロジックは、URL が存在することのみを確認します。パスは、window.location.pathname から切り取って直接取得されます。パスは index.html で終わるため、「/」と一致できず、「file:// で index.html を開いても機能しません」。 ハッシュモードをもう一度見てみましょう。HashHistoryでは次のようになります。 エクスポートクラスHashHistoryはHistoryを拡張します{ コンストラクタ (ルータ: Router、ベース: ?string、フォールバック: boolean) { ... スラッシュを保証する() } // アプリがマウントされるまで遅延されます // ハッシュ変更リスナーが早すぎるタイミングで起動されるのを避けるため セットアップリスナー() { window.addEventListener('ハッシュ変更', () => { if (!ensureSlash()) { 戻る } ... }) } 現在の場所を取得します(){ getHash() を返す } } 関数ensureSlash(): ブール値{ 定数パス = getHash() (path.charAt(0) === '/'の場合){ 真を返す } replaceHash('/' + パス) 偽を返す } エクスポート関数 getHash(): 文字列 { 定数 href = window.location.href 定数インデックス = href.indexOf('#') インデックス === -1 を返します ? '' : href.slice(index + 1) } コード ロジックでは、関数 EnsureSlash() が何度も登場していることがわかります。# 記号の後に '/' が続く場合は true を返し、そうでない場合は '/' が強制的に挿入されます。したがって、ファイル システムから index.html を開いた場合でも、URL は次の形式になることがわかります。 ファイル:///C:/Users/dist/index.html#/ getHash() メソッドによって返されるパスは '/' であり、これはホーム ビューのルートと一致します。 したがって、バックエンド サーバーの助けを借りずにファイル システムから直接 Vue シングルページ アプリケーションをロードする場合は、パッケージ化後のいくつかのパス設定に加えて、vue-router がハッシュ モードを使用していることを確認する必要もあります。 上記は、vue-router の観点から見たフロントエンドルーティングの 2 つの実装の詳細です。vue フロントエンドルーティングの 2 つの実装の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: linuxdeployqt を使用して Ubuntu で Qt プログラムをパッケージ化する問題を解決する
>>: Mysql データベースをバージョン 5.6.28 からバージョン 8.0.11 にアップグレードするときにプロジェクトを展開するときに発生する問題と解決策
この記事では、配列フィルタリングを実装するためのJavaScriptの具体的なコードを参考までに紹介...
開発中にこのような要件に遭遇したので、将来使用するために記録しました。需要背景キーボード ショートカ...
目次JSX環境の構築NPMを初期化するwebpackをインストールするBabelをインストールするw...
1. <body background=画像ファイル名 bgcolor=color text=...
mysql5.7.18のインストール時に次の問題が発生しました: プログラム入力ポイントfesetr...
背景私は新しいプロジェクト チームに配属されたので、プロジェクトでは js を使用する必要があります...
MySQL は、コミュニティ エディション (コミュニティ サーバー) とエンタープライズ エディシ...
仕える: # chkconfig --list すべてのシステム サービスを一覧表示します # ch...
MySQL のインデックスの種類には、通常のインデックス、一意のインデックス、全文インデックスがあり...
この記事の例では、jsでテーブルを動的に追加および削除するための具体的なコードを参考までに共有してい...
DOCTYPE 宣言 作成するすべてのページの先頭に、ドキュメント宣言が必要です。はい、そうでしょう...
目次ストレージエンジンMySQL でサポートされているストレージ エンジン同時実行制御ロック粒子をロ...
序文通常のユーザーはcrontabスケジュールタスクを定義します。たとえば、Oracleユーザーはス...
証券会社にいた頃、設計業務が忙しくなかったため、商品のマニュアルを書く役割を担ったことがありました。...
1. はじめにLinux でファイルの作成時刻が見つかるかどうかは、ファイル システムの種類によって...