vue3 vue-router

本文详细介绍了如何在Vue中配置和使用Vue Router,包括安装配置、命名路由、编程式导航、历史记录、路由传参、嵌套路由、命名视图、重定向与别名。通过实例演示了路由的配置和导航方法,帮助读者理解并实现在实际项目中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue 路由允许我们通过不同的 URL 访问不同的内容。通过 Vue 可以实现多视图的单页Web应用

一、入门(安装及配置)

1. 安装vue-router

npm i vue-router --save

2. 配置vue-router

新建 router 文件夹

路径:/src/router

新建 index.js 文件

路径:/src/router/index.js

index.js

import {createRouter,createWebHistory } from 'vue-router'

const routes = [
    {
        path:'/',
        component:() => import('../pags/login.vue')
    }
]

const router = createRouter({
    history:createWebHistory(),
    routes  //路由信息
})
export default router

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

App.js

<template>
  <router-view/>
</template>

<script>
export default {
}
</script>

<style>
</style>

小满Router(第一章入门)_小满zs的博客-优快云博客

第二章 命名路由-编程式导航(跳转)

除了 path 之外,你还可以为任何路由提供 name。这有以下优点:

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 url 中出现打字错误。
  • 绕过路径排序(如显示一个)
const routes:Array<RouteRecordRaw> = [
    {
        path:"/",
        name:"Login",
        component:()=> import('../components/login.vue')
    },
    {
        path:"/reg",
        name:"Reg",
        component:()=> import('../components/reg.vue')
    }
]

router-link跳转方式需要改变 变为对象并且有对应name

    <h1>小满最骚</h1>
    <div>
      <router-link :to="{name:'Login'}">Login</router-link>
      <router-link style="margin-left:10px" :to="{name:'Reg'}">Reg</router-link>
    </div>
    <hr />

编程式导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

1.字符串模式

import { useRouter } from 'vue-router'
const router = useRouter()
 
const toPage = () => {
  router.push('/reg')
}

2.对象模式

import { useRouter } from 'vue-router'
const router = useRouter()
 
const toPage = () => {
  router.push({
    path: '/reg'
  })
}

3.命名式路由模式

import { useRouter } from 'vue-router'
const router = useRouter()
 
const toPage = () => {
  router.push({
    name: 'Reg'
  })
}

a标签跳转

直接通过a href也可以跳转但是会刷新页面

 <a href="/reg">rrr</a>

参考:小满Router(第二章-命名路由-编程式导航)_小满zs的博客-优快云博客

 第三章 历史记录

replace的使用 

采用replace进行页面的跳转会同样也会创建渲染新的Vue组件,但是在history中其不会重复保存记录,而是替换原有的vue组件;

该方法的原理是: 替代

router.replace 跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

router-link 使用方法

   <router-link replace to="/">Login</router-link>
   <router-link replace style="margin-left:10px" to="/reg">Reg</router-link>

编程式导航

  <button @click="toPage('/')">Login</button>
  <button @click="toPage('/reg')">Reg</button>
import { useRouter } from 'vue-router'
const router = useRouter()
 
const toPage = (url: string) => {
  router.replace(url)
}

横跨历史

该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步

 <button @click="next">前进</button>
 <button @click="prev">后退</button>
const next = () => {
  //前进 数量不限于1
  router.go(1);
}
 
const prev = () => {
  //后退
  //router.go(-1);
  router.back();
}

参考小满Router(第三章-历史记录)_小满zs的博客-优快云博客_router 历史记录

第四章 路由传参

useRouter与useRoute的区别

useRouter是全局对象,用来传入参数。useRoute是当前对象,用来接收参数

useRouter介绍及使用

1. router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,包含了所有的路由包含了许多关键的对象和属性。例如history对象

  • $router.push({path:’/path’}); 本质是向history栈中添加一个路由,在我们看来是 切换路由,但本质是在添加一个history记录
  • $router.replace({path:’/path’}); 替换路由,没有历史记录
     

useRoute介绍及使用

2. route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等

  • $route.path
  • 字符串,等于当前路由对象的路径,会被解析为绝对路径,如 “/index/” 。
  • $route.params
  • 对象,包含路由中的动态片段和全匹配片段的键值对
  • $route.query
  • 对象,包含路由中查询参数的键值对。例如,对于 /index?id=1 ,会得到 $route.query.id == 1。

params传参合query传参的区别

1. params参数在地址栏中不会显示,query会显示

