关于Vue3(1)
目录
一、Vue2和Vue3,我们应该选哪一个
二、声明响应式状态
一、Vue2和Vue3,我们应该选哪一个
之前的博客有说Vue3相对于Vue2来说,有哪些优点。但我们应该选哪一个呢?
选项式 API(Vue2) 以“组件实例”的概念为中心,对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。
组合式 API (Vue3)的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。
官方大大说:在学习的过程中,推荐采用更易于自己理解的风格。大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,也能够很快地理解另一种风格。
二、声明响应式状态
用reactive()函数创建一个响应式对象或数组:
import { reactive } from 'vue'const state = reactive({ count: 0 })
要在组件模板中使用响应式状态,需要在setup()函数中定义并返回。
<div>{{ state.count }}</div>
import { reactive } from 'vue'export default {// `setup` 是一个专门用于组合式 API 的特殊钩子函数setup() {const state = reactive({ count: 0 })// 暴露 state 到模板return {state}}
}
当然,我们也可以在同一个作用域下定义更新响应式状态的函数,并将他们作为方法与状态一起暴露出去:
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>
reactive()
的局限性:
-
仅对对象类型有效(对象、数组和
Map
、Set
这样的集合类型),而对string
、number
和boolean
这样的 原始类型 无效。 -
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })
在 Vue 3 中,使用
reactive
函数将一个对象转换为响应式对象时,会为该对象创建一个 Proxy 对象,通过对 Proxy 对象本身的响应来实现对原始对象的监测,并在变化时触发依赖更新。在上面的代码中,当
reactive
函数第一次被调用时,它会为{ count: 0 }
对象创建一个响应式代理对象,并将它赋值给state
变量。随后,当reactive
函数第二次被调用时,它会为新对象{ count: 1 }
创建一个新的响应式代理对象,并将其赋值给state
变量。这个过程会改变state
变量的引用指向,即将原先的代理对象替换为一个新的代理对象。需要注意的是,当
{ count: 0 }
对象不再被任何变量引用时,它会被 JavaScript 的垃圾回收机制所回收,并从内存中删除。此外,由于没有任何引用指向{ count: 0 }
对象,因此它不会被 Vue 响应式系统所追踪,更不可能触发依赖更新。从实现的层面上来说,如果我们没有缓存原始对象的代理对象并且始终返回一个新的代理对象,那么旧的代理对象将无法跟踪到原始对象的变化,从而会导致响应性丢失。
同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:
const state = reactive({ count: 0 })// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)
<script setup>
语法糖?
但是在 setup()
函数中手动暴露大量的状态和方法非常繁琐。所以我们通过使用构建工具来简化该操作。当使用单文件组件(SFC)时,我们可以使用 <script setup>
来大幅度地简化代码。
<script setup>
import { reactive } from 'vue'const state = reactive({ count: 0 })function increment() {state.count++
}
</script><template><button @click="increment">{{ state.count }}</button>
</template>
用ref()定义响应式变量:
reactive()
的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'const count = ref(0)
ref()
将传入参数的值包装为一个带 .value
属性的 ref 对象:
const count = ref(0)console.log(count) // { value: 0 }
console.log(count.value) // 0count.value++
console.log(count.value) // 1
和响应式对象的属性类似,ref 的 .value
属性也是响应式的。同时,当值为对象类型时,会用 reactive()
自动转换它的 .value
。
一个包含对象类型值的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 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, object
是顶层属性,但 object.foo
不是。
所以我们给出以下object:
const object = { foo: ref(1) }
但是它并不会向我们想要的那样工作:
{{ object.foo + 1 }}
渲染的结果会是一个 [object Object]1
,因为 object.foo
是一个 ref 对象。我们可以通过将 foo
改成顶层属性来解决这个问题:
const { foo } = object
{{foo + 1}}
现在渲染结果将是 2
。
需要注意的是,如果一个 ref 是文本插值(即一个 {{ }}
符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1
:
{{ object.foo }}
这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}
。
ref 在响应式对象中的解包:
当一个ref被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现的和一般的属性一样:
const count = ref(0)
const state = reactive({count
})console.log(state.count) // 0state.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 解包。当其作为浅层响应式对象的属性被访问时不会解包。