Let's create a data-driven router as follows:
new Router({ id: 'router-view', // Container view mode: 'hash', // pattern routes: [ { path: '/', name: 'home', component: '<div>Home</div>', beforeEnter: (next) => { console.log('before enter home') next() }, afterEnter: (next) => { console.log('enter home') next() }, beforeLeave: (next) => { console.log('start leave home') next() } }, { path: '/bar', name: 'bar', component: '<div>Bar</div>', beforeEnter: (next) => { console.log('before enter bar') next() }, afterEnter: (next) => { console.log('enter bar') next() }, beforeLeave: (next) => { console.log('start leave bar') next() } }, { path: '/foo', name: 'foo', component: '<div>Foo</div>' } ] })
Thinking arrangement
The first is data driven, so we can express the current routing state through a route object, such as:
current = { path: '/', // route query: {}, // query params: {}, // params name: '', // Route name fullPath: '/', // Full path route: {} // Record current route properties }
current.route stores the configuration information of the current route, so we only need to listen to current Dynamically render the page according to the change of route.
Then we need to monitor different routing changes and do corresponding processing. And implement hash and history mode.
Data driven
Here we continue to use vue data-driven model to realize a simple data hijacking and update the view. First, define our observer
class Observer { constructor (value) { this.walk(value) } walk (obj) { Object.keys(obj).forEach((key) => { // If it is an object, call walk recursively to ensure that each attribute can be defineReactive if (typeof obj[key] === 'object') { this.walk(obj[key]) } defineReactive(obj, key, obj[key]) }) } } function defineReactive(obj, key, value) { let dep = new Dep() Object.defineProperty(obj, key, { get: () => { if (Dep.target) { // Dependency collection dep.add() } return value }, set: (newValue) => { value = newValue // Notification update, corresponding update view dep.notify() } }) } export function observer(value) { return new Observer(value) }
Next, we need to define Dep and Watcher:
export class Dep { constructor () { this.deppend = [] } add () { // Collect watcher this.deppend.push(Dep.target) } notify () { this.deppend.forEach((target) => { // Call the update function of the watcher target.update() }) } } Dep.target = null export function setTarget (target) { Dep.target = target } export function cleanTarget() { Dep.target = null } // Watcher export class Watcher { constructor (vm, expression, callback) { this.vm = vm this.callbacks = [] this.expression = expression this.callbacks.push(callback) this.value = this.getVal() } getVal () { setTarget(this) // Trigger the get method to complete the collection of watcher s let val = this.vm this.expression.split('.').forEach((key) => { val = val[key] }) cleanTarget() return val } // Update action update () { this.callbacks.forEach((cb) => { cb() }) } }
So far, we have implemented a simple subscription publisher, so we need to update current Route does data hijacking. Once current Route update, we can update the current page in time:
// Responsive data hijacking observer(this.current) // Yes, current The route object collects dependencies and updates them through render when changes occur new Watcher(this.current, 'route', this.render.bind(this))
okay.... So far, we seem to have completed a simple responsive data update. In fact, render is to dynamically render the corresponding content for the specified area of the page. Here is only a simplified version of render:
render() { let i if ((i = this.history.current) && (i = i.route) && (i = i.component)) { document.getElementById(this.container).innerHTML = i } }
hash and history
Next is the implementation of hash and history mode. Here we can follow the idea of Vue router and establish different processing models. Take a look at the core code I implemented:
this.history = this.mode === 'history' ? new HTML5History(this) : new HashHistory(this)
When the page changes, we only need to listen to hashchange and pop state events and make routing transition to:
/** * Route conversion * @param target Target path * @param cb Callback after success */ transitionTo(target, cb) { // Obtain the matched targetRoute object by comparing the incoming routes const targetRoute = match(target, this.router.routes) this.confirmTransition(targetRoute, () => { // The view update is triggered here this.current.route = targetRoute this.current.name = targetRoute.name this.current.path = targetRoute.path this.current.query = targetRoute.query || getQuery() this.current.fullPath = getFullPath(this.current) cb && cb() }) } /** * Confirm jump * @param route * @param cb */ confirmTransition (route, cb) { // Hook function execution queue let queue = [].concat( this.router.beforeEach, this.current.route.beforeLeave, route.beforeEnter, route.afterEnter ) // Scheduling execution through step let i = -1 const step = () => { i ++ if (i > queue.length) { cb() } else if (queue[i]) { queue[i](step) } else { step() } } step(i) } }
In this way, on the one hand, we pass this current. Route = targetroute updates the previously hijacked data to update the view. On the other hand, through the scheduling of task queue, we realize the basic hook functions beforeEach, beforeLeave, beforeEnter and afterEnter.
In fact, it's almost here. Next, let's implement several API s:
/** * Jump to add history * @param location * @example this.push({name: 'home'}) * @example this.push('/') */ push (location) { const targetRoute = match(location, this.router.routes) this.transitionTo(targetRoute, () => { changeUrl(this.router.base, this.current.fullPath) }) } /** * Jump to add history * @param location * @example this.replaceState({name: 'home'}) * @example this.replaceState('/') */ replaceState(location) { const targetRoute = match(location, this.router.routes) this.transitionTo(targetRoute, () => { changeUrl(this.router.base, this.current.fullPath, true) }) } go (n) { window.history.go(n) } function changeUrl(path, replace) { const href = window.location.href const i = href.indexOf('#') const base = i >= 0 ? href.slice(0, i) : href if (replace) { window.history.replaceState({}, '', `${base}#/${path}`) } else { window.history.pushState({}, '', `${base}#/${path}`) } }
It's almost over here. Source address