在Vue2中,我们编写组件的方式是Options APl:
- Options API的一大特点就是在
对应的属性
中编写对应的功能模块
;- 比如
data定义数据
、methods中定义方法
、computed中定义计算属性
、watch中监听属性改变
,也包括生命周期钩子
;
但是这种代码有一个很大的弊端:
- 当我们
实现某一个功能
时,这个功能对应的代码逻辑
会被拆分到各个属性
中;- 当我们
组件变得更大、更复杂
时,逻辑关注点的列表
就会增长,那么同一个功能的逻辑就会被拆分的很分散
;- 尤其对于那些一
开始没有编写这些组件的人
来说,这个组件的代码是难以阅读和理解的
(阅读组件的其他人);
下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
- 这种
碎片化的代码
使用理解和维护这个复杂的组件
变得异常困难,并且隐藏了潜在的逻辑问题
;- 并且当我们
处理单个逻辑关注点
时,需要不断的跳到相应的代码
块中;
如果我们能将
同一个逻辑关注点相关的代码
收集在一起
会更好。
这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
那么既然知道Composition API想要帮助我们做什么事情,接下来看一下到底是怎么做呢?
(编写代码)的地方
;setup函数
;setup其实就是组件的另外一个选项:
用它来替代之前所编写的大部分其他选项
;methods、computed、watch、data、生命周期
等等;接下来我们一起学习这个函数的使用:
我们先来研究一个setup函数的参数,它主要有两个参数:
- 第一个参数:
props
- 第二个参数:
context
props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:
- 对于
定义props的类型
,我们还是和之前的规则是一样的,在props选项中定义
;- 并且
在template中
依然是可以正常去使用props中的属性
,比如message;- 如果我们
在setup函数中想要使用props
,那么不可以通过this去获取
(后面我会讲到为什么);- 因为props有直接
作为参数传递到setup函数
中,所以我们可以直接通过参数
来使用即可;
另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:
attrs
:所有的非prop的attribute;slots
:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);emit
:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?
模板template中被使用
;通过setup的返回值来替代data选项
;甚至是我们可以返回一个执行函数来代替在methods中定义的方法:
<template>
<div class="app">
<h2>当前计数: {{ counter }}h2>
<button @click="increment">+button>
<button @click="decrement">-button>
div>
template>
<script>
export default {
// 使用setup函数
setup() {
//1.定义counter的内容
// 默认定义的数据都不是响应式数据
let counter = 100
// setup函数定义函数(方法)
const increment = () => {
counter++
}
// setup函数定义函数(方法)
const decrement = () => {
counter--
}
// 外部要使用的数据需要通过return导出
return {
counter,
increment,
decrement
}
}
}
script>
运行上述代码,发现点击 increment 或者 decrement按钮进行操作时,发现counter展示的数值没有发生变化:
因为
默认定义的数据不是响应式数据
,即对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;
如果想为在setup中定义的数据提供响应式的特性,那么我们有如下两种常见的方案:
如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数:
<template>
<div class="app">
<h2>message:{{ message }}h2>
<button @click="changeMessage">修改messagebutton>
div>
<hr>
<h2>账号:{{ account.username }}h2>
<h2>密码:{{ account.password }}h2>
<button @click="changeAccount">修改账户名button>
<hr>
template>
<script>
import { reactive, ref } from "vue";
export default {
setup() {
// 1.定义普通的数据
// 缺点:数据不是响应的
let message = "hello world";
function changeMessage() {
message = "message已被修改"//不生效
console.log(222);//222
}
// 2.定义响应式的数据
// 2.1 reactive函数:定义复杂类型的数据(不常用)
const account = reactive({
username: "wxx",
password: "1212135"
})
function changeAccount() {
account.username = "飒飒"//生效
}
return {
message,
changeMessage,
account,
changeAccount,
}
}
}
script>
那么这是什么原因呢?为什么就可以变成响应式的呢?
- 这是因为当我们
使用reactive函数处理我们的数据之后
,数据再次被使用
时就会进行依赖收集
;- 当
数据发生改变
时,所有收集到的依赖
都是进行对应的响应式操作
(比如更新界面);- 事实上,我们编写的
data选项
,也是在内部交给了reactive函数
将其编程响应式对象的;
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:
- 如果我们传入一个
基本数据类型(String、Number、Boolean)会报一个警告
;
这个时候Vue3给我们提供了另外一个APl: ref API
- ref 会返回一个
可变的响应式对象
,该对象作为一个响应式的引用
维护着它内部的值
,这就是ref名称的来源
;- 它内部的值是
在ref的 value属性
中被维护的;
使用示例:
<template>
<h2>当前计数:{{ counter }}h2>
<button @click="increment">+1button>
template>
<script>
import { ref } from "vue";
export default {
setup() {
// 2.2 Ref函数:定义简单类型的数据(也可以定义复杂类型数据)
//错误写法: const counter = ref(0);也能实现变动,因为修改的是counter的value值不少修改的counter本身
let counter = ref(0)
function increment() {
// 错误写法:counter++
counter.value++
}
return {
counter,
increment
}
}
}
script>
这里有两个注意事项:
- 在
模板中引入ref的值
时,Vue会自动帮助我们进行解包
操作,所以我们并不需要在模板中通过ref.value
的方式来使用;- 但是在
setup函数内部
,它依然是一个ref引用
,所以对其进行操作时,我们依然需要使用ref.value的方式
;
常见使用ref情况:
1. readonly函数
先来看下边的代码:
- 在下面的示例中,通过props
接收到了父组件传递过来的数据
后,直接在当前组件进行了修改
,这样是违背了单向数据流
的
如上述代码所示:
我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
- Vue3为我们提供了
readonly的方法
;readonly会返回原始对象的只读代理
〈也就是它依然是一个Proxy,这是一个proxy的set方法被劫持
,并且不能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
- 类型一:
普通对象
;- 类型二:
reactive返回的对象
;- 类型三:
ref的对象
;
在readonly的使用过程中,有如下规则:
readonly返回的对象都是不允许修改的, 但是经过readonly处理的原来的对象是允许被修改的;
- 比如 const info = readonly(obj),
info对象是不允许被修改
的;- 但是可以通过修改obj来修改info, obj被修改时,readonly返回的info对象也会被修改;
- 但是我们不能去修改readonly返回的对象info;
setup() {
const info = reactive({
name: "chenyq",
age: 18,
height: 1.88
})
// 为info包裹一个readonly, 子组件就无法修改
const newInfo = readonly(info)
console.log(newInfo)
return {
info,
changeInfo,
newInfo
}
}
2. Reactive判断的API(了解)
isProxy
检查对象`是否是由reactive或 readonly创建的proxy。
isReactive
检查对象是否是由reactive创建的响应式代理:
如果该代理是readonly创建的,但包裹了由reactive创建的另一个代理,它也会返回true;
isReadonly
检查对象是否是由readonly 创建的只读代理。
toRaw
返回reactive或 readonly代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
创建一个响应式代理,它跟踪其自身property 的响应性,但不执行嵌套对象的深层响应式转换(深层还是原生对象)。
shallowReadonly
创建一个proxy,使其自身的property为只读,但不执行嵌套对象的深度只读转换(深层还是可读 可写的)。
3. toRef(s)函数
如果我们希望在模板中展示name,age
等数据的值不是info.name,info.age这样的形式
,而是直接使用所有对其解构
,如下代码示例:
setup() {
const info = reactive({
name: "sevgilid",
age: 19,
height: 1.87
})
const { name, age, height } = info
}
我们会发现直接解构后
无论是修改解构后的变量,还是修改reactive返回的state对象,数据都不再是响应式
的:
那么如何让解构出来的属性变成响应式
的呢?这就需要使用到toRefs
和toRef函数:
toRefs 函数
可以将一个响应式对象
转换为一个基本类型的对象
,并且对象中每个属性都是一个单独的源,并且可以进行解构和响应性访问toRef 函数
可以将一个响应式对象中的 指定属性
转换为一个新的响应式数据
,这个新的数据可以独立地更新,并且对原响应式对象不会造成影响;
- 这个函数
接收对象
和键名称两个参数
,返回一个带有 value 属性的 ref 对象\
代码示例:
<template>
<div class="app">
<!-- <h2>info:{{ info.name }}-{{ info.age }}</h2> -->
<h2>info:{{ name }}-{{ age }}-{{ height }}</h2>
<button @click="age++">修改age</button>
</div>
</template>
<script>
import { reactive, toRefs, toRef } from 'vue';
export default {
setup() {
const info = reactive({
name: "sevgilid",
age: 18,
height: 1.88
})
// reactive被解构后会变成普通的值,失去响应式
// 使用toRefs,toRef方法就可以
const { name, age } = toRefs(info)
// 单独对一个结构
const height = toRef(info, "height")
return {
name,
age,
height
}
}
}
Vue 3 中的 setup 函数是一个新的组件选项
。它允许我们在组件渲染之前执行一些前置操作,比如数据处理、事件绑定等;
与 Vue 2.x 中的 data、computed、methods 等属性不同,
setup 函数只接收 props 作为参数,并且不能访问 this
为什么不能使用this:
- 在 Vue 3 中,
模板编译的过程
发生了很大的变化
,这就导致了this 不能再指向 Vue 实例对象
;- 在 Vue 2.x 中,我们可以通过 this.$xxx 访问全局实例,但是在 Vue 3 中,由于模板编译的变化,我们需要使用 provide 和 inject 手动注入/引用全局实例对象,而不能再通过 this 来访问它们。
下面的代码演示了 setup 函数内使用 this 的错误示例:
export default {
setup() {
console.log(this.$route) // 无法访问 this
}
}