一、声明响应式状态 ref()
- 声明响应式状态,接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回(打印:RefImpl {value:Proxy {}});
- 一般用于包装“基本数据类型”的响应式对象的实现方式,基本数据类型(如字符串、数字、布尔值等)
import { ref } from 'vue';
const count = ref(0);定义一个响应式数据count,
console.log(count.value)
count.value++
console.log(count.value)
- 当 ref 的值是一个非原始值(对象)时,ref() 会在内部调用reactive()转换为响应式代理。
- Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
- Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到;
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
obj.value.nested.count++
obj.value.arr.push('baz')
}
- ref 对象在作为响应式对象的属性被访问或修改时会自动解包
const countRef = ref(0)
const state = reactive({
count:countRef
})
console.log(state.count)
state.count = 1
console.log(countRef.value)
- 在模板中使用 ref 时会自动解包,我们不需要附加.value
<div>{{ count }}</div>
ref 解包细节
1、作为 reactive 对象的属性,
- 一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说,它的行为就像一个普通的属性:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count)
state.count = 1
console.log(countRef.value)
- 如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count)
console.log(count.value)
- 只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
2、数组和集合的注意事项
- 与 reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型 (如 Map) 中的元素被访问时,它不会被解包:
const books = reactive([ref('Vue 3 Guide')])
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
console.log(map.get('count').value)
3、在模板中解包的注意事项
- 在模板渲染上下文中,只有顶级的 ref 属性才会被解包。
const count = ref(0)
const object = { id: ref(1) }
<div>{{ count + 1 }}</div>
<div>{{ object.id + 1 }}</div>
const { id } = object
<div>{{ id.id + 1 }}</div>
- 另一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 {{ }} 标签),那么它将被解包,因此以下内容将渲染为 1:
- 该特性仅仅是文本插值的一个便利特性,等价于 {{ object.id.value }}。
{{ object.id }}
二、声明响应式状态 reactive(params)
- 只能声明引用数据类型,返回的是一个原始对象params的响应式代理(Proxy对象)(打印:Proxy {})
- 响应式转换是“深层”的:它会影响到所有嵌套的属性
- 只有代理对象是响应式的,更改原始对象不会触发更新
let obj = reactive({
name: '小明',
age: 18
})
obj.name;
<button>
{{ state.age }}
</button>
- 返回的是一个原始对象params的 Proxy,它和原始对象是不相等的
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
- 为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:
console.log(reactive(raw) === proxy)
console.log(reactive(proxy) === proxy)
局限性
- 1、有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
- 2、不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用,赋值给 reactive 一个整个对象或 reactive 对象,会失去响应
let state = reactive({ count: 0 })
// 以下2种整体赋值将导致 state 失去响应
state = { count: 1 }
state = reactive({ count: 11 })
- 2、对应解决方案
- 不要直接整个对象替换,一个个属性赋值
- 使用 Object.assign
- 使用 ref 定义对象
- 使用数组的 push 方法
//以下方式赋值都不会失去响应
let state = reactive({ count: 0 })
state.count = 1
Object.assign(state, { count: 1 })
state = Object.assign(state, { count: 1 })
let state = ref({ count: 0 })
state.value = { count: 1 }
- 3、对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接,ref定义对象解构也会失去响应式
const state = reactive({ count: 0 })
let { count } = state
count++
callSomeFunction(state.count)
const state = reactive({ count: 0 })
let { count } = toRefs(state)
count.value++
- 将 reactive 对象的属性赋值给变量(断开连接/深拷贝),对该变量的赋值不会影响原来对象的属性值。
let state = reactive({ count: 0 })
let n = state.count
n++
console.log(state.count)
三、readonly
- 接受一个对象 (不论是响应式还是普通的) 或是一个ref,返回一个原值的
只读代理。
let rd = readonly({name:'哈哈'})
console.log(rd)
rd.name = '新的'
console.log(rd.name)