Vue -Routerのコアを簡単に分析
プラグインメカニズム-$ルーターミックスイン
VueRouter.install = function(_Vue) {
Vue = _Vue;
Vue.mixin({u
beforeCreate() {
if (this.$options.router) { // ルートコンポーネントだけがルーター属性を持つ
Vue.prototype.$router = this.$options.router;
}
}
})
}
// install ==> use(CM)
// mixin ==> ミックスイン:オブジェクトのマージ--- コンポーネントのインスタンスを作成するとき
routesオプションの解析
- イベントリスナー
- ルートマップの作成
- コンポーネントの作成
イベントをバインド
window.addEventListener('hashchange', this.onHashChange.bind(this), false)
window.addEventListener('load', this.onHashChange.bind(this), false)
onHashChange = () => { this.currentPath = window.location.hash.slice(1) || '/' }
ルートマップの作成
this.$options.routes.forEach(item => {
this.routerMap[item.path] = item.component;
})
グローバルコンポーネント
// グローバル・コンポーネントを作成する
function renderComponent() {
Vue.component('router-link', {
props: { to: {type: String, required: true} },
render = h => {
return h('a', { attrs: {href: this.to}}, [this.$options.default])
// return <a href={this.to}>{this.$slots.default}</a> jsx
}
})
Vue.component('router-view', {
render = h => {
const component = this.routerMap[this.currentPath].component;
return h(component);
}
})
}
ソースコードセクション
// install
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
//
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)
},
destroyed () {
registerInstance(this)
}
})
// インターセプトを登録する
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
// グローバルコンポーネントをマウントする
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
// ルーターのフックを登録する ルートを入力する ルートを離脱する ルート更新など
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
// history api
import { inBrowser } from './dom'
import { saveScrollPosition } from './scroll'
import { genStateKey, setStateKey, getStateKey } from './state-key'
import { extend } from './misc'
export const supportsPushState =
inBrowser &&
(function () {
const ua = window.navigator.userAgent
/* ブラウザサポート
Chrome Safari Firefox Opera IE Android iOS
+ + + 7.1+ */
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'
})()
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) {
// preserve existing history state as it could be overriden by the user
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)
}
}
export function replaceState (url?: string) {
pushState(url, true)
}
/* @RouterLink */
import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
import { extend } from '../util/misc'
import { normalizeLocation } from '../util/location'
import { warn } from '../util/warn'
// work around weird flow bug
const toTypes: Array<Function> = [String, Object]
const eventTypes: Array<Function> = [String, Array]
const noop = () => {}
export default {
name: 'RouterLink',
props: {
to: {
type: toTypes,
required: true
},
tag: {
type: String,
default: 'a'
},
exact: Boolean,
append: Boolean,
replace: Boolean,
activeClass: String,
exactActiveClass: String,
ariaCurrentValue: {
type: String,
default: 'page'
},
event: {
type: eventTypes,
default: 'click'
}
},
render (h: Function) {
// .... プロパティのマウントやイベントを省略し、classNameの起動判定などを残す。
return h(this.tag, data, this.$slots.default)
}
}
function guardEvent (e) { ... }
function findAnchor (children) {...}
/* RouterView */
import { warn } from '../util/warn'
import { extend } from '../util/misc'
export default {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render (_, { props, children, parent, data }) {
// used by devtools to display a router-view badge
data.routerView = true
// directly use parent context's createElement() function
// so that components rendered by router-view can resolve named slots
const h = parent.$createElement // hファンクションタイムのソースコード VDOMコア
const name = props.name
const route = parent.$route
const cache = parent._routerViewCache || (parent._routerViewCache = {}) //
// ... 省略
return h(component, data, children)
}
}
function fillPropsinData (component, data, route, configProps) {...}
function resolveProps (route, config) {...}