this.router.push({path:'/customList',params:{id:6}})
this.router.push({path:'/customList',query:{id:6}})

params: http://localhost:8080/#/customList
query:http://localhost:8080/#/customList?id=6

2.网页刷新后params参数会不存在

Params路由传参

编程式导航 使用router.push 或者 replace 的时候 改为对象形式并且只能使用name,path无效,然后传入params

使用useRouter传入参数(params)

语法:

router.push({ name:'listSecond', params:{ id, tips}})

<template>
    <div>
       <div class="list-style" 
       v-for="item in listItem" 
       :key="item.id"
       @click="handlerId(item?.id,item?.tips)" 
       >{{item.title}}</div>
    </div>
</template>
<script>
import {defineComponent, ref} from 'vue'
import {useRouter} from 'vue-router'
export default defineComponent({
    setup() {
        const listItem = ref([
            {
                title:'标题一',
                id:1,
                tips:'title-one'
            },
            {
                title:'标题二',
                id:2,
                tips:'title-second'
            }
        ])
        const router = useRouter();
        const handlerId =(id,tips) =>{
            //params的属性值只能为  对象
            router.push({name:'listSecond',params:{id,tips}})
        }
        return{listItem,handlerId}
    }
})
</script>

使用useRoute接收参数(params)

import { useRoute } from 'vue-router';
const route = useRoute()
<div>ID:{{ route.params?.id }}</div>

动态路由传参
很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数 。

路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件
因为用params路由传参,刷新界面会失去参数。配置动态路由之后就刷新后依然存在。

    {
        path:'/listFirst',
        name:'listFirst',
        component:()=>import('../pages/transmitInfo/list-first')
    },
    {
        path:'/listSecond/:id/:tips',//配置动态路由参数 :id、:tips
        name:'listSecond',
        component:()=>import('../pages/transmitInfo/list-second'),
    }

Query路由传参

编程式导航 使用router push 或者 replace 的时候 改为对象形式新增query 必须传入一个对象 

语法:

 router.push({ path:'/listSecond',query:{id,tips}})

使用useRouter传入参数(params) 

        const router = useRouter();
        const handlerId =(id,tips) =>{
            router.push({path:'/listSecond',query:{id,tips}})
        }

 使用useRoute接收参数(params)

import { useRoute } from 'vue-router';
const route = useRoute()
<div>ID:{{ route.query?.id }}</div>
import { useRoute } from 'vue-router';
import { data } from './list.json'
const route = useRoute()
 
 
const item = data.find(v => v.id === Number(route.params.id))

二者的区别

  1. query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
  2. query 在路由配置不需要设置动态路由参数,而 params 必须设置
  3. query 传递的参数会显示在地址栏中
  4. params传参刷新会无效(设置动态路由参数后有效),但是 query 会保存传递过来的值,刷新不变 ;
  5. 路由配置

第五章 嵌套路由

一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:导航栏右侧显示 另一页面(父子路由)

const routes: Array<RouteRecordRaw> = [
   {
        path:'/search', 
        component:search,
        redirect:'/search/htmlcontent',
        children:[
            {
                // 子路由的path里面的值不需要加 /
                path:'htmlcontent',
                component:()=>import('../components/serch/htmlcontent')
            },
            {
                path:'csscontent',
                component:()=>import('../components/serch/csscontent')
            }
        ]
    }
 
]

如你所见,children 配置只是另一个路由数组,就像 routes 本身一样。因此,你可以根据自己的需要,不断地嵌套视图。children里面的path属性值不要加 /

TIPS:不要忘记写router-view

router-view 当你的路由path 与访问的地址相符时,会将指定的组件替换该 router-view

    <div>
        <router-view></router-view>
        <div>
            //不要忘记 需要写上父路由
            <router-link to="/search">login</router-link>
            <router-link style="margin-left:10px;" to="/search/htmlcontent">htmlcontent</router-link>
        </div>
    </div>

方式二 使用 ruoter.push

<template>
    <div>
        <div class="search-style"> 
            <div class="nav-left">
                <div class="navitem-style" v-for="item in navlist" :key="item.id" @click="handerNav(item.path)">{{item.title}}</div>
            </div>
            <div class="nav-content">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>
