一、响应式状态、函数的暴露(对外提供)
两种方式
1)export指令 + setup函数
2)单文件组件中(SFC)中使用<script setup>
1)export指令 + setup函数
return的对象中内容,包括响应式状态,函数,都可以用在模板中。
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}
2)单文件组件(SFC)的<script setup>
顶层中的内容,包括导入、声明的变量和函数,都可以用于模板中。
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
二、响应式对象声明
包括:
1)ref()函数
2)reactive()函数
1)ref() 函数
1)基于依赖追踪的响应式系统实现,ref对象能追踪模板中的使用者,当自身被修改时及时更新模板;
2)ref对象传给函数,也能保持响应式特性(优于reactive())
3)深层相应式能力,
4)浅层响应式能力,解决深层响应式过于“沉重”问题,shallowRef();
5)适用于任何值类型(优于reactive());
2)reactive()函数
1)建立原对象的JavaScript 代理,Vue 能够拦截对 reactive()产生的代理对象所有属性的访问和修改,以便进行依赖追踪和触发更新。这是和ref()的差异所在;
2)只适用于对象,JS内置集合型对象等。不适用于string, number,boolean等原子类型;
3)深层相应式能力,
4)浅层响应式能力,解决深层响应式过于“沉重”问题,shallowReactive();
三、ref对象和reactive对象的本质
两者本质差别源于javascript的两种劫持 property 访问的方式:getter / setters 和 Proxies。
1) ref对象仅仅使用了getter / setters特性的对象。其行为已经和被封闭对象不同。为方便使用,Vue增加了自动解包功能。
2) reactive对象除使用了getter / setters特性,还是个proxy对象。其行为和被封闭对象是一样的。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。为方便使用和行为一致,Vue增加了自动封包功能。
3)实现伪代码
// 伪代码
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
trigger(target, key)
}
})
}
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
}
}
return refObject
}
4)重复封包,结果一致
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
5)自动封包,如下例
const proxy = reactive({})
const raw1 = {}
const raw2 = reactive({});
proxy.raw1 = raw1 // set响应式对象时也会在内部调用reactive();
proxy.raw2 = raw2
console.log(proxy.raw1 === raw1) // false
console.log(proxy.raw2 === raw2) // true
四、自动解包还是替换
1)将ref对象作为reactive对象属性,在被访问时ref对象自动解包而成为普通变量
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1 // 自动解包,赋值
console.log(count.value) // 1,即实际上还是个ref对象
2)将ref对象赋值给已声明属性,则覆盖原有ref
3)ref对象作为数组或者集合对象的元素,被访问时不会自动解包
4)模板中,只有ref对象作为“顶层”被访问元素时,才会被解包
const count = ref(0)
const object = { id: ref(1) }
因此,这个表达式按预期工作:
{{ count + 1 }}
...但这个不会:
{{ object.id + 1 }}
五、测试
<script setup>
import { ref, reactive } from 'vue'
const state = reactive({})
const raw1= {a:0}
const raw2= reactive({a:0})
const ref1 = ref({a:0})
state.raw1 = raw1
state.raw2 = raw2
state.ref1 = ref1
function increment() {
raw1.a++;
raw2.a++;
ref1.value.a++;
}
</script>
<template>
<button @click="increment">
{{ state}}
</button>
<p>raw1 : {{raw1 }}</p>
<p>raw2: {{raw2 }}</p>
<p>ref1: {{ref1 }}</p>
<p>state: {{state }}</p>
<p>state.raw1 === raw1 : {{state.raw1 === raw1}}</p>
<p>state.raw2 === raw2 : {{state.raw2 === raw2}}</p>
</template>
六、计算属性 - 核心应用
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref - publishedBooksMessage
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
1)基于响应式状态对象
2)使用computed()函数,提供一个get()方法
3)返回一个计算属性ref对象,是一个“临时快照”
4)具有缓存特性
5)可以实现为可写的 “计算属性”
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})