フロントエンドルーティング原理の解析と実現
25993 ワード
原文リンク:github.com/whinc/blog/…
このように1ページのアプリケーションが流行している今日、驚くべきフロントエンドルーティングはすでに各フレームワークの基礎的な標準となり、各フレームワークは強力なルーティング機能を提供し、ルーティングの実現が複雑になっている.ルーティングの内部実装を理解するのは難しいが,ルーティング実装の基本原理だけを理解するのは簡単である.本稿では,フロントエンドルーティングの主流の実装方式hashとhistoryについて,オリジナルJS/React/Vueの計6バージョンを参考に,各バージョンの実装コードは約25~40行程度(空行を含む)提供する.
フロントエンドルーティングとは?
ルーティングの概念は、URLと処理関数とのマッピング関係を記述するサービス側に由来する.
Webフロントエンド単一ページアプリケーションSPA(Single Page Application)では、ルーティングは、ページをリフレッシュすることなく、URLとUIとのマッピング関係を記述する.
フロントエンドルーティングを実現するにはどうすればいいですか?
フロントエンドルーティングを実現するには、2つのコアを解決する必要があります. URLを変更してページのリフレッシュを起こさないにはどうすればいいですか? URLの変化をどのように検出しますか?
以下では,hashとhistoryの2つの実装方式を用いて,上記の2つのコア問題に答える.
hash実装 hashはURL中のhash( を引き起こすことはない. hashchangeイベントによってURLの変化を傍受し、URLを変更する方法は、ブラウザの前進後退によってURLを変更し、 がトリガーされる.
history実装 historyはpushStateとreplaceStateの2つの方法を提供し、この2つの方法はURLのpath部分を変更してページリフレッシュ を引き起こさない. historyではhashchangeイベントのようなpopstateイベントが提供されていますが、popstateイベントにはブラウザが前進後退してURLを変更するとpopstateイベントがトリガーされ、
オリジナルJS版フロントエンドルーティング実現
前節で説明した2つの実装方式に基づいて,hashバージョンとhistoryバージョンのルーティングをそれぞれ実装し,例はフレームワークに依存せずにオリジナルHTML/JS実装を用いた.
hashベースの実装
実行効果:
HTMLセクション:
JavaScriptセクション:
historyベースの実装
実行効果:
HTMLセクション:
JavaScriptセクション:
React版フロントエンドルーティング実装
hashベースの実装
実行効果:
使用方法はreact-routerと似ています.
BrowserRouter実装
Route実装
Link実装
historyベースの実装
実行効果:
使用方法はreact-routerと似ています.
HistoryRouter実装
Route実装
Link実装
Vueバージョンフロントエンドルーティング実装
hashベースの実装
実行効果:
使用方法はvue-routerと似ています(vue-routerはプラグインメカニズムを介してルーティングに注入されますが、実装の詳細は隠されています.コードを直感的に維持するために、ここではVueプラグインパッケージは使用されません):
router-view実装
router-link実装
historyベースの実装
実行効果:
使用方法はvue-routerと似ています.
router-view実装:
router-link実装
小結
フロントエンドルーティングのコア実装原理は簡単であるが、特定のフレームワークと組み合わせると、ダイナミックルーティング、ルーティングパラメータ、ルーティングアニメーションなど、フレームワークに多くの特性が追加され、ルーティング実装が複雑になる.本稿では、フロントエンドルーティングの最もコア部分の実装のみを分析し、hashとhistoryの2つのモードに基づいて、それぞれオリジナルJS/React/Vueの3つの実装を提供し、合計6つの実装バージョンを参考にして、あなたの役に立つことを望んでいます.
すべての例のコードはGithub倉庫:githubに置かれています.com/whinc/web-r…
リファレンス単一ページルーティングのいくつかの実装原理 を詳細に説明する単ページ応用ルーティング実現原理:React-Routerを例に .
このように1ページのアプリケーションが流行している今日、驚くべきフロントエンドルーティングはすでに各フレームワークの基礎的な標準となり、各フレームワークは強力なルーティング機能を提供し、ルーティングの実現が複雑になっている.ルーティングの内部実装を理解するのは難しいが,ルーティング実装の基本原理だけを理解するのは簡単である.本稿では,フロントエンドルーティングの主流の実装方式hashとhistoryについて,オリジナルJS/React/Vueの計6バージョンを参考に,各バージョンの実装コードは約25~40行程度(空行を含む)提供する.
フロントエンドルーティングとは?
ルーティングの概念は、URLと処理関数とのマッピング関係を記述するサービス側に由来する.
Webフロントエンド単一ページアプリケーションSPA(Single Page Application)では、ルーティングは、ページをリフレッシュすることなく、URLとUIとのマッピング関係を記述する.
フロントエンドルーティングを実現するにはどうすればいいですか?
フロントエンドルーティングを実現するには、2つのコアを解決する必要があります.
以下では,hashとhistoryの2つの実装方式を用いて,上記の2つのコア問題に答える.
hash実装
#
)および後の部分であり、アンカーとしてページ内をナビゲートすることがよく用いられ、URL中のhash部分を変更するとページリフレッシュ
タグによってURLを変更し、window.location
によってURLを変更する、これらの状況によってURLを変更するとhashchangeイベントhistory実装
pushState/replaceState
または
ラベルでURLを変更してもpopstateイベントはトリガーされません.幸いなことに、pushState/replaceState
の呼び出しと
ラベルのクリックイベントをブロックしてURLの変化を検出することができるので、リスニングURLの変化は実現できますが、hashchangeほど便利ではありません.オリジナルJS版フロントエンドルーティング実現
前節で説明した2つの実装方式に基づいて,hashバージョンとhistoryバージョンのルーティングをそれぞれ実装し,例はフレームワークに依存せずにオリジナルHTML/JS実装を用いた.
hashベースの実装
実行効果:
HTMLセクション:
<body>
<ul>
<li><a href="#/home">homea>li>
<li><a href="#/about">abouta>li>
<div id="routeView">div>
ul>
body>
JavaScriptセクション:
// hashchange, hashchange
window.addEventListener('DOMContentLoaded', onLoad)
//
window.addEventListener('hashchange', onHashChange)
//
var routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onHashChange()
}
// , UI
function onHashChange () {
switch (location.hash) {
case '#/home':
routerView.innerHTML = 'Home'
return
case '#/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
historyベースの実装
実行効果:
HTMLセクション:
<body>
<ul>
<li><a href='/home'>homea>li>
<li><a href='/about'>abouta>li>
<div id="routeView">div>
ul>
body>
JavaScriptセクション:
// hashchange, hashchange
window.addEventListener('DOMContentLoaded', onLoad)
//
window.addEventListener('popstate', onPopState)
//
var routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onPopState()
// ラベルはイベントのデフォルト をクリックし、クリック にpushStateを してURLを し、 UIを することで、リンクをクリックしてURLとUIを する を します。
var linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
onPopState()
}))
}
// , UI
function onPopState () {
switch (location.pathname) {
case '/home':
routerView.innerHTML = 'Home'
return
case '/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
React版フロントエンドルーティング実装
hashベースの実装
実行効果:
使用方法はreact-routerと似ています.
<ul>
<li>
<Link to="/home">homeLink>
li>
<li>
<Link to="/about">aboutLink>
li>
ul>
"/home" render={() => <h2>Homeh2>} />
<Route path="/about" render={() => <h2>Abouth2>} />
BrowserRouter>
BrowserRouter実装
export default class BrowserRouter extends React.Component {
state = {
currentPath: utils.extractHashPath(window.location.href)
};
onHashChange = e => {
const currentPath = utils.extractHashPath(e.newURL);
console.log("onHashChange:", currentPath);
this.setState({ currentPath });
};
componentDidMount() {
window.addEventListener("hashchange", this.onHashChange);
}
componentWillUnmount() {
window.removeEventListener("hashchange", this.onHashChange);
}
render() {
return (
<RouteContext.Provider value={{currentPath: this.state.currentPath}}>
{this.props.children}
RouteContext.Provider>
);
}
}
Route実装
export default ({ path, render }) => (
<RouteContext.Consumer>
{({currentPath}) => currentPath === path && render()}
RouteContext.Consumer>
);
Link実装
export default ({ to, ...props }) => <a {...props} href={"#" + to} />;
historyベースの実装
実行効果:
使用方法はreact-routerと似ています.
<ul>
<li>
<Link to="/home">homeLink>
li>
<li>
<Link to="/about">aboutLink>
li>
ul>
"/home" render={() => <h2>Homeh2>} />
<Route path="/about" render={() => <h2>Abouth2>} />
HistoryRouter>
HistoryRouter実装
export default class HistoryRouter extends React.Component {
state = {
currentPath: utils.extractUrlPath(window.location.href)
};
onPopState = e => {
const currentPath = utils.extractUrlPath(window.location.href);
console.log("onPopState:", currentPath);
this.setState({ currentPath });
};
componentDidMount() {
window.addEventListener("popstate", this.onPopState);
}
componentWillUnmount() {
window.removeEventListener("popstate", this.onPopState);
}
render() {
return (
<RouteContext.Provider value={{currentPath: this.state.currentPath, onPopState: this.onPopState}}>
{this.props.children}
RouteContext.Provider>
);
}
}
Route実装
export default ({ path, render }) => (
<RouteContext.Consumer>
{({currentPath}) => currentPath === path && render()}
RouteContext.Consumer>
);
Link実装
export default ({ to, ...props }) => (
{({ onPopState }) => (
{
e.preventDefault();
window.history.pushState(null, "", to);
onPopState();
}}
/>
)}
);
Vueバージョンフロントエンドルーティング実装
hashベースの実装
実行効果:
使用方法はvue-routerと似ています(vue-routerはプラグインメカニズムを介してルーティングに注入されますが、実装の詳細は隠されています.コードを直感的に維持するために、ここではVueプラグインパッケージは使用されません):
<div>
<ul>
<li><router-link to="/home">homerouter-link>li>
<li><router-link to="/about">aboutrouter-link>li>
ul>
<router-view>router-view>
div>
const routes = {
'/home': {
template: 'Home
'
},
'/about': {
template: 'About
'
}
}
const app = new Vue({
el: '.vue.hash',
components: {
'router-view': RouterView,
'router-link': RouterLink
},
beforeCreate () {
this.$routes = routes
}
})
router-view実装
import utils from '~/utils.js'
export default {
data () {
return {
routeView: null
}
},
created () {
this.boundHashChange = this.onHashChange.bind(this)
},
beforeMount () {
window.addEventListener('hashchange', this.boundHashChange)
},
mounted () {
this.onHashChange()
},
beforeDestroy() {
window.removeEventListener('hashchange', this.boundHashChange)
},
methods: {
onHashChange () {
const path = utils.extractHashPath(window.location.href)
this.routeView = this.$root.$routes[path] || null
console.log('vue:hashchange:', path)
}
}
}
router-link実装
export default {
props: {
to: String
},
methods: {
onClick () {
window.location.hash = '#' + this.to
}
}
}
historyベースの実装
実行効果:
使用方法はvue-routerと似ています.
<div>
<ul>
<li><router-link to="/home">homerouter-link>li>
<li><router-link to="/about">aboutrouter-link>li>
ul>
<router-view>router-view>
div>
const routes = {
'/home': {
template: 'Home
'
},
'/about': {
template: 'About
'
}
}
const app = new Vue({
el: '.vue.history',
components: {
'router-view': RouterView,
'router-link': RouterLink
},
created () {
this.$routes = routes
this.boundPopState = this.onPopState.bind(this)
},
beforeMount () {
window.addEventListener('popstate', this.boundPopState)
},
beforeDestroy () {
window.removeEventListener('popstate', this.boundPopState)
},
methods: {
onPopState (...args) {
this.$emit('popstate', ...args)
}
}
})
router-view実装:
import utils from '~/utils.js'
export default {
data () {
return {
routeView: null
}
},
created () {
this.boundPopState = this.onPopState.bind(this)
},
beforeMount () {
this.$root.$on('popstate', this.boundPopState)
},
beforeDestroy() {
this.$root.$off('popstate', this.boundPopState)
},
methods: {
onPopState (e) {
const path = utils.extractUrlPath(window.location.href)
this.routeView = this.$root.$routes[path] || null
console.log('[Vue] popstate:', path)
}
}
}
router-link実装
export default {
props: {
to: String
},
methods: {
onClick () {
history.pushState(null, '', this.to)
this.$root.$emit('popstate')
}
}
}
小結
フロントエンドルーティングのコア実装原理は簡単であるが、特定のフレームワークと組み合わせると、ダイナミックルーティング、ルーティングパラメータ、ルーティングアニメーションなど、フレームワークに多くの特性が追加され、ルーティング実装が複雑になる.本稿では、フロントエンドルーティングの最もコア部分の実装のみを分析し、hashとhistoryの2つのモードに基づいて、それぞれオリジナルJS/React/Vueの3つの実装を提供し、合計6つの実装バージョンを参考にして、あなたの役に立つことを望んでいます.
すべての例のコードはGithub倉庫:githubに置かれています.com/whinc/web-r…
リファレンス