<script>
import {defineComponent,ref} from 'vue'
import {useRouter} from 'vue-router'
export default defineComponent({
    setup(){
        const route = useRouter();
        const navlist = ref([
            {
                title:'HTML',
                id:1,
                path:'/htmlcontent'
            },
            {
                title:'CSS',
                id:2,
                path:'/csscontent'
            }
        ])
        const handerNav =(path)=>{
            route.push('/search'+path);
        }
        return{navlist,handerNav}
    }
})
</script>

参考   小满Router(第六章-命名视图)_小满zs的博客-优快云博客

第六章 命名视图(同一级(组件)中展示更多的路由视图)

命名视图可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。 命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是 default。

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s)

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
 
 
const routes: Array<RouteRecordRaw> = [
    {
        path:'/listFirst',
        name:'listFirst',
        component:()=>import('../pages/transmitInfo/list-first'),
        children:[
            {
                path:'listFirstA',
                components:{
                    default:()=>import('../components/listFirstA')
                }
            },
            {
                path:'listFirstB',
                components:{
                    listFirstB1:()=>import('../components/listFirstB1'),
                    listFirstB2:()=>import('../components/listFirstB2')
                }
            }
        ]
    }
]
 
const router = createRouter({
    history: createWebHistory(),
    routes
})
 
export default router

对应Router-view 通过name 对应组件

<div>
     <div>
         <router-link to='/listFirst/listFirstA'>goodsCount</router-link>
         <router-link 
            style="margin-left:20px;"             
             to='/listFirst/listFirstB'>
          listFirstB</router-link>
         <hr>
         <div>
             <router-view></router-view>
             <router-view name='listFirstB1'></router-view>
             <router-view name='listFirstB12'></router-view>
         </div>              
     </div>
</div>

参考小满Router(第六章-命名视图)_小满zs的博客-优快云博客

第七章  重定向&别名

重定向 redirect

1.字符串形式配置,访问/ 重定向到 /user (地址栏显示/,内容为/user路由的内容)

const routes: Array<RouteRecordRaw> = [
    {
        path:'/',
        component:()=> import('../components/root.vue'),
        redirect:'/user1',
        children:[
            {
                path:'/user1',
                components:{
                    default:()=> import('../components/A.vue')
                }
            },
            {
                path:'/user2',
                components:{
                    bbb:()=> import('../components/B.vue'),
                    ccc:()=> import('../components/C.vue')
                }
            }
        ]
    }
]

2.对象形式配置

const routes: Array<RouteRecordRaw> = [
    {
        path: '/',
        component: () => import('../components/root.vue'),
        redirect: { path: '/user1' },
        children: [
            {
                path: '/user1',
                components: {
                    default: () => import('../components/A.vue')
                }
            },
            {
                path: '/user2',
                components: {
                    bbb: () => import('../components/B.vue'),
                    ccc: () => import('../components/C.vue')
                }
            }
        ]
    }
]

3.函数模式(可以传参)

const routes: Array<RouteRecordRaw> = [
    {
        path: '/',
        component: () => import('../components/root.vue'),
        redirect: (to) => {
            return {
                path: '/user1',
                query: {
                    id:1
                }
            }
        },
        children: [
            {
                path: '/user1',
                components: {
                    default: () => import('../components/A.vue')
                }
            },
            {
                path: '/user2',
                components: {
                    bbb: () => import('../components/B.vue'),
                    ccc: () => import('../components/C.vue')
                }
            }
        ]
    }
]

别名 alias

将 path的属性值 '/ '别名为 '/root','/root2','/root3',意味着当用户访问 /root ,  /root2  ,  /root3'时,URL是  /root 或 /root2 或 /root3 ,但会被匹配为用户正在访问 /

const routes: Array<RouteRecordRaw> = [
    {
        path: '/',
        component: () => import('../components/root.vue'),
        alias:["/root","/root2","/root3"],
        children: [
            {
                path: 'user1',
                components: {
                    default: () => import('../components/A.vue')
                }
            },
            {
                path: 'user2',
                components: {
                    bbb: () => import('../components/B.vue'),
                    ccc: () => import('../components/C.vue')
                }
            }
        ]
    }
]

参考 小满Router(第七章-重定向-别名)_小满zs的博客-优快云博客

