持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
虽然现在是开始用了一段时间的Vue3了,但是也没真正的去系统了解Vue3和Vue2的差别。这导致我在开发过程中老是遇见问题时就去百度,同时也踩了很多的坑。所以我认为我应该认真去瞧瞧Vue3了。
Vue3的setup函数
在vue3中,它不像Vue2那样需要写一个又一个的配置项了,他是你需要用什么,就去取什么,使用的是组合API的方式。所以我们需要对数据的定义、方法的定义、生命周期的使用、watch和computed的使用等,都不必单独写在外面了,而是全部写在setup函数里面。
setup函数有几个注意点:
-
数据和方法定义了,如果需要在页面中使用,必须得将它返回(一般都是在setup函数里面返回一个对象,对面里就是页面要使用的属性和方法)。
-
Vue3调用setup函数的时间很早,在beforeCreate钩子函数之前就已经调用了,所以setup的this为undefined。
-
setup有两个参数
- 第一个参数是props,如果我们想在setup里面使用父组件传递给本组件的数据,就得在setup里面使用这个参数。
- 第二个参数是context,代表的是上下文对象,里面有3个方法,一个是
attrs,一个是slots、一个是emit。(感觉一般常用的就是emit吧?)
此外,Vue3.2更新了setup的语法糖。就不用再去声明setup函数并return一个对象了。但是setup语法糖会有一些其他的不同,比如父组件或其他组件获取当前组件时,是获取不到定义的数据或者方法的,需要expose出去,且setup的参数props以及emit需要额外的定义。
setup语法糖
新增的API:
- defineProps:接收父组件中传来的props
- defineEmits:接收父组件中的方法
- defineExpose:子组件暴露属性,父组件中可以拿到
- useAttrs:接收父组件中的属性
- useSlots:接收父组件中的插槽
<script lang="ts" setup>
import {defineProps, useSlots, useAttrs,ref} from 'vue';
const slots = useSlots()
const attrs = useAttrs()
const value = ref(10)
defineProps({ num:{ type:Number, default:NaN } })
const emit = defineEmits(["change"]); // 声明触发事件 change
const sonClick = () =>{ emit('change' , value) }
//暴露出子组件中的属性
defineExpose({ value })
</script>
ref和reactive
vue3的响应式原始数据类型用的还是Object.defineProperty,引用对象类型用的是Proxy。在Vue2中我们将数据直接写在data函数里面,系统会自动给我们进行数据代理、数据劫持,然后成为响应式。而Vue3我们在setup里面声明的变量,在返回之后,却并不是响应式的,这需要我们自己手动进行响应式(不是手动写响应式,就是手动调用官方的方法,让其变为响应式...)。
- 如果是原始数据类型,我们就用
ref函数对其进行包装。 - 如果是引用数据类型,我们就用
reactive对其进行包装。
<script>
import {ref,reactive} from 'vue'
export default {
setup() {
let num = ref(0);
const person = reactive({
name:'ly',
age:18
})
return {
num,
person
}
}
}
</script>
但是需要注意的是,用ref包装的数据类型,返回的是一个对象,这个对象身上有一个属性value,如果我们要在setup函数里面使用or修改这个原始数据,我们就必须使用xxx.value才可以。至于为什么要这样呢?是因为ref内部采用的是Object.defineProperty,而ref为什么要出现,就是因为reactive只能将引用类型的进行响应式,而不能对基础数据类型进行响应式,所以才有的ref专门用于处理基础数据类型。而Object.defineProperty它也只能对对象进行数据的劫持,所以vue就将基础数据类型给包裹了一层。
...
let num = ref(0);
const addNum = () => num.value++;
...
源码逻辑是这样的:
function ref(value) {
return createRef(value); //将普通类型变成一个对象 因为普通类型不能数据劫持
}
function createRef(rawValue,shallow=false) {
return new RefImpl(rawValue,shallow)
}
class RefImpl {
public _value; //声明一个value属性
public _v_isRef = true; //产生的实例会被添加 表面这是ref属性
constructor(public rowValue,public shallow) { // public表明会放在实例上
this._value =shallow? rowValue:convert(rowValue); //如果是深度就把里面都变成响应式的,也就是说当是对象或数组时,这里就调用reactive的方法。
}
get value() { //这里使用了Object.defineProperty进行了数据劫持与依赖收集
track(this,TrackOpTypes.GET,'value');
return this._value;
}
set value(newValue) { //这里进行了数据的比对,如果新旧数据不一致就更新数据,然后更新视图
if(hasChanged(newValue,this.rowValue)) {
this._value = this.shallow? newValue:convert(rowValue);
this.rowValue = newValue;
trigger(this,TriggerOpType.SET,'value',newValue)
}
}
}
而reactive包装的引用数据类型,返回的是一个proxy实例对象。
而关于reactive的讲解,我推荐一个大佬的,写得非常好!
toRef
可以把一个对象的值转化成ref类型。
我们什么时候会用到它呢?答案就是当我们想取出一个对象(响应式的)的某个属性时,我们直接赋值后,这个值不会是响应式的。
比如:
setup() {
const person = reactive({
name:'ly',
age:18
})
let name = person.name;
return {
person,
name
}
}
但是我们又想他是响应式的咋办?最笨的方法就是使用ref去包裹一层。
let name = ref(person.name);
但是,这就有一个问题了,那就是name和person.name就完全不相关了,属于是两个东西了,以后修改person.name不会导致name的变化,修改name不会导致person.name的变化。所以这就有了toRef。
let age = toRef(person,'age');
function ageAdd() {
age++; //person.age++
}
现在就可以实现两者的共同响应更新了。
它的大致原理:
function toRef(target,key) {
return new ObjectRefImpl(target,key);
}
class ObjectRefImpl {
public _v_isRef = true;
constructor(public target,public key) {
}
get value() {
return this.target[this.key]
}
set value(newValue) {
this.target[this.key] = newValue;
}
}
所以它内部最终对于对象或数组属性还是会去触发proxy,toRef只是一个中间站。
toRefs
上面我们说了toRef的作用,但是它有一个缺点,那就是它只能一次将对象的一个属性变成ref,如果我们想将多个属性变成ref类型咋办呢?于是就有了toRefs。
setup(){
let data=reactive({
state:'normal',
pageSize:20,
currentPage:1,
dialogShow:false
})
const {state,pageSize} = {...toRefs(data)};
return {
...toRefs(data); //对对象进行解构,返回的就是state、pageSize等对象了
}
}
它的大致原理就是底层调用了toRef。
function toRefs(object) {
const ret = isArray(object)?[]:{};
for(let key in object) {
ret[key] = toRef(object,key)
}
return ret;
}
相当于Vue自己写了个对象解构方法。
总的来说,vue3的新改动还是很多的,下次再更新更新watch相关的用法和坑。