Vue核心 路由


基本路由

  1. 安装vue-router,命令 npm i vue-router (vue-router的4版本以上只能在vue3中使用,vue2需要执行npm i vue-router@3)

  2. 应用插件 Vue.use(VueRouter)

  3. 编写router配置项,并引入:

    src/router/index.js该文件专门用于创建整个应用的路由器

    // 该文件专门用于创建整个应用的路由器
    import VueRouter from 'vue-router'
    //引入组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建并暴露一个路由器
    export default new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })

    src/main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入路由器
    import router from './router'
    
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    //应用插件
    Vue.use(VueRouter)
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	router:router
    })
    

    src/App.vue

    <template>
      <div>
        <div class="row">
          <div class="col-xs-offset-2 col-xs-8">
            <div class="page-header"><h2>Vue Router Demo</h2></div>
          </div>
        </div>
        <div class="row">
          <div class="col-xs-2 col-xs-offset-2">
            <div class="list-group">
    					<!-- 原始html中我们使用a标签实现页面的跳转 -->
              <!-- <a class="list-group-item active" href="./about.html">About</a> -->
              <!-- <a class="list-group-item" href="./home.html">Home</a> -->
    
    					<!-- Vue中借助router-link标签实现路由的切换 -->
    					<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
              <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
            </div>
          </div>
          <div class="col-xs-6">
            <div class="panel">
              <div class="panel-body">
    						<!-- 指定组件的呈现位置 -->
                <router-view></router-view>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    	export default {
    		name:'App',
    	}
    </script>
    
  4. 实现切换

    <router-link></router-link> 浏览器会被替换为a标签

    active-class可配置高亮样式

  5. 指定展示位 <router-view></router-view>

几个注意事项

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息
  4. 整个应用只有一个router,可以通过组件的$router属性获取到

多级路由

  1. 配置路由规则,使用children配置项

    export default new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home,
    			children:[
    				{
    					path:'news',
    					component:News,
    				},
    				{
    					path:'message',
    					component:Message,
    				}
    			]
    		}
    	]
    })
  2. 跳转(要写完整路径)

    <router-link to="/home/news">News</router-link>

路由的query参数

  1. 传递参数

    <!-- 跳转路由并携带query参数,to的字符串写法 -->
    				<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
    
    				<!-- 跳转路由并携带query参数,to的对象写法 -->
    				<router-link :to="{
    					path:'/home/message/detail',
    					query:{
    						id:m.id,
    						title:m.title
    					}
    				}">
    					{{m.title}}
    				</router-link>
  2. 接收参数

    $route.query.id
    $route.query.title

命名路由

  1. 作用:可以简化路由的跳转

  2. 如何使用

    • 给路由命名

      export default new VueRouter({
      	routes:[
      		{
      			name:'guanyu',
      			path:'/about',
      			component:About
      		},
      		{
      			path:'/home',
      			component:Home,
      			children:[
      				{
      					path:'news',
      					component:News,
      				},
      				{
      					path:'message',
      					component:Message,
      					children:[
      						{
      							name:'xiangqing',
      							path:'detail',
      							component:Detail,
      						}
      					]
      				}
      			]
      		}
      	]
      })
      
    • 简化跳转

      <!-- 跳转路由并携带query参数,to的字符串写法 -->
      				<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->
      
      				<!-- 跳转路由并携带query参数,to的对象写法 -->
      				<router-link :to="{
      					name:'xiangqing',
      					query:{
      						id:m.id,
      						title:m.title
      					}
      				}">
      					{{m.title}}
      				</router-link>

路由的 params 参数

  1. 配置路由,声明接收 params 参数

    export default new VueRouter({
    	routes:[
    		{
    			name:'guanyu',
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home,
    			children:[
    				{
    					path:'news',
    					component:News,
    				},
    				{
    					path:'message',
    					component:Message,
    					children:[
    						{
    							name:'xiangqing',
    							path:'detail/:id/:title',
    							component:Detail,
    						}
    					]
    				}
    			]
    		}
    	]
    })
  2. 传递参数

    注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!

  3. 接收参数

    $route.params.id
    $route.params.title

