Appearance
声明响应式状态
ref() 在组合式API中,推荐使用ref()函数来声明响应式状态
javascript
import { ref } from 'vue'
const count = ref(0)ref()接收参数,并将其包裹在一个带有.value属性的 ref 对象中返回
javascript
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1要在组件模板中使用ref,要在组件的setup函数中声明并返回
javascript
import { ref } from 'vue'
export default {
// `setup` 是一个特殊的钩子,专门用于组合式 API。
setup() {
const count = ref(0)
// 将 ref 暴露给模板
return {
count,
}
},
}注意: 在模板中使用
ref时不需要添加.value
对于复杂i点的逻辑,可以在同一作用域中生命更改ref的函数,并暴露给模板
JavaScript
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}暴露出来的方法就可以用在模板页面上
html
<button @click="increment">{{ count }}</button><script setup>
在setup函数中手动暴露大量的方法十分繁琐, Vue3 提供了对应的语法糖来简化操作
html
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template><script setup> 中的顶层的导入、声明的变量和函数可在同一组件的模板中直接使用。
为社么使用ref而不是普通变量
当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式 系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染
在标准的JavaScript中,检测普通变量的访问或修改是行不通 的.热纳尔我们可以通过getter和ssetter方法来拦截对象属性的get和set操作
该.value属性给予了Vue一个机会来检测ref何时被访问或修改,在其内部,Vue在他的getter中执行追踪,在setter中执行触发 原理类似于:
javascript
const obj = {
_value: 0,
get value() {
// 在这里执行追踪
return this._value
},
set value(newValue) {
// 在这里执行触发
this._value = newValue
},
}深层响应性
Ref可以持有任何类型的值,包括深层嵌套的对象,数组或者JavaScript内置的数据结构,比如Map和Set
Ref会使它的值具有深层响应性. 这意味着即使改变嵌套对象或数组时,变化也会被检测到
javascript
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar'],
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.value.nested.count++
obj.value.arr.push('baz')
}也可以通过shallow ref来放弃深层响应性,使用shallowRef()函数
javascript
import { shallowRef } from 'vue'
const obj = shallowRef({
nested: { count: 0 },
arr: ['foo', 'bar'],
})
function mutateShallowly() {
// 以下不会触发更新
obj.value.nested.count++
obj.value.arr.push('baz')
}对于浅层ref只有.value的访问会被追踪,而嵌套对象的属性访问不会被追踪,因此不会触发更新.浅层ref适用于那些不需要深层响应性的场景,例如当你只关心对象的引用变化时
Dom更新时机
DOM 会在响应式状态被修改时自动更新,但不是同步的. Vue会在next tick更新周期中会从所有的状态修改,以确保不管进行了多少次修改每个组件都只会被更新一次 要等待Dom更新完成后在执行额外的代码,可以使用nextTick()全局API
javascript
import { ref, nextTick } from 'vue'
const count = ref(0)
function increment() {
count.value++
nextTick(() => {
// 在 DOM 更新后执行
console.log('DOM 已更新')
})
}reactive()
除ref外,还可以使用reactive()API声明响应式状态,不同的是,reactive()将使对象本身具有响应性
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0,
message: 'Hello Vue!',
})
function increment() {
state.count++
}响应式对象是 JavaScript 代理(Proxy) 其行为就喝普通对象一样. 不同的是 Vue能够拦截对响应式对象所有属性的访问和修改,一遍进行依赖追踪和触发更新 (关于Proxy可以阅读这篇文章) reactive()将深层的转换对象: 当访问嵌套对象时,他们也会被reactive()包装.
当ref的值是一个对象时,ref()也会在内部调用它,与浅层ref类似,也有一个shallowReactive()API可以选择退出深层响应
javascript
import { shallowReactive } from 'vue'
const state = shallowReactive({
count: 0,
nested: { message: 'Hello Vue!' },
})
function increment() {
state.count++
// 以下不会触发更新
state.nested.message = 'Hello Vue!'
}Reactive Proxy 和 Original
当你使用reactive()或shallowReactive()创建一个响应式对象时,它会返回一个代理对象,这个代理对象是原始对象的代理 并不等同于原始对象.只有对代理的修改是响应式的,对原始对象修改不会触发更新.因此在Vue中,最佳实践是仅使用声明对象的代理版本
为了保证代理对象更多一致性,对同一个原始对象调用reactive()和shallowReactive()会返回同一个代理对象 对同一个原始对象调用reactive()或shallowReactive()也会返回同一个代理对象, 而对一个已存在的代理对象调用reactive()或shallowReactive()会返回这个代理对象本身,而不是新的代理对象
reactive() 的局限性
1.有限的值类型 : 只能用于对象类型(包括数组和函数),不能用于原始值类型(如字符串、数字、布尔值等) 2.不能替换整个对象 : reactive()只能使对象的属性具有响应性,不能替换整个对象.如果需要替换整个对象,可以使用ref()或shallowRef()
javascript
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })3.对解构操作不友好 : 将响应式对象解构后,解构的属性将不再具有响应性
javascript
import { reactive } from 'vue'
const state = reactive({ count: 0 })
const { count } = state
// 这里的 count 是一个普通变量,不再具有响应性
count++ // 不会触发更新由于这些限制,简易使用ref()来声明响应式状态,除非需要处理复杂的对象或数组
额外的ref解包细节
当你在模板中使用ref时,Vue会自动解包它的值,因此你可以直接使用count而不是count.value
html
<template>
<button @click="increment">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>如果将一个新的ref赋值给一个已有的ref,则会自动解包并重新赋值
javascript
import { ref } from 'vue'
const count = ref(0)
count = ref(1) // count.value 现在是 1在 Vue 3 的响应式系统中,ref 的解包行为取决于它所处的上下文环境:
深层响应式对象内:如果一个 ref 被嵌套在一个由 reactive() 创建的深层响应式对象中,当直接通过属性访问时,它会自动解包(即无需通过 .value 获取其值)。例如:
javascript
const deepObj = reactive({
nested: {
count: ref(1),
},
})
console.log(deepObj.nested.count) // 输出 1(自动解包)浅层响应式对象中:如果 ref 是浅层响应式对象(如 shallowReactive 创建的对象)的直接属性,访问时 不会 自动解包,仍需使用 .value:
javascript
const shallowObj = shallowReactive({
count: ref(1),
})
console.log(shallowObj.count.value) // 需要手动 .value总结:ref 的自动解包仅发生在深层响应式对象的嵌套属性中,而在浅层对象的顶层属性或非响应式对象中保持原始 ref 形态。
数组和集合的注意事项
当ref的值是一个数组或集合时,可以直接使用数组或集合的方法来修改它们的内容,而不需要使用.value
javascript
import { ref } from 'vue'
const items = ref(['apple', 'banana', 'orange'])
function addItem(item) {
// 直接使用数组方法
items.value.push(item)
}
function removeItem(index) {
// 直接使用数组方法
items.value.splice(index, 1)
}当ref作为响应式数组或原生集合类型(如Set或Map)中的元素被访问时,它不会解包
javascript
import { ref } from 'vue'
const books = reactive([ref('Vue3 Guide')])
console.log(books[0].value) // Vue3 Guide在模板中解包的注意事项
在模板渲染上下文中,只有顶级ref属性才会被解包
html
<template>
<div>
<p>{{ count }}</p>
<!-- 自动解包 -->
<p>{{ object.id +1 }}</p>
<!-- 不会自动解包 -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const object = { id: ref(1) }
</script>这个例子中,id将会被渲染为[object Object] + 1,因为它没有被解包,而count会被自动解包并渲染为数字 为了解决这个问题,我们可以将id结构为一个顶级属性
html
<template>
<div>
<p>{{ count }}</p>
<!-- 自动解包 -->
<p>{{ id + 1 }}</p>
<!-- 自动解包 -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const object = { id: ref(1) }
const { id } = object // 将 id 结构为顶级属性
</script>这样id在使用时就符合我们的预期了
另外,如果ref是文本插值的最终计算结果,它也会被解包,这个特性是文本插值的便利特性
html
<template>
<div>
<p>{{ count }}</p>
<!-- 自动解包 -->
<p>{{ object.id }}</p>
<!-- 自动解包 -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const object = { id: ref(1) }
</script>