VUE3 dynamic routing

Dynamic route matching with parameters

Many times, we need to map routes for a given matching pattern to the same component. For example, we might have a Usercomponent that should render for all users, but with different user IDs. In Vue Router, we can do this with a dynamic field in the path, which we call a path parameter :

const User = {
    
    
  template: '<div>User</div>',
}

// 这些都会传递给 `createRouter`
const routes = [
  // 动态字段以冒号开始
  {
    
     path: '/users/:id', component: User },
]

Now URLs like /users/johnnyand /users/jolynewill map to the same route.

Path parameters are denoted by a colon :. When a route is matched, the value of its paramsthis.$route.params will be exposed in each component as . Therefore, we can Userrender the current user ID by updating the template:

const User = {
    
    
  template: '<div>User {
    
    { $route.params.id }}</div>',
}

You can set multiple path parameters in the same route , and they will be mapped to $route.paramsthe corresponding fields on the . For example:

match pattern match path $route.params
/users/:username /users/eduardo { username: 'eduardo' }
/users/:username/posts/:postId /users/eduardo/posts/123 { username: 'eduardo', postId: '123' }

In addition $route.paramsto , $routethe object also exposes other useful information like $route.query(if there are parameters in the URL), $route.hashetc. You can view full details in the API reference .

A demo of this example can be found here .

Responding to changes in route parameters

The caveat when using routes with parameters is that the same component instance will be reused when the user /users/johnnynavigates from to . Because both routes render the same component, reuse is more efficient than destroying and recreating. However, this also means that the component's lifecycle hooks will not be called ./users/jolyne

To respond to parameter changes in the same component, you can simply watch $routeany property on the object, in this case, it is $route.params:

const User = {
    
    
  template: '...',
  created() {
    
    
    this.$watch(
      () => this.$route.params,
      (toParams, previousParams) => {
    
    
        // 对路由变化做出响应...
      }
    )
  },
}

Alternatively, use beforeRouteUpdate a navigation guard , which also cancels navigation:

const User = {
    
    
  template: '...',
  async beforeRouteUpdate(to, from) {
    
    
    // 对路由变化做出响应...
    this.userData = await fetchUser(to.params.id)
  },
}

Catch all routes or 404 Not found routes

Regular parameters only match characters between url fragments, /separated by . If we want to match any path , we can use a custom path parameter regular expression, and add the regular expression in parentheses after the path parameter :

const routes = [
  // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
  {
    
     path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  // 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下
  {
    
     path: '/user-:afterUser(.*)', component: UserGeneric },
]

In this particular scenario, we used a custom regular expression between the brackets and pathMatchmarked the parameter as optional repeatable . pathThis is done so that we can navigate directly to the route when needed by splitting the into an array:

this.$router.push({
    
    
  name: 'NotFound',
  // 保留当前路径并删除第一个字符,以避免目标 URL 以 `//` 开头。
  params: {
    
     pathMatch: this.$route.path.substring(1).split('/') },
  // 保留现有的查询和 hash 值,如果有的话
  query: this.$route.query,
  hash: this.$route.hash,
})

See the Repeated Parameters section for more.

If you are using history mode , be sure to follow the instructions to configure your server properly.

Advanced Matching Mode

Vue Router uses its own path matching syntax, inspired by Vue express, so it supports many advanced matching modes, such as optional parameters, zero or more/one or more, and even custom regular matching rules. Check out the advanced matching documentation to explore them.

Match syntax for routing

Most apps will use /aboutstatic routes like this and /users/:userIddynamic routes like this, as we just saw in Dynamic Route Matching , but Vue Router can do so much more!

:::tipFor
simplicity, all routes omit componentthe attribute and focus only on paththe value.
:::

Custom regularization in parameters

When defining :userIdparameters like this we internally use the following regex ([^/]+)(where at least one character is not a slash /) to extract the parameter from the URL. This works fine, unless you need to differentiate between two routes based on the content of the parameter. Imagine two routes /:orderIdand /:productName, both would match the exact same URL, so we need a way to differentiate them. The easiest way is to add a static part to the path to differentiate them:

const routes = [
  // 匹配 /o/3549
  {
    
     path: '/o/:orderId' },
  // 匹配 /p/books
  {
    
     path: '/p/:productName' },
]

But in some cases, we don't want to add a static /o /psection. Since , orderIdis always a number, and productNamecan be anything, we can specify a custom regex for the argument in parentheses:

const routes = [
  // /:orderId -> 仅匹配数字
  {
    
     path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  {
    
     path: '/:productName' },
]

Now, going to /25will match /:orderIdand other cases will match /:productName. routesThe order of the array doesn't matter!

:::tip
Make sure to escape backslashes ( \) like we did for \d(become \\d) to actually pass backslash characters in strings in JavaScript.
:::

repeatable parameter

If you need to match routes with multiple parts, e.g. /first/second/third, you should mark the parameter as repeatable with *(0 or more) and +(1 or more):

const routes = [
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  {
    
     path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  {
    
     path: '/:chapters*' },
]

This will give you an array of arguments instead of a string, and also requires you to pass an array when using named routes:

// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: [] } }).href
// 产生 /
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: ['a', 'b'] } }).href
// 产生 /a/b

// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空 

These can also be combined with custom regexes by adding them after the closing parenthesis :

const routes = [
  // 仅匹配数字
  // 匹配 /1, /1/2, 等
  {
    
     path: '/:chapters(\\d+)+' },
  // 匹配 /, /1, /1/2, 等
  {
    
     path: '/:chapters(\\d+)*' },
]

Sensitive and strict routing configuration

By default, all routes are case-insensitive and will match routes with or without a trailing slash. For example, the route /userswill match /users, /users/, and even /Users/. This behavior can be modified with strictand sensitiveoptions, which can be applied to the entire global route or to the current route:

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    // 将匹配 /users/posva 而非:
    // - /users/posva/ 当 strict: true
    // - /Users/posva 当 sensitive: true
    {
    
     path: '/users/:id', sensitive: true },
    // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
    {
    
     path: '/users/:id?' },
  ],
  strict: true, // applies to all routes
})

optional parameters

You can also ?mark a parameter as optional by using the modifier (0 or 1):

const routes = [
  // 匹配 /users 和 /users/posva
  {
    
     path: '/users/:userId?' },
  // 匹配 /users 和 /users/42
  {
    
     path: '/users/:userId(\\d+)?' },
]

Note that *also technically marks a parameter as optional, but ?parameters cannot be repeated.

debugging

If you need to explore how your routes are translated into regex, to understand why a route is not being matched, or, to report a bug, you can use the route ranking tool . It supports sharing your routes via URL.

programmatic navigation

In addition to using <router-link>the create a tag to define navigation links, we can also use the instance method of router to implement it by writing code.

navigate to a different location

Note: In a Vue instance, you can $routeraccess the routing instance via . Therefore you can call this.$router.push.

To navigate to a different URL, use router.pushthe method. This method will add a new record to the history stack, so when the user clicks the browser back button, they will return to the previous URL.

When you click <router-link>, this method will be called internally, so clicking <router-link :to="...">is equivalent to calling router.push(...):

Declarative programmatic
<router-link :to="..."> router.push(...)

