学习Vue3的第三天
Vue3 生命周期
概念:生命周期钩子是 Vue 组件在其生命周期内不同阶段触发的函数,允许开发者在这些关键时刻插入自定义逻辑。
规律:
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。
Vue2 生命周期钩子
创建阶段
- beforeCreate:组件实例刚创建,数据观测和事件/侦听器尚未设置。
- created:组件实例创建完成,数据观测和事件/侦听器已设置,但 DOM 尚未挂载。
挂载阶段
- beforeMount:DOM 挂载前调用,模板已编译,虚拟 DOM 已创建。
- mounted:DOM 挂载完成后调用,可以进行 DOM 操作。
更新阶段
- beforeUpdate:数据更新后,虚拟 DOM 重新渲染前调用。
- updated:虚拟 DOM 更新完成后调用,数据已更新且 DOM 已重新渲染。
销毁阶段
- beforeDestroy:组件实例销毁前调用,可以进行清理操作。
- destroyed:组件实例销毁后调用,所有子组件也已销毁。
Vue3 生命周期钩子
创建阶段
- setup:组件创建之前调用,初始化逻辑在这里执行,此时组件的 props 和 context 可用。
挂载阶段
- onBeforeMount:组件挂载前调用,DOM 还未插入文档。
- onMounted:组件挂载完成后调用,可以进行 DOM 操作。
更新阶段
- onBeforeUpdate:组件数据更新前调用,虚拟 DOM 更新前。
- onUpdated:组件数据更新后调用,虚拟 DOM 已更新。
卸载阶段
- onBeforeUnmount:组件卸载前调用,可以进行清理操作。
- onUnmounted:组件卸载后调用,组件及其子组件都已卸载。
常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
示例代码:
<template>
<div class="person">
<!-- 显示当前求和结果 -->
<h2>当前求和为:{{ sum }}</h2>
<!-- 点击按钮时调用 changeSum 方法 -->
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {
ref, // 引入 ref 函数用于创建响应式数据
onBeforeMount, // 组件挂载前执行的钩子
onMounted, // 组件挂载完成后执行的钩子
onBeforeUpdate, // 组件更新前执行的钩子
onUpdated, // 组件更新完成后执行的钩子
onBeforeUnmount, // 组件卸载前执行的钩子
onUnmounted // 组件卸载完成后执行的钩子
} from 'vue'
// 声明一个初始值为 0 的响应式变量 sum
let sum = ref(0)
// 定义一个方法来增加 sum 的值
function changeSum() {
sum.value += 1 // 将 sum 的值加 1
}
console.log('setup') // 打印 setup 阶段的信息
// 组件挂载前执行
onBeforeMount(() => {
console.log('挂载之前') // 打印组件挂载前的信息
})
// 组件挂载完成后执行
onMounted(() => {
console.log('挂载完毕') // 打印组件挂载完成后的信息
})
// 组件更新前执行
onBeforeUpdate(() => {
console.log('更新之前') // 打印组件更新前的信息
})
// 组件更新完成后执行
onUpdated(() => {
console.log('更新完毕') // 打印组件更新完成后的信息
})
// 组件卸载前执行
onBeforeUnmount(() => {
console.log('卸载之前') // 打印组件卸载前的信息
})
// 组件卸载完成后执行
onUnmounted(() => {
console.log('卸载完毕') // 打印组件卸载完成后的信息
})
</script>
自定义hook
在 Vue3 中,自定义钩子(或称为自定义组合函数)是一个函数,利用了 Vue 的组合式 API(Composition API),它封装了逻辑并允许在多个组件之间共享和复用这些逻辑。这与 Vue2 中的 mixin 类似,但自定义钩子更具灵活性和可维护性。
自定义 hook 的优势
- 复用代码: 自定义 hook 可以将逻辑封装在一个函数中,并在多个组件中使用。这避免了重复编写相同的逻辑,并提高了代码的重用性。
- 逻辑清晰: 将逻辑提取到自定义 hook 中,可以使 setup 函数中的代码更简洁。这样有助于将关注点分离,使组件的主要逻辑更加专注。
- 易于维护: 自定义 hook 将逻辑集中在一个地方,使得更新和维护逻辑变得更加容易。如果逻辑有变化,只需修改 hook 中的实现,而不需要在每个使用该逻辑的组件中修改。
示例代码:
useSum.ts - 自定义 hook 用于管理计数器逻辑。
import { ref, onMounted } from 'vue';
/**
* 用于管理一个累加器的钩子函数
*
* 提供累加、累减操作以及当前累加和的获取
*/
export default function useSum() {
// 定义一个响应式变量 sum,用于存储当前的累加和
let sum = ref(0);
/**
* 累加操作
* 将 sum 的值增加 1
*/
const increment = () => {
sum.value += 1;
};
/**
* 累减操作
* 将 sum 的值减少 1
*/
const decrement = () => {
sum.value -= 1;
};
// 在组件挂载后调用 increment,对 sum 进行初始累加
onMounted(() => {
increment();
});
// 返回当前的累加和以及改变累加和的方法
return { sum, increment, decrement };
}
useDog.ts - 自定义 hook 用于管理狗狗图片的获取和加载。
import { reactive, onMounted } from 'vue';
import axios, { AxiosError } from 'axios';
/**
* 用于获取狗狗图片的自定义钩子
*
* 该钩子提供了一个函数用于获取狗狗图片,并管理图片加载状态和图片URL列表
*/
export default function useDog() {
// 状态管理对象,用于存储狗狗图片的URL列表和加载状态
let dogList = reactive<{ urlList: string[], isLoading: boolean }>({
urlList: [],
isLoading: false
});
/**
* 异步获取狗狗图片
*
* 该函数通过调用API获取狗狗图片的URL,并将其添加到urlList中
* 在获取图片前后更新isLoading状态,以反映加载状态
*/
const getDog = async () => {
dogList.isLoading = true;
try {
const { data } = await axios.get('https://dog.ceo/api/breed/pembroke/images/random');
dogList.urlList.push(data.message);
} catch (error) {
const err = <AxiosError>error;
console.log(err.message);
} finally {
dogList.isLoading = false;
}
};
// 在组件挂载时自动调用getDog函数以获取狗狗图片
onMounted(() => {
getDog();
});
// 返回包含dogList状态和getDog函数的对象,供外部使用
return { dogList, getDog };
}
组件使用 - 使用这两个自定义钩子。
<template>
<h2>当前求和为:{{ sum }}</h2>
<button @click="increment">点我+1</button>
<button @click="decrement">点我-1</button>
<hr>
<img v-for="(u, index) in dogList.urlList" :key="index" :src="u">
<span v-show="dogList.isLoading">加载中......</span><br>
<button @click="getDog">再来一只狗</button>
</template>
<script lang="ts" setup>
import useSum from '../hooks/useSum';
import useDog from '../hooks/useDog';
const { sum, increment, decrement } = useSum();
const { dogList, getDog } = useDog();
</script>
路由
Vue 3 的路由处理主要通过 Vue Router 库来实现。
Vue Router 是 Vue.js 的官方路由库,用于实现单页应用中的路由功能。它允许开发者定义 URL 与视图之间的映射,使得应用能够根据不同的 URL 显示不同的组件或页面,而不需要重新加载整个页面。
基本切换效果
要在 Vue 3 项目中使用 Vue Router,首先需要安装它:
npm install vue-router@4
然后,在项目中创建和配置路由。以下是一个简单的配置示例:
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView }
];
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 History API
routes
});
export default router;
在 src/main.ts 中将路由实例添加到 Vue 应用中:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
路由组件
在 Vue Router 中,路由通常通过以下组件来显示:
- <router-view>:这是一个占位组件,它显示当前激活路由所对应的组件。每个路由组件都会被渲染在 <router-view> 中。
- <router-link>:这是一个用于导航的链接组件,它生成的链接可以用于跳转到指定的路由。它相当于 HTML 中的 <a> 标签,但它会以 SPA 的方式进行导航,不会重新加载页面。
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view></router-view>
</template>
两个注意点
- 路由组件通常存放在`pages` 或 `views`文件夹,一般组件通常存放在`components`文件夹。
- 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载。
路由模式
哈希模式:使用 createWebHashHistory() 来创建路由实例,URL 会包含一个 # 符号。适用于一些不支持 HTML5 History API 的环境。
- 优点:兼容性更好,因为不需要服务器端处理路径。
- 缺点:`URL`带有`#`不太美观,且在`SEO`优化方面相对较差。
历史模式:使用 createWebHistory() 来创建路由实例,它提供更自然的 URL 结构。需要服务器配置,以便能够处理所有的 URL 请求。
- 优点:URL更加美观,不带有`#`,更接近传统的网站URL。
- 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有`404`错误。
const router = createRouter({
history: createWebHistory(), // 或者 createWebHashHistory()
routes
});
to的写法
在模板中使用 <router-link> 组件时,可以通过 to 属性指定导航目标。to 属性可以是字符串、对象或函数,具体如下:
- 字符串:用于简单的路径导航。
- 对象:用于更复杂的导航,例如包含查询参数或哈希片段。
- 函数:动态生成导航目标。
<template>
<nav>
<!-- 字符串写法 -->
<router-link to="/about">About</router-link>
<!-- 对象写法 -->
<router-link :to="{ path: '/user', query: { id: 123 } }">User</router-link>
<!-- 函数写法 -->
<router-link :to="generatePath">Dynamic</router-link>
</nav>
</template>
<script>
export default {
methods: {
generatePath() {
return { path: '/dynamic', query: { timestamp: Date.now() } };
}
}
}
</script>
命名路由
命名路由是 Vue Router 提供的一种功能,它允许我们为路由定义一个名称,从而在导航时可以使用这个名称而不是路径。这种方式特别适用于大型应用,能提高代码的可维护性和灵活性。
如何定义命名路由
在路由配置中,可以为每个路由定义一个 name 属性。以下是一个示例:
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
const routes = [
{
path: '/',
name: 'home', // 命名路由
component: HomeView
},
{
path: '/about',
name: 'about', // 命名路由
component: AboutView
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
使用命名路由进行导航
可以在 <router-link> 中使用命名路由,通过 :to 属性传递路由名称:
<template>
<nav>
<!-- 使用命名路由进行导航 -->
<router-link :to="{ name: 'home' }">Home</router-link>
<router-link :to="{ name: 'about' }">About</router-link>
</nav>
</template>
命名路由的好处
- 简化代码:使用名称而非路径,避免硬编码路径字符串,特别是当路径发生变化时,可以减少修改的地方。
- 灵活性:路径修改时,只需更新路由配置,不需要更改组件中的链接。
- 清晰的意图:路由名称更具描述性,能更清晰地表达导航意图。
嵌套路由
Vue Router 支持嵌套路由,使得可以在一个页面内展示子页面。例如,一个博客文章页面可以有评论作为嵌套的子路由:
const routes = [
{
path: '/post/:id',
component: PostView,
children: [
{
path: 'comments',
component: CommentsView
}
]
}
];
路由传参
在 Vue Router 中,可以通过多种方式传递参数到路由。常见的方式包括路径参数、查询参数和路由元数据。
路径参数
路径参数是路由定义中的动态部分。例如,定义一个用户详情页面路由时,可以在路径中使用占位符:
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import UserProfile from '../views/UserProfile.vue';
const routes = [
{
path: '/user/:id',
name: 'user-profile',
component: UserProfile
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
在组件中,可以通过 this.$route.params 访问这些参数:
<template>
<div>User ID: {{ userId }}</div>
</template>
<script>
export default {
computed: {
userId() {
return this.$route.params.id;
}
}
}
</script>
- 备注1:传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path。
- 备注2:传递params参数时,需要提前在规则中占位。
查询参数
查询参数附加在 URL 的末尾,以 ? 开始,多个参数时,例如 :“/news/detail?a=1&b=2&content=欢迎你”。可以通过 :to 属性传递查询参数:
<template>
<router-link :to="{ name: 'user-profile', query: { id: 123 } }">User Profile</router-link>
</template>
在组件中,使用 this.$route.query 访问查询参数:
<template>
<div>Query ID: {{ queryId }}</div>
</template>
<script>
export default {
computed: {
queryId() {
return this.$route.query.id;
}
}
}
</script>
路由元数据
虽然不直接用于导航,但路由元数据(meta 字段)可以用于存储其他信息,如访问权限或页面标题:
const routes = [
{
path: '/profile',
name: 'profile',
component: UserProfile,
meta: { requiresAuth: true }
}
];
在导航守卫中可以访问这些元数据:
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login');
} else {
next();
}
});
总结
- 路径参数:在路由定义中用 :param 形式定义,访问方式为 this.$route.params.param。
- 查询参数:通过 URL 中 ? 后的参数传递,访问方式为 this.$route.query.param。
- 路由元数据:用来存储附加信息,通过 meta 字段配置。
路由的props配置
在 Vue Router 中,props 配置允许将路由参数直接作为组件的 props 传递,这样可以使组件更具可重用性和测试性。
在路由定义中,可以通过 props 属性将路由参数传递给组件。可以配置为 true、false 或一个函数:
- 布尔值 true:将路由参数作为组件的 props 传递。
- 布尔值 false:不传递任何 props。
- 函数:可以自定义如何将路由信息作为 props 传递。
布尔值 true
const routes = [
{
path: '/user/:id',
name: 'user-profile',
component: UserProfile,
props: true // 将路由参数作为 props 传递
}
];
在 UserProfile 组件中,可以直接使用 id prop:
<template>
<div>User ID: {{ id }}</div>
</template>
<script>
export default {
props: ['id'] // 声明 `id` 作为 prop
}
</script>
函数
const routes = [
{
path: '/user/:id',
name: 'user-profile',
component: UserProfile,
props: route => ({ id: route.params.id }) // 自定义 props
}
];
在 UserProfile 组件中,同样可以使用 id prop:
<template>
<div>User ID: {{ id }}</div>
</template>
<script>
export default {
props: ['id']
}
</script>
为什么使用 props
- 增强组件可重用性:将路由参数作为 props 传递可以使组件更容易重用和测试。
- 简化组件逻辑:避免在组件内部直接访问 $route,使组件逻辑更简洁明了。
- 提高可测试性:通过 props 传递数据,使组件的测试更加直接,无需模拟 $route 对象。
replace 属性
作用
replace 属性用于控制路由跳转时如何操作浏览器的历史记录。它决定了是否在历史记录中替换当前条目,而不是新增一个条目。
浏览器的历史记录写入方式
- push: 默认方式,添加新的历史记录条目。使用 push 进行的导航会在浏览器的历史记录中增加一个新的条目,从而允许用户通过“后退”按钮返回到之前的页面。
- replace: 替换当前的历史记录条目。使用 replace 进行的导航不会添加新的历史记录条目,而是替换掉当前的条目。这样,用户无法通过“后退”按钮返回到之前的页面。
开启 replace 模式
在 <router-link> 中使用,可以在 <router-link> 组件中使用 replace 属性来开启 replace 模式。点击链接时,当前的历史记录条目将被替换,而不会新增条目:
<template>
<router-link :to="{ name: 'user-profile', params: { id: 123 } }" replace>
Go to User Profile
</router-link>
</template>
在编程式导航中使用
在编程式导航中,可以通过将 replace 选项设置为 true 来替换当前的历史记录条目:
// 使用 replace 进行编程式导航
this.$router.push({ name: 'user-profile', params: { id: 123 }, replace: true });
在这种情况下,replace: true 确保了当前的历史记录条目被新的条目替换,而不是在历史记录中添加新的条目。
何时使用 replace 属性
- 表单提交:在处理表单提交后,通常会使用 replace 属性来避免在提交后返回表单页的历史记录。
- 重定向:在某些场景中,可能会进行重定向而不希望用户能通过“后退”按钮返回到原页面。使用 replace 属性可以实现这种效果。
- 程序逻辑:在根据程序逻辑进行导航时,如果你希望替换当前历史记录而不是创建新的条目,可以使用 replace。
编程式导航
编程式导航允许你在代码中控制路由跳转,而不是仅仅依赖于 <router-link>。它通常用于处理复杂的导航逻辑,比如在用户操作之后或在条件满足时自动跳转。
基本用法
在 Vue 组件中,可以使用 this.$router 对象进行编程式导航。例如,跳转到某个路径:
this.$router.push('/new-path');
带参数的导航
可以在导航时传递参数,例如:
this.$router.push({ name: 'user-profile', params: { id: 123 } });
重定向与 replace 属性
使用 replace 选项来替换当前历史记录条目,而不是添加新条目:
this.$router.replace('/new-path');
处理导航错误
可以使用 catch 处理导航错误,例如路由不存在的情况:
this.$router.push('/unknown-path').catch(err => {
console.error(err);
});
使用导航守卫
路由守卫用于在路由导航前或后执行特定的逻辑。Vue Router 提供了多种类型的路由守卫:
- 全局守卫:在 router 实例中定义,适用于所有路由。
- 路由独享守卫:在路由配置中定义,适用于特定路由。
- 组件内守卫:在组件内部定义,适用于组件的生命周期。
beforeRouteEnter(to, from, next) {
if (to.meta.requiresAuth) {
// 需要认证的逻辑
next('/login');
} else {
next();
}
}
重定向
在 Vue Router 中,重定向用于将用户自动导航到另一个路由。当某个特定的路径被访问时,可以使用重定向将用户引导到预定的路径。
基本重定向
可以在路由配置中使用 redirect 属性来设置重定向。例如,当访问 /old-path 时,将自动重定向到 /new-path:
const routes = [
{ path: '/old-path', redirect: '/new-path' },
{ path: '/new-path', component: NewPathComponent },
];
动态重定向
可以使用函数动态决定重定向的目标。例如,根据某个条件决定重定向的路径:
const routes = [
{
path: '/redirect-me',
redirect: to => {
return to.query.loggedIn ? '/home' : '/login';
}
},
{ path: '/home', component: HomeComponent },
{ path: '/login', component: LoginComponent },
];
重定向与 replace 属性
在编程式导航中,可以结合 replace 属性来控制是否在历史记录中创建新的条目。例如,在重定向后使用 replace 属性来避免在历史记录中添加新条目:
// 使用 replace 进行编程式重定向
this.$router.replace('/home');
总结
- redirect 属性: 用于设置路由重定向。
- 动态重定向: 可以使用函数根据条件动态决定重定向的目标。
- replace 属性: 在编程式重定向中控制是否替换当前历史记录条目。