Vue2进阶到3的一点笔记(二)

42 阅读5分钟

前言

此文章为 Vue2 熟练工使用 Vue3 做项目途中的一些学习笔记,笔记内容以官方为主,主要记录 Vue3 新出的一些内容,以及对一些知识点的探索和回顾

Vue2进阶到3的一点笔记(一)

响应式基础

reactive()

另外一种声明响应式状态的方式,使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应式:

<script setup lang="ts">
import { reactive } from 'vue'

const state = reactive({ count: 0 })
</script>

在模板中使用

<template>
  <button @click="state.count++">{{ state.count }}</button>
</template>

响应式对象是 JavaScript 代理,其行为和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。

reactive() 将深层地转换对象:当访问嵌套对象时,他们也会被 reactive() 包装。当 ref 的值是一个对象时, ref() 也会在内部调用它。与浅层 ref 类似,shallowReactive() API可以选择退出深层响应性。

Reactive Proxy vs. Original

reactive() 返回的是一个原始对象的 Proxy,他和原始对象是不相等的:

<script setup lang="ts">
import { reactive } from 'vue'
const raw = {}
const proxy = reactive(raw)

console.log(proxy === raw) // false
</script>

只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是仅使用你声明对象的代理版本。

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

console.log(reactive(raw) === proxy); // true
console.log(reactive(proxy) === proxy); // true

这个规则对嵌套对象也适用。依靠深层响应式,响应式对象内的嵌套对象依然是代理:

const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false;

reactive() 的局限性

1.有限的值类型:只能用于对象类型(对象、数组、Map、Set 等)。不能持有如 stringnumberboolean 这种的原始类型

2.不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着不能轻易“替换”响应式对象,这样的话第一个引用的响应式连接将丢失:

let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// 响应式连接将丢失
state = reative({ count: 1 })

3.对解构操作不友好:将响应式对象的原始类型属性解构为本地变量时,或将该属性传递给函数时,将丢失响应式连接:

const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
count++

console.log(count) // 1

console.log(state.count) // 0

由于这些限制,建议使用 ref() 作为申明响应式状态的首选方式。

额外的 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 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的ref:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

const otherCount = ref(2)

state.count = otherCount

console.log(state.count) // 2

// 原始 ref 现在已经和 state.count 解绑了
console.log(count.value) // 1

只有当嵌套在一个深层响应式对象时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。

数组和集合的注意事项

与 reactive 对象不同的是,当 ref 作为响应式数组或集合类型(如 Map)中的元素被访问时,它不会解包:

const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value) // 'Vue 3 Guide'

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value) // 0

在模板中解包的注意事项

在模板渲染上下文中,只有顶级的 ref 属性才会被解包。

在下面例子中, count 和 object 是顶级属性,但 object.id 不是:

const count = ref(0)
const object = { id: ref(1) }

下面表达式按预期工作

{{ count + 1 }}

但是下面表达式不会:

{{ object.id + 1 }}

渲染结果是 [object Object]1,因为在计算表达式时 object.id 没有被解包,仍然是一个 ref 对象。解决这个问题,可以将 id 解构为一个顶级属性:

const { id } = object
{{ id + 1 }}

现在渲染的结果将是 2。

另一个需要注意的点事,如果 ref 是文本差值的最终计算值(即 {{}} 标签),那么它将被解包,因此以下内容将渲染为 1:

{{ object.id }}

该特性仅仅是文本插值的一个便利特性,等价于 {{ object.id.value }}

为什么在模板渲染的上下文中,只有顶级的 ref 属性才会被解包

在 Vue 的模板渲染上下文中,只有顶级的 ref 属性被解包的原因主要与 Vue 的响应式系统和性能优化有关。以下是一些关键点:

1.性能优化:Vue 的响应式系统通过依赖追踪来优化性能。只有顶级的 ref 属性被解包,可以减少对嵌套属性的监测和更新,从而提高渲染性能。

2.简化依赖追踪:如果嵌套的 ref 属性也被解包,Vue 将需要在每次访问时进行更复杂的依赖追踪。这会增加计算开销,尤其是在深层嵌套的对象中。

3.一致性:在模板中,顶级 ref 的解包行为保持了一致性,使得开发者在使用时更容易理解和预测其行为。嵌套的 ref 属性如果被解包,可能会导致意外的行为和难以调试的问题。

4.避免解构问题:如果嵌套的 ref 属性被解包,可能会导致解构操作时失去响应式连接。通过只解包顶级 ref,可以避免这种情况。

总之,Vue 选择只在模板渲染上下文中解包顶级 ref 属性,是为了优化性能、简化依赖追踪、保持一致性以及避免潜在的解构问题