The method's argument can be a string path, or an object describing an address. For example:

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({
    
     path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({
    
     name: 'user', params: {
    
     username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({
    
     path: '/register', query: {
    
     plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({
    
     path: '/about', hash: '#team' })

Note : If provided path, it will be ignored, which is not the case paramsin the above example . queryInstead, as in the following example, you need to provide a routed nameor hand-written complete with parameters path:

const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${
      
      username}`) // -> /user/eduardo
// 同样
router.push({
    
     path: `/user/${
      
      username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({
    
     name: 'user', params: {
    
     username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({
    
     path: '/user', params: {
    
     username } }) // -> /user

When specifying params, stringan or argument (or an array numberfor repeatable arguments ) can be provided. Any other types (eg undefined, , falseetc.) will be automatically stringified . For optional parameters , you can provide an empty string ( "") to skip it.

Since properties toand router.pushaccept the same kind of object, the rules for both are exactly the same.

router.pushand all other navigation methods return a Promise , allowing us to wait until the navigation is complete to know whether it succeeded or failed. We'll cover that in detail in Navigation Handling .

replace current location

It works similarly router.push, the only difference is that it doesn't add new records to history when navigating, as its name suggests - it replaces the current entry.

Declarative programmatic
<router-link :to="..." replace> router.replace(...)

It is also possible to add an attribute directly to the passed router.pushto :routeLocationreplace: true

router.push({
    
     path: '/home', replace: true })
// 相当于
router.replace({
    
     path: '/home' })

across history

The method takes an integer as a parameter, indicating how many steps forward or backward in the history stack, similar to window.history.go(n).

example

// 向前移动一条记录,与 router.forward() 相同
router.go(1)

// 返回一条记录,与 router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)

falsification of history

As you may have noticed, router.push, router.replaceand router.goare clones of window.history.pushState, window.history.replaceStateandwindow.history.go , and they do mimic window.historythe API of .

So if you're already familiar with Browser History APIs , manipulating history will feel familiar when using Vue Router.

It's worth mentioning that Vue Router's navigation methods ( , , ) always work correctly no matter what historyconfiguration is passed when creating the router instance.pushreplacego

navigation guard

As the name suggests, the navigation guard provided by vue-router is mainly used to guard navigation by jumping or canceling. There are many ways to implement route navigation: globally, specific to a single route, or at the component level.

Global Front Guard

You can router.beforeEachregister a global front guard with:

const router = createRouter({
    
     ... })

router.beforeEach((to, from) => {
    
    
  // ...
  // 返回 false 以取消导航
  return false
})

When a navigation triggers, the global front guards are called in order of creation. Guards are executed asynchronously, and the navigation is waiting until all guards are resolved .

Each guard method receives two parameters:

The values ​​that can be returned are as follows:

  • false: Cancel the current navigation. If the URL of the browser changes (perhaps manually by the user or the browser's back button), the URL address will be reset to the fromaddress corresponding to the route.
  • A routing address : Jump to a different address through a routing address, just like you call router.push(), you can set configurations such as or replace: true. name: 'home'The current navigation is interrupted, and then a new navigation is performed, just like from.
 router.beforeEach(async (to, from) => {
    
    
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
    
    
     // 将用户重定向到登录页面
     return {
    
     name: 'Login' }
   }
 })

One may be thrown if an unexpected condition is encountered Error. This will cancel the navigation and call router.onError()the registered callback.

If there is nothing, undefinedor return true, the navigation is valid and the next navigation guard is called

All of the above work the same way with asyncFunctions and Promises:

router.beforeEach(async (to, from) => {
    
    
  // canUserAccess() 返回 `true` 或 `false`
  const canAccess = await canUserAccess(to)
  if (!canAccess) return '/login'
})

optional third argumentnext

In the previous version of Vue Router, the third parameter next can also be used . This is a common source of bugs that can be eliminated through RFCs . However, it is still supported, which means you can pass a third argument to any navigation guard. In this case, make sure to be nextcalled exactly once in any given navigation guard . It can appear more than once, but only if all logical paths do not overlap, otherwise the hook will never be resolved or an error will be reported. Here's an error use case to/login redirect to when the user fails to authenticate :

// BAD
router.beforeEach((to, from, next) => {
    
    
  if (to.name !== 'Login' && !isAuthenticated) next({
    
     name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})

Here is the correct version:

// GOOD
router.beforeEach((to, from, next) => {
    
    
  if (to.name !== 'Login' && !isAuthenticated) next({
    
     name: 'Login' })
  else next()
})

Global Parsing Guard

You can router.beforeResolveregister a global guard with . This is router.beforeEachsimilar to in that it fires on every navigation , except that the resolve guard is called just before the navigation is confirmed and after all in-component guards and async routed components are resolved . Here's an example that ensures users can access a route with a custom meta attribute requiresCamera:

router.beforeResolve(async to => {
    
    
  if (to.meta.requiresCamera) {
    
    
    try {
    
    
      await askForCameraPermission()
    } catch (error) {
    
    
      if (error instanceof NotAllowedError) {
    
    
        // ... 处理错误,然后取消导航
        return false
      } else {
    
    
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

router.beforeResolveIdeal place to fetch data or perform any other action that you want to avoid if the user can't get to the page.

global post hook

You can also register global post hooks, however unlike guards, these hooks will not accept nextfunctions nor change the navigation itself:

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

They are useful for accessibility like analytics, changing page titles, declaring pages, and many other things.

They also reflect navigation failures as the third argument:

router.afterEach((to, from, failure) => {
    
    
  if (!failure) sendToAnalytics(to.fullPath)
})

Learn more about navigation failures in its guide .

Route exclusive guards

You can define guards directly on the route configuration beforeEnter:

const routes = [
  {
    
    
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
    
    
      // reject the navigation
      return false
    },
  },
]

beforeEnterGuards only fire when entering a route , not when params, queryor hashchanges. For example, from /users/2to /users/3or from /users/2#infoto /users/2#projects. They are only triggered when navigating from a different route.

You can also pass an array of functions to beforeEnter, which is useful when reusing guards for different routes:

function removeQueryParams(to) {
    
    
  if (Object.keys(to.query).length)
    return {
    
     path: to.path, query: {
    
    }, hash: to.hash }
}

function removeHash(to) {
    
    
  if (to.hash) return {
    
     path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    
    
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    
    
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

Note that you can also achieve similar behavior by using path meta fields and global navigation guards .

Guards within components

Finally, you can define route navigation guards (passed to the route configuration) directly inside the route component

Available Configuration APIs

You can add the following configuration to the routing component:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {
    
    
  template: `...`,
  beforeRouteEnter(to, from) {
    
    
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    
    
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    
    
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

beforeRouteEnterThe guard cannot be accessed this, because the guard is called before the navigation is confirmed, so the new component that is about to appear has not yet been created.

However, you can nextaccess the component instance by passing a callback to . Execute the callback when the navigation is confirmed, and pass the component instance as the parameter of the callback method:

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

Note beforeRouteEnterthat is the only guard that supports nextpassing callbacks to . For beforeRouteUpdateand are already available, beforeRouteLeavepassing callbacks is not supported as it is unnecessary:this

beforeRouteUpdate (to, from) {
    
    
  // just use `this`
  this.name = to.params.name
}

This leave guard is usually used to prevent users from suddenly leaving without saving changes. This navigation can be canceled falseby .

beforeRouteLeave (to, from) {
    
    
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}

Using Composition API

If you're writing components using the composition API and setupfunctions , you can add update and leave guards via onBeforeRouteUpdateand , respectively. onBeforeRouteLeavePlease refer to the Composition API section for more details.

Complete navigation analysis process

  1. Navigation is triggered.
  2. Call guards in deactivated components beforeRouteLeave.
  3. Call the global beforeEachguard.
  4. beforeRouteUpdateCall guards (2.2+) in reusable components .
  5. Called in routing configuration beforeEnter.
  6. Parse asynchronous routing components.
  7. Called in the activated component beforeRouteEnter.
  8. Calls the global beforeResolveguard (2.5+).
  9. Navigation is confirmed.
  10. Call the global afterEachhook.
  11. Triggers a DOM update.
  12. Call the callback function beforeRouteEnterpassed in the guard next, and the created component instance will be passed in as the parameter of the callback function.

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/132249125