4

【vue】Vue-Router路由管理

 2 years ago
source link: https://segmentfault.com/a/1190000040774062
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Vue-Router路由管理

  1. 安装与基本使用
  2. 编程式导航
  3. 重定向和别名
  4. 路由组件传参
  5. History模式
  6. Modules

1.1 安装与基本使用

//安装
npm install vue-router --save
//main.js内引入
import VueRouter from 'vue-router'
Vue.use(VueRouter)
//此时在vue实例上会多出`$route`和`$router`两个属性

创建两个页面

//About.vue
<template>
  <div>关于页面</div>
</template>
//Detail.vue
<template>
  <div>关于页面</div>
</template>
//router.js文件
import VueRouter from "vue-router";
import About from './pages/About'
import Detail from './pages/Detail'

export const router = new VueRouter({
    routes: [
        {
            path: '/about',
            component: About,
        },
        {
            path: '/detail',
            component: Detail
        }
    ]
})
  • 引入VueRouter并实例化,引入组件
  • 配置对象的routes选项,以数组-对象形式注册路由,包括路径与组件

在mian.js中引入router实例并注册

//mian.js
import { router } from './router'

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')
//此时可以观察到$router与$route的相关配置

在App.vue中使用路由

<template>
  <div id="app">
    <router-link to="/about">跳转about</router-link><br />
    <router-link to="/detail">跳转detail</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

router-link为路由入口,在to中配置路径
router-view为路由出口,跳转的页面在此处显示
类似于动态组件,实现了基本的跳转功能,路由的功能更加强大
$router类似于$store,在根实例注册后,子组件都可以访问到原路由实例,不需要频繁的在子组件中引入。$route是当前路由

1.2 动态路由

根据不同的路径参数跳转至同一组件,显示不同的数据

//router.js
{
    path: '/about:id',
    component: About,
},
//App.vue
<template>
  <div id="app">
    <router-link to="/about/:'1'">用户1</router-link><br />
    <button @click="to">用户2</button>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    to() {
      this.$router.push('/about/:"2"');
    },
  },
};
</script>
//About.vue
<template>
  <div>关于页面{{ userId }}</div>
</template>

<script>
export default {
  computed: {
    userId() {
      return this.$route.params.id;
    },
  },
};
</script>
  • 在router中注册的路径添加:param
  • 跳转时可以使用router-link或者使用$router.push,路径中携带参数
  • 在跳转至的路由可以通过$route.params访问动态路径的参数

    可以根据参数去请求数据再渲染页面

  • :,对应定义的是params参数
  • ? = &,可以定义query参数,通过$route.query访问

    <router-link to="/about/?id=1">用户1</router-link><br />

通配符
捕获所有路由或404 Not found路由

{    //匹配所有路径
    path:'*'
}
{    //匹配以'/user'开头的任意路径
    path:'/user-*'
}

使用通配符路由时,$route.params会添加一个名为pathMatch参数。包含URL通配符匹配的部分

//router.js
{
    path: '*',
    component: () => import('./pages/Notfound')
}

当跳转的路径匹配失败时,可以匹配通配符路径,并通过pathMatch参数提示错误的路径
使用'/user-*',可以设置特定路径下某种页面未找到的提示信息

1.3 嵌套路由

有时在路由跳转后可能还需要路由跳转,这时就需要嵌套多级路由
在路由注册时添加children属性

//router.js
{
    path: '/about',
    component: About,
    children: [
        {
            path: 'child',
            component: () => import('./pages/Child')
        }
    ]
},
//跳转
<router-link to="/about/child">二级跳转</router-link>

子路由的路径前不需要/,不然会认为从跟路径开始

1.4 编程式导航

除了使用router-link声明式创建a标签的方式进行跳转,也可以是借助router的实例方法编程式实现跳转
push:向history栈添加一个新的记录,可以点击浏览器的后退至之前的URL
replace替换当前的history栈顶记录
go(n):前进或后退|n|步,负数为后退,正数为前进,记录不够用则失败

this.$router.push({
  path: "/detail",
  // params: { userId: 123 },
  query: { user: "张三" },
});

push中的数据可以使用对象形式,params参数不能与path同时使用,router-link中的to也支持对象形式的写法
可以使用name属性传递params参数

  • params参数:必须路径上添加参数名,并且跳转时使用name属性
  • query参数:不必在注册时在路径上添加信息,跳转时使用path或name都可

    当在路径中添加了参数,但是跳转时没有传递params参数,也会跳转失败
    使用name属性时,支持同时传递params与query参数

1.5 命名路由

