【vue-router 源码02】初始化

88 阅读1分钟

new Router

一般使用
Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')
入口源码
export default class VueRouter {
    constructor(options: RouterOptions = {}) {
        this.app = null
        this.apps = []
        this.options = options
        // 存留vue根实例、options
        
        this.beforeHooks = []
        this.resolveHooks = []
        this.afterHooks = []
        // router *守卫回调数组*
        
        this.matcher = createMatcher(options.routes || [], this)
        // 生成 *路由匹配器*
        
        let mode = options.mode || 'hash'
        this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
        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
        }
        // 生成 history *路由控制*
        
    }
    
    /* router守卫 */
    beforeEach(fn: Function): Function {
        return registerHook(this.beforeHooks, fn)
    }
    beforeResolve(fn: Function): Function {
        return registerHook(this.resolveHooks, fn)
    }
    afterEach(fn: Function): Function {
        return registerHook(this.afterHooks, fn)
    }
    
    /* matcher方法映射 */
    match(raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
        return this.matcher.match(raw, current, redirectedFrom)
    }
    getRoutes() {
        return this.matcher.getRoutes()
    }
    addRoute(parentOrRoute: string | RouteConfig, route?: RouteConfig) {
        this.matcher.addRoute(parentOrRoute, route)
        if (this.history.current !== START) {
            this.history.transitionTo(this.history.getCurrentLocation())
        }
    }
    addRoutes(routes: Array<RouteConfig>) {
        this.matcher.addRoutes(routes)
        if (this.history.current !== START) {
            this.history.transitionTo(this.history.getCurrentLocation())
        }
    }
    
    /* history方法映射 */
    onReady(cb: Function, errorCb?: Function) {
        this.history.onReady(cb, errorCb)
    }
    onError(errorCb: Function) {
        this.history.onError(errorCb)
    }
    push(location: RawLocation, onComplete?: Function, onAbort?: Function) {
        if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
            return new Promise((resolve, reject) => {
                this.history.push(location, resolve, reject)
            })
        } else {
            this.history.push(location, onComplete, onAbort)
        }
    }
    replace(location: RawLocation, onComplete?: Function, onAbort?: Function) {
        if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
            return new Promise((resolve, reject) => {
                    this.history.replace(location, resolve, reject)
            })
        } else {
            this.history.replace(location, onComplete, onAbort)
        }
    }
    go(n: number) {
        this.history.go(n)
    }
    back() {
        this.go(-1)
    }
    forward() {
        this.go(1)
    }
    get currentRoute(): ?Route {
        return this.history && this.history.current
    }
    getMatchedComponents(to?: RawLocation | Route): Array<any> {
        const route: any = to ? (to.matched ? to : this.resolve(to).route) : this.currentRoute
        if (!route) {
            return []
        }
        return [].concat.apply(
            [],
            route.matched.map(m => {
                return Object.keys(m.components).map(key => {
                    return m.components[key]
                })
            })
        )
    }
    
    /* 剩余 */
    init(app: any /* Vue component instance */) {...}
}

function registerHook(list: Array<any>, fn: Function): Function {
    list.push(fn)
    return () => {
        const i = list.indexOf(fn)
        if (i > -1) list.splice(i, 1)
    }
}

router.init()

vue根实例 beforeCreate => this.$options.router.init(this)
init(app: any /* Vue component instance */) {
    this.apps.push(app)
    app.$once('hook:destroyed', () => {
        const index = this.apps.indexOf(app)
        if (index > -1) this.apps.splice(index, 1)
        if (this.app === app) this.app = this.apps[0] || null
        if (!this.app) this.history.teardown()
    })
    // vue实例记存与清理
    
    if (this.app) return
    this.app = app
    // vue实例单次挂载
    
    const history = this.history
    if (history instanceof HTML5History || history instanceof HashHistory) {
        const handleInitialScroll = routeOrError => {
            const from = history.current
            const expectScroll = this.options.scrollBehavior
            const supportsScroll = supportsPushState && expectScroll

            if (supportsScroll && 'fullPath' in routeOrError) {
                handleScroll(this, routeOrError, from, false)
            }
        }
        const setupListeners = routeOrError => {
            history.setupListeners()
            handleInitialScroll(routeOrError)
        }
        history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners)
    }
    history.listen(route => {
        this.apps.forEach(app => {
            app._route = route
        })
    })
    // history *路由控制* 初始化
}