目录
前置知识?
pushState() 方法
1?为什么需要?Vue?Router?
2??一个不用?Vue?Router?的例子
3?Vue?Router?安装和入门
通过??标签中的to传参
4 嵌套路由:实现页面子组件切换
?5 动态路由匹配和参数获取
6?一个问题:路由组件不刷新
7??高级路由匹配语法和优先级
8?获取?query?查询参数
9?使用命名路由精确控制跳转
10??路由别名:不同路径渲染同一组件? Router-??alias
11?重定向:把当前?URL?跳转到其它?URL
12?编程式的控制导航
13?命名视图:同一路由渲染多个组件
14? 在嵌套路由中使用命名视图
15?给当前活跃导航设置高亮样式
16?导航守卫? ?
1 全局导航守卫?
2??路由导航守卫
3?组件导航守卫
17 路由?meta?元数据
18?页面滚动行为控制
19?代码分割 : 路由组件的懒加载进行?代码分割
20?给组件传递?Props
21?封装?router-link?组件
22?处理导航错误
23?动态的添加路由
24?路由过渡动画
25? Vue router? ??Composition?API
前置知识?
1.后退:
window.history.back() -- 相当于用户在浏览器的工具栏上点击返回按钮;
2.前进:
window.history.forward() -- 相当于用户在浏览器的工具栏上点击前进按钮;
3.移动到指定历史记录点:
通过go()方法,指定一个相对于当前页面位置的数值,从当前会话的历史记录中加载页面(当前位置页面索引:0,上一页:-1,下一页:1);
window.history.go(-2)--后退2页,相当于调用两次back();
window.history.go(1)--前进1页,相当于调用forward();
可以通过window.history.length,得到历史记录栈中一共有多少页。
window.onpopstate、
window.history.pushState、
window.history.replaceState? ? ? API的区分
HTML5引入了 history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate 配合使用
pushState() 方法
pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个URL. 让我们来解释下这三个参数详细内容:
1?? ? 状态对象可以是能被序列化的任何东西。原因在于Firefox将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于640k的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.
2 标题?— Firefox 目前忽略这个参数,但未来可能会用到。在此处传一个空字符串应该可以安全的防范未来这个方法的更改。或者,你可以为跳转的state传递一个短标题。
3?URL — 该参数定义了新的历史URL记录。注意,调用 pushState() 后浏览器并不会立即加载这个URL,但可能会在稍后某些情况下加载这个URL,比如在用户重新打开浏览器时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。 ?
replaceState() 方法 history.replaceState() 的使用与 history.pushState() 非常相似,区别在于 replaceState() 是修改了当前的历史记录项而不是新建一个。 注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。
replaceState() 的使用场景在于为了响应用户操作,你想要更新状态对象state或者当前历史记录的URL。
popstate 事件
? ?每当活动的历史记录项发生变化时, popstate 事件都会被传递给window对象。如果当前活动的历史记录项是被 pushState 创建的,或者是由 replaceState 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝。
? ? 页面加载时,或许会有个非null的状态对象。这是有可能发生的,举个例子,假如页面(通过pushState() 或 replaceState() 方法)设置了状态对象而后用户重启了浏览器。那么当页面重新加载时,页面会接收一个onload事件,但没有 popstate 事件。然而,假如你读取了history.state属性,你将会得到如同popstate 被触发时能得到的状态对象。
?
1?为什么需要?Vue?Router?
SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。 这里的路由就是SPA(单页应用)的路径管理器
2??一个不用?Vue?Router?的例子
<template>
<nav>
<a
v-for="(route, path) in routes"
:href="path"
@click.prevent="changeRoute(path)"
>{{ route.label }}</a
>
</nav>
<component :is="currentPage" />
</template>
<script setup>
import PageOne from "./components/PageOne.vue";
import PageTwo from "./components/PageTwo.vue";
import PageThree from "./components/PageThree.vue";
import { ref, computed } from "vue";
const routes = {
"/": {
component: PageOne,
label: "页面1",
},
"/2": {
component: PageTwo,
label: "页面2",
},
"/3": {
component: PageThree,
label: "页面3",
},
};
const currentPath = ref(location.pathname);
const currentPage = computed(
() => routes[currentPath.value].component || PageOne
);
function changeRoute(path) {
history.pushState(null, null, path);
currentPath.value = location.pathname;
}
</script>
3?Vue?Router?安装和入门
1 yarn add vue-router@4?
2 新建配置文件 router.js
router.js
import PageOne from "./components/PageOne.vue";
import PageTwo from "./components/PageTwo.vue";
import PageThree from "./components/PageThree.vue";
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
component: PageOne,
},
{
path: "/2",
component: PageTwo,
},
{
path: "/3",
component: PageThree,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
main.js?
import { createApp } from "vue";
import App from "./App.vue";
import router from "./routes"; //引入vue router
const app = createApp(App);
app.use(router); //使用router
app.mount("#app");
App.vue
?<router-link to="/">页面1</router-link>
?<router-view></router-view>
<template>
<nav>
<router-link to="/">页面1</router-link>
<router-link to="/2">页面2</router-link>
<router-link to="/3">页面3</router-link>
</nav>
<router-view></router-view>
</template>
<script>
export default {};
</script>
通过?<router-link> ?标签中的to传参
<router-link :to="{name:xxx,params:{key:value}}">valueString</router-link>
4 嵌套路由:实现页面子组件切换
?文件目录:
更新配置:? 嵌套路由
const routes = [
{
path: "/",
component: HomePage,
},
{
path: "/about",
component: AboutMe,
children: [
{ path: "work", component: WorkExperience },
{ path: "education", component: EducationExperience },
],
},
];
页面:
App.vue
<template>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<router-view></router-view>
</template>
AboutMe.vue
<template>
<div class="container">
<p>Hi 你好!这是关于我的页面</p>
<p>更多详情查看:</p>
<nav>
<router-link to="/about/work">工作经历</router-link>
<router-link to="/about/education">教育经历</router-link>
</nav>
<router-view></router-view>
</div>
</template>
?5 动态路由匹配和参数获取
配置:
const routes = [
{
path: "/",
component: BlogListPage,
},
{
path: "/:postId",
component: BlogPostPage,
},
];
BlogListPage.vue
<template>
<main>
<div className="blogPosts">
<article v-for="blogPost in blogPosts" :key="blogPost.id">
<h2>
<router-link :to="`/${blogPost.id}`">{{
blogPost.title
}}</router-link>
</h2>
<p>{{ blogPost.body.substring(0, 100) + "..." }}</p>
</article>
</div>
</main>
</template>
<script>
import { getAllPosts } from "../data/blogPosts";
?blogPosts.vue?
blogPosts.vue
<template>
<article>
<h2>
{{ blogPost.title }}
</h2>
<p>{{ blogPost.body }}</p>
<footer>
<router-link to="/">回到主页</router-link>
</footer>
</article>
</template>
<script>
export default {
data() {
return { blogPost: {} };
},
created() {
this.blogPost = getBlogPostById(this.$route.params.postId);
},
};
export function getBlogPostById(id) {
return blogPosts.find((post) => post.id === Number(id));
}
</script>
6?一个问题:路由组件不刷新
动态路由组件复用问题 :?
这个就是动态路由组件用的问题。动态路由使用同一个组件实例进行数据展示,如果路径是在动态路由之间进行跳转,也就是同组件跳转,例如斜杠一斜杠二,那么vue router会复用这个组件实例,导致生命周期失效。那么,这里我们可以使用watch来监听this.$?route的parents 参数?对象,它是响应性的,根据它的变化执行我们的业务逻辑。
<template>
<article>
<h2>
{{ blogPost.title }}
</h2>
<p>{{ blogPost.body }}</p>
<footer>
<router-link to="/">回到主页</router-link>
<router-link :to="`/${blogPost.id - 1}`">上一篇</router-link>
<router-link :to="`/${blogPost.id + 1}`">下一篇</router-link>
</footer>
</article>
</template>
<script>
import { getBlogPostById } from "../data/blogPosts";
export default {
data() {
return { blogPost: {} };
},
// created() {
// this.$watch(
// () => this.$route.params,
// function (params, OldParams) {
// this.blogPost = getBlogPostById(this.$route.params.postId);
// },
// {
// immediate: true,
// }
// );
// },
//或者使用 watch
watch: {
"$route.params": {
handler(params, oldParams) {
this.blogPost = getBlogPostById(params.postId);
},
immediate: true,
},
},
};
export function getBlogPostById(id) {
return blogPosts.find((post) => post.id === Number(id));
}
</script>
7??高级路由匹配语法和优先级
?
import { createRouter, createWebHistory } from "vue-router";
// 请一个一个尝试,注释掉其它路径
const routes = [
// {
// path: "/users/:userId/posts/:postId",
// component: PrintRoute,
// },
// {
// path: "/:categories+",
// component: PrintRoute,
// },
// {
// path: "/:categories*",
// component: PrintRoute,
// },
// {
// path: "/:categories?",
// component: PrintRoute,
// },
// {
// path: "/posts/:title([a-zA-Z0-9-]+)",
// component: PrintRoute,
// },
// {
// path: "/:notFound(.*)*",
// component: PrintRoute,
// },
// 优先级
// {
// path: "/:postId",
// component: PrintRoute,
// },
// {
// path: "/:userId",
// component: PrintRoute,
// },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
8?获取?query?查询参数
{{ $route.query }}
9?使用命名路由精确控制跳转
name属性????????
?
?如果两个路由匹配了相同的路径? 通过 router-link 中的to属性 明确指定name ,来避免歧义
?
10??路由别名:不同路径渲染同一组件? Router-??alias
?
const routes = [
{
path: "/",
component: BlogListPage,
// alias: "/posts",
alias: ["/posts", "/blogs"],
},
{
path: "/:postId",
component: BlogPostPage,
alias: "/posts/:postId",
},
];
11?重定向:把当前?URL?跳转到其它?URL
const routes = [
{
path: "/",
component: BlogListPage,
name: "blogList",
},
{
path: "/posts",
// redirect: "/",
redirect: { name: "blogList" }, //第一种直接设置 name
},
{
path: "/:postId",
component: BlogPostPage,
name: "blogPost",
},
{
path: "/posts/:postId",
// redirect: "/:postId", // 错误
redirect: (to) => { //第二种 设置一个to 函数
return { path: `/${to.params.postId}` };
},
},
// 或使用命名方式
// {
// path: "/posts/:postId",
// redirect: (to) => { //第三种 设置一个函数 返回 对象
// return { name: "blogPost", params: { postId: to.params.postId } };
// },
// },
];
12?编程式的控制导航
<!-- 使用字符串 -->
<!-- <button @click="$router.push(`/${blogPost.id}`)">查看全文</button>
-->
<!-- 使用配置对象 -->
<!-- <button
@click="
$router.push({
name: 'blogPost',
params: { postId: blogPost.id },
})
"
>
查看全文
</button> -->
<button
@click="
$router.replace({
name: 'blogPost',
params: { postId: blogPost.id },
})
"
>
查看全文
</button>
<!-- <button
@click="
$router.go(-2)
"
>
历史记录跳转
</button> -->
13?命名视图:同一路由渲染多个组件
1 给组件添加name?
<template>
<nav>
<router-view name="nav"></router-view>
</nav>
<main>
<router-view></router-view>
</main>
<footer>
<router-view name="footer"></router-view>
</footer>
</template>
2 在配置中 使用?components 属性?
import HomePage from "./pages/HomePage.vue";
import NavBar from "./pages/NavBar.vue";
import FooterView from "./pages/FooterView.vue";
import FooterViewAbout from "./pages/FooterViewAbout.vue";
import AboutMe from "./pages/AboutMe.vue";
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
components: {
nav: NavBar,
default: HomePage,
footer: FooterView,
},
},
{
path: "/about",
components: {
nav: NavBar,
default: AboutMe,
footer: FooterViewAbout,
},
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
14? 在嵌套路由中使用命名视图
name 属性??<router-view name="rightSideBar"></router-view>
<div class="container">
<aside class="leftSideBar">
<h2>博客管理</h2>
<nav>
<router-link to="/blogs/new">添加博客</router-link>
<router-link to="/blogs/details">博客详情</router-link>
</nav>
</aside>
<main class="content">
<router-view></router-view>
<router-view name="rightSideBar"></router-view>
</main>
</div>
配置 :
const routes = [
{
path: "/blogs",
component: BlogManagement,
children: [
{
path: "new",
components: {
default: AddBlog,
rightSideBar: RightSidebar,
},
},
{
path: "details",
component: BlogDetails,
},
],
},
];
15?给当前活跃导航设置高亮样式
BlogManagement.vue? 菜单栏?
/* .router-link-active { */
.router-link-exact-active {
position: relative;
color: hsl(280deg, 100%, 90%);
}
/* .router-link-active::before { */
.router-link-exact-active::before {
content: "";
position: absolute;
width: 3px;
height: 100%;
background: hsl(280deg, 100%, 70%);
left: -14px;
}
?
<router-link to="/blogs">管理首页</router-link>
<router-link to="/blogs/new">添加博客</router-link>
<router-link to="/blogs/details">博客详情</router-link>
<!-- <router-link to="/blogs/new" activeClass="link-active">添加博客</router-link> -->
<!-- <router-link to="/blogs/details" activeClass="link-active"
>博客详情</router-link
> -->
<!-- <router-link to="/blogs/details" linkExactActiveClass="link-active"
>博客详情</router-link
> -->
配置 两种 模式:
const router = createRouter({
history: createWebHistory(),
routes,
// linkActiveClass: "link-active",
// linkExactActiveClass: "link-exact-active" 精确的
});
export default router;
16?导航守卫? ?
限制路由的跳转?
vue-router 的生命周期钩子?
beforeEach 做权限验证
?
?
?
?
?
?
?
?
1 全局导航守卫?
const loggedIn = true;
router.beforeEach((to, from) => {
? // console.log(from);
? // console.log(to);
? // if (to.path === "/blogs") {
? if (to.path.startsWith("/blogs")) {
? ? // return false;
? ? if (!loggedIn) {
? ? ? return "/login";
? ? ? // return { path: "/login" };
? ? ? // return { name: "login" };
? ? }
? }
});
router.beforeResolve((to) => {
? if (to.path.startsWith("/blogs")) {
? ? console.log("用户已登录...");
? }
});
router.afterEach((to) => {
? document.title = to.path;
});
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
component: HomePage,
},
{
path: "/login",
name: "login",
component: LoginPage,
},
{
path: "/blogs",
component: BlogManagement,
children: [
{
path: "new",
components: {
default: AddBlog,
rightSideBar: RightSidebar,
},
},
{
path: "details",
component: BlogDetails,
},
],
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
const loggedIn = true;
router.beforeEach((to, from) => {
// console.log(from);
// console.log(to);
// if (to.path === "/blogs") {
if (to.path.startsWith("/blogs")) {
// return false;
if (!loggedIn) {
return "/login";
// return { path: "/login" };
// return { name: "login" };
}
}
});
router.beforeResolve((to) => {
if (to.path.startsWith("/blogs")) {
console.log("用户已登录...");
}
});
router.afterEach((to) => {
document.title = to.path;
});
export default router;
2??路由导航守卫
const routes = [
{
path: "/",
component: HomePage,
},
{
path: "/login",
component: LoginPage,
},
{
path: "/blogs",
component: BlogManagement,
children: [
{
path: "new",
components: {
default: AddBlog,
rightSideBar: RightSidebar,
},
},
{
path: "details/:postId",
component: BlogDetails,
beforeEnter(to, from) {
console.log(to);
console.log(from);
},
},
],
// beforeEnter(to, from) {
// if (!loggedIn) {
// return "/login";
// }
// },
beforeEnter: [authBlogsPage],
},
];
function authBlogsPage(to, from) {
if (!loggedIn) {
return "/login";
}
}
3?组件导航守卫
beforeRouteEnter、
beforeRouteUpdate
beforeRouteLeave
<template>
<article>
<h2>{{ blog.title }}</h2>
<p>{{ blog.body }}</p>
</article>
</template>
<script>
import { getBlogPostById } from "../data/blogPosts";
export default {
data() {
return {
blog: {},
};
},
// 也只在路由变化的时候调用,组件复用的时候不会重新执行
beforeRouteEnter(to, from, next) {
// console.log(to);
// console.log(this);
next((vm) => {
setTimeout(() => {
vm.blog = getBlogPostById(to.params.postId);
}, 1000);
});
},
beforeRouteUpdate(to) {
this.blog = {};
setTimeout(() => {
this.blog = getBlogPostById(to.params.postId);
}, 1000);
},
beforeRouteLeave() {
const result = confirm("真的要离开吗?");
if (!result) {
return false;
}
},
};
</script>
做一些权限验证?
meta 合并 子meta会覆盖父meta
matched??匹配到的所有路由对象 会进行合并?
matched:{ private: true??}?
const routes = [
{
path: "/",
component: HomePage,
},
{
path: "/login",
name: "login",
component: LoginPage,
},
{
path: "/blogs",
component: BlogManagement,
meta: { private: true }, // 3. 合并,值必须一致,否则重复的属性,会以最后一个属性值为准,子路由的 meta 属性可以省略
children: [
{
path: "new",
components: {
default: AddBlog,
rightSideBar: RightSidebar,
},
// meta: { private: true },
// meta: { private2: true }, // 3. 合并
},
{
path: "details",
component: BlogDetails,
// meta: { private: true },
// meta: { private: false },
},
],
},
];
18?页面滚动行为控制
scrollBehavior
多种用法
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
component: BlogListPage,
},
{
path: "/:postId",
component: BlogPostPage,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// return { top: 0 };
// return {
// top: 200,
// behavior: "smooth",
// };
// 延迟
// return new Promise((resolve) => {
// setTimeout(() => resolve({ top: 200, behavior: "smooth" }), 1000);
// });
// return {
// el: "#app",
// top: -60,
// };
console.log(savedPosition);
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
},
});
export default router;
19?代码分割 : 路由组件的懒加载进行?代码分割
// import BlogListPage from "./pages/BlogListPage.vue";
// import BlogPostPage from "./pages/BlogPostPage.vue";
const BlogListPage = () => import("./pages/BlogListPage.vue");
const BlogPostPage = () => import("./pages/BlogPostPage.vue");
// import BlogListPage from "./pages/BlogListPage.vue";
// import BlogPostPage from "./pages/BlogPostPage.vue";
import { createRouter, createWebHistory } from "vue-router";
const BlogListPage = () => import("./pages/BlogListPage.vue");
const BlogPostPage = () => import("./pages/BlogPostPage.vue");
const routes = [
{
path: "/",
component: BlogListPage,
},
{
path: "/:postId",
component: BlogPostPage,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
20?给组件传递?Props
配置? ?多种用法
const routes = [
{
path: "/",
component: BlogListPage,
},
{
path: "/:postId",
component: BlogPostPage,
// props: true,
// props: { postId: 5 },
props: (route) => {
console.log(route);
return { postId: Number(route.params.postId) };
},
},
];
组件中:
export default {
props: ["postId"],
data() {
return { blogPost: {} };
},
created() {
// this.blogPost = getBlogPostById(this.$route.params.postId);
console.log(this.postId);
console.log(typeof this.postId); // 函数形式,可以改变 postId 的类型
this.blogPost = getBlogPostById(this.postId);
},
};
21?封装?router-link?组件
作用: 自定义UI样式? ?, 实现复杂业务逻辑?
<template>
<router-link v-if="!private || isLoggedIn()" v-bind="$props">
<slot />
</router-link>
</template>
<script>
import { RouterLink } from "vue-router";
export default {
props: {
...RouterLink.props,
private: {
type: Boolean,
default: false,
},
},
methods: {
isLoggedIn() {
// localStorage.getItem("user") !== null;
return false;
},
},
};
</script>
<style scoped></style>
22?处理导航错误
?使用?isNavigationFailure方法??
? ?await this.$router.push("/blogs");
? ?console.log(this.$router.currentRoute.value);
<template>
<nav class="globalNav">
<router-link to="/login">登录</router-link>
<button @click="redirectToManagementPage">博客管理</button>
</nav>
</template>
<script>
import { isNavigationFailure, NavigationFailureType } from "vue-router";
export default {
methods: {
async redirectToManagementPage() {
// 1. aborted
// const failure = await this.$router.push("/blogs");
// console.log(failure); // Error: Navigation aborted from "/" to "/blogs" via a navigation guard.
// console.log(isNavigationFailure(failure)); // true
// console.log(isNavigationFailure(failure, NavigationFailureType.aborted)); // true
// console.log(failure.to, failure.from);
// alert("请先登录"); // 实际开发中,可以用一个用户体验友好的弹窗
// 2. cancelled
// new Promise(async (resolve) => {
// const failure = await this.$router.push("/blogs");
// console.log(failure);
// console.log(
// isNavigationFailure(failure, NavigationFailureType.cancelled)
// ); // true
// resolve();
// });
// this.$router.push("/login");
// 3. duplicated
// const failure = await this.$router.push("/");
// console.log(failure);
// console.log(
// isNavigationFailure(failure, NavigationFailureType.duplicated)
// );
// 4. redirect
await this.$router.push("/blogs");
console.log(this.$router.currentRoute.value);
},
},
};
</script>
?
23?动态的添加路由
?
handlePageSubmit() {
this.pages.push({
pageUrl: this.pageUrl,
pageName: this.pageName,
pageContent: this.pageContent,
});
this.$router.addRoute({
path: this.pageUrl,
name: this.pageUrl.slice(1),
component: PageTemplate,
props: {
pageContent: this.pageContent,
},
});
console.log(this.$router.getRoutes());
},
removePage(pageUrl) {
this.$router.removeRoute(pageUrl.slice(1));
console.log(this.$router.getRoutes());
this.pages.splice(
this.pages.findIndex((page) => page.pageUrl === pageUrl),
1
);
},
<button type="submit" @click="handlePageSubmit">提交</button>
</form>
<div>
<ul>
<li v-for="page in pages" :key="page.pageUrl">
{{ page.pageName }}
<span @click="removePage(page.pageUrl)"> 删除</span>
</li>
</ul>
</div>
</main>
export default {
data() {
return {
pageUrl: "",
pageName: "",
pageContent: "",
pages: [],
};
},
methods: {
handlePageSubmit() {
this.pages.push({
pageUrl: this.pageUrl,
pageName: this.pageName,
pageContent: this.pageContent,
});
this.$router.addRoute({
path: this.pageUrl,
name: this.pageUrl.slice(1),
component: PageTemplate,
props: {
pageContent: this.pageContent,
},
});
console.log(this.$router.getRoutes());
},
removePage(pageUrl) {
this.$router.removeRoute(pageUrl.slice(1));
console.log(this.$router.getRoutes());
this.pages.splice(
this.pages.findIndex((page) => page.pageUrl === pageUrl),
1
);
},
},
};
24?路由过渡动画
?vue会自动添加 进场和离场的 Class?
?
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" />
<!-- 对于 /1,/2类似的跳转,组件会进行复用,过渡不会执行。添加 key 可以强制执行过渡 -->
<!-- <component :is="Component" :key="route.path" /> -->
</transition>
</router-view>
</template>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fadeAndMove-enter-active {
transition: all 0.4s;
}
.fadeAndMove-enter-from {
transform: translateY(-10px);
opacity: 0;
}
25? Vue router? ??Composition?API
? const route = useRoute();? //访问 Route对象?
? ?watchEffect(() => {? ?// 监听变化 重新获取数据
? ? ? blogPost.value = getBlogPostById(route.params.postId);
? ? });
?const router = useRouter();? ? ?// 控制导航
? ? function next() {
? ? ? router.push(`/${blogPost.value.id + 1}`);
? ? }
? ?onBeforeRouteUpdate((to, from) => {? ?//组件导航守卫
? ? ? console.log(to, from);
? ? });
? ? // onBeforeRouteLeave? ?//组件导航守卫
<template>
<article>
<h2>
{{ blogPost.title }}
</h2>
<p>{{ blogPost.body }}</p>
<footer>
<router-link to="/">回到主页</router-link>
<router-link :to="`/${blogPost.id - 1}`">上一篇</router-link>
<!-- <router-link :to="`/${blogPost.id + 1}`">下一篇</router-link> -->
<!-- <button @click="next">下一篇</button> -->
<ButtonLink :to="`/${blogPost.id + 1}`">下一篇</ButtonLink>
</footer>
</article>
</template>
<script>
import { getBlogPostById } from "../data/blogPosts";
import ButtonLink from "../components/ButtonLink.vue";
import { ref, watchEffect } from "vue";
import {
onBeforeRouteLeave,
onBeforeRouteUpdate,
useRoute,
useRouter,
} from "vue-router";
export default {
components: { ButtonLink },
setup() {
const blogPost = ref({});
const route = useRoute();
watchEffect(() => {
blogPost.value = getBlogPostById(route.params.postId);
});
const router = useRouter();
function next() {
router.push(`/${blogPost.value.id + 1}`);
}
onBeforeRouteUpdate((to, from) => {
console.log(to, from);
});
// onBeforeRouteLeave
return { blogPost, next };
},
// 在 setup 之前执行,所以还需要配置式的声明
// beforeRouteEnter: (to, from) {
// }
};
</script>
|