上述已经对name进行了一些介绍,是对路由的命名
可以简化路径过长的问题,同时避免params参数的问题

routes: [
  {
    path: '/user/:userId',
    name: 'user',
    component: User
  }
]

1.6 命名视图

router-view中可以添加name属性,对应路由中的组件
注册路由时的components可以是对象形式,包含多个组件,key提供给name选择

//router.js
{
    name: 'detail',
    path: '/detail/:userId',
    components: {
        default: Detail,
        a: () => import('./pages/Ads')
    }
},
//视图
<router-view></router-view>
<router-view name="a"></router-view>

没有name属性的视图,使用default的组件

1.7 重定向和别名

重定向
访问'/a'重定向'/b',URL与匹配的路由都换成'/b'

//路径
routes: [
  { path: '/a', redirect: '/b' }
]
//命名
routes: [
  { path: '/a', redirect: { name: 'foo' }}
]
//动态方法
routes: [
  { path: '/a', redirect: to => {
    // 方法接收 目标路由 作为参数
    // return 重定向的 字符串路径/路径对象
  }}
]

可以在返回主页时将根路径重定向为子路由路径,保证页面的填充

别名
'/a'的别名'b',当访问'/b'时,URL仍然是'/b',但是路由匹配为'/a',相当于访问'/a',只是名字不同

routes: [
  { path: '/a', component: A, alias: '/b' }
]

可以自由地将UI结构映射到任意的URL,不受限与配置的嵌套的路由结构

1.8 路由组件传参

props属性:boolean|Object|Function
$route会使组件与对应路由高耦合,导致组件只能在特定的URL上使用,限制灵活性。
使用props属性可以将组件和路由解耦

//对象形式
{
    name:'detail',
    path:'/detail',
    component:Detail,
    props:{foo:'a',bar:'b'}
}

在Detail组件中,可以直接在props中声明后使用
对象形式传递的是静态数据,在配置的时候就写好的,使用场景较少

//布尔形式
{
    ...
    props:true,
}
//多组件
{
    ...
    components:{Detial,Ads},
    props:{Detail:true,Ads:false}
}

将路由跳转时的params参数传入组件中,在组件的props中声明后使用

//函数形式
{
    ...
    props($route){
        return {
            id:$route.params.userId,
            name:$route.query.user
        }
    }
}
//解构赋值简写 - 类似于vuex的action中的context对象
{
    ...
    props({params,query}){
        return {
            id:params.userId,
            name:query.user
        }
    }
}

在不使用props的情况下,传递的参数全部都在URL上,需要使用计算属性逐个获取
使用props的函数形式,可以将params和query参数以及静态数据在组件中的props中声明
这样简化了取值的操作,解耦合

1.9 History模式

router中的配置除了routes选项外,还要其他选项,例如mode
mode默认为hash,地址栏会有一个#号,URL改变时,页面不会重新加载
更改为正常的网址形式,使用mode:'history'
history模式需要一定的后台支持,不然会出现404

这是因为hash模式下#后的地址作为路由跳转,不会发起http请求,但是在history模式下,刷新页面时,会发起http请求,由于没有#的分隔会将整个地址都作为请求地址
nginx

location / {
  try_files $uri $uri/ /index.html;
}

Node.js

const http = require('http')
const fs = require('fs')
cosnt httpPort = 80

http.createServer((req,res)=>{
    fs.readFile('index.html','utf-8',(err,content)=>{
        if(err){
            ...
        }
        res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'})
        
        res.end(content)
    })
}).listen(httpPort,()=>{
    console.log('Server listening on:http://loaclhost:%s',httpPort)
})

可以在Node.js中使用connect-history-api-fallback插件,快速解决history模式后端地址和前端路由地址的冲突,造成请求失败

2.10 Modules

当嵌套路由的层级过多时,可以将其分割成多个模块
与vuex拆分的方式类似,不用创建module数组,引入后直接在routes数组中注册即可

//routers文件夹中的index.js 入口文件
import VueRouter from 'vue-router'
import { about } from './modules/about'
import { detail } from './modules/detail'
import { Notfound } from './Notfound'

export const router = new VueRouter({
    routes: [
        { path: '/' },
        about,
        detail,
        Notfound
    ]
})

修改为模块化后需要重新在main.js中引入并注册

  1. 路由元信息
  2. 路由懒加载

2.1 路由守卫

  1. 全局前置守卫
  2. 全局解析守卫
  3. 全局后置钩子
  4. 路由独享守卫
  5. 组件内的守卫

2.1.1 全局前置守卫

beforeEach

