Vue3基础学习之setup速通笔记

177 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

学前准备

光看不练假把式,只有边看边练,加以实践才能真正掌握一门技术,所以先创建一个vue3项目吧~

如何选择项目脚手架:项目脚手架

通过vue-cli创建

  1. 安装vue-cli全局依赖
npm install -g @vue/cli
  1. 创建项目
vue create vue3-cli-demo

注意选择vue3版本

vue-cli文档

通过vite创建

  1. 确保node版本 >= 12
  2. 创建项目
npm create vite@latest

按照步骤选择vue即是vue3版本了

vite文档

setup是啥

setup是Vue3的一个新的函数项配置,用来承放组合式(coposition)的API语法。

组件需要用到的data、methods都写在setup函数中,并通过函数的返回值传递给模板(在模板中可以直接使用)。

setup可以有两种返回值:

  1. 对象:对象的值传递给模板。
export default {
    setup() {
        const name = 'zs' // 目前不是响应式变量
        
        function sayHello() {
            alert('你好,' + name)
        }
        
        // 注意需要return出去
        return {
            name,
            sayHello
        }
    }
}
<template>
    <div @click="sayHello">{{name}}</div>
</template>
  1. 函数(渲染函数):忽略模板,直接通过渲染函数进行渲染。(了解)
import {h} from 'vue'

export default {
    setup() {
        const name = 'zs'
        
        // h是vue的渲染函数,需要从vue中导入,如果vue文件中有模板,会忽略模板内容。
        return () => h('h1',name)
    }
}

setup注意事项

  1. 尽量不要和data、methods配置项混合使用。

    data、methods中可以获取到setup的return值,但是setup中无法获取data、methods中的值。

    如果data、methods和setup中的变量名冲突,setup的权重高于data、methods的。

  2. setup不能是async函数

  3. 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是一个函数,参数为:

  1. 监听的变量
  2. 监听的回调函数
  3. 监听的配置项(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改名为beforeUnmountdestroyed改名为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生命周期执行')
        })
    }
}

注意:

  1. setup中没有提供 beforeCreatecreated 生命周期函数,setup函数执行时可视为等同于 beforeCreatecreated 生命周期,但是setup中不能调用this
  2. 组合式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