天天看点

字节二面:说一下Vue3中你知道的Composition API

作者:尚硅谷教育

大家都知道,现在Vue3的各个版本已经陆续发布了,并且有很多的团队已经着手各个库的开发与Vue2向Vue3的升级,我们当然也不能落后,所以赶紧将跟着本文一起学习新的API吧

一、setup

setup的理解:

Vue3.0的一个配置项,值为一个函数,setup是所有Composition API(组合API)的入口“表演舞台”,组件中所用到的:数据、方法等等,均要配置在setup中。

setup函数有两种返回值:

若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用

若返回一个渲染函数:则可以自定义渲染内容。

setup使用注意点:

尽量不要与Vue2.x配置混用

setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

vue2可以获取vue3中的属性和方法,vue3无法获取vue2中的属性和方法。

字节二面:说一下Vue3中你知道的Composition API

setup函数的使用:

setup 函数是在 created 时候(实例被初始化前)执行的。里边返回数据或者方法,插值函数可以检测到。

因为是实例初始化前执行,因此 setup 里不能使用 this 访问到实例。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年龄:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

export default {

setup() {

let name = "张三";

let age = 18;

let skill = "犯罪";

return {

name,

age,

skill,

};

},

};

</script>

<style scoped></style>

setup 函数里有两个参数,第一个是 props,指组件从父组件那儿接收到的参数,第二个是 context,暴露了 attrs、slot、emit 等实用方法。

二、ref和reactive

上面看着很合理,但是 name 和 age 都是非响应式的,即数据改变并不会触发渲染。

如何解决问题呢?现在需要响应式的引用:通过 proxy 对数据进行封装。当数据变化时,触发模板等内容的更新。

后面的例子都是通过一秒后修改 name 的值来查看是否有响应式。如果页面数据没有改变,说明没有响应式。

ref:响应式封装基本类型数据

使用ref函数,如ref(‘张三’),对基本类型的数据进行封装,变成proxy({value:‘张三’})这样一个响应式的数据。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年龄:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

import { ref } from "vue";

export default {

setup() {

let name = ref("张三");

let age = ref(18);

let skill = ref("犯罪");

setTimeout(() => {

name.value = "李四",

age.value = 19,

skill.value = "飞刀"

}, 1000);

return {

name,

age,

skill,

};

},

};

</script>

<style scoped>

</style>

改成响应式引用后数据变化,页面也会发生变化

备注:

接收的数据可以是:基本类型、也可以是对象类型

  • 基本类型:响应式依然是靠Object.defineProperty()的get与set完成的
  • 对象类型:内部借用了Vue3.0中的一个新函数-reactive函数

(ref处理基本类型用的是get与set处理对象数据用的是es6中的Proxy)

reactive:响应式封装非基本类型数据

使用reactive函数,将非基本数据类型转为响应式,一般是对象或者数组。

原理:reactive({name:‘张三’})转为proxy({name:‘张三’})

<template>

<div>

<h3>姓名:{{ obj.name }}</h3>

<h3>年龄:{{ obj.age }}</h3>

<h3>技能:{{ obj.skill }}</h3>

</div>

</template>

<script>

import { reactive } from "vue";

export default {

setup() {

let obj = reactive({

name: '张三',

age: 18,

skill: '犯罪'

})

setTimeout(() => {

obj.name = '李四',

obj.age = 19,

obj.skill = '飞刀'

})

return {

obj

};

},

};

</script>

<style scoped>

</style>

toRefs

使用过对象解构的同学会觉得插值函数里的写法好麻烦啊,所以会用对象解构。但是这里需要注意的一点,如果直接使用结构后的值,这些值不是响应式的。

解构会将对象或数组解构成普通的数据,丢失了响应。因此解构之前需要用 toRefs 包裹一下,使解构后所有值变成响应式的。

原理:toRefs({ name: "张三", age: 18,skill:"犯罪" }) 转化成 { name: proxy({ value: "张三" }), age: proxy({ value: 18 }),skill:proxy({value:"犯罪"}) },将内部的所有数据变成响应式的。现在使用解构后就不会丢失响应式了。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年龄:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

import { reactive, toRefs } from "vue";

export default {

setup() {

let obj = reactive({

name: "张三",

age: 18,

skill: "犯罪",

});

setTimeout(() => {

obj.name = "李四",

obj.age = 19,

obj.skill = "飞刀";

});

let { name, age, skill } = toRefs(obj);

return {

name,

age,

skill,

};

},

};

</script>

<style scoped>

</style>

reactive对比ref

从定义数据角度对比:

ref用来定义:基本类型数据。

reactive用来定义:对象(或数组)类型数据。

备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。

从原理角度对比:

ref定义一个基本数据类型是通过Object.defineProperty()的get()与set()来实现响应式(数据劫持)。

ref如果定义的是一个引用数据类型,其底层本质还是reactive,会将我们传入的值自动转变为:ref(1) ==> reactive({value:1})

reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。

从使用角度对比:

ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。

reactive定义的数据:操作数与读取数据:均不需要.value。

三、computed和watch

computed函数

与Vue2.X的computed的配置功能一致

写法:

<template>

<h1>一个人的信息</h1>

姓:<input type="text" v-model="person.firstName">

<br>

名:<input type="text" v-model="person.lastName">

<!-- 只读 -->

<br>

