在Vue2中,我们编写组件的方式是Options API:
但是这种代码有一个很大的弊端:
代码逻辑会被拆分到各个属性中
;下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
也有人把Vue Composition API简称为VCA
。
那么既然知道Composition API想要帮助我们做什么事情,接下来看一下到底是怎么做呢?
setup
函数;setup其实就是组件的另外一个选项:
接下来我们一起学习这个函数的使用:
我们先来研究一个setup函数的参数,它主要有两个参数:
props
context
props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:
另外一个参数是context,我们也称之为是一个SetupContext
,它里面包含三个属性:
attrs
:所有的非prop的attribute;slots
:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);emit
:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?
替代data
选项;甚至是我们可以返回一个执行函数来代替在methods
中定义的方法:
const name = 'abc'
let counter = 100
const increment = () => {
counter++
}
const decrement = () => {
counter--
}
return {
name,
counter,
increment,
decrement
}
但是,如果我们将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?
Vue并不会跟踪它的变化
,来引起界面的响应式操作;如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive
的函数:
const info = reactive({
name: 'zzx',
age: 18
})
那么这是什么原因呢?为什么就可以变成响应式的呢?
依赖收集
;reactive函数
将其变成响应式对象的;reactive API对传入的类型是有限制
的,它要求我们必须传入的是一个对象或者数组类型:
这个时候Vue3给我们提供了另外一个API:ref
API
value
属性中被维护的;这里有两个注意事项:
解包
操作,所以我们并不需要在模板中通过 ref.value
的方式来使用;ref.value
的方式;模板中的解包是浅层
的解包,如果我们的代码是下面的方式,就不能自动解包:
<template>
<div class="app">
<h2>{{ typeof info.message }}h2>
<h2>{{ info.title }}h2>
<h2>{{ typeof message }}h2>
div>
template>
<script>
import { ref } from 'vue'
export default {
setup() {
const info = {}
const message = ref('hello beigjing')
info.message = message
info.title = 'abcd'
return {
info,
message
}
}
}
script>
如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包:
<template>
<div>
<h2>message: {{ message }}h2>
<button @click="changeMessage">修改messagebutton>
<hr>
<h2>账号: {{ account.username }}h2>
<h2>密码: {{ account.password }}h2>
<button @click="changeAccount">修改账号button>
<hr>
<h2>当前计数: {{ counter }}h2>
<button @click="increment">+1button>
<button @click="counter++">+1button>
<hr>
<h2>当前计数: {{ info.counter }}h2>
<button @click="info.counter.value++">+1button>
div>
template>
<script>
import { reactive, ref } from 'vue'
export default {
setup() {
// 1.定义普通的数据: 可以正常的被使用
// 缺点: 数据不是响应式的
let message = "Hello World"
function changeMessage() {
message = "你好啊,李银河!"
console.log(message)
}
// 2.定义响应式数据
// 2.1.reactive函数: 定义复杂类型的数据
const account = reactive({
username: "coderwhy",
password: "123456"
})
function changeAccount() {
account.username = "kobe"
}
// 2.2.ref函数: 定义简单类型的数据(也可以定义复杂类型的数据)
// counter定义响应式数据
const counter = ref(0)
function increment() {
counter.value++
}
// 3.ref是浅层解包
const info = {
counter
}
return {
message,
changeMessage,
account,
changeAccount,
counter,
increment,
info
}
}
}
script>
<style scoped>
style>
<template>
<div>
<form>
账号: <input type="text" v-model="account.username">
密码: <input type="password" v-model="account.password">
form>
<form>
账号: <input type="text" v-model="username">
密码: <input type="password" v-model="password">
form>
<hr>
<show-info :name="name" :age="age">show-info>
div>
template>
<script>
import { onMounted, reactive, ref } from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
data() {
return {
message: "Hello World"
}
},
setup() {
// 定义响应式数据: reactive/ref
// 强调: ref也可以定义复杂的数据
const info = ref({})
console.log(info.value)
// 1.reactive的应用场景
// 1.1.条件一: reactive应用于本地的数据
// 1.2.条件二: 多个数据之间是有关系/联系(聚合的数据, 组织在一起会有特定的作用)
const account = reactive({
username: "coderwhy",
password: "1234567"
})
const username = ref("coderwhy")
const password = ref("123456")
// 2.ref的应用场景: 其他的场景基本都用ref(computed)
// 2.1.定义本地的一些简单数据
const message = ref("Hello World")
const counter = ref(0)
const name = ref("why")
const age = ref(18)
// 2.定义从网络中获取的数据也是使用ref
// const musics = reactive([])
const musics = ref([])
onMounted(() => {
const serverMusics = ["海阔天空", "小苹果", "野狼"]
musics.value = serverMusics
})
return {
account,
username,
password,
name,
age
}
}
}
script>
<style scoped>
style>
我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
只读代理
(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);在开发中常见的readonly方法会传入三个类型的参数:
在readonly的使用过程中,有如下规则:
不允许修改的
;其实本质上就是readonly返回的对象的setter方法被劫持了而已;
那么这个readonly有什么用呢?
readonly
了;isProxy
isReactive
isReadonly
toRaw
shallowReactive
shallowReadonly
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的:
const state = reactive({
name: 'abc',
age: 18
})
const { name, age } = state
那么有没有办法让我们解构出来的属性是响应式的呢?
toRefs
的函数,可以将reactive返回的对象中的属性都转成ref;//当我们这样来做的时候,会返回两个ref对象,它们是响应式的
const { name, age } = toRefs(state)
这种做法相当于已经在state.name和ref.value之间建立了链接,任何一个修改都会引起另外一个变化;
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
//如果我们只希望转换一个reactive对象中的属性为ref,那么可以使用toRef的方法
const name =toRef(state,'name');
const {age} = state;
const changeName = () => state.name="abc"
unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
如果参数是一个 ref,则返回内部值,否则返回参数本身;
这是 val = isRef(val) ? val.value : val
的语法糖函数;
isRef
shallowRef
triggerRef
const info = shallowRef({name: 'abc'})
// 下面的修改不是响应式的
const changeInfo = () => {
info.value.name = 'wbc'
// 手动触发
triggerRef(info)
}
官方关于this有这样一段描述: