如何手写简易的 Vue Router
前言
还是那样,懂得如何使用一个常用库,还得了解其原理或者怎么模拟实现,今天实现一下vue-router。
有一些知识我这篇文章提到了,这里就不详细一步步写,请看我手写一个简易的Vuex
基本骨架
- Vue里面使用插件的方式是Vue.use(plugin),这里贴出它的用法:
安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数传入。这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象。
- 全局混入
使用Vue.mixin(mixin)
全局注册一个混入,影响注册之后所有创建的每个Vue实例。可以使用混入向组件注入自定义的行为,它将影响每一个之后创建的Vue实例。
- 路由用法
比如简单的:
//路由数组
constroutes=[
{
path:'/',
name:'Page1',
component:Page1,
},
{
path:'/page2',
name:'Page2',
component:Page2,
},
]
constrouter=newVueRouter({
mode:'history',//模式
routes,
})
它是传入了mode和routes,我们实现的时候需要在VueRouter构造函数中接收。
在使用路由标题的时候是这样:
GotoFoo GotoBar
故我们需要使用Vue.component(id,[definition])注册一个全局组件。
了解了大概,我们就可以写出一个基本骨架
letVue=null
classVueRouter{
constructor(options){
this.mode=options.mode||'hash'
this.routes=options.routes||[]
}
}
VueRouter.install=function(_Vue){
Vue=_Vue
Vue.mixin({
beforeCreate(){
//根组件
if(this.$options&&this.$options.router){
this._root=this//把当前vue实例保存到_root上
this._router=this.$options.router//把router的实例挂载在_router上
}elseif(this.$parent&&this.$parent._root){
//子组件的话就去继承父组件的实例,让所有组件共享一个router实例
this._root=this.$parent&&this.$parent._root
}
},
})
Vue.component('router-link',{
props:{
to:{
type:[String,Object],
required:true,
},
tag:{
type:String,
default:'a',//router-link默认渲染成a标签
},
},
render(h){
lettag=this.tag||'a'
return{this.$slots.default}
},
})
Vue.component('router-view',{
render(h){
returnh('h1',{},'视图显示的地方')//暂时置为h1标签,下面会改
},
})
}
exportdefaultVueRouter
mode
vue-router有两种模式,默认为hash模式。
history模式
通过window.history.pushStateAPI来添加浏览器历史记录,然后通过监听popState事件,也就是监听历史记录的改变,来加载相应的内容。
- popstate事件
当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。
- History.pushState()方法
window.history.pushState(state,title,url)
该方法用于在历史中添加一条记录,接收三个参数,依次为:
- state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。
- title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
- url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
hash模式
使用URL的hash来模拟一个完整的URL。,通过监听hashchange事件,然后根据hash值(可通过window.location.hash属性读取)去加载对应的内容的。
继续增加代码,
letVue=null
classHistoryRoute{
constructor(){
this.current=null//当前路径
}
}
classVueRouter{
constructor(options){
this.mode=options.mode||'hash'
this.routes=options.routes||[]
this.routesMap=this.createMap(this.routes)
this.history=newHistoryRoute()//当前路由
this.initRoute()//初始化路由函数
}
createMap(routes){
returnroutes.reduce((pre,current)=>{
pre[current.path]=current.component
returnpre
},{})
}
initRoute(){
if(this.mode==='hash'){
//先判断用户打开时有没有hash值,没有的话跳转到#/
location.hash?'':(location.hash='/')
window.addEventListener('load',()=>{
this.history.current=location.hash.slice(1)
})
window.addEventListener('hashchange',()=>{
this.history.current=location.hash.slice(1)
})
}else{
//history模式
location.pathname?'':(location.pathname='/')
window.addEventListener('load',()=>{
this.history.current=location.pathname
})
window.addEventListener('popstate',()=>{
this.history.current=location.pathname
})
}
}
}
VueRouter.install=function(_Vue){
Vue=_Vue
Vue.mixin({
beforeCreate(){
if(this.$options&&this.$options.router){
this._root=this
this._router=this.$options.router
Vue.util.defineReactive(this,'_route',this._router.history)//监听history路径变化
}elseif(this.$parent&&this.$parent._root){
this._root=this.$parent&&this.$parent._root
}
//当访问this.$router时即返回router实例
Object.defineProperty(this,'$router',{
get(){
returnthis._root._router
},
})
//当访问this.$route时即返回当前页面路由信息
Object.defineProperty(this,'$route',{
get(){
returnthis._root._router.history.current
},
})
},
})
}
exportdefaultVueRouter
router-link和router-view组件
VueRouter.install=function(_Vue){
Vue=_Vue
Vue.component('router-link',{
props:{
to:{
type:[String,Object],
required:true,
},
tag:{
type:String,
default:'a',
},
},
methods:{
handleClick(event){
//阻止a标签默认跳转
event&&event.preventDefault&&event.preventDefault()
letmode=this._self._root._router.mode
letpath=this.to
this._self._root._router.history.current=path
if(mode==='hash'){
window.history.pushState(null,'','#/'+path.slice(1))
}else{
window.history.pushState(null,'',path.slice(1))
}
},
},
render(h){
letmode=this._self._root._router.mode
lettag=this.tag||'a'
letto=mode==='hash'?'#'+this.to:this.to
console.log('render',this.to)
return(
{this.$slots.default}
)
//returnh(tag,{attrs:{href:to},on:{click:this.handleClick}},this.$slots.default)
},
})
Vue.component('router-view',{
render(h){
letcurrent=this._self._root._router.history.current//current已经是动态响应
letroutesMap=this._self._root._router.routesMap
returnh(routesMap[current])//动态渲染对应组件
},
})
}
至此,一个简易的vue-router就实现完了,案例完整代码附上:
letVue=null
classHistoryRoute{
constructor(){
this.current=null
}
}
classVueRouter{
constructor(options){
this.mode=options.mode||'hash'
this.routes=options.routes||[]
this.routesMap=this.createMap(this.routes)
this.history=newHistoryRoute()//当前路由
//初始化路由函数
this.initRoute()
}
createMap(routes){
returnroutes.reduce((pre,current)=>{
pre[current.path]=current.component
returnpre
},{})
}
initRoute(){
if(this.mode==='hash'){
//先判断用户打开时有没有hash值,没有的话跳转到#/
location.hash?'':(location.hash='/')
window.addEventListener('load',()=>{
this.history.current=location.hash.slice(1)
})
window.addEventListener('hashchange',()=>{
this.history.current=location.hash.slice(1)
})
}else{
//history模式
location.pathname?'':(location.pathname='/')
window.addEventListener('load',()=>{
this.history.current=location.pathname
})
window.addEventListener('popstate',()=>{
this.history.current=location.pathname
})
}
}
}
VueRouter.install=function(_Vue){
Vue=_Vue
Vue.mixin({
beforeCreate(){
//根组件
if(this.$options&&this.$options.router){
this._root=this//把当前vue实例保存到_root上
this._router=this.$options.router//把router的实例挂载在_router上
Vue.util.defineReactive(this,'_route',this._router.history)//监听history路径变化
}elseif(this.$parent&&this.$parent._root){
//子组件的话就去继承父组件的实例,让所有组件共享一个router实例
this._root=this.$parent&&this.$parent._root
}
//当访问this.$router时即返回router实例
Object.defineProperty(this,'$router',{
get(){
returnthis._root._router
},
})
//当访问this.$route时即返回当前页面路由信息
Object.defineProperty(this,'$route',{
get(){
returnthis._root._router.history.current
},
})
},
})
Vue.component('router-link',{
props:{
to:{
type:[String,Object],
required:true,
},
tag:{
type:String,
default:'a',
},
},
methods:{
handleClick(event){
//阻止a标签默认跳转
event&&event.preventDefault&&event.preventDefault()//阻止a标签默认跳转
letmode=this._self._root._router.mode
letpath=this.to
this._self._root._router.history.current=path
if(mode==='hash'){
window.history.pushState(null,'','#/'+path.slice(1))
}else{
window.history.pushState(null,'',path.slice(0))
}
},
},
render(h){
letmode=this._self._root._router.mode
lettag=this.tag||'a'
letto=mode==='hash'?'#'+this.to:this.to
return(
{this.$slots.default}
)
//returnh(tag,{attrs:{href:to},on:{click:this.handleClick}},this.$slots.default)
},
})
Vue.component('router-view',{
render(h){
letcurrent=this._self._root._router.history.current//current已经是动态
letroutesMap=this._self._root._router.routesMap
returnh(routesMap[current])//动态渲染对应组件
},
})
}
exportdefaultVueRouter
ps:个人技术博文Github仓库,觉得不错的话欢迎star,给我一点鼓励继续写作吧~
以上就是如何手写简易的VueRouter的详细内容,更多关于手写简易的VueRouter的资料请关注毛票票其它相关文章!