vue3学习笔记

166 阅读4分钟

响应式基础

注:本章使用的都是响应式API

reactive

使用 reactive函数创建一个响应式对象或数组

import { reactive } from 'vue'
const obj = reactive({ nested: { count: 0 }, arr: ['foo', 'bar'] }) 

在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。

function mutateDeeply() { 
    // 以下都会按照期望工作
    obj.nested.count++ 
    obj.nested.count = 100
    obj.nested = { count:-1 }
    obj.arr.push('baz') 
    obj.arr = []
   
}

reactive() API 有两条限制:

  1. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型,而对 stringnumber 和 boolean 这样的原始类型无效。Vue会对原始类型的reactive操作抛出warning
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
// 对obj重新赋值将破坏响应式
obj = { nested: { count: -1 } }
// 后续对obj.nested和obj.nested.count操作将不会被响应

同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:

const obj = reactive({ nested: { count: 0 }, arr: ['foo', 'bar'] }) 

// n 是一个局部变量
// 失去响应性连接,不影响原始的 obj.nested.count
let n = obj.nested.count
n++
console.log(n) // 1
console.log(obj.nested.count) // 0

// count 也和 state.count 失去了响应性连接
let { count } = obj.nested;
// 不会影响原始的 state
count++;
console.log("count", count); // 1
console.log("obj.nested.count", obj.nested.count); // 0

// 解构赋值后是一个对象,会影响原始的 state
let { nested,arr } = obj;
nested.count++;
arr.push(111);
console.log("nested.count", nested.count); // 1
console.log("obj.nested.count", obj.nested.count); // 1
console.log("arr", arr); // Proxy {0: "foo", 1: "bar", 2: 111} 
console.log("obj.arr", obj.arr); //  Proxy {0: "foo", 1: "bar", 2: 111} 

// 该函数无法跟踪 state.count 的变化
// 传入obj,obj.nested,obj.nested.count都不能跟踪
callSomeFunction(state.count)

ref

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref 方法来允许我们创建可以使用任何值类型的响应式 refref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

import { ref } from 'vue'

const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

对ref对象重新赋值后仍然具有响应式

obj = ref({ nested: { count: 1 }, arr: ["foo"] });
obj.value = { nested: { count: 2 }, arr: ["foo"] };

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

const obj = {
  foo: ref(1),
  bar: ref(2)
}
// 该函数接收一个 ref,需要通过 .value 取值,但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }} <!-- 无需 .value -->
  </button>
</template>

仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 所以我们给出以下 object,foo 是顶层属性,但 object.foo 不是。

const object = { foo: ref(1) }

下面的表达式将不会像预期的那样工作:

{{ object.foo + 1 }}

渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

{{ object }}  // { "foo": 1 }
{{ object.foo }}// 1 
{{ object.foo.value + 1 }} // 2

现在渲染结果将是 2。 需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1

{{ object.foo }}

这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}

ref 在响应式对象中的解包 

当一个 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 otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1

数组和集合类型的 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)