# Vue Router前言: 因为 vue 是单页应用,不会有那么多 html 让我们跳转,所有要使用路由做页面的跳转 Vue 路由允许我们通过不同的 URL 访问不同的内容。通过 Vue 可以实现多视图的单页 Web 应用
安装 src/router/index.ts
:import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from "vue-router" ; const routes: Array < RouteRecordRaw> = [ { path: "/" , component : ( ) => import ( "../components/a.vue" ) , } , { path: "/register" , component : ( ) => import ( "../components/b.vue" ) , } , ] ; const router = createRouter ( { history: createWebHistory ( ) , routes, } ) ; export default router;
使用 <router-link> 和 < router-view>: 请注意,我们没有使用常规的 a 标签,而是使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。我们将在后面看到如何从这些功能中获益。 router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。 <template>
<div>
<h1>小满最骚</h1>
<div>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link tag="div" to="/">跳转a</router-link>
<router-link tag="div" style="margin-left:200px" to="/register">跳转b</router-link>
</div>
<hr />
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
最后在 main.ts 挂载 import { createApp } from "vue" ; import App from "./App.vue" ; import router from "./router" ; createApp ( App) . use ( router) . mount ( "#app" ) ;
# 编程式导航除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。 想要导航到不同的 URL,可以使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。 当你点击 <router-link>
时,内部会调用这个方法,所以点击 <router-link :to="...">
(声明式) 相当于调用 router.push(...)
(编程式).
编程式导航,字符串模式 import { useRouter } from "vue-router" ; const router = useRouter ( ) ; const toPage = ( ) => { router. push ( "/reg" ) ; } ;
编程式导航,对象模式 import { useRouter } from "vue-router" ; const router = useRouter ( ) ; const toPage = ( ) => { router. push ( { path : "/reg" , } ) ; } ;
编程式导航,命名式路由模式 import { useRouter } from "vue-router" ; const router = useRouter ( ) ; const toPage = ( ) => { router. push ( { name : "Reg" , } ) ; } ;
# 命名路由除了 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 />
# 历史记录replace 的使用 采用 replace 进行页面的跳转会同样也会创建渲染新的 Vue 组件,但是在 history 中其不会重复保存记录,而是替换原有的 vue 组件; 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 = ( ) => { router. go ( 1 ) ; } ; const prev = ( ) => { router. back ( ) ; } ;
# 路由传参useRouter:传参 useRoute:接收参数 传递参数两种传参方式(query/params)
query 传参(用 path) query 只能接收一个对象,这里的 item 一个就是对象 传参: import { useRouter } from "vue-router" ; const router = userRouter ( ) ; const toDetail = ( item ) => { router. push ( { path : "/" , query : item, } ) ; } ;
接收参数:
import { useRoute } from "vue-router" ; const route = useRoute ( ) ;
< div> 品牌:</ div> < div> 价格:</ div> < div> ID:</ div>
params 传参(用 name) params 存在于内存中,刷新会丢失 传参: ………… const toDetail = ( item ) => { router. push ( { name : "Login" , params : item, } ) ; } ;
接收参数:
import { useRoute } from "vue-router" ; const route = useRoute ( ) ;
< div> 品牌:</ div> < div> 价格:</ div> < div> ID:</ div>
动态路由参数 很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数 使用带有参数的路由时需要注意的是,当用户从 /users/johnny
导航到 /users/jolyne
时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。 路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件 需要先配置路由:
const routes : Array< RouteRecordRaw> = [ { path : "/" , name : "Login" , component : ( ) => import ( "../components/login.vue" ) , } , { path : "/reg/:id" , name : "Reg" , component : ( ) => import ( "../components/reg.vue" ) , } , ] ;
传参:
const toDetail = ( item ) => { router. push ( { name : "Reg" , params : { id : item. id, } , } ) ; } ;
接收参数:
import { useRoute } from "vue-router" ; import { data } from "./list.json" ; const route = useRoute ( ) ; const item = data. find ( ( v ) => v. id === Number ( route. params. id) ) ;
query 传参配置的是 path,而 params 传参配置的是 name,在 params 中配置 path 无效
query 在路由配置不需要设置参数,而 params 必须设置
query 传递的参数会显示在地址栏中
params 传参刷新会无效,但是 query 会保存传递过来的值,刷新不变;
路由配置
const $router = useRouter ( ) ; const $route = useRoute ( ) ;
$router
是路由器对象,是全局对象,可以传递参数。r o u t e r . p u s h ; ‘ router.push; ` r o u t e r . p u s h ; ‘ route` 是路由对象,是局部对象,可以接收参数,r o u t e . p a t h , route.path, r o u t e . p a t h , route.params,$route.query
# Sensitive 与 strict 路由配置默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users
将匹配 /users
、 /users/
、甚至 /Users/
。可以通过 strict
和 sensitive
选项来修改:
const router = createRouter ( { history : createWebHistory ( ) , routes : [ { path : '/users/:id' , sensitive : true } , { path : '/users/:id?' } , ] strict : true , } )
# 可选参数可以通过使用?修饰符 (0 个或 1 个) 将一个参数标记为可选:
const routes = [ { path : "/users/:userId?" } , ] ;
# 嵌套路由一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
const routes : Array< RouteRecordRaw> = [ { path : "/user" , component : ( ) => import ( "../components/footer.vue" ) , children : [ { path : "" , name : "Login" , component : ( ) => import ( "../components/login.vue" ) , } , { path : "reg" , name : "Reg" , component : ( ) => import ( "../components/reg.vue" ) , } , ] , } , ] ;
需要在路由中配置 children
, 不写 /
# 命名视图有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default。 命名视图的概念非常类似于 “具名插槽”,并且视图的默认名称也是 default。
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s): import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router" ; const routes : Array< RouteRecordRaw> = [ { path : "/" , components : { default : ( ) => import ( "../components/layout/menu.vue" ) , header : ( ) => import ( "../components/layout/header.vue" ) , content : ( ) => import ( "../components/layout/content.vue" ) , } , } , ] ; const router = createRouter ( { history : createWebHistory ( ) , routes, } ) ; export default router;
对应 Router-view 通过 name 对应组件 < div> < router-view> </ router-view> < router-view name = " header" > </ router-view> < router-view name = " content" > </ router-view> </ div>
# 重定向 redirect 和别名 alias# redirect重定向也是通过 routes 配置来完成,下面例子是从 /home
重定向到 /
: 字符串形式配置 const routes = [ { path : "/home" , redirect : "/" } ] ;
对象形式配置,重定向的目标也可以是一个命名的路由: const routes = [ { path : "/home" , redirect : { name : "homepage" } } ] ;
函数模式(可以传参) const routes = [ { path : "/home" , redirect : ( to ) => { return { path : "/" , query : to. query, } ; } , } , ] ;
请注意,导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home
路由中添加 beforeEnter
守卫不会有任何效果。
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
# alias重定向是指当用户访问 /home
时,URL 会被 /
替换,然后匹配成 /
。那么什么是别名呢? 将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
const routes = [ { path : "/" , component : Homepage, alias : "/home" } ] ;
# 路由组件传参const routes = [ { path : "/user/:id" , components : { default : User, sidebar : Sidebar } , props : { default : true , sidebar : false } , } , ] ;
# 路由的默认路径默认情况下,进入网站的首页,我们希望 <router-view>
渲染首页的内容,但是我们的实现中,默认没有显示首页组件,必须让用户点击才可以。如何可以让路径默认跳到到首页,并且 <router-view>
渲染首页组件呢?
const routes = [ { path : "/" , redirect : "/home" } , { path : "/home" , component : Home } , { path : "/about" , component : About } , ] ;
在 routes 中再配置一个映射, path
配置的是根路径: /
, redirect
是重定向,也就是我们将根路径重定向到 /home
的路径下,这样就可以得到我们想要的结果了
# history 模式# 路由懒加载当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。component
可以传入一个组件,也可以接收一个函数,该函数需要放回一个 Promise. 而 import
函数就是返回一个 Promise
const routes = [ { path : "/home" , component : ( ) => import ( "../pages/Home.vue" ) } ] ;
# 路由的其他属性name
属性:路由记录独一无二的名称meta
属性:自定义的数据
{ path : '/about' , name : 'about-router' , component : ( ) => import ( '../pages/About.vue' ) , meta : { name : 'why' , age;18 } }
# 路由元信息通过路由记录的 meta 属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据,例如:
权限校验标识。 路由组件的过渡名称。 路由组件持久化缓存 (keep-alive) 的相关配置。 标题名称 我们可以在导航守卫或者是路由对象中访问路由的元信息数据。
const router = createRouter ( { history : createWebHistory ( import . meta. env. BASE_URL ) , routes : [ { path : "/" , component : ( ) => import ( "@/views/Login.vue" ) , meta : { title : "登录" , } , } , { path : "/index" , component : ( ) => import ( "@/views/Index.vue" ) , meta : { title : "首页" , } , } , ] , } ) ;
使用 TS 扩展。如果不使用扩展 将会是 unknow 类型
declare module "vue-router" { interface RouteMeta { title? : string ; } }
# 动态路由我们一般使用动态路由都是后台会返回一个路由表前端通过调接口拿到后处理 (后端处理路由) 主要使用的方法就是 router.addRoute
# 添加路由动态路由主要通过两个函数实现。 router.addRoute()
和 router.removeRoute()
。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push()
或 router.replace()
来手动导航,才能显示该新路由
router. addRoute ( { path : "/about" , component : About } ) ;
# 删除路由有几个不同的方法来删除现有的路由:
通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由: router. addRoute ( { path : "/about" , name : "about" , component : About } ) ; router. addRoute ( { path : "/other" , name : "about" , component : Other } ) ;
通过调用 router.addRoute () 返回的回调: const removeRoute = router. addRoute ( routeRecord) ; removeRoute ( ) ;
当路由没有名称时,这很有用。
通过使用 router.removeRoute () 按名称删除路由: router. addRoute ( { path : "/about" , name : "about" , component : About } ) ; router. removeRoute ( "about" ) ;
需要注意的是,如果你想使用这个功能,但又想避免名字的冲突,可以在路由中使用 Symbol 作为名字。
# 查看现有路由Vue Router 提供了两个功能来查看现有的路由:
router.hasRoute ():检查路由是否存在。 router.getRoutes ():获取一个包含所有路由记录的数组。 # 案例const initRouter = async ( ) => { const result = await axios. get ( "http://localhost:9999/login" , { params : formInline } ) ; result. data. route. forEach ( ( v : any) => { router. addRoute ( { path : v. path, name : v. name, component : ( ) => import ( ` ../views/ ${ v. component} ` ) , } ) ; router. push ( "/index" ) ; } ) ; console. log ( router. getRoutes ( ) ) ; } ;
import express, { Express, Request, Response } from "express" ; const app : Express = express ( ) ; app. get ( "/login" , ( req : Request, res : Response) => { res. header ( "Access-Control-Allow-Origin" , "*" ) ; if ( req. query. user == "admin" && req. query. password == "123456" ) { res. json ( { route : [ { path : "/demo1" , name : "Demo1" , component : "demo1.vue" , } , { path : "/demo2" , name : "Demo2" , component : "demo2.vue" , } , { path : "/demo3" , name : "Demo3" , component : "demo3.vue" , } , ] , } ) ; } else { res. json ( { code : 400 , mesage : "账号密码错误" , } ) ; } } ) ; app. listen ( 9999 , ( ) => { console. log ( "http://localhost:9999" ) ; } ) ;
# NotFound对于没有匹配到的路由,我们通常会匹配到固定的某个页面,比如 NotFound 的错误页面中,这个时候我们可编写一个动态路由用于匹配所有的页面;
{ path : '/:pathMatch(.*)' , component : ( ) => import ( '../pages/NotFound.vue' ) }
# 路由的嵌套什么是路由的嵌套? 目前我们匹配的 Home、About、User 等都属于底层路由,我们在它们之间可以来回进行切换。但是,我们 Home 页面本身,也可能会在多个组件之间来回切换:比如 Home 中包括 Product、Message,它们可以在 Home 内部来回切换。 这个时候我们就需要使用嵌套路由,在 Home 中也使用 router-view 来占位之后需要渲染的组件。
{ path : '/home' , component : ( ) => import ( '.../pages/Home.vue' ) children : [ { path : '' , redirect : '/home/product' } , { path : 'product' , component : ( ) => import ( '../pages/HomeProduct.vue' ) } , { path : 'message' , component : ( ) => import ( '../pages/HomeMessage.vue' ) } ] }
import { useRouter} from 'vue-rourer' const router = useRouter ( ) const jumpToHome = ( ) => { router. push ( { path : '/profile' , quert : { name : 'why' , age:18 } } ) }
# router-view 的 v-slotrouter-view
也提供给我们一个插槽,可以用于 <transition>
和 <keep-alive>
组件来包裹你的路由组件:
Component
:要渲染的组件route
:解析出的标准化路由对象<router-view v-slot="{ Component }">
<transition name="why">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
# 动态添加路由某些情况下我们可能需要动态的来添加路由:比如根据用户不同的权限,注册不同的路由。这个时候我们可以使用一个方法 addRoute
添加顶级路由: ![](https://eucli-1314359160.cos.ap-beijing.myqcloud.com/test/P%I_0((DM`ITK%DRPCF2COO.png) 添加二级路由对象 # 动态删除路由添加一个 name 相同的路由 通过 removeRoute 方法,传入路由的名称 通过 addRoute 方法的返回值回调 # 路由导航守卫# 全局前置守卫# 登录守卫功能 router.beforeEachrouter. beforeEach ( ( to, form, next ) => { console. log ( to, form) ; next ( ) ; } ) ;
每个守卫方法接收三个参数:
to: Route, 即将要进入的目标 路由对象; from: Route,当前导航正要离开的路由; next( ) : 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed ( 确认的) 。 next( false) : 中断当前的导航。如果浏览器的 URL 改变了 ( 可能是用户手动或者浏览器后退按钮) ,那么 URL 地址会重置到 from 路由对应的地址。 next( '/' ) 或者 next( { path: '/' } ) : 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
const whileList = [ "/" ] ; router. beforeEach ( ( to, from, next ) => { let token = localStorage. getItem ( "token" ) ; if ( whileList. includes ( to. path) || token) { next ( ) ; } else { next ( { path : "/" , } ) ; } } ) ;
比如我们完成一个功能,只有登录后才能看到其他页面: T)UFO1.png) ![](https://eucli-1314359160.cos.ap-beijing.myqcloud.com/test/6U0 {1(~VVLP7IR99W5WGRHU.png)
# 全局后置守卫使用场景一般可以用来做 loadingBar
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router. afterEach ( ( to, from ) => { Vnode. component?. exposed?. endLoading ( ) ; } ) ;
< template> < div class = " wraps" > < div ref = " bar" class = " bar" > </ div> </ div> </ template> < script setup lang = " ts" > import { ref, onMounted } from "vue" ; let speed = ref< number> ( 1 ) ; let bar = ref< HTMLElement> ( ) ; let timer = ref< number> ( 0 ) ; const startLoading = ( ) => { let dom = bar. value as HTMLElement; speed. value = 1 ; timer. value = window. requestAnimationFrame ( function fn ( ) { if ( speed. value < 90 ) { speed. value += 1 ; dom. style. width = speed. value + "%" ; timer. value = window. requestAnimationFrame ( fn) ; } else { speed. value = 1 ; window. cancelAnimationFrame ( timer. value) ; } } ) ; } ; const endLoading = ( ) => { let dom = bar. value as HTMLElement; setTimeout ( ( ) => { window. requestAnimationFrame ( ( ) => { speed. value = 100 ; dom. style. width = speed. value + "%" ; } ) ; } , 500 ) ; } ; defineExpose ( { startLoading, endLoading, } ) ; </ script> < style scoped lang = " less" > .wraps { position : fixed; top : 0; width : 100%; height : 2px; .bar { height : inherit; width : 0; background : blue; } } </ style>
mian.ts
import loadingBar from "./components/loadingBar.vue" ; const Vnode = createVNode ( loadingBar) ; render ( Vnode, document. body) ; console. log ( Vnode) ; router. beforeEach ( ( to, from, next ) => { Vnode. component?. exposed?. startLoading ( ) ; } ) ; router. afterEach ( ( to, from ) => { Vnode. component?. exposed?. endLoading ( ) ; } ) ;
# Vuex 状态管理什么是状态管理? 在开发中,应用程序需要处理各种各样的数据,这些 数据需要保存在我们应用程序中的某一个位置,对于这些数据 的管理我们就称之为是 状态管理
在前面我们是如何管理自己的状态呢? 在 Vue 开发中,我们使用组件化的开发方式,而在组件中我们定义 data
或者在 setup
中返回使用的数据,这些数据我们称之为 state
在模块 template
中我们可以使用这些数据,模块最终会被渲染成 DOM,我们称之为 View
在模块中我们会产生一些行为事件,处理这些行为事件时, 有可能会修改 state,这些行为事件我们称之为 actions
![](https://eucli-1314359160.cos.ap-beijing.myqcloud.com/test/%MA5OURUF }(PO5C%MNS6{AM.png)
复杂的状态管理 JavaScript 需要管理的状态越来越多,越来越复杂,这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些 UI 的状态,比如某些元素是否被选中,是否显示加载动效,当前分页; 当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏; 对于一些简单的状态,确实可以通过 props 的传递或者 Provide 的方式来共享状态,但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?
# 路由过渡动效< router-view #default = " {route,Component}" > < transition :enter-active-class = " `animate__animated ${route.meta.transition}`" > < component :is = " Component" > </ component> </ transition> </ router-view>
上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在 <transition> 上:
declare module 'vue-router' { interface RouteMeta { title : string, transition : string, } } const router = createRouter ( { history : createWebHistory ( import . meta. env. BASE_URL ) , routes : [ { path : '/' , component : ( ) => import ( '@/views/Login.vue' ) , meta : { title : "登录页面" , transition : "animate__fadeInUp" , } } , { path : '/index' , component : ( ) => import ( '@/views/Index.vue' ) , meta : { title : "首页!!!" , transition : "animate__bounceIn" , } } ] } )
# 滚动行为使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。vue-router 可以自定义路由切换时页面如何滚动。 当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法
………… const router = createRouter ( { history : createWebHistory ( ) , routes, scrollBehavior : ( to, from, savePosition ) => { console. log ( to, '==============>' , savePosition) ; return new Promise ( ( r ) => { setTimeout ( ( ) => { r ( { top : 10000 } ) } , 2000 ) } ) } } ) ;
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进 / 后退 按钮触发) 时才可用。 scrollBehavior 返回滚动位置的对象信息,长这样: { left: number, top: number }
const router = createRouter ( { history : createWebHistory ( ) , routes, scrollBehavior : ( to, from, savePosition ) => { \ return { top : 200 } } } ) ;