Vue组件生命周期函数
每个 Vue 生命周期事件都会对应 2 个钩子函数,分别在事件开始前和事件结束后调用。
在Vue app中,可以应用 4 个主要事件,所有有 8 个主要钩子函数(main hooks)。
- Creation - 组件创建时运行
- Mounting - 当 DOM 挂载时运行
- Updates - 响应数据修改时运行
- Destruction - 元素销毁时运行
Vue2 生命周期函数
- beforeCreate - 实例初始化后立即调用
- Created - 实例处理完状态相关的选项后立即调用
- beforeMount - 组件被挂载前调用
- Mounted - 组件被挂载后调用
- beforeUpdate - 响应数据被修改,并重现渲染前调用
- Updated - 重新渲染后调用
- beforeDestroy - 实例销毁前调用
- Destroyed - 实例销毁后调用
Vue3 生命周期函数
相对于 Vue2 的改变如下
- beforeCreate -> setup
- Created -> setup
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- Destroyed -> onUnmounted
Vue3 组件的通讯方式
Vue3 组件的通讯方式有
- props - 父传子
- $emit - 子传父
- expose/ref - 暴露属性或方法
- $attrs - 获取传过来除 props 外的属性
- v-model - 双向绑定
- provide/inject - 使用依赖
- Vuex - 状态管理模式
props
2 种接收方法
<!-- 第一种方法,声明要接受的数据 -->
<script>
export default {
// props:['list'], //数组形式
//对象形式,定义数据类型
props: {
// list: Array,
list: {
type: Array,
default: ()=>{return []},
},
},
//可以在setup函数中作为参数获取
setup(props) {
console.log(props); //传过来数据,必须要有上面的声明
},
};
</script>
<!-- 第二种,纯Vue3写法 -->
<script setup>
const props = defineProps({
// list: Array,
list: {
type: Array,
default: () => {return []},
},
});
console.log(props);
</script>
$emit
用于声明组件触发的自定义事件
//发送数据的组件
<script setup>
//声明事件名和要发送的数据
//写法一
this.$emit("getName","张三");
this.$emit("getSex",18);
//写法二
const emits = defineEmits(["getName", "getSex"]);
emits("getName", "张三");
emits("getAge", 18);
</script>
//接收数据的组件
<template>
<MyInput @getName="emitsGetName" @getAge="emitsGetAge" /> //接收自定义事件
<template/>
<script setup>
const emitsGetName = (data) => { //data是子组件传递过来的数据
console.log(data); //张三
};
const emitsGetAge = (data) => {
console.log(data);
};
</script>
expose/ref
用于声明组件被父组件通过模板引用访问时暴露的公共属性
//发送数据的组件
<script setup>
defineExpose({
name:'子组件的属性',
func(){
console.log('子组件的方法')
}
})
</script>
//接收数据的组件
<template>
<MyInput ref="comp" /> //发送数据的组件,使用ref接收
</template>
<script setup>
import MyInput from "./components/MyInput.vue";
import { ref, onMounted } from "vue";
const comp = ref(null);
onMounted(() => {
console.log(comp.value.name);
comp.value.func()
});
</script>
$attrs
主要用于接收在 props 中没定义,但父组件传过来的属性
//接收数据的组件
<script setup>
import { useAttrs } from "vue";
const props = defineProps({
// list: Array,
list: {
type: Array,
default: () => {
return [];
},
},
});
console.log(props);
console.log(useAttrs().data); //props 中没定义,但父组件要传的属性
</script>
//发送数据的组件
<template>
<MyList :list="list" data="props 中没定义,但父组件要传的属性"/>
</template>
v-model
双向绑定
//子组件
<template>
<div class="input">
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue',$event.target.value)"
/>
</div>
</template>
<script setup>
defineProps(["modelValue"]);
defineEmits(["update:modelValue"]);
</script>
//父组件
<MyInput v-model="data" />
<script setup>
import { ref } from "vue";
const data = ref("传入的数据");
</script>
provide/inject
provide - 标记父组件中可以让后代组件访问的变量,无论多少层
inject - 子组件使用其访问变量
// Parent.vue
<script setup>
import { provide } from "vue"
provide("name", "沐华")
</script>
// Child.vue
<script setup>
import { inject } from "vue"
const name = inject("name")
console.log(name) // 沐华
</script>
Vuex
// store/index.js
import { createStore } from "vuex"
export default createStore({
state:{ count: 1 },
getters:{
getCount: state => state.count
},
mutations:{
add(state){
state.count++
}
}
})
// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")
// Page.vue
// 方法一 直接使用
<template>
<div>{{ $store.state.count }}</div>
<button @click="$store.commit('add')">按钮</button>
</template>
// 方法二 获取
<script setup>
import { useStore, computed } from "vuex"
const store = useStore()
console.log(store.state.count) // 1
const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
console.log(count) // 1
</script>
Vue 常见的优化方式
- 路由懒加载 - import()
- 异步组件 - defineAsyncComponent
- 组件缓存 - keep-alive
- 计算属性稳定性 - computed()
- 浅响应 - shallowRef()/shallowReactive()
路由懒加载
当打包时,js文件会变得非常大,影响页面加载。我们可以将不同路由对应的组件分割成不同的代码块,在访问路时只加载相应的组件,这样就会更加高效。
使用 import()
//普通加载
import home from "../components/Home.vue";
//懒加载
const about = import("../components/About.vue")
const routes = [
{
path: "/",
name: "Home",
component: home,
},
{
path: "/about",
name: "About",
component: about,
//或者直接使用
// component: () => import("../components/About.vue"),
},
];
异步组件
将应用拆分成尽可能小的块,通过异步加载组件,在需要使用时才从服务器上加载相关组件,从而加快加载速度。
使用 defineAsyncComponent()
import { defineAsyncComponent } from "vue";
//像其他组件一样使用 AsnycComp
const AsnycComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(/*服务器获取的组件*/);
});
});
//或者使用import() 按需引入
const AsyncComp = defineAsyncComponent(() =>
import('./components/about.vue')
)
组件缓存
在组件切换时将状态保留在缓存中,避免重复渲染DOM,减少加载时间及性能消耗。
使用 keep-alive
<template>
<router-link to="/" active-class="isActive">home</router-link>
<router-link to="/about" active-class="isActive">about</router-link>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</template>
计算属性
计算属性可以减少不必要的重复计算,从而提高性能。
使用 computed
import { computed, ref, watchEffect } from "vue";
const count = ref(0);
//判断每次值的变化,如果变化返回新的值,如果没变化,使用旧值
const coumputedObj = computed((oldValue) => {
const newValue = { isEven: count.value % 2 === 0 };
if (oldValue && oldValue.isEven === newValue.isEven) return oldValue;
return newValue;
});
watchEffect(() => {
console.log(coumputedObj.value.isEven);
});
count.value = 2; // 没变化 true
count.value = 4; // 没变化 true
count.value = 3; // 变化 false
浅响应
在类似大型列表的大小数据量中,深度响应会导致不小的性能负担,因为每个属性访问都会触发代理个跟踪。这时最好使用浅响应。
- shallowReactive - 只处理对象外层属性的响应式
- shallowRef - 只处理基本类型的响应式
shallowReactive
const hugeList = shallowReactive({
//只有这一层有响应式
name: "张三",
age: 22,
parent: {
//以下都没有响应式
fa: {
realFa: "哈哈",
},
},
});
shallowRef
//响应式
const sum = shallowRef(0);
//非响应式
const sum1 = shallowRef({
n: 0,
});
Vue 组件的多种用法
- 动态组件 - component 组件
- 异步组件 - defineAsyncComponent()
- 组件缓存 - keep-alive 组件
- 插槽 - slot 组件
动态组件
适用于多个组件来回切换的场景
component 组件
<button @click="curComp = comp[0]">组件1</button>
<button @click="curComp = comp[1]">组件2</button>
<keep-alive>
<component :is="curComp" /> // :is是当前显示的哪个组件
</keep-alive>
import home from "./components/Home.vue";
import about from "./components/About.vue";
import { ref } from "vue";
const comp = ref([home, about]);
const curComp = ref(comp[0]);
异步组件
在用到该组件时才会加载,用于优化性能
defineAsyncComponent()
从服务器加载
defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(/*从服务器获取的组件*/);
});
});
从本地加载 配合 import()
defineAsyncComponent(() => {
import('./component/Mycomp.vue')
});
组件缓存
组件缓存,用于缓存组件的状态,当切换时不会重新加载
keep-alive 组件
<keep-alive>
<component :is="curComp" />
</keep-alive>
插槽
为子组件传入一下模板片段,在子组件固定位置中渲染这些片段
slot 组件
//父组件使用
<home>
<template v-slot:header> 插槽1 </template>
<template v-slot:main> 插槽2 </template>
<template v-slot:footer> 插槽3 </template>
</home>
//home.vue
<template>
<div>家</div>
<header>
<!-- 如果没有传值,里面就是默认值 -->
<slot name="header">默认值</slot>
</header>
<main>
<slot name="main"></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
nextTick
用于在修改数据时,获取最新的 DOM
大概原理是理由事件循环机制,进行异步操作,在DOM异步渲染结束后,才执行里面的回调函数,这样就可以获取最新的 DOM
<ul>
<li v-for="(item, key) in list" :key="key">{{ item.name }}</li>
</ul>
<button @click="add">添加</button>
const list = ref([
{
name: "张三",
age: 10,
},
{
name: "李四",
age: 12,
},
{
name: "王五",
age: 15,
},
]);
const add = () => {
list.value.push({
name: "新增",
});
// 添加前的长度
console.log(document.querySelectorAll("li").length);
//添加后的长度
nextTick(() => {
console.log(document.querySelectorAll("li").length);
});
};
Vue3 的 ref 和 reactive
ref
ref 的作用是将一个原始数据类型转换成一个响应式的数据类型,在这个数据改变时,用到这个数据的地方就会改变,原始数据类型共七个:Number、String、BigInt、Boolean、Symbol、Null、Undefined
const data = ref("你好")
//模板内容 当 data 的值改变时,这里也会更新
<p>{{ data }}</p>
reactive
reactive 的作用是接收一个对象/副本的参数,返回一个响应式副本,在对象的属性改变时,用到这个对象的地方就会更新
const list = reactive([
{
name: "张三",
age: 10,
},
{
name: "李四",
age: 12,
},
{
name: "王五",
age: 15,
},
]);
//模板内容 当 list 的值改变时,这里也会更新
<ul>
<li v-for="(item, key) in list" :key="key">{{ item.name }}</li>
</ul>
Vue2 和 Vue3 的区别
- 双向绑定数据的原理不同
- 是否支持碎片
- API 类型
- 定义数据变量和方法不同
- 父子传参方法不同
- 生命周期的钩子函数不同
双向绑定数据的原理不同
vue2:通过 OObject.defineProperty() 它会遍历每个属性,都给加上get和set方法,进行数据劫持,结合发布订阅模式,达到双向绑定。
vue3:使用 ES6 的 proxy 代理实现。
是否支持碎片
vue2:template中必须有根标签,不支持碎片。
vue3:可以没有跟标签,支持碎片。
//vue2
<template>
<div>
<p>template 下必须有根标签</p>
</div>
</template>
//vue3
<template>
<p>template 下可以没有根标签</p>
</template>
API 类型
vue2:选项式 API,vue2 中将代码分割成不同部分,比如:数据放在 data 里、方法放在methods,还有 computed、watch之类的。
vue3:组合式 API,vue3 中将代码都放在一块,可以用我们自己的方法来分割,这样使得更简洁,也便于维护。
定义数据变量和方法不同
vue2:定义的数据放在 data 中,方法反正 methods 中
vue3:定义的数据和方法都放在 setup 中
//vue2
<script>
export default {
data() {
return {
data: "0",
data1: "1",
};
},
methods: {
func() {},
func1() {},
},
};
</script>
//vue3
<script setup>
data1 = ref("0");
data2 = ref("1");
const func = ()=>{}
const func1 = ()=>{}
</script>
父子传参方法不同
vue2:
- 父传子 props 需要先声明传过来的 props,才能获取
- 子传父 emit 传入事件名和参数才能调用函数
vue3:
- 父传子 props 可以在 setup 第一个参数获取
- 子传父 emit 可以在 setup 第二个参数用分解对象直接使用
生命周期的钩子函数不同
vue2:八个钩子函数
vue3:去掉了 beforeCreate 和 created 用 setup 代替
Vuex 的五个属性
- state - 存储数据
- getters - 储存对stae中数据做处理后的结果
- mutations - 提交更新数据的方法,只能是同步操作
- actions - 和mutations类似,但是提交的 mutations,可以包含异步操作
- muduels - vuex模块化,使每个模块都有自己的 state、getter、mutations、actions
export default createStore({
//储存数据
state: {
count: 1,
},
//储存获取数据的方法
getters: {
getCOunt: state => state.count,
},
//储存更新数据的方法,同步
mutations: {
add(state) {
state.count++;
},
},
//异步
actions: {
add(state) {
setTimeout(() => {
state.count++;
});
},
},
//模块化
modules: {},
});
使用:
import { useStore } from "vuex";
const store = useStore();
//获取数据
console.log(store.state.count);
const handleClick = () => {
// 触发mutations,用于同步修改state的信息
store.commit("updateInfo", "nihao");
// 触发actions,用于异步修改state的信息
store.dispatch("updateInfo", "hi");
};
Vue 路由守卫
路由守卫分类
- 全局守卫
- beforeEach(to, from, next) - 进入路由前触发
- beforeResolve(to, from, next) - 路由解析前触发
- afterEach(to, from, next) - 进入路由后触发
- 路由守卫
- beforeEnter(to, from, next) - 进入特定路由前触发
- 组件守卫
- beforeRouteEnter(to, from, next) - 进入组件前触发
- beforeRouteUpdate(to, from, next) - 组件更新前触发
- beforeRouteLeave(from, next) - 离开组件前触发
路由守卫的回调参数
- to - 要进入的目标路由对象
- from - 要离开的路由对象
- next - 进行管道中的下一个钩子,有 next 参数的钩子必须使用 next() 来 resolve() 这个钩子,不然会卡在这里,不会进行下一个钩子
全局守卫
//全局前置守卫
router.beforeEach((to, from, next) => {
//身份验证
const user = store.state.user;
if (user || to.path === "login") {
next();
} else {
next("login");
}
});
//全局解析守卫
router.beforeResolve((to, from, next) => {
//在解析前执行的逻辑
next();
});
//全局后置守卫
router.afterEach((to, from, next) => {
//在导航完成后执行的逻辑
next();
});
路由守卫
const routes = [
{
path: "/",
name: "Home",
component: home,
},
{
path: "/about",
name: "About",
component: about,
beforeEnter: () => {
//在进入该路由前执行的逻辑
console.log("进入about");
},
},
];
组件守卫
<script setup>
import {
onBeforeRouteUpdate,
onBeforeRouteLeave,
} from "vue-router";
//组件中使用
//onBeforeRouteEnter 不能使用
onBeforeRouteUpdate((to,from,next) => {
//组件更新前执行的逻辑
console.log("组件更新");
next();
});
onBeforeRouteLeave((to,from,next) => {
//离开组件前执行的逻辑
console.log("离开组件");
next();
});
</script>