本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。
vue2响应式
<template>
<div>
<p>{{name}}</p>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
name: 'YnacyZhang'
}
},
created() {
setTimeout(() => {
this.name = "Hello World!"
}, 1000)
},
}
</script>
我们在data选项中返回一个对象,当我们修改data中的数据时,页面也会相应的改变,即响应式。
vue2响应式基本原理:
1.通过Object.defineProperty来实现监听数据的改变和读取(属性中的getter和setter方法) 实现数据劫持
2.观察者模式(发布者-订阅者) 观察者(订阅者) – Watcher: update():当事件发生时,具体要做的事情目标(发布者) – Dep: ①subs 数组:存储所有的观察者 ②addSub():添加观察者 ③notify():当事件发生,调用所有观察者的 update() 方法
3.当数据发生改变通过发布者订阅者模式来进行通知进行界面刷新
vue3响应式
Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了Proxy来创建响应式对象,仅将 getter / setter 用于 ref。
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
}
在vue3组合式API中,主要使用reactive和ref来创建响应式数据。
用 reactive()函数创建响应式数据
我们可以使用 reactive() 函数创建一个响应式对象或数组:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
在模板中使用:
<div>{{ state.count }}</div>
深层响应式
在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.nested.count++
obj.arr.push('baz')
}
reactive()的局限性
-
仅对
对象类型有效(对象、数组和Map、Set这样的集合类型),而对string、number和boolean这样的 原始类型 无效。 -
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地
“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:let state = reactive({ count: 0 }) // 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!) state = reactive({ count: 1 })同时这也意味着当我们将响应式对象的
属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性。
toRefs()
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// ...基于状态的操作逻辑
// 在返回时都转为 ref
return toRefs(state)
}
// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()
用ref()定义响应式变量
reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'
const count = ref(0)
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象,所以访问它的值需要使用count.value:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
对象类型的数据:内部 “ 求助 ” 了reactive函数。
值为对象类型
和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value。
一个包含对象类型值的 ref 可以响应式地替换整个对象:
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
ref 在模板中的解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- 无需 .value -->
</button>
</template>
请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, foo 是顶层属性,但 object.foo 不是。
const object = { foo: ref(1) }
{{ object.foo + 1 }}
渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。
ref 在响应式对象中的解包
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
数组和集合类型的 ref 解包
跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)