路由的 props 配置

props 作用:让路由组件更方便的收到参数

export default new VueRouter({
	routes:[
		{
			name:'guanyu',
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home,
			children:[
				{
					path:'news',
					component:News,
				},
				{
					path:'message',
					component:Message,
					children:[
						{
							name:'xiangqing',
							path:'detail',
							component:Detail,

							//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
							// props:{a:1,b:'hello'}

							//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
							// props:true

							//props的第三种写法,值为函数
							props(params) {
                                return {
                                    id: params.id
                                    title:params.title
                                }
                            }

						}
					]
				}
			]
		}
	]
})

路由跳转的replace方法

  1. 作用:控制路由跳转时操作浏览器历史记录的模式

  2. 浏览器的历史记录有两种写入方式:pushreplace

    push是追加历史记录

    replace是替换当前目录,路由跳转时候默认为push方式

  3. 开启replace模式

    <router-link :replace="true" ...>News</router-link>

    简写<router-link replace ...>News</router-link>

总结:浏览记录本质是一个栈,默认push,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace就是不追加,而将栈顶地址替换。

作用:不借助 <router-link>实现路由跳转,让路由跳转更加灵活

this.$router.push({}):内传的对象与<router-link>中的to相同

this.$router.replace({})

this.$router.forward({}):前进

this.$router.back({}):后退

this.$router.go(n):可前进也可后退,n为正数前进n,n为负数后退n

this.$router.push({
	name:'xiangqing',
	params:{
		id:xxx,
		title:xxx
	}
})

缓存路由组件

Keep-alive 是什么

keep-alivevue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM

keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

作用:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例

在组件中使用

<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

⚠️设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activateddeactivated):

  • 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > … … > beforeRouteLeave > deactivated
  • 再次进入组件时:beforeRouteEnter >activated > … … > beforeRouteLeave > deactivated

在路由中使用

<keep-alive include = "News"><router-view></router-view></keep-alive>

<keep-alive :include = "['News','Message']"><router-view></router-view></keep-alive>

// 缓存一个路由组件
<keep-alive include = "News">
	<router-view></router-view>
</keep-alive>

// 缓存多个路由组件
<keep-alive :include = "['News','Message']">
	<router-view></router-view>
</keep-alive>

使用场景

使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive

举个栗子:

  • 当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive

  • 首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive

    例如:

    <template>
      <div>
        <keep-alive :include="shouldCache ? ['home'] : []">
          <!-- 列表页的内容 -->
        </keep-alive>
      </div>
    </template>
    
    <script>
    export default {
      computed: {
        shouldCache() {
          // 判断列表页的来源是否是首页
          return this.$route.params.from !== 'home';
        }
      }
    };
    </script>

在路由中设置keepAlive属性判断是否需要缓存

{
  path: 'list',
  name: 'itemList', // 列表页
  component (resolve) {
    require(['@/pages/item/list'], resolve)
 },
 meta: {
  keepAlive: true,
  title: '列表页'
 }
}

使用<keep-alive>

<div id="app" class='wrapper'>
    <keep-alive>
        <!-- 需要缓存的视图组件 --> 
        <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>
      <!-- 不需要缓存的视图组件 -->
     <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

原理分析

keep-alivevue中内置的一个组件

源码位置:src/core/components/keep-alive.js

export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: [String, RegExp, Array],
    exclude: [String, RegExp, Array],
    max: [String, Number]
  },

  created () {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render() {
    /* 获取默认插槽中的第一个组件节点 */
    const slot = this.$slots.default
    const vnode = getFirstComponentChild(slot)
    /* 获取该组件节点的componentOptions */
    const componentOptions = vnode && vnode.componentOptions

    if (componentOptions) {
      /* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
      const name = getComponentName(componentOptions)

      const { include, exclude } = this
      /* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
      if (
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      /* 获取组件的key值 */
      const key = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
     /*  拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      }
        /* 如果没有命中缓存,则将其设置进缓存 */
        else {
        cache[key] = vnode
        keys.push(key)
        // prune oldest entry
        /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

可以看到该组件没有template,而是用了render,在组件渲染的时候会自动执行render函数

this.cache是一个对象,用来存储需要缓存的组件,它将以如下形式存储:

this.cache = {
    'key1':'组件1',
    'key2':'组件2',
    // ...
}

在组件销毁的时候执行pruneCacheEntry函数

function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  /* 判断当前没有处于被渲染状态的组件,将其销毁*/
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

mounted钩子函数中观测 includeexclude 的变化,如下:

mounted () {
    this.$watch('include', val => {
        pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
        pruneCache(this, name => !matches(val, name))
    })
}

如果includeexclude 发生了变化,即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化,那么就执行pruneCache函数,函数如下:

function pruneCache (keepAliveInstance, filter) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode = cache[key]
    if (cachedNode) {
      const name = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

在该函数内对this.cache对象进行遍历,取出每一项的name值,用其与新的缓存规则进行匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用pruneCacheEntry函数将其从this.cache对象剔除即可

关于keep-alive的最强大缓存功能是在render函数中实现

首先获取组件的key值:

const key = vnode.key == null? 
componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key

拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,如下:

/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */
if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance
    /* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */
    remove(keys, key)
    keys.push(key)
} 

直接从缓存中拿 vnode 的组件实例,此时重新调整该组件key的顺序,将其从原来的地方删掉并重新放在this.keys中最后一个

this.cache对象中没有该key值的情况,如下:

/* 如果没有命中缓存,则将其设置进缓存 */
else {
    cache[key] = vnode
    keys.push(key)
    /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
    if (this.max && keys.length > parseInt(this.max)) {
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
}

表明该组件还没有被缓存过,则以该组件的key为键,组件vnode为值,将其存入this.cache中,并且把key存入this.keys

此时再判断this.keys中缓存组件的数量是否超过了设置的最大缓存数量值this.max,如果超过了,则把第一个缓存组件删掉

缓存后如何获取数据

解决方案可以有以下两种:

  • beforeRouteEnter
  • actived

beforeRouteEnter

每次组件渲染的时候,都会执行beforeRouteEnter

beforeRouteEnter(to, from, next){
    next(vm=>{
        console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据
    })
},

actived

keep-alive缓存的组件被激活的时候,都会执行actived钩子

activated(){
   this.getData() // 获取数据
},

注意:服务器端渲染期间avtived不被调用

路由守卫

activateddeactivated

activateddeactivated是路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

具体使用

  1. activated路由组件被激活时触发
  2. deactivated路由组件失活时触发

路由守卫

作用:对路由进行权限控制

分类:

  • 全局守卫

    meta路由源信息

    //全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
    router.beforeEach((to,from,next)=>{
    	console.log('前置路由守卫',to,from)
    	if(to.meta.isAuth){ //判断是否需要鉴权
    		if(localStorage.getItem('school')==='atguigu'){
    			next()
    		}else{
    			alert('无权限查看!')
    		}
    	}else{
    		next()
    	}
    })
    
    
    //全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
    router.afterEach((to,from)=>{
    	console.log('后置路由守卫',to,from)
    	if(to.meta.title){
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
  • 独享守卫

    beforeEnter(to,from,next){
    	if(localStorage.getItem('school') === 'uestc'){
    		next()
    	}else{
    		alert('暂无权限查看')
    	}
    }
  • 组件内守卫

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from,next){...next()},
    
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){...next()}

路由器的两种工作模式

  1. 对于一个url来说,什么是hash

    #及其后面的内容就是hash

  2. hash值不会包含在HTTP请求中,即hash值不会带给服务器

  3. hash模式

    • 地址中永远带着#号,不美观
    • 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
    • 兼容性较好
  4. history模式

    • 地址干净,美观

    • 兼容性和hash模式相比略差

    • 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题

      在node.js中,使用connect-history-api-fallback中间件,必须要在引入静态资源中间件之前引用,例如app.use(history())

const router = new VueRouter({
	mode:'history',
	routes:[..]
})

export default router

文章作者: QT-7274
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 QT-7274 !
评论
  目录