vue-router
vue router是 vue 官方推荐的管理单页应用路由的库.本文是对官网 guide 内容的总结.
基础
vue router 的使用可简单的添加 npm 包,或使用<script scr="url">
引入.
vue router 提供了两个自定义的组件,router-link
用于导航到某个界面,router-view
显示导航到的界面.
使用:
// 1. Define route components.
// These can be imported from other files
const Home = { template: "<div>Home</div>" };
const About = { template: "<div>About</div>" };
// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
];
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = VueRouter.createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: VueRouter.createWebHashHistory(),
routes, // short for `routes: routes`
});
// 5. Create and mount the root instance.
const app = Vue.createApp({});
// Make sure to _use_ the router instance to make the
// whole app router-aware.
app.use(router);
app.mount("#app");
// Now the app has started!
然后通过this.$router
或在 composition api 中使用useRouter
或useRoute
获得实例.
Dynamic Route Matching with Params
route 可以带参数(称作 param):
const User = {
template: "<div>User</div>",
};
// these are passed to `createRouter`
const routes = [
// dynamic segments start with a colon
{ path: "/users/:id", component: User },
];
然后通过$route.params
获得参数:
const User = {
template: "<div>User {{ $route.params.id }}</div>",
};
还可以在一个 route 中有多个参数,如/users/:username/posts/:postId
.
当匹配通过 route 但参数不同时,我们为了效率仍回使用相同的组件,此时需要使用一个 watcher 来观察参数的变换,或使用beforeRouteUpdate
Routes' Matching Syntax
我们可以使用中则表达式来匹配:
const routes = [
// /:orderId -> matches only numbers
{ path: "/:orderId(\\d+)" },
// /:productName -> matches anything else
{ path: "/:productName" },
];
可以匹配重复个/
分隔的节:
const routes = [
// /:chapters -> matches /one, /one/two, /one/two/three, etc
{ path: "/:chapters+" },
// /:chapters -> matches /, /one, /one/two, /one/two/three, etc
{ path: "/:chapters*" },
];
其中+
匹配一个或多个,*
匹配 0 个或多个.此时得到的参数 chapter 会是一个数组.
默认情况下,vue router 对大小写不敏感,允许结尾的/
,故/users
匹配 /users
, /users/
, 和 /Users/
.我们可以通过strict
和sensitive
进行设置:
const router = createRouter({
history: createWebHistory(),
routes: [
// will match /users/posva but not:
// - /users/posva/ because of strict: true
// - /Users/posva because of sensitive: true
{ path: '/users/:id', sensitive: true },
// will match /users, /Users, and /users/42 but not /users/ or /users/42/
{ path: '/users/:id?' },
]
strict: true, // applies to all routes
})
我们可以用?
让某个参数可选:
const routes = [
// will match /users and /users/posva
{ path: "/users/:userId?" },
// will match /users and /users/42
{ path: "/users/:userId(\\d+)?" },
];
注意要和*
区分开,?
不可匹配多个节.
几个特殊的规则:
const routes = [
// will match everything and put it under `$route.params.pathMatch`
{ path: "/:pathMatch(.*)*", name: "NotFound", component: NotFound },
// will match anything starting with `/user-` and put it under `$route.params.afterUser`
{ path: "/user-:afterUser(.*)", component: UserGeneric },
];
Nested Routes
我们可以嵌套路由,即路由匹配的组件里也有router-view
.
我们通过children
设置子路由:
const routes = [
{
path: "/user/:id",
component: User,
children: [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: "profile",
component: UserProfile,
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: "posts",
component: UserPosts,
},
],
},
];
注意不要加/
开头,不然不会自动添加/user/:id/
的前缀作为路由.如果是''
则会匹配所有,可作为默认界面.
children 组件也可以继续嵌套.
Programmatic Navigation
除了使用 router-link,我们还可以使用 router 实例导航:
// literal string path
router.push("/users/eduardo");
// object with path
router.push({ path: "/users/eduardo" });
// named route with params to let the router build the url
router.push({ name: "user", params: { username: "eduardo" } });
// with query, resulting in /register?plan=private
router.push({ path: "/register", query: { plan: "private" } });
// with hash, resulting in /about#team
router.push({ path: "/about", hash: "#team" });
push
会加入一个新界面,注意如果使用了 path 参数,params 会被忽略.
push
会返回一个 promise 对象让可以可以异步处理.
router 提供router.push
, router.replace
and router.go
,于 history api 中的window.history.pushState
,window.history.replaceState
and window.history.go
相同.
Named routes
我们还可以使用name
来定义路由,这样就不用编写负责的 url 了.
定义:
const routes = [
{
path: "/user/:username",
name: "user",
component: User,
},
];
使用:
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>
或
router.push({ name: "user", params: { username: "erina" } });
Named Views
有时候我们需要在同个界面显示多个 view,此时我们需要给 view 提供name
:
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
此时我们也需要多个组件:
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/",
components: {
default: Home,
// short for LeftSidebar: LeftSidebar
LeftSidebar,
// they match the `name` attribute on `<router-view>`
RightSidebar,
},
},
],
});
它可以于嵌套一起使用.
Redirect and Alias
定义重定向:
const routes = [{ path: "/home", redirect: "/" }];
const routes = [{ path: "/home", redirect: { name: "homepage" } }];
const routes = [
{
// /search/screens -> /search?q=screens
path: "/search/:searchText",
redirect: (to) => {
// the function receives the target route as the argument
// we return a redirect path/location here.
return { path: "/search", query: { q: to.params.searchText } };
},
},
{
path: "/search",
// ...
},
];
我们还可以使用相对地址(不加/
):
const routes = [
{
// will always redirect /users/123/posts to /users/123/profile
path: "/users/:id/posts",
redirect: (to) => {
// the function receives the target route as the argument
// a relative location doesn't start with `/`
// or { path: 'profile'}
return "profile";
},
},
];
注意 navigation guard 对重定向的路由无效.
添加别名:
const routes = [
{
path: "/users",
component: UsersLayout,
children: [
// this will render the UserList for these 3 URLs
// - /users
// - /users/list
// - /people
{ path: "", component: UserList, alias: ["/people", "list"] },
],
},
];
注意如果有参数在别名中也应该添加参数.
Passing Props to Route Components
我们可以给路由对于的组件传 props.
我们可以使用参数传给 props:
const User = {
// make sure to add a prop named exactly like the route param
props: ["id"],
template: "<div>User {{ id }}</div>",
};
const routes = [{ path: "/user/:id", component: User, props: true }];
当设为对象时,会自动传给对应的 prop,这在参数是静态的时候很有用:
const routes = [
{
path: "/promotion/from-newsletter",
component: Promotion,
props: { newsletterPopup: false },
},
];
对于 Named view,需要分别定义:
const routes = [
{
path: "/user/:id",
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false },
},
];
我们还可以设为参数动态生成:
const routes = [
{
path: "/search",
component: SearchUser,
props: (route) => ({ query: route.query.q }),
},
];
Different History modes
我们有两种 history mode,一种是hash mode
:
import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
});
这种模式下 url 会加一个#
前缀,切换时不会向服务器发出请求.
另外还有HTML5 Mode
:
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
});
这种情况下没有#
前缀,但切换时会发出请求,我们需要配置服务器不是静态资源的所有链接都指向index.html
.
Navigation Guards
navigation guards
用于验证跳转.
我们可以全局注册navigation guards
:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// explicitly return false to cancel the navigation
return false
})
其中的参数意义:
- to:目标路由
- from:源路由
返回值意义:
false
: 取消跳转- 路由: 重定位到该路由,格式如
{path:''}
. - 不返回或 true: 正常跳转
整个跳转流程:
- Navigation triggered.
- Call
beforeRouteLeave
guards in deactivated components. - Call global
beforeEach
guards. - Call
beforeRouteUpdate
guards in reused components. - Call
beforeEnter
in route configs. - Resolve async route components.
- Call
beforeRouteEnter
in activated components. - Call global
beforeResolve
guards. - Navigation is confirmed.
- Call global
afterEach
hooks. - DOM updates triggered.
- Call callbacks passed to
next
inbeforeRouteEnter
guards with instantiated instances.
其中需要注意:
beforeEnter
直接在 route 设置(和path
等并列)里填写,只会在从一个不同的路由跳转到时触发.它可以接受一个函数数组作为值.afterEach
不可影响跳转,可以接受第三个参数 failure 对 navigation failure 进行处理.
我们还可以直接在组件中定义一些navigation guards
:
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// called before the route that renders this component is confirmed.
// does NOT have access to `this` component instance,
// because it has not been created yet when this guard is called!
},
beforeRouteUpdate(to, from) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, given a route with params `/users/:id`, when we
// navigate between `/users/1` and `/users/2`, the same `UserDetails` component instance
// will be reused, and this hook will be called when that happens.
// Because the component is mounted while this happens, the navigation guard has access to `this` component instance.
},
beforeRouteLeave(to, from) {
// called when the route that renders this component is about to
// be navigated away from.
-- As with `beforeRouteUpdate`, it has access to `this` component instance.
},
}
Composition API
在 composition 中使用:
import { useRouter, useRoute } from "vue-router";
export default {
setup() {
const router = useRouter();
const route = useRoute();
function pushWithQuery(query) {
router.push({
name: "search",
query: {
...route.query,
},
});
}
},
};
navigation guards:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})
const userData = ref()
-- same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
使用router-link
的相关 methods(和使用 v-slot 相同):
import { RouterLink, useLink } from "vue-router";
import { computed } from "vue";
export default {
name: "AppLink",
props: {
// add @ts-ignore if using TypeScript
...RouterLink.props,
inactiveClass: String,
},
setup(props) {
const { route, href, isActive, isExactActive, navigate } = useLink(props);
const isExternalLink = computed(
() => typeof props.to === "string" && props.to.startsWith("http")
);
return { isExternalLink, href, navigate, isActive };
},
};