10 组合式API(一)
摘要:在进行前面的学习后,我们为了方便和Ts搭配使用Vue,需要放弃原有的选项式API写法,转向更为复杂的
组合式API
的学习;当然,这个学习过程无需担心学习成本过大,因为当你掌握了选项式API后,组合式API很容易上手。在本文中,我们将详细探讨响应式的基础内容。声明:为了文章的清爽性,在文章内部的代码演示中只会附上部分演示代码。
作者:来自ArimaMisaki创作
文章目录
10.1 setup
组合式写法:组合式API,我们可以通过vue包下导入的API函数来描述组件的逻辑。在名为.vue的单文件组件
中,我们常常会和<script setup>标签搭配使用。
说明:setup
是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。和以往选项式API地不同是,通过<script setup>中导入的顶层变量/函数都能够在模板中直接使用;setup函数中可以有返回值,如果返回的是一个对象,则对象中的属性、方法,在模板中均可以直接使用。
提示:在Vue3中,我们更推荐组合式写法,选项式写法是Vue2的传统写法了;组合式写法中组件用到的数据
、方法
等均需配置在setup中。
对比:选项式API和组合式API的异同
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
10.2 初探组合式
说明:从下面的代码我们可以看出,组合式写法没有了选项式写法中的各类选项,如data选项;在选项式中,我们在data选项中定义响应式对象(数据),而在组合式中取而代之的是用vue中的reactive函数来创建响应式对象;简而言之我们可以这么理解,reactive函数的作用等同于data选项。
<script>
import { reactive } from 'vue'
export default {
// `setup` 是一个专门用于组合式 API 的特殊钩子函数
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 暴露 state 到模板
return {
state,
increment
}
}
}
</script>
<template>
<div>
{{state.count}}
<button @click="increment">
{{ state.count }}
</button>
</div>
</template>
10.3 组合式简化
说明:在 setup()
函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用构建工具来简化该操作。当使用单文件组件(SFC)时,我们可以使用 <script setup>
来大幅度地简化代码;在script标签中引入setup后,我们无需通过return暴露数据和方法,系统默认暴露。
提示:处于script setup标签中的变量和函数声明可以理解为和模板处于同一个作用域下;当我们更改响应式对象的状态时,DOM也会自然更新,但是DOM的更新是有一定时间缓冲的,这在6.1.5中我们已经谈论过;如果想要等待至一个状态改变后DOM更新完成后的效果,可以使用nextTick()
函数。
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
10.4 深层响应性
说明:在Vue中,状态都是默认深层响应式的,这就是说即使我们更改了数组中的某一个元素的状态,它也能够被检测到。当然, 我们也可以创建一个浅层次的响应式状态对象
,这种对象里的内容被改变不会被检测到,这个话题在这里我们暂且不提及。
10.5 ref函数
引入:定义数据可以完全采取传统js的写法,如定义一个数据count,我们可以使用const count = 1
这种写法,但这种写法的坏处是,该数据并非响应式数据。
说明:
- 用ref()来定义一个响应式的数据是一种非常好的方式,将我们上述的例子改写的话,即
const count = ref(1)
;请注意,使用ref函数定义的响应式数据并非单纯是一个数据,而是一个RefImpl
引用实现对象,也就是说通过count = 2
这种方式不能修改数据内容;如果想要修改数据内容,请调用RefImpl对象中的value属性。 - 通过RefImpl的value属性来获取属性本质上是使用了get方法;这反映了
数据劫持
才是响应式的根基。 - 通过ref定义的响应式对象在模板使用时,无需再使用
.value
来访问RefImpl对象中的数据,Vue3会自动解析该对象中的数据。 - ref传入的数据可以是对象,对象中的属性并非是RefImpl对象,而是一个Proxy对象,这也就导致了或许对象中的属性无需使用value。
提示:
- 有些人可能会认为ref是万能的,实际上也确实如此;但在实际使用中,我们还是提出了这样一种标准——基本类型用ref,引用类型用reactive,这是因为reactive定义的对象类型可以不用使用
.value
。 - ref接收基本数据类型依靠Object.defineProperty()的get和set来完成,ref接收对象类型依靠reactive,但最终靠的是reactive中的Proxy。
TS:ref初始化时会根据传入的参数来自动推到其类型;如果我们想要一个更复杂的类型,可以使用Ref来定义一个联合类型
;除此之外,传入一个泛型参数也可以让其自动推导,不过需要注意的是,如果指定了泛型参数却没有给出初始值,那么最后得到的将会是一个包含undefined的联合类型。
<script lang="ts" setup>
import { ref,reactive } from 'vue'
// 1.比较两者异同
let Test1 = ref({
name:'ArimaMisaki',
job:{
job1:'测试工程师',
job2:'UI设计师'
}
})
const changeData = ()=>{
Test1.value.name = 'none'
}
const changeData2 = ()=>{
Test1.value.job.job1 = 'none'
}
</script>
<template>
<div>
<h2>{{Test1.name}}</h2>
<h2>{{Test1.job.job1}}</h2>
<button @click="changeData">点击后改变名字</button>
<button @click="changeData2">点击后改变工作</button>
</div>
</template>
10.6 reactive函数
说明:reactive只能定义一个对象类型
的响应式数据;reactive()返回的并非是普通的响应式对象,而是一个proxy代理对象;也就是说,只有proxy对象才是响应式对象。
提示:
- reactive()创建的响应式只对对象类型有效,而对原始类型无效,而且从上面的体验我们知道,通过reactive()创建的对象会赋予给一个变量,这个变量必须是固定的,等号的两端不能改变,如更改响应式对象接收的变量。
- reactive()的本质是Proxy而非RefImpl,这也就使得我们无需通过
.value
来获取数据了。
TS:reactive()会隐式地从其proxy对象中推导参数类型,不推荐显式地去标注参数类型。
<script lang="ts" setup>
import { ref,reactive } from 'vue'
let counter = reactive({count:1})
function add(){
counter.count++;
}
</script>
<template>
<div>
<h1>{{counter.count}}</h1>
<button @click="add">++1</button>
</div>
</template>
10.7 ref在响应式对象中的解包
说明:当一个ref被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样。
提示:不是所有的ref都会被解包,例如数组或者Map这种原生集合类型中的元素被访问时,不会进行解包。
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1