如果对大佬有帮助,请给小弟一个赞哦。
简述
单页面的优缺点不在本文讲,只讲原理。实现单页面的功能有两种方式:
- 使用锚点,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.KaTeX parse error: Expected '}', got 'EOF' at end of input: …n">this</span>.options.router
this._router.init(this)
} else {
this._routerRoot = (this.KaTeX parse error: Expected 'EOF', got '&' at position 8: parent &̲amp;& <span…parent._routerRoot) || this
}
}
})
Object.defineProperty(Vue.prototype, ‘$router’, {
get () { return this._routerRoot._router }
})
<span class="hljs-built_in">Object</span>.defineProperty(Vue.prototype, <span class="hljs-string">'$route'</span>, {
get () { <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._routerRoot._route }
})
Vue.component(<span class="hljs-string">'router-view'</span>,{
<span class="hljs-function"><span class="hljs-title">render</span>(<span class="hljs-params">h</span>)</span>{
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">this</span>._routerRoot._router.history.app.path;
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">this</span>._routerRoot._router.routes;
<span class="hljs-keyword">const</span> route = routes.find(<span class="hljs-function">(<span class="hljs-params">i</span>) =></span> i.path === <span class="hljs-string">`/<span class="hljs-subst">${path}</span>`</span>)
<span class="hljs-keyword">const</span> com = route ? route.component : routes.find(<span class="hljs-function">(<span class="hljs-params">i</span>) =></span> i.path === <span class="hljs-string">`/404`</span>).component
<span class="hljs-keyword">return</span> h(com)
}
})
}
export default Router;
复制代码