<!-- <span>全名:{{person.fullName}}</span> -->

全名:<input type="text" v-model="person.fullName">

</template>

<script>

import { reactive, computed } from 'vue'

export default {

name: 'Demo',

setup() {

//数据

let person = reactive({

firstName: '李',

lastName: '四'

})

//计算属性---简写(不考虑计算属性被修改的情况)

// person.fullName = computed(()=>{

// return person.firstName + '-' +person.lastName

// })

//计算属性---完整版(考虑读和写)

person.fullName = computed({

get() {

return person.firstName + '-' + person.lastName

},

set(value) {

const nameArr = value.split('-')

person.firstName = nameArr[0]

person.lastName = nameArr[1]

}

})

//返回一个对象

return {

person,

}

}

}

</script>

可以看到,在 Composition API 中,需要先引入 computed 函数,然后正常使用即可。

watch函数

与Vue2.X的配置功能一致

注意:

监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)

监视reactive定义的响应式数据中的某个属性(属性为对象时)时:deep配置有效

<template>

<h2>当前求和为:{{ sum }}</h2>

<button @click="sum++">点我+1</button>

<hr>

<h2>信息为:{{ msg }}</h2>

<button @click="msg += '!'">修改信息</button>

<hr>

<h2>一个人的信息:</h2>

<h2>姓名:{{ person.name }}</h2>

<h2>年龄:{{ person.age }}</h2>

<h3>工资:{{ person.job.j1.salary }}</h3>

<button @click="person.name += '~'">修改姓名</button>

<button @click="person.age++">修改年龄</button>

<button @click="person.job.j1.salary++">涨薪</button>

</template>

<script>

import { ref, reactive, watch } from 'vue'

export default {

name: 'Demo',

setup() {

//数据

let sum = ref(0)

let msg = ref("nihao")

let person = reactive({

name: '李四',

age: 39,

job: {

j1: {

salary: 20

}

}

})

//情况一:监视ref所定义的一个响应式数据

// watch(sum,(newValue,oldValue)=>{

// console.log('sum变了',newValue,oldValue)

// },{immediate:true})

//情况二:监视ref所定义的多个响应式数据

// watch([sum,msg],(newValue,oldValue)=>{

// console.log('sum或msg变了',newValue,oldValue)

// },{immediate:true})

//情况三:监视reactive所定义的一个响应式数据的全部属性

//1.此处无法正确的获取oldValue

//2.强制开启了深度监视,且无法关闭(deep配置无效)

// watch(person,(newValue,oldValue)=>{

// console.log('person变了',newValue,oldValue)

// },{immediate:true,deep:false})

//情况四:监视reactive所定义的一个响应式数据的某个属性

// watch(()=>person.age,(newValue,oldValue)=>{

// console.log('年龄变了',newValue,oldValue)

// },{immediate:true})

//情况四:监视reactive所定义的一个响应式数据的某些属性

// watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{

// console.log('年龄或姓名变了',newValue,oldValue)

// })

//特殊情况

//此处由于监视的是reactive定义的对象中的某个属性,所以deep有效

watch(() => person.age, (newValue, oldValue) => {

console.log('年龄变了', newValue, oldValue)

}, { deep: true })

//返回一个对象

return {

sum,

msg,

person

}

}

}

</script>

<style>

</style>

watchEffect

watchEffect 和 watch 的区别:

只要写了 watchEffect,回调就立即执行,没有惰性

watchEffect 会自己感知内部的代码数据是否因为相关依赖发生了改变,如果发生了改变就会执行,因此不需要传递很多参数。watch 需要通过参数指定监听哪个数据。

watchEffect 无法获取之前的数据和当前的数据。watch 可以获取之前和当前的数据

watchEffect有点像computed:

但computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值。

而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

watchEffect(() => {

// 当 nameObj.name 发生改变,会触发 watchEffect 的回调函数

console.log(nameObj.name)

})

暂停watchEffect

例如:5S后结束监听

const stop = watchEffect(() => {

console.log(nameObj.name)

setTimeout(() => {

stop()

}, 5000)

})

四、生命周期函数

现在用 Composition API 来写生命周期钩子和Vue2的生命周期钩子的对比:

选项式API 组合式API
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
activated onActivated
deactivated onDeactivated

选项式 API 就是 vue2 的那种 API,因为 vue 组件是通过对象内的配置新建出来的。

因为 setup 函数执行的时间就在 beforeCreate 和 created 之间,因此 Composition API 没有提供对应的钩子。

export default {

setup() {

// mounted

onMounted(() => {

console.log('Component is mounted!')

})

}

}

总结

Vue3.X与Vue2.X的区别

1.重构响应式系统,使用Proxy替换Object.defineProperty。

2.使用Proxy优势:可直接监听数组类型的数据变化,监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升,可拦截apply、ownKeys、has等13种方法,而Object.defineProperty不行,直接实现对象属性的新增/删除。

3.新增Composition API,更好的逻辑复用和代码组织。

4.重构 Virtual DOM:模板编译时的优化,将一些静态节点编译成常量,slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组件,模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)。

5.代码结构调整,更便于Tree shaking,使得体积更小。

6.使用Typescript替换Flow。

7.当然vue3.0,完全兼容vue2.0,不喜欢新语法的伙伴可以保持使用2.0语法。

继续阅读