如果对大佬有帮助,请给小弟一个赞哦。
简述
单页面的优缺点不在本文讲,只讲原理。实现单页面的功能有两种方式:
- 使用锚点,hashHistory
- 利用浏览器的browserHistory原理
hashHistory原理
添加hashchange监听
window.addEventListener(
'hashchange',
function() {
// hash改变时,进行页面更换
}
)
改变hash
window.location.hash = 'aaa';
browserHistory原理
添加popstate监听,可以监听到浏览器前进、后退。但不能监听到pushState、replaceState,所以在执行pushState、replaceState的时候进行页面更换。
window.addEventListener(
'popstate',
function() {
// url改变时,进行页面更换
}
)
改变url,pushState、replaceState的具体文档可以去看MDN。
history.pushState({}, '', path);
history.replaceState({}, '', path);
Vue-Router的实践
一、Router类收集数据,确定路由类型
- mode:确定采用哪种方式的路由;对HTML5History或HashHistory进行实例化。
- routes:收集所有的路由信息;
class Router {
constructor(options) {
this.options = options;
let mode = options.mode || 'hash';
this.routes = options.routes;
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
default:
// 报错
}
}
...
}
二、HTML5History
- setupListeners方法会监听popstate,获取到当前的path
- push方法改变路由
class HTML5History {
constructor (route, base) {
this.current = '';
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.pathname?.slice(1) || '/';
}
window.addEventListener(
'popstate',
handleRoutingEvent
)
}
push (path) {
history.pushState({}, '', path);
}
}
三、HashHistory
- setupListeners方法会监听hashchange,获取到当前的path
- push方法改变路由
class HashHistory {
constructor (route, base) {
this.current = '';
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.hash?.slice(1) || '/';
}
window.addEventListener(
'hashchange',
handleRoutingEvent
)
}
push (location) {
window.location.hash = location
}
}
四、触发setupListeners
Router中会有一个init初始化,用来执行监听;push方法执行路由修改。
class Router {
init() {
this.history.setupListeners()
}
push (location) {
this.history.push(location)
}
}
为保障阅读体验,先断开,讲一下vue怎样使用插件,然后回来继续写Vue-Router
使用Router(看main.js)
会发现router出现在app.$options中
const app = new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
console.log(app)
理解Vue.use
Vue.use(Router)执行,回调用插件Router的静态方法install,并把Vue当作参数传进去,所以我们的Router需要一个install的静态方法。
import Router from '@/utils/router'
Vue.use(Router)
五、静态方法install
- 在组件实例化前beforeCreate阶段,把实例赋值给this._routerRoot,并执行init,触发上面的步骤四;
- 设置劫持,访问this.$router,返回this._routerRoot._router
- 新增全局组件router-view,用来渲染路由组件
Router.install = function(Vue) {
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
}
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Vue.component('router-view',{
render(h){
return h(com) // com是内容组件
}
})
}
目前我们设置了路由监听,注册了router-view组件,那么路由改变时,怎样把对应的组件渲染近router-view组件呢?
六、router-view内容切换
- 在外部作用域声明一个_Vue,在install执行时把Vue赋值给_Vue。
let _Vue;
Router.install = function(Vue) {
_Vue = Vue;
}
- HashHistory、HTML5History使用_Vue创建实例
class HashHistory {
constructor (route, base) {
this.current = '';
this.app = new _Vue({
data() {
return {
path: '/'
}
},
})
}
}
class HTML5History {
constructor (route, base) {
this.current = '';
this.app = new _Vue({
data() {
return {
path: '/'
}
},
})
}
}
- router-view组件使用步骤2创建的实例中的path,对所有路由信息筛选,找到对应的组件,作为内容。因为router-view组件用到了app.path,就添加了依赖。当app.path改变时,router-view组件重新执行。
Router.install = function(Vue) {
...
Vue.component('router-view',{
render(h){
const path = this._routerRoot._router.history.app.path;
const routes = this._routerRoot._router.routes;
const route = routes.find((i) => i.path === `/${path}`)
const com = route ? route.component : routes.find((i) => i.path === `/404`).component
return h(com)
}
})
}
- 监听到路由变化,改变this.app.path,使router-view组件重新执行。pushState不能触发popstate监听,所有单独修改this.app.path
class HTML5History {
...
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.pathname?.slice(1) || '/';
this.app.path = this.current;
}
window.addEventListener(
'popstate',
handleRoutingEvent
)
}
push (path) {
history.pushState({}, '', path);
this.app.path = path;
}
}
class HashHistory {
...
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.hash?.slice(1) || '/';
this.app.path = this.current;
}
window.addEventListener(
'hashchange',
handleRoutingEvent
)
}
push (location) {
window.location.hash = location
}
}
完整代码
let _Vue;
class HTML5History {
constructor (route, base) {
this.current = '';
this.app = new _Vue({
data() {
return {
path: '/'
}
},
})
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.pathname?.slice(1) || '/';
this.app.path = this.current;
}
window.addEventListener(
'popstate',
handleRoutingEvent
)
}
push (path) {
history.pushState({}, '', path);
this.app.path = path;
}
}
class HashHistory {
constructor (route, base) {
this.current = '';
this.app = new _Vue({
data() {
return {
path: '/'
}
},
})
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.hash?.slice(1) || '/';
this.app.path = this.current;
}
window.addEventListener(
'hashchange',
handleRoutingEvent
)
}
push (location) {
window.location.hash = location
}
}
class Router {
constructor(options) {
this.options = options;
let mode = options.mode || 'hash';
this.routes = options.routes;
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
default:
// 报错
}
}
init() {
this.history.setupListeners()
}
push (location) {
this.history.push(location)
}
}
Router.install = function(Vue) {
_Vue = Vue;
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
}
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
Vue.component('router-view',{
render(h){
const path = this._routerRoot._router.history.app.path;
const routes = this._routerRoot._router.routes;
const route = routes.find((i) => i.path === `/${path}`)
const com = route ? route.component : routes.find((i) => i.path === `/404`).component
return h(com)
}
})
}
export default Router;