const router = new VueRouter({})
router.beforeEach((to,from,next)=>{})
  • to : 目标路由
  • from : 离开的路由
  • next : 放行函数,无参数直接跳转至目标路由

    • next(false) 中断当前导航
    • next('/')或next({path:'/'}) 指定路由跳转,可以使用name以及其他选项
    • next(error) 中断当前导航,并将Error实例传入router.onError()执行回调
    • next函数仅能被调用一次
//index.js - 模拟未登录用户访问页面,重定向至登录页面
router.beforeEach((to, from, next) => {
    if (to.name !== 'login' && to.name !== 'register' && !isAuthenticated) {
        next({ name: 'login' })
    } else {
        next()
    }
})

如果跳转使用的push的方式,被守卫拦截重定向后会报错,但不影响使用
可以在push后使用catch捕获错误

2.1.2 全局解析守卫

beforeResolve
类似于beforeEach
区别:在导航被确认之前,同时在所有组件内守卫异步路由组件被解析之后,解析守卫被调用

2.1.3 全局后置钩子

afterEach
与守卫不同的是没有next放行函数

router.afterEach((to, from) => {
  // ...
})

2.1.4 路由独享守卫

beforeEnter
与全局不同的在于它作为一个路由的配置项,单独控制一个路由

routes: [
  {
    path: '/foo',
    component: Foo,
    beforeEnter: (to, from, next) => {
      // ...
    }
  }
]

to : 跳转至当前路由
from : 由某路由跳转来

可以限制from可以从何处跳转至该路由

2.1.5 组件内的守卫

在组件中作为配置选项
beforeRouteEnter:类似于独享守卫,在组件实例创建前调用,故不能获取vm实例

可以在next中添加回调函数访问组件实例

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

beforeRouteUpdate:路径改变,组件被复用时调用,可以访问vm实例

此时的this代表的时from的路由组件实例

beforeRouteLeave:离开该组件的对应路由时调用,可以访问该组件vm实例

可以在离开前对组件中的内容进行判断,确认是否离开

2.1.6 整体流程

image.png

2.2 路由元信息

meta配置项,存储路由元信息
在连续的路由嵌套中,每一层的路由信息都会被收集到$route.matched数组
可以使用数组的遍历方法遍历matched数组对meta字段进行检查

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

2.3 过渡动效

router-view是基本的动态组件,相当于component标签
所以可以使用transition标签对其进行包裹实现过渡效果
同样的使用keep-alive实现组件缓存
transition与keep-alive

2.4 数据获取

在路由跳转时经常会伴随着从服务器获取数据
导航完成后请求:完成导航后,在组件的声明周期钩子中请求数据。请求完成期间显示"加载"提示。

使用v-if条件渲染加载页面、错误页面以及请求成功页面
在created钩子中请求数据

导航完成前请求:在路由守卫中获取数据,获取成功后执行导航

beforeRouteEnter中请求数据,在next()回调中配置请求获得的数据

2.5 滚动行为

scrollBehavior配置选项
切换至新路由时,页面滚动到顶部或者保持原先的滚动位置

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})

默认情况下不发生滚动,返回svaedPosition时前进/后退与浏览器原生操作一致

return {
    x:0,y:0
}

根据坐标直接滚动

meta:{x:0,y:0}
return {
    selector:to.meta,
    behavior:'smooth'
}

根据元信息进行平滑滚动

return new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ x: 0, y: 0 })
  }, 500)
})

可以返回一个Promise,resolve中传入滚动信息,实现异步滚动

2.6 路由懒加载

//异步组件工厂函数
const AsyncComponent = (url) => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import(url),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

使用import()函数异步引入组件,可以在首次渲染时先不下载部分组件
在访问异步组件时再进行下载

2.7 导航故障

在路由导航失败时可以在push后链式处理错误信息

  • isNavigationFailure(failure,NavigationFailureType)
  • NavigationFailureType

    • redirected:调用了 next(newLocation) 重定向到了其他地方
    • aborted:调用了 next(false) 中断了本次导航
    • cancelled:当前导航还没有完成之前又有了一个新的导航
    • duplicated:导航被阻止,因为我们已经在目标位置了
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter

// 正在尝试访问 admin 页面
router.push('/admin').catch(failure => {
  if (isNavigationFailure(failure, NavigationFailureType.redirected)) {
    // 向用户显示一个小通知
    showToast('Login in order to access the admin panel')
  }
})
// 正在尝试访问 admin 页面
router.push('/admin').catch(failure => {
  if (isNavigationFailure(failure, NavigationFailureType.redirected)) {
    failure.to.path // '/admin'
    failure.from.path // '/'
  }
})

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK