Options API 的弊端
在Vue2中,我们编写组件的方式是Options API:
- Options API的一大特点就是在对应的属性中编写对应的功能模块;
- 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;
但是这种代码有一个很大的弊端:
- 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
- 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
- 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
- 这种碎片化的代码使得理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
- 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;
认识 Composition API
因为 vue2 碎片化的缺陷,所以如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。这就是Composition API 想要做的事情。setup 函数
Composition API 中集合代码逻辑的地方就是setup()
函数
setup 其实就是组件的另外一个选项(属性):只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;比如methods、computed、watch、data、生命周期等等;setup 函数的参数
我们先来研究一个setup函数的参数,它主要有两个参数:
- 第一个参数:
props
- 第二个参数:
context
props 非常好理解,它其实就是父组件传递过来的属性会被放到 props 对象中,我们在 setup 中如果需要使用,那么就可以直接通过 props 参数获取
- 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
- 并且在template中依然是可以正常去使用props中的属性;
- 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取;
- 因为 setup 函数没有 this
- 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;
另外一个参数是 context,我们也称之为是一个 SetupContext,它是一个对象,里面包含三个属性,使用的时候可以解构方便使用。
attrs
:保存所有的非 prop 的 attribute;slots
:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);emit
:当我们组件内部需要发出事件时会用到 emit(因为我们不能访问this,所以不可以通过 this.$emit 出事件) ```html插槽
————home—————-
插槽默认内容
<a name="kFdAb"></a>
## setup 函数的返回值
setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?<br />setup 的返回值是一个对象,属性可以在模板 template 中被使用,也就是说我们可以通过 setup 的返回值来替代data 选项;<br />甚至是我们可以返回一个执行函数来代替在 methods 中定义的方法:
```html
<template>
<div>
<h1>{{ q }}</h1>
<button @click="print">按钮</button>
</div>
</template>
<script>
export default {
setup(props, {attrs, slots, emit}) {
const print = () => console.log(789);
let q = 666
return {
q,
}
},
}
</script>
但是现在还存在一个问题,q 并不是响应式的,模块的值并不会随着 q 在 setup 中改变而改变。这是因为对于一个定义的变量来说,默认情况下,Vue 并不会跟踪它的变化,来引起界面的响应式操作;
Reactive API
reactive()
函数可以为在 setup 中定义的数据提供响应式的特性。
它接收对象或者数组,返回的是一个 Proxy 代理对象,Proxy 对象实现了接收对象中属性或数组中元素的响应式。
15. Proxy-Reflect 响应式原理
<template>
<div>
<h2>{{proxy.count}}</h2>
<button @click="proxy.count++">+1</button>
</div>
</template>
<script>
// 导入 reactive 函数
import { reactive } from 'vue'
export default {
setup() {
// 实现 count 的响应式
const proxy = reactive({
count: null
})
return { proxy }
}
}
</script>
Ref API
eactive AP I对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
const proxy = reactive(100) // value cannot be made reactive: 100
这个时候Vue3给我们提供了另外一个API:ref API**ref()**
函数会返回一个 响应式对象的引用,并且维护着它内部的值,这就是 ref 名称的来源,reference。
响应式对象内部的值保存在 value 属性中,也就是**ref函数的返回值.value**
可以访问到非响应式数据变成响应式后的样子。
注意:在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;
- 但是这个解包是个浅层解包,只能直接对 ref 对象进行解包
```html
{{ count }}
{{ obj.count }}
<a name="Tjuk7"></a>
# readonly
我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个<br />响应式对象在另外一个地方(组件)被使用,希望是只读的。<br />Vue3为我们提供了 `readonly()` 方法;
- readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,但是这个proxy的set方法被劫持,并且不能对其进行修改)
在开发中常见的 readonly 方法会传入三个类型的参数:
- 类型一:普通对象;
- 类型二:reactive返回的对象;
- 类型三:ref的对象;
```html
<template>
<div>
<button @click="updateState">修改状态</button>
</div>
</template>
<script>
// 导入 readonly 函数
import { reactive, ref, readonly } from 'vue';
export default {
setup() {
// 1.普通对象
const info1 = {name: "why"};
const readonlyInfo1 = readonly(info1);
// 2.响应式的对象reactive
const info2 = reactive({
name: "why"
})
const readonlyInfo2 = readonly(info2);
// 3.响应式的对象ref
const info3 = ref("why");
const readonlyInfo3 = readonly(info3);
const updateState = () => {
info3.value = "";
}
return {
updateState,
}
}
}
</script>
Reactive 判断的 API
toRefs
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,解构出来的值都不是响应式的。
因为这个解构是个赋值操作,只是传了值,而真正实现响应式的是包裹属性值的代理对象。
<template>
<div>
<h2>{{name}}</h2>
<button @click="updateState">修改状态</button>
</div>
</template>
<script>
import { reactive, ref, readonly } from 'vue';
export default {
setup() {
// 解构赋值
const {name, age} = reactive({name: 'zs', age: 29})
// 相当于 let name = 'zs'
const updateState = () => {
name++; // "name" is read-only
}
return {
name
}
}
}
</script>
Vue为我们提供了一个toRefs
的函数,可以将reactive返回的对象中的属性都转成 ref 对象;
那么我们再次进行解构出来的 name 和 age 本身都是 ref 对象,而不再是一个拥有相同值的变量;
<template>
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<button @click="updateState">修改状态</button>
</div>
</template>
<script>
// 导入 toRefs 函数
import { reactive, toRefs } from 'vue';
export default {
setup() {
// 解构赋值
const info = reactive({name: 'zs', age: 29})
// toRefs 转成 ref
const {name, age} = toRefs(info) // 相当于 const name = ref(name)
const updateState = () => {
name.value += 1; // 现在是 ref 对象,使用别忘了 .value
info.age++; // reactive 对象的变化,也会导致 ref.value 的变化,两者已经互相绑定了
}
return {
name,
age,
updateState
}
}
}
</script>
toRef
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:toRef()
有两个参数,第一个参数为 reactive 对象,第二个为要转换的属性
<template>
<div>
<h2>{{name}}</h2>
<button @click="updateState">修改状态</button>
</div>
</template>
<script>
// 导入 toRef 函数
import { reactive, toRef } from 'vue';
export default {
setup() {
// 解构赋值
const info = reactive({name: 'zs', age: 29})
const name = toRef(info, "name")
const updateState = () => {
name.value += 1; // 现在是 ref 对象,使用别忘了 .value
}
return {
name,
updateState
}
}
}
</script>
ref 其他的 API
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显示控制,说白了就是实现了响应式:
参数为一个工厂函数,该函数接受 track 和 trigger 函数作为参数;并且返回一个带有 get 和 set 的对象;
这里我们使用一个的案例:对双向绑定的属性进行debounce(节流)的操作;