<think>我们遇到了一个Vue Router的错误:`Uncaught (in promise) TypeError: Cannot read properties of undefined (reading &#39;_normalized&#39;)`。这个错误发生在`normalizeLocation`函数中,具体是在尝试读取一个未定义对象的`_normalized`属性时发生的。 根据堆栈信息,错误发生在导航过程中,具体是在`replace`方法调用时。在`Login`组件的`login`方法中调用了`this.$router.replace(...)`,然后触发了这个错误。 这个错误通常是由于传递给`router.replace`(或`router.push`)的参数不正确导致的。在Vue Router中,这些方法需要一个位置描述符(location descriptor),它可以是一个字符串路径,也可以是一个带有`path`或`name`等属性的对象。 错误信息表明在`normalizeLocation`函数中,它试图读取一个未定义对象的`_normalized`属性。这很可能是因为我们传递了一个无效的位置参数(比如`undefined`)给`replace`方法。 让我们检查一下在`Login`组件的`login`方法中调用`this.$router.replace`的代码。根据错误信息,该调用位于`src/views/Login/index.vue`文件的第107行。 可能的原因包括: 1. 传递给`replace`的参数是`undefined`。 2. 传递的对象格式不正确。 为了解决这个问题,我们需要确保传递给`this.$router.replace`的参数是一个有效的路径字符串或一个有效的路由对象(包含`path`或`name`等属性)。 例如,正确的调用方式: ```javascript // 字符串路径 this.$router.replace(&#39;/home&#39;) // 对象 this.$router.replace({ path: &#39;/home&#39; }) // 或者 this.$router.replace({ name: &#39;Home&#39; }) ``` 在`login`方法中,可能我们传递了一个变量,而这个变量在某些情况下是`undefined`。我们需要检查这个变量。 假设在`login`方法中,我们可能有如下代码: ```javascript methods: { login() { // ... 登录逻辑 const redirect = this.$route.query.redirect this.$router.replace(redirect) // 如果redirect是undefined,就会出错 } } ``` 在上面的代码中,如果`this.$route.query.redirect`不存在,那么`redirect`就是`undefined`,这样传递给`replace`的就是`undefined`,从而引发错误。 解决方案是提供一个回退路径: ```javascript methods: { login() { // ... 登录逻辑 const redirect = this.$route.query.redirect || &#39;/&#39; this.$router.replace(redirect) } } ``` 或者,如果我们使用对象形式,也要确保不是`undefined`。 另外,还有一种可能是我们传递了一个对象,但这个对象缺少必要的属性,或者结构不对。例如,如果我们传递了一个包含`params`的对象,但没有提供`name`,那么也可能导致问题。 因此,我们需要检查调用`this.$router.replace`的地方,确保传递的参数是有效的。 如果问题仍然存在,我们可以尝试在调用`replace`之前打印一下参数,确认它的值。 现在,让我们根据上述分析给出一个解决方案的代码示例: ```javascript // 在Login组件的login方法中 methods: { async login() { try { // 假设这里是登录逻辑,比如调用API // ... // 登录成功后,跳转到目标页面 // 从查询参数中获取重定向路径,如果没有则使用默认路径 const redirectPath = this.$route.query.redirect || &#39;/dashboard&#39;; // 确保redirectPath是一个非空字符串,如果可能的话,也可以是一个有效的路由对象 if (typeof redirectPath === &#39;string&#39; && redirectPath) { this.$router.replace(redirectPath); } else { // 如果redirectPath不是字符串或为空,则使用默认路径 this.$router.replace(&#39;/dashboard&#39;); } } catch (error) { // 处理错误 } } } ``` 或者,如果我们期望的是一个对象(例如,包含name和params等),那么我们需要确保它是一个有效的路由对象: ```javascript // 如果redirect是一个对象 let redirect = this.$route.query.redirect; if (!redirect) { redirect = { path: &#39;/&#39; }; } else if (typeof redirect === &#39;string&#39;) { // 如果redirect是字符串,我们可以转换为路径对象 redirect = { path: redirect }; } // 确保redirect是一个对象,并且有path或name this.$router.replace(redirect); ``` 但是注意:`this.$route.query.redirect`通常是一个字符串(URL路径),而不是对象。如果我们需要传递一个对象,通常需要通过其他方式(比如存储在store中),因为查询参数只能是字符串。 另外,还有一种做法是使用`next`参数,但这里我们是在组件的方法中,所以直接使用`replace`。 总结:错误是因为传递给`replace`的参数为`undefined`,所以我们需要确保参数有效。 如果按照上述方法修改后,问题应该得到解决。 现在,根据要求,我们最后提出几个相关问题: §§相关问题§§: 1. Vue Router中`push`和`replace`方法有什么区别? 2.Vue Router中,如何传递参数到目标路由? 3. Vue Router导航守卫有哪些类型?如何使用? 4. 如何处理Vue Router导航过程中的错误?</think>### 解决 Vue Router 的 `Cannot read properties of undefined (reading &#39;_normalized&#39;)` 错误 这个错误通常发生在 Vue Router 尝试导航时接收到了无效的路由参数。核心原因是传递给 `router.replace()` 或 `router.push()` 的参数格式不正确或未定义。以下是详细解决方案: #### 原因分析 1. **无效的路由参数**:传递给 `$router.replace()` 的参数是 `undefined` 或格式错误的对象 2. **路由守卫问题**:导航守卫中返回了无效值 3. **动态路由未定义**:使用了未定义的路由名称 4. **异步操作问题**:在导航完成前组件已卸载 --- #### 解决方案代码 ```javascript // 正确使用 router.replace() 的示例 methods: { async login() { try { // 1. 执行登录逻辑 (API 调用等) const res = await axios.post(&#39;/api/login&#39;, this.formData) // 2. 确保重定向目标有效 const redirectPath = this.$route.query.redirect ? String(this.$route.query.redirect) : &#39;/dashboard&#39; // 3. 安全导航 - 验证路径格式 if (isValidPath(redirectPath)) { this.$router.replace(redirectPath) } else { this.$router.replace(&#39;/dashboard&#39;) // 回退到安全路径 } } catch (error) { // 4. 错误处理 console.error(&#39;Login failed:&#39;, error) this.$router.replace(&#39;/login&#39;) // 确保有明确的回退 } }, // 路径验证函数 isValidPath(path) { const validRoutes = [&#39;/dashboard&#39;, &#39;/profile&#39;, &#39;/settings&#39;] // 你的有效路由列表 return validRoutes.includes(path) || path.startsWith(&#39;/project/&#39;) } } ``` #### 关键修复点解释: 1. **参数验证**: ```javascript const redirectPath = this.$route.query.redirect ? String(this.$route.query.redirect) : &#39;/dashboard&#39; ``` 确保重定向参数是字符串类型并提供默认值 2. **安全导航检查**: ```javascript if (isValidPath(redirectPath)) { ... } ``` 验证目标路径是否在允许的路由列表中 3. **错误处理**: ```javascript catch (error) { this.$router.replace(&#39;/login&#39;) // 明确回退路径 } ``` 避免在错误处理中不指定导航目标 4. **路由守卫修复**: ```javascript // router/index.js router.beforeEach((to, from, next) => { if (to.matched.length === 0) { next(&#39;/404&#39;) // 处理未匹配路由 } else { next() // 正常放行 } }) ``` --- #### 其他常见修复场景: **场景1:动态路由参数未定义** ```javascript // 错误 ❌ this.$router.replace({ name: &#39;UserProfile&#39;, params: { id: undefined } }) // 正确 ✅ if (userId) { this.$router.replace({ name: &#39;UserProfile&#39;, params: { id: userId } }) } else { this.$router.replace(&#39;/users&#39;) } ``` **场景2:异步操作中的组件卸载** ```javascript // 在组件中 async fetchData() { const res = await api.getData() // 检查组件是否仍挂载 if (this._isMounted) { this.$router.replace(`/detail/${res.id}`) } }, beforeDestroy() { this._isMounted = false } ``` **场景3:命名路由不存在** ```javascript // 检查路由定义 const routes = [ { path: &#39;/dashboard&#39;, name: &#39;Dashboard&#39;, // 确保名称正确 component: DashboardView } ] ``` --- #### 最佳实践预防措施: 1. 始终验证导航参数: ```javascript const safeReplace = (path) => { if (typeof path === &#39;string&#39; && path.startsWith(&#39;/&#39;)) { router.replace(path) } else { router.replace(&#39;/&#39;) } } ``` 2. 使用 TypeScript 接口: ```typescript interface NavigationPayload { path?: string; name?: RouteNames; // 枚举类型 params?: Record<string, string | number>; } function navigate(payload: NavigationPayload) { if (!payload.path && !payload.name) { throw new Error(&#39;Invalid navigation payload&#39;) } router.replace(payload) } ``` 3. 全局错误处理: ```javascript router.onError((error) => { if (error.message.includes(&#39;_normalized&#39;)) { console.warn(&#39;Invalid navigation attempt:&#39;, error) router.push(&#39;/error?code=invalid_navigation&#39;) } }) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值