响应式|Vue3
响应式是Vue的核心功能之一,即自动跟踪 JavaScript 状态变化并在改变发生时响应式地更新 DOM。
声明响应式状态
使用 reactive()
函数创建一个响应式对象或数组:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
响应式对象其实是Javascript代理,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象 property 的访问与更改操作。
reactive()
API的局限性
- 仅对对象类型有效(对象、数组和Map、Set这样的集合类型),对string、number和boolean这样的原始类型无效。
- 必须始终保持对该响应式对象的相同引用,因为Vue 的响应式系统是通过 property 访问进行追踪的。
在模板组件中使用响应式状态,需要在setup中定义并返回。
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
// 暴露 state 到模板
return {
state
}
}
}
<div>{{ state.count }}</div>
接下来我们修改响应式state对象。setup作用域下支持定义和更改响应式对象的函数,并作为一个方法与响应式对象一起暴露出去。
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 不要忘记同时暴露 increment 函数
return {
state,
increment
}
}
}
暴露的方法通常会被用作事件监听器:
<button @click="increment">
{{ state.count }}
</button>
这里可能会有疑问,如果有很多对象和方法,如此手动暴露状态和方法岂不是非常繁琐?
所以,Vue提供了构建工具来简化该操作。
setup
一个专门用于组合式 API 的特殊钩子
针对单文件模板(SFC)时,可以使用<script setup>简化大量样板代码。以上修改state的操作也可以这样写:
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
<script setup>
中的顶层的导入和变量声明可在同一组件的模板中自动使用。
DOM更新时机
我们知道vue是响应式更新DOM,但是DOM更新不是同步的,Vue首先将缓冲他们直到更新周期的 “下个时机” 以确保无论进行了多少次声明更改,每个组件都只需要更新一次(这里想到了拦截器,TMD语言果然是想通的!)。如果要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
深层响应性
在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,也能被检测到。
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.nested.count++
obj.arr.push('baz')
}
当然,也可以通过 shallowReactive() 直接创建一个 浅层响应式 对象。它们仅在顶层具有响应性,一般仅在某些特殊场景中需要。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++
“响应式代理”和 “.原始对象”的区别
- 响应式代理返回的是一个原始对象的代理,和原始对象不相等。
- 只有代理是响应式,更改原始对象不会触发更新。
定义响应式变量
为了解决 reactive()
带来的限制,Vue 也提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'
const count = ref(0)
ref()
从参数中获取到值,将其包装为一个带 .value
property 的 ref 对象:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
对象类型值的 ref 可以响应式地替换整个对象:
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
ref()在模板中解包
当ref
在模板中作为顶层 property 被访问时,它们会被自动“解包”,所以不需要使用 .value
。
注意,仅当 ref 是模板渲染上下文的顶层 property 时才适用自动“解包”。例如, foo 是顶层 property,但 object.foo 不是。
const object = { foo: ref(1) }
表达式将不会像预期的那样工作:
{{ object.foo + 1 }}
ref 在响应式对象中的解包
当ref
作为一个响应式对象的 property 被访问或更改时,它会自动解包。
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
将一个新的 ref
赋值给一个关联了已有 ref 的 property,那么它会替换掉旧的 ref:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1
注意:只有嵌套深层响应式对象内时才会解包,作为浅层响应式对象的property 被访问时不会解包。
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)
本文来自博客园,作者:七月的枫丶 ,部分内容摘自互联网,转载请注明原文链接:https://www.cnblogs.com/easybook/p/16179521.html