更快的渲染速度:Vue3使用了Proxy代理对象,可以更快地跟踪数据变化,从而提高渲染速度。
更小的体积:Vue3的体积比Vue2更小,同时也支持按需加载,减少了页面加载时间。
更好的TypeScript支持:Vue3对TypeScript的支持更加完善,可以更好地进行类型检查和代码提示。
更好的组件封装:Vue3引入了Composition API,可以更好地封装组件逻辑,使得组件更加可复用和易维护。
更好的响应式系统:Vue3的响应式系统进行了重构,可以更好地处理嵌套对象和数组的变化,同时也提供了更多的API来处理响应式数据。
总之,Vue3带来了更好的性能、更小的体积、更好的TypeScript支持、更好的组件封装和更好的响应式系统,使得开发者可以更加高效地开发Vue应用。
vue-cli官网
vite官网
vue3中的组合API都是函数,使用时需要先引入(setup不需要引入)
// import {h} from 'vue'
export default {
name: 'App',
setup() {
// 数据(相当于vue2中的data)
let name = 'dudu'
// 方法(相当于vue2中的methods)
const sayHi = () => {
console.log(`hi,${name}`)
}
// 返回一个对象
return {
name,
sayHi
}
// 返回一个渲染函数
// return () => {
// return h('要返回的元素','要返回的内容')
// }
},
}
<script setup>
// 按需引入
import { ref, reactive, onMounted } from "vue";
import { ElMessage } from "element-plus";
import axios from "axios";
// 定义响应式数据
const showButton = ref(false);
const value1 = ref("");
const inputValue = ref("");
// 定义函数
const handleCommand = (command) => {
ElMessage(`click on item ${command}`);
};
const handleFocus = () => {
showButton.value = true;
};
</script>
setup(){
...
//计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//计算属性——完整
let fullName = computed({
// 读
get(){
return person.firstName + '-' + person.lastName
},
// 写
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
// 监视(简单写法)
/**
* 第一个参数:要监视的内容
* 第二个参数:监视的值:newValue、oldValue
*/
watch(sum,(newVal,oldVal) => {
console.log('sum值发生了变化')
})
watch([xxx,xxxxx],(newVal,oldVal) => {
console.log('监视多个内容!',newVal,oldVal)
})
watch([xxx,xxxxxx],(newVal,oldVal) => {
console.log('监视多个内容!',newVal,oldVal)
},{
immediate:true,
deep:true
})
注意有坑!!!
/* 监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
watch
和 watchEffect
都是 Vue 3 中的响应式 API,它们的主要区别在于:
watch
是一个有返回值的函数,需要手动停止监听,而 watchEffect
是一个自动清理的函数,不需要手动停止监听。watch
可以监听多个数据源,而 watchEffect
只能监听一个数据源。watch
可以通过第三个参数 options
来配置监听行为,比如是否立即执行回调函数、是否深度监听等,而 watchEffect
没有这些配置选项。computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
computed若是值没有被使用时不会调用,但是watchEffect始终会调用一次
包含创建 、运行、销毁三个阶段
vue2的生命周期 | ue3的生命周期 |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount(() => {}) |
mounted | onMounted(() => {}) |
beforeMount | onBeforeUpdate(() => {}) |
updated | onUpdated(() => {}) |
beforeDestroy | onBeforeUnmount(() => {}) |
destroyed | onUnmounted(() => {}) |
toRef
和 toRefs
是 Vue 3 中的两个响应式函数,它们的作用是将一个普通的 JavaScript 对象或者数组转换成响应式对象或数组。
toRef
函数将一个普通的 JavaScript 对象的属性转换成一个响应式对象的属性,返回一个 Ref
对象。toRefs
函数将一个普通的 JavaScript 对象的所有属性转换成响应式对象的属性,返回一个包含所有 Ref
对象的对象。toRefs
函数返回的是一个包含所有 Ref
对象的对象,而不是一个响应式对象。如果需要将这个对象传递给子组件,需要使用 toRef
函数将其转换成响应式对象。toRef
函数将一个普通的 JavaScript 对象的属性转换成一个响应式对象的属性,返回一个 Ref
对象。Ref
对象是一个包装器,可以通过 .value
属性获取或设置值。当原始对象的属性值发生变化时,Ref
对象的值也会相应地更新。
import { reactive, toRef } from 'vue'
const state = reactive({
count: 0
})
const countRef = toRef(state, 'count')
console.log(countRef.value) // 0
state.count++
console.log(countRef.value) // 1
toRefs
函数将一个普通的 JavaScript 对象的所有属性转换成响应式对象的属性,返回一个包含所有 Ref
对象的对象。这个函数的主要作用是将一个响应式对象解构成普通的 JavaScript 对象,方便在模板中使用。
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
message: 'Hello, world!'
})
const refs = toRefs(state)
console.log(refs.count.value) // 0
console.log(refs.message.value) // 'Hello, world!'
需要注意的是,toRefs
函数返回的是一个包含所有 Ref
对象的对象,而不是一个响应式对象。如果需要将这个对象传递给子组件,需要使用 toRef
函数将其转换成响应式对象。
import { reactive, toRefs, toRef } from 'vue'
const state = reactive({
count: 0,
message: 'Hello, world!'
})
const refs = toRefs(state)
export default {
setup() {
return {
count: toRef(refs, 'count'),
message: toRef(refs, 'message')
}
}
}
shallowReactive
和 shallowRef
是 Vue 3 中的两个响应式 API,它们都可以用来创建响应式数据,但是它们的使用场景和行为有所不同。
shallowReactive
用于创建一个浅层响应式对象,它只会对对象的第一层属性进行响应式处理,而不会对嵌套的对象进行处理。这意味着,如果你修改了嵌套对象的属性,那么这个修改不会触发响应式更新。例如:
import { shallowReactive } from 'vue'
const state = shallowReactive({
name: '张三',
age: 18,
address: {
province: '广东',
city: '深圳'
}
})
// 修改嵌套对象的属性,不会触发响应式更新
state.address.province = '北京'
// 修改第一层属性,会触发响应式更新
state.name = '李四'
shallowRef
用于创建一个浅层响应式引用,它可以将任何类型的数据转换为响应式数据。与 shallowReactive
不同的是,shallowRef
不会对对象进行浅层处理,而是直接将整个对象转换为响应式数据。这意味着,如果你修改了嵌套对象的属性,那么这个修改会触发响应式更新。例如:
import { shallowRef } from 'vue'
const state = shallowRef({
name: '张三',
age: 18,
address: {
province: '广东',
city: '深圳'
}
})
// 修改嵌套对象的属性,会触发响应式更新
state.value.address.province = '北京'
// 修改第一层属性,会触发响应式更新
state.value.name = '李四'
总的来说,shallowReactive
和 shallowRef
都是用于创建响应式数据的 API,但是它们的使用场景和行为有所不同。如果你需要对嵌套对象进行响应式处理,那么应该使用 shallowRef
;如果你只需要对第一层属性进行响应式处理,那么可以使用 shallowReactive
。
readonly
和 shallowReadonly
都是 Vue3 中提供的响应式 API,用于创建只读的响应式对象。它们的区别在于:
readonly
可以递归地将一个对象转换为只读的响应式对象,而 shallowReadonly
只会将对象的第一层属性转换为只读的响应式对象,不会递归转换嵌套的对象。
readonly
返回的对象是完全只读的,无法修改对象的属性值,也无法添加或删除属性。而 shallowReadonly
返回的对象只是第一层属性只读,如果对象的属性是一个对象,那么这个对象的属性仍然可以修改。
readonly
的使用方法如下:
import { reactive, readonly } from 'vue'
const state = reactive({
count: 0,
obj: {
name: '张三',
age: 18
}
})
const readonlyState = readonly(state)
console.log(readonlyState.count) // 0
console.log(readonlyState.obj.name) // 张三
// 尝试修改只读对象的属性值
readonlyState.count = 1 // 报错,无法修改只读对象的属性值
readonlyState.obj.name = '李四' // 成功,只读对象的属性值可以修改
从上面的例子可以看出,readonly
可以将一个响应式对象转换为只读的响应式对象,可以通过访问只读对象的属性值来获取数据,但是无法修改只读对象的属性值。
shallowReadonly
的使用方法如下:
import { reactive, shallowReadonly } from 'vue'
const state = reactive({
count: 0,
obj: {
name: '张三',
age: 18
}
})
const readonlyState = shallowReadonly(state)
console.log(readonlyState.count) // 0
console.log(readonlyState.obj.name) // 张三
// 尝试修改只读对象的属性值
readonlyState.count = 1 // 报错,无法修改只读对象的属性值
readonlyState.obj.name = '李四' // 成功,只读对象的属性值可以修改
// 尝试修改只读对象的嵌套对象的属性值
readonlyState.obj.age = 20 // 成功,只读对象的嵌套对象的属性值可以修改
从上面的例子可以看出,shallowReadonly
可以将一个响应式对象的第一层属性转换为只读的响应式对象,可以通过访问只读对象的属性值来获取数据,但是无法修改只读对象的第一层属性值,而嵌套对象的属性值可以修改。
toRaw
和 markRaw
是 Vue.js 3.x 中的两个 API,用于处理响应式数据。
toRaw
方法用于获取一个响应式对象的原始数据,即非响应式的数据。markRaw
方法用于标记一个对象为“非响应式的”,即使这个对象被包含在响应式对象中,也不会被转换为响应式数据markRaw
方法只能标记对象本身为非响应式的,而不能标记对象的属性为非响应式的。如果需要标记对象的属性为非响应式的,可以使用 markRaw 方法嵌套对象。toRaw使用:
在下面的例子中,toRaw
方法将响应式对象 state
转换为了原始数据 rawState
,并将其输出到控制台。
import { reactive, toRaw } from 'vue'
const state = reactive({
count: 0
})
const rawState = toRaw(state)
console.log(rawState) // { count: 0 }
markRaw使用:
在下面的例子中,markRaw
方法将对象 { name: 'John', age: 30 }
标记为非响应式的,并将其作为 state
对象的一个属性。即使 state
对象是响应式的,data
属性也不会被转换为响应式数据。因此,当我们修改 data
属性的值时,不会触发响应式更新。
import { reactive, markRaw } from 'vue'
const state = reactive({
count: 0,
data: markRaw({
name: 'John',
age: 30
})
})
console.log(state.data.name) // 'John'
state.data.name = 'Mike'
console.log(state.data.name) // 'Mike'
在下面的例子中,我们使用 markRaw
方法将对象 { name: 'John', age: 30 }
标记为非响应式的,并将其作为 state
对象的一个属性 data.info
。这样,即使 state
对象是响应式的,data.info
属性也不会被转换为响应式数据。因此,当我们修改 data.info
属性的值时,不会触发响应式更新。
import { reactive, markRaw } from 'vue'
const state = reactive({
count: 0,
data: {
info: markRaw({
name: 'John',
age: 30
})
}
})
console.log(state.data.info.name) // 'John'
state.data.info.name = 'Mike'
console.log(state.data.info.name) // 'Mike'
在 Vue 3 中,customRef
是一个新的 API,它允许我们创建一个自定义的 ref。
使用 customRef
,我们可以自定义 ref 的读取和写入行为,从而实现更加灵活的数据绑定。
下面是一个使用 customRef
的示例:
import { customRef } from 'vue';
function useCustomRef(initialValue) {
let value = initialValue;
return customRef((track, trigger) => ({
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
}
}));
}
export default {
setup() {
const customRef = useCustomRef('initial value');
return {
customRef
};
}
};
在上面的示例中,我们定义了一个 useCustomRef
函数,它接受一个初始值,并返回一个自定义的 ref。
在 customRef
的工厂函数中,我们定义了 get
和 set
方法,它们分别对应 ref 的读取和写入操作。在 get
方法中,我们使用 track
函数来追踪依赖,以便在数据变化时更新组件。在 set
方法中,我们使用 trigger
函数来触发更新。
最后,在组件中使用 useCustomRef
函数创建一个自定义的 ref,并将其返回。
使用自定义的 ref,我们可以实现更加灵活的数据绑定,例如在 set
方法中添加一些额外的逻辑,或者在 get
方法中返回一个经过计算的值。
在 Vue 3 中,provide
和 inject
仍然可以用来实现父组件向子组件传递数据,但是与 Vue 2 中的用法略有不同。
provide
和 inject
都是在组件实例上定义的属性,provide
定义在父组件中,inject
定义在子组件中。provide
可以是一个对象或者一个函数,inject
可以是一个数组或者一个对象。
下面是一个使用 provide
和 inject
实现父组件向子组件传递数据的例子:
<template>
<div>
<child-component></child-component>
</div>
</template>
<script>
import { provide, inject } from 'vue'
const MyProvideComponent = {
setup() {
const data = 'Hello, World!'
provide('myData', data)
}
}
const ChildComponent = {
setup() {
const data = inject('myData')
return { data }
},
template: '{{ data }}'
}
export default {
components: {
ChildComponent
},
extends: MyProvideComponent
}
</script>
在上面的例子中,我们在父组件中定义了一个 provide
,将数据 Hello, World!
以 myData
的键名提供给子组件使用。在子组件中,我们使用 inject
获取父组件提供的数据,并将其绑定到模板中。
需要注意的是,provide
和 inject
并不是响应式的,如果需要在子组件中响应式地使用父组件提供的数据,可以使用 reactive
或 ref
等响应式 API 进行包装。
在Vue3中,插槽可以分为以下两种类型:
普通插槽是Vue2中的插槽的升级版,使用方式与Vue2中的插槽类似。在父组件中使用
标签,子组件中使用
标签的name属性来定义插槽,父组件中使用子组件时,可以在子组件标签中使用或
来具体定义插槽内容。
示例代码:
父组件模板:
<template>
<div>
<h1>普通插槽示例</h1>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
子组件模板:
<template>
<div>
<h2>子组件</h2>
<slot name="header">
<p>默认头部内容</p>
</slot>
<p>默认插槽内容</p>
<slot name="footer">
<p>默认底部内容</p>
</slot>
</div>
</template>
父组件使用子组件:
<template>
<div>
<my-component>
<template v-slot:header>
<h3>自定义头部内容</h3>
</template>
<p>自定义插槽内容</p>
<template v-slot:footer>
<h3>自定义底部内容</h3>
</template>
</my-component>
</div>
</template>
作用域插槽是Vue3中新增的功能,可以让父组件向子组件传递数据,子组件可以在插槽中使用这些数据。在父组件中使用
标签,并在其中使用v-bind
指令来传递数据,子组件中使用
标签的name属性来定义插槽,并在其中使用v-slot
指令来接收数据。
示例代码:
父组件模板:
<template>
<div>
<h1>作用域插槽示例</h1>
<my-component>
<template v-slot:default="slotProps">
<p>父组件传递的数据:{{ slotProps.text }}</p>
</template>
</my-component>
</div>
</template>
子组件模板:
<template>
<div>
<h2>子组件</h2>
<slot :text="message"></slot>
</div>
</template>
<script>
export default {
data() {
return {
message: '这是子组件传递的数据'
}
}
}
</script>
在上面的示例中,父组件向子组件传递了一个名为text
的数据,子组件在插槽中使用了这个数据,并将其显示出来。
//前置路由
import { userStore } from '../store/userInfo';
router.beforeEach((to, from, next) => {
const store = userStore();
//判断是否有权限返回登录界面
if (from.meta.isAuth) {
if (store.userInfo.token) {
next()
} else {
next("/login")
}
} else {
console.log("没有权限");
next()
}
})
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置(用的不多)
对于分析、更改页面标题、声明页面等辅助功能都很有用
.....
, {
name: 'order',
path: 'order',
meta: { isAuth: true, title: '订单管理' },
component: () => import("../components/page/Order.vue"),
//独享守卫
beforeEnter: (to: any, from: any, next: any) => {
console.log("路由独享守卫beforeEnter");
next()
}
}
可以通过 onBeforeRouteUpdate 和 onBeforeRouteLeave 分别添加 update 和 leave 守卫
beforeRouteEnter(to, from) {
// 通过路由规则,进入该组件时被调用
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 通过路由规则,离开该组件时被调用
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
useRoute
函数返回当前路由的信息对象,包括路由路径、参数、查询参数等信息。传参使用query
useRouter
函数返回Vue Router的实例,我们可以在组件中使用useRouter
函数来获取Vue Router的实例。传参使用params
useRoute
是一个函数,它返回当前路由的信息对象,包括路由参数、查询参数和路径等。使用useRoute
可以方便地获取当前路由的信息,而不需要在组件中引入$route
对象。
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
console.log(route.params) // 获取路由参数
console.log(route.query) // 获取查询参数
console.log(route.path) // 获取路径
console.log(route.fullPath) // 获取完整路径
console.log(route.name) // 获取路由名称
}
}
useRouter
是一个函数,它返回Vue Router实例。使用useRouter
可以方便地在组件中进行编程式导航。
import { useRouter } from 'vue-router'
export default {
setup() {
const router = useRouter()
function handleClick() {
router.push('/about')
}
return {
handleClick
}
}
}
在Vue 3中,使用Vue Router时,需要先通过createRouter
函数创建一个Vue Router实例,然后在应用程序中使用该实例。例如:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
const app = createApp(App)
app.use(router)
app.mount('#app')
什么是 nextTick?
在 Vue3中,nextTick 是一个非常重要的API,它可以在下一次DOM 更新周期之后执行一些代码。这个API 非常有用,因为在 Vue3中,数据的变化并不是实时的,而是通过异步的方式进行处理的。也就是说,当我们修改了数据之后,Vue3 并不会立即更新 DOM,而是会在下一个更新周期中进行处理。这样做的好处是可以让 Vue3 更加高效地处理数据变化,但同时也会带来一些问题
比如说,当我们需要在修改数据之后立即获取 DOM 元素的一些信息时,就会遇到问题。因为此时 DOM 还没有被更新,所以我们无法获取到最新的信息。这时,nextTick 就可以派上用场了。它可以让我们在下一个更新周期中获取最新的 DOM 信息,从而避免了这个问题。
nextTick的用法
在 Vue3中,我们可以通过调用 Vue.nextTick(方法来使用nextTick。这个方法接受一个回调函数作为参数,当下一个更新周期开始时,这个回调函数就会被执行。
Vue.nextTick(()=> {
在下一个更新周期中执行的代码
})
在这个示例中,我们定义了一个回调函数,当下一个更新周期开始时,这个回调函数就会被执行。这个回调函数可以包含任何需要在下一个更新周期中执行的代码,比如获取 DOM 元素的信息、更新组件的状态等等
使用技巧
在 Vue3中,mounted 钩子函数会在组件挂载到 DOM之后被调用如果我们需要在组件挂载后立即获取 DOM 元素的信息,就可以在mounted 钩子函数中使用nextTick。这样做可以确保我们在获取DOM元素信息时,DOM 已经被完全渲染出来了
当我们修改了组件的数据之后,Vue3 并不会立即更新 DOM。如果我们需要在数据变化后立即获取 DOM 元素的信息,就可以在数据变化的回调函数中使用nextTick。这样做可以确保我们在获取 DOM 元素信息时,DOM 已经被更新了。
在 Vue3 中,很多操作都是异步的,比如发送网络请求、获取浏览器位置信息等等。如果我们需要在异步操作后立即获取 DOM 元素的信息,就可以在异步操作的回调函数中使用 nextTick。这样做可以确保我们在获取 DOM 元素信息时,DOM 已经被完全渲染出来了
Vue 3 中已经废弃了过滤器(filters)这个概念。过滤器的主要作用是对模板中的数据进行格式化,例如将日期格式化为特定的字符串,或者将文本转换为大写字母等。在 Vue 3 中,过滤器的功能可以通过计算属性(computed)或者方法(methods)来实现。
计算属性是一个函数,它会根据依赖的数据动态计算出一个新的值,这个值可以在模板中直接使用。例如,我们可以定义一个计算属性来将日期格式化为特定的字符串:
<template>
<div>{{ formattedDate }}</div>
</template>
<script>
export default {
data() {
return {
date: new Date(),
};
},
computed: {
formattedDate() {
return this.date.toLocaleDateString();
},
},
};
</script>
方法也可以用来实现过滤器的功能,只需要在模板中调用方法并传入需要格式化的数据即可。例如,我们可以定义一个方法来将文本转换为大写字母:
<template>
<div>{{ toUpperCase('hello world') }}</div>
</template>
<script>
export default {
methods: {
toUpperCase(text) {
return text.toUpperCase();
},
},
};
</script>
总之,虽然 Vue 3 中废弃了过滤器,但是我们可以使用计算属性或者方法来实现相同的功能。
keep-alive
是Vue提供的一个抽象组件,主要用于保留组件状态或避免重新渲染。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们。
和
相似,
是一个抽象组件,它自身不会渲染一个DOM元素,也不会出现在父组件链中。
但是 keep-alive
会把其包裹的所有组件都缓存起来。
怎么办呢,我们只是需要让列表页缓存啊.中使用keep-alive
在实战过程中,发现keepalive缓存组件时,不能跨级使用,比如在App.vue中使用include属性进行name="a"匹配时,只能匹配缓存name为a的子组件(路由页面),而不能缓存name为"a"的孙子组件(子页面引的组件)。若想缓存孙子组件,可以将整个子组件缓存,或者在子组件里再使用keepalive