开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
学前准备
光看不练假把式,只有边看边练,加以实践才能真正掌握一门技术,所以先创建一个vue3项目吧~
如何选择项目脚手架:项目脚手架
通过vue-cli创建
- 安装vue-cli全局依赖
npm install -g @vue/cli
- 创建项目
vue create vue3-cli-demo
注意选择vue3版本
通过vite创建
- 确保node版本 >= 12
- 创建项目
npm create vite@latest
按照步骤选择vue即是vue3版本了
setup是啥
setup是Vue3的一个新的函数项配置,用来承放组合式(coposition)的API语法。
组件需要用到的data、methods都写在setup函数中,并通过函数的返回值传递给模板(在模板中可以直接使用)。
setup可以有两种返回值:
- 对象:对象的值传递给模板。
export default {
setup() {
const name = 'zs' // 目前不是响应式变量
function sayHello() {
alert('你好,' + name)
}
// 注意需要return出去
return {
name,
sayHello
}
}
}
<template>
<div @click="sayHello">{{name}}</div>
</template>
- 函数(渲染函数):忽略模板,直接通过渲染函数进行渲染。(了解)
import {h} from 'vue'
export default {
setup() {
const name = 'zs'
// h是vue的渲染函数,需要从vue中导入,如果vue文件中有模板,会忽略模板内容。
return () => h('h1',name)
}
}
setup注意事项
-
尽量不要和data、methods配置项混合使用。
data、methods中可以获取到setup的return值,但是setup中无法获取data、methods中的值。
如果data、methods和setup中的变量名冲突,setup的权重高于data、methods的。
-
setup不能是async函数
-
setup的执行在beforeCreated配置项之前,在Props解析之后,所以setup中不能通过this调用vue实例对象,但可以获取到Props和其他一些东西(通过形参传入,后面细说)
setup中定义响应式变量
setup中可以通过 ref 函数将一个一般变量包装成一个vue中响应式变量。
refs函数返回的是一个 RefImpl(引用属性) 对象,这个对象拥有一个value属性,在js中需要通过 .value 去访问和修改变量值。
import {ref} from 'vue'
export default {
setup() {
let name = ref('zs')
function changeName() {
name.value = 'ls' // 注意:读取变量通过name.value访问
}
return {
name,
changeName
}
}
}
<template>
<!-- 访问ref变量不需要.value,vue会正常识别ref变量并获取到正确的值 -->
<div @click="changeName">{{name}}</div>
</template>
unref
vue3中提供了另外一个函数(语法糖),可以让我们在js中也不需要使用 .value 去访问ref的值 (仅能用于读取,不能用于赋值):
import {ref, unref} from 'vue'
export default {
setup() {
let name = ref('zs')
console.log(name.value) // zs
console.log(unref(name)) // zs
}
}
reactive 函数
setup中reactive函数用于对象类型的响应式封装(基本变量只能用ref,不能用reactive)
使用reactive的好处在于,对象不需要通过.value进行访问。
import { ref, reactive } from 'vue'
export default {
setup() {
let user = reactive({
name: 'zs',
age: 18
})
let user2 = ref({
name: "lili",
age: 20,
});
function changeName() {
uesr.name = 'ls'
// user2.name = 'haha' // 直接调用,数据并不会被修改
user2.value.name = 'haha'
}
return {
user,
user2,
changeName
}
}
}
ref可以让基本变量类型和引用变量类型变成响应式数据,其中对于基本类型,使用的依旧是vue2中的响应式原理(Object.defineProperty()),而对于引用类型,ref函数中调用了reactive函数,而reactive函数里面的响应式原理是使用了ES6的Proxy语法。
shallowReactive 函数
reactive的浅解析版,reactive将对象转换成响应式时,无论数据嵌套多深,都会变成响应式,而shallowReactive只会将第一层数据变成响应式,有点类似与深拷贝和浅拷贝。
import { reactive, markRaw, isReactive, shallowReactive } from 'vue'
const zs = shallowReactive({
age: 18,
girlFriend: {
name: 'lili'
}
})
function changeName() {
zs.girlFriend.name = 'ls' // 数据修改了,页面并不更新
zs.age = 20 // 数据修改了,页面更新
}
setup形参
export default {
props: ['text'],
emits: ['callback'],
setup(props,context) {
// props为组件中接受的prop属性,prop必须声明才能在setup中获取到
console.log(props.text)
// context是一个对象,里面结构为:
/**
{
attrs: {},
emit: function(){},
slots: {}
}
*/
// 父组件给子组件绑定自定义事件时,必须在子组件中的emits中声明该事件,否则会产生警告
// vue3中推荐使用v-slot填充插槽,否则context.slots中会有兼容问题(无法识别命名插槽)
}
}
computed和watch
vue3中的computed使用方法和vue2中几乎一样,只有在setup中定义computed时有区别。
import {computed,ref} from vue
export default {
let firstName = ref('张'),lastName = ref('三')
const fullName = computed(() => {
return firstName + lastName
})
// 可修改的计算属性
// const fullName = computed({
// get() {
//
// },
// set(val) {
//
// }
// })
}
vue3中setup的watch是一个函数,参数为:
- 监听的变量
- 监听的回调函数
- 监听的配置项(deep等)
在vue3中,watch可以同时监听多个数据
import {watch,ref,reactive} from vue
export default {
let name = ref('zs')
let age = ref(18)
watch(name,(newValue,oldValue) => {
console.log(`监听到数据${newValue}-${oldValue}`)
},{
immediate: true
})
// 监听多个数据:
watch([name,age],(newValue,oldValue) => {
console.log(`监听到数据${newValue}-${oldValue}`)
// 监听多个数据时,newValue、oldValue也得到一个数组
})
// 监听对象中的某个值,需要使用函数返回的形式
let obj = reactive({
name: 'ls',
age: 18
})
watch(() => obj.name,(newValue,oldValue) => {
console.log(`监听到数据${newValue}-${oldValue}`)
})
}
vue3中watch监听reactive对象时,无法正确获取到oldValue值,所以项目中应尽量避免使用oldValue
vue3中watch监听reactive对象时,强制开启了深度监听,deep无效,无法关闭。但是监听reactive对象的某个对象属性时,仍需要设置deep属性,如下例子:
import {watch,ref,reactive} from vue
export default {
let obj = reactive({
name: 'ls',
age: 18,
pet: {
price: 100
}
})
// 监听reactive对象,强制开启deep监听,不需要配置deep
watch(obj,(newValue,oldValue) => {
console.log(`${newValue}-${oldValue}`)
})
obj.pet.price+=1 // watch能监听到变化
// 监听reactive对象上的某一个对象属性,需要开启deep监听才能监听到该对象的变化
watch(() => obj.pet,(newValue,oldValue) => {
console.log(`${newValue}-${oldValue}`)
},{deep: true})
obj.pet.price+=1 // 开启了deep,watch才能监听到变化
}
watchEffect函数
vue3中新增了一种监听方式,watchEffect传入一个函数,当这个函数中用到的数据发生变化时,vue会监听到并重新执行该函数。
import { watchEffect,ref } from 'vue'
export default {
setup() {
let name = ref('zs'),age = ref(18)
let result = ref('')
watchEffect(() => { // 函数中用到了name和age,当这个值发生变化时,函数会监听到并重新执行该函数。
result = `名字:${name},年龄${age}`
})
}
}
vue3的生命周期
vue3的生命周期函数基本和vue2中一样,只是beforeDestroy改名为beforeUnmount,destroyed改名为unmounted。
除此之外,vue3提供了在setup中使用生命周期函数的方法:
import { onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmount } from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('onBeforeMount生命周期执行')
})
onMounted(() => {
console.log('onBeforeMount生命周期执行')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate生命周期执行')
})
onUpdated(() => {
console.log('onUpdated生命周期执行')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount生命周期执行')
})
onUnmount(() => {
console.log('onUnmount生命周期执行')
})
}
}
注意:
- setup中没有提供
beforeCreate和created生命周期函数,setup函数执行时可视为等同于beforeCreate和created生命周期,但是setup中不能调用this - 组合式API的生命周期比配置式的生命周期先执行
hooks
把setup函数中使用的组合式API封装进一个函数,实现逻辑复用
hooks只是一个概念,没有什么新的API
在vue2中,如果想实现更高级的复用,可以使用mixins,mixins可以做到将一个vue实例中的data、methods和生命周期等配置式的东西复用,而vue3中的组合式概念,把数据、函数、生命周期等都弄成了函数,以前mixins的写法现在可以全部封装进一个函数里面了,这就是hooks这个概念做的事情
toRef和toRefs
toRef的作用在于:关联一个响应式对象(源对象)的某个值,赋值给一个新的变量,新的变量被修改时,源对象对应的值也跟着变。
简单一点理解就是:对象响应式的解构,解构出来的变量关联着对象的值。
setup() {
const source = reactive({
name: 'zs'
})
let name = toRef(source, 'naame')
name = 'ls' // name和source.name有关联关系
console.log(name) // ls
console.log(source.name) // ls
}
而toRefs则是可以一次性执行多次toRef:
setup() {
const source = reactive({
name: 'zs',
age: 11
})
let obj = toRefs(source)
obj.name = 'ls' // obj和source.name有关联关系
source.age++
console.log(obj.name) // ls
console.log(source.name) // ls
console.log(obj.age) // 12
console.log(source.age) // 12
}
到此,vue3的组合式用法基本够用了,还有一些不常用或者有点绕的API,可以放后一点再进行学习。
其他组合式API
readonly 只读
readonly函数可以让一个对象(响应式或纯对象)变成只读对象,这个变化影响深层次,包括property也会变成只读:
import { readonly } from 'vue'
// setup
const obj = readonly({
name: 'zs'
})
console.log(obj.name)
obj.name = 'ls' // 控制台警告
console.log(obj.name)
shallowReadonly 浅层次只读
shallowReadonly和readonly的区别在于:shallowReadonly只有第一层数据是只读的,第二层以下的引用类变量仍然可以进行修改,值得注意的是,两者的property都会变成只读
import { shallowReadonly } from 'vue'
// setup
const man = shallowReadonly({
name: 'zs',
girlfriend: {
name: 'Jenny',
age: 19
}
})
man.name = 'ls' // 控制台警告
man.girlfriend.age = '22'
console.log(man.name) // zs
console.log(man.girlfriend.age) // 22
isReadonly 判断变量是否只读
readonly和shallowReadonly都为true
import { isReadonly, reactive, readonly, shallowReadonly } from 'vue'
// setup
const man = shallowReadonly({
name: 'zs'
})
const woman = readonly({
name: 'lili'
})
const proxyObj = reactive({
name: 'zs'
})
const obj = {
name: 'ls'
}
console.log(isReadonly(man)) // true
console.log(isReadonly(woman)) // true
console.log(isReadonly(proxyObj)) // false
console.log(isReadonly(obj)) // false
响应式变量的判断
isProxy:检查对象是否是由 reactive 或 readonly 创建的 proxy。
两个关键点:1、是不是proxy,2、是不是reactive 或 readonly创建的。
import { ref, readonly, reactive, isProxy } from 'vue'
const obj = reactive({
name: 'zs'
})
const obj1 = readonly({
name: 'zs'
})
const obj2 = new Proxy({}, () => {})
const obj3 = ref({
name: 'zs'
})
console.log(isProxy(obj)) // true
console.log(isProxy(obj1)) // true
console.log(isProxy(obj2)) // false
console.log(isProxy(obj3)) // false
通过代码可以看出,自己创建一个Proxy以及通过ref创建的对象,即使ref底层也使用reactive方法,在isProxy中都为false
isReactive:检查对象是否是由 reactive 创建的响应式代理
import { isReactive, ref, readonly, reactive } from 'vue'
const obj = reactive({
name: 'zs'
})
const obj1 = readonly({
name: 'zs'
})
const obj2 = readonly(obj)
const obj3 = ref({
name: 'zs'
})
console.log(isReactive(obj)) // true
console.log(isReactive(obj1)) // false
console.log(isReactive(obj2)) // true
console.log(isReactive(obj3)) // false
通过代码可以看出,ref同样无法通过isReactive的检查,比较特殊的是,readonly对象如果底层是reactive创建的对象,可以通过该函数,反之底层是其他对象则为false。
isRef
判断变量是否为ref创建的响应式对象,除了ref创建,通过toRef关联的变量同样可以通过isRef的判断,且readonly包裹的ref对象也能通过判断
import { ref, isRef, readonly, toRef } from 'vue'
const user = ref({
name: 'ze'
})
const user2 = readonly(user)
const name = toRef(user.value.name)
const user3 = reactive({
name: 'zs'
})
console.log(isRef(user)) // true
console.log(isRef(user2)) // true
console.log(isRef(name)) // true
console.log(isRef(user3)) // false
取消响应式
在一些情况,我们需要取消数据的响应式,或者让一个对象永远不要成为响应式,vue中提供了对应的方法给我们:
toRaw 让一个响应式变量变成非响应式变量
import { reactive, toRaw, isReactive, onBeforeMount } from 'vue'
const obj = {
name: 'zs'
}
const proxyObj = reactive(obj)
const raw = toRaw(proxyObj)
console.log(isReactive(raw)) // false
console.log(obj === raw) // true
function changeName() {
raw.name = 'ls'
console.log('非响应式', raw.name) // 页面不会更新
}
function changeProxyName() {
proxyObj.name = 'ls'
console.log('响应式', proxyObj.name) // 页面更新
}
markRaw 让一个变量永远不要变成响应式数据
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false