一、前提思考
我们如何给vue写插件,router其实也是一个插件,一个全局插件。相信大家对于vue.use(xxx)很眼熟了,这个时候让我们来看vue-router是如何使用的吧,最后自己写一个router替换掉vue-router。
vue-router源码地址:https://github.com/vuejs/vue-router
1.1)搭建项目
我们使用vue-cli脚手架搭建项目,你可以在自定义中勾选vue-router,或者自己下载yarn add vue-router
vue create my-vue-router
yarn add vue-router / npm install vue-router
1.2)vue-router使用方式
如果是自定义勾选router后的项目,我们会发现vue-router已经引入项目中使用了,我们来看看它的使用方式。
- 引入vue-router插件
// 在main只是一个文件的应用,不是插件的引用
import router from './router'
// 真正的引用在router/index.js 并思考use方法,做了什么事情
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
- 创建router插件
const router = new VueRouter({
mode: 'xxx',
base: process.env.BASE_URL,
routes
})
export default router
- 挂载router插件
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 使用router插件跳转路由并渲染dom
<router-link to='xiaojuzi'></router-link>
<router-view></router-view>
this.$router.push('xx')
1.3)vue-router的功能分析
- 实现spa单页面应用的路由更新且不刷新页面【我们要实现hash路由和history路由】
- 一个路由地址对应一个页面的渲染地址【我们要实现监听和渲染页面,时时render页面】
1.4)写vue插件的思想
1. 实现一个router功能类【你需要完成的功能】
2. 实现一个初始化插件的方法【你需要完成的功能】
- $ruoter的组册
- 完成两个全局组件,即router-view和router-link
二、手写自己的router【实现最简单的router功能】
2.1)创建my-vue-router.js
// 我们在初始化的时候,给我们申明的Vue赋值,这个时候在vue.use使用的时候,
// 我们就可以在自己的my-vue-router.js中拿到vue的构造函数
// 目前只是hash功能/且只支持一层router
let Vue;
class MyRouter {
constructor(options) {
this.$options = options
// 把current作为响应式数据
// 将来发生变化,router-view的render函数能够再次执行
const initial = window.location.hash.slice(1) || "/"
Vue.util.defineReactive(this, 'current', initial)
// 监听hash变化
window.addEventListener("hashchange", () => {
console.log(this.current)
this.current = window.location.hash.slice(1)
})
}
}
// 参数是Vue.use调用时传入的,这里就可以拿到vue咯,保存你的vue吧
MyRouter.install = function (_Vue) {
Vue = _Vue
// 1.挂载$router属性
// this.$router.push()
// 我们在use的时候,实际上这个时候new vue实例还没有开始执行,我们无法拿到vue的run-time的结果
// 这个时候我就借用Vue混入的思想,并借用生命周期做到延时效果
// 最后我们在全局挂载$router
Vue.mixin({
beforeCreate () {
// 次钩子在每个组件创建实例时都会调用
// 根实例才有该选项
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 2.注册实现两个组件router-view,router-link
Vue.component('router-link', {
// 必须传入to地址
props: {
to: {
type: String,
required: true,
},
},
// <a href="to">xxx</a>
// return <a href={'#'+this.to}>{this.$slots.default}</a>
render (h) {
return h(
"a",
{
attrs: {
href: "#" + this.to,
},
},
this.$slots.default
)
}
})
Vue.component('router-view', {
render (h) {
// 获取当前路由对应的组件
let component = null
// 查询routes映射表中,是否有这个route地址
const route = this.$router.$options.routes.find(
(route) => route.path === this.$router.current
)
// 有这个ruoter地址,则返回route对应的组件地址
if (route) {
component = route.component
}
console.log(this.$router.current, component)
return h(component)
}
})
}
export default MyRouter
2.1)使用自己的my-vue-router
import Vue from 'vue'
// 这里把 import VueRouter from 'vue-router'改成刚刚自己写的my-vue-router.js文件
import VueRouter from './my-vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
export default router
三、撸人家源码去了
3.1) 人家是怎么监听history路由
hash路由大家好理解,可以兼容hashchange事件
上面我们写了一个监听hash变化的简单版router,想想history也是原理一样,监听popstate事件
// 在人家的vue-router的类中的constructor
constructor (router: Router, base: ?string) {
window.addEventListener('popstate', e => {
const current = this.current
this.transitionTo(getLocation(this.base), route => {
if (expectScroll) {
handleScroll(router, route, current, true)
}
})
})
}
3.2) 源码vuerouter类
看人家这个类,干了几件事
- mode是我们传入的路由类型,这个大家都好理解,在这个文件中,主要是区分三种路由模式
- 第二个主要是给暴露init/push/replace方法,但其实这里只是一个代理,真正的实现根据模式来区分
export default class VueRouter {
mode: string; // 传入的字符串参数,指示history类别
history: HashHistory | HTML5History | AbstractHistory; // 实际起作用的对象属性,必须是以上三个类的枚举
fallback: boolean; // 如浏览器不支持,'history'模式需回滚为'hash'模式
constructor (options: RouterOptions = {}) {
let mode = options.mode || 'hash' // 默认为'hash'模式
this.fallback = mode === 'history' && !supportsPushState // 通过supportsPushState判断浏览器是否支持'history'模式
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式
}
this.mode = mode
// 根据mode确定history实际的类并实例化
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
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
init (app: any /* Vue component instance */) {
const history = this.history
// 根据history的类别执行相应的初始化操作和监听
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
// VueRouter类暴露的以下方法实际是调用具体history对象的方法
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.replace(location, onComplete, onAbort)
}
}
3.4)源码install方法【实在是妙啊】建议先看我的手写板和注解
- 熟悉的mixin和钩子beforeCreate
- 还有借用vue的util中的defineReactive方法,是这个对象变成响应式对象
- 最后挂载两个全局组件
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
}
3.4)源码history
/* @flow */
import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { START } from '../util/route'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'
export class HTML5History extends History {
_startLocation: string
constructor (router: Router, base: ?string) {
super(router, base)
this._startLocation = getLocation(this.base)
}
setupListeners () {
if (this.listeners.length > 0) {
return
}
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
const handleRoutingEvent = () => {
const current = this.current
// Avoiding first `popstate` event dispatched in some browsers but first
// history route not updated since async guard at the same time.
const location = getLocation(this.base)
if (this.current === START && location === this._startLocation) {
return
}
this.transitionTo(location, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
})
}
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {
window.removeEventListener('popstate', handleRoutingEvent)
})
}
go (n: number) {
window.history.go(n)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
push ? pushState(current) : replaceState(current)
}
}
getCurrentLocation (): string {
return getLocation(this.base)
}
}
export function getLocation (base: string): string {
let path = window.location.pathname
if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
path = path.slice(base.length)
}
return (path || '/') + window.location.search + window.location.hash
}
3.5)源码hash
/* @flow */
import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { getLocation } from './html5'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash()
}
// this is delayed until the app mounts
// to avoid the hashchange listener being fired too early
setupListeners () {
if (this.listeners.length > 0) {
return
}
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
const handleRoutingEvent = () => {
const current = this.current
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
if (supportsScroll) {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
}
})
}
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {
window.removeEventListener(eventType, handleRoutingEvent)
})
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
replaceHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
go (n: number) {
window.history.go(n)
}
ensureURL (push?: boolean) {
const current = this.current.fullPath
if (getHash() !== current) {
push ? pushHash(current) : replaceHash(current)
}
}
getCurrentLocation () {
return getHash()
}
}
function checkFallback (base) {
const location = getLocation(base)
if (!/^\/#/.test(location)) {
window.location.replace(cleanPath(base + '/#' + location))
return true
}
}
function ensureSlash (): boolean {
const path = getHash()
if (path.charAt(0) === '/') {
return true
}
replaceHash('/' + path)
return false
}
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
let href = window.location.href
const index = href.indexOf('#')
// empty path
if (index < 0) return ''
href = href.slice(index + 1)
return href
}
function getUrl (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
你的文章让我感受到了不一样的视角,非常精彩。 https://www.yonboz.com/video/97213.html
你的文章让我感受到了艺术的魅力,谢谢! https://www.yonboz.com/video/17340.html
《出国》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/115218.html
想想你的文章写的特别好www.jiwenlaw.com
不错不错,我喜欢看 https://www.237fa.com/
不错不错,我喜欢看