Skip to content

defineComponent:

响应式基础:

ref

在组合式 API 中,推荐使用 ref() 函数来声明响应式状态: ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

typescript
const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

ref 的自动解包

顶级的 ref 属性 在模板中会被自动“解包〞,所以不需要使用 .value。

typescript
const count = ref(0)
const object = { id: ref(1) }
// count 和 object 是顶级属性,但 object.id 不是:

// 我们可以将 id 解构为一个顶级属性:
const { id } = object 
{{id + 1}} // 响应式生效

如果ref作为文本的最终计算值(ref在中),依然会被解包

typescript
// ts
const object = { id: ref(1) }

// template 这里没有多余的加1
{{ object.id }}  // =>  在{{}}中, object.id 值是ref,自动解包

为什么要使用ref?

为什么我们需要使用带有 .value 的 ref,而不是普通的变量? 检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作.value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改

typescript
// 伪代码,不是真正的实现
const myRef = {
  _value: 0,
  get value() {
    track()
    return this._value
  },
  set value(newValue) {
    this._value = newValue
    trigger()
  }
}

深层响应式

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map

typescript
import { ref } from 'vue'

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

function mutateDeeply() {
  // 以下都会按照期望工作
  obj.value.nested.count++
  obj.value.arr.push('baz')
}

reactive

与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

typescript
import { reactive } from 'vue'

const state = reactive({ count: 0 })

Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。 reactive() 将深层地转换对象:当访问嵌套对象时,它们也会被 reactive() 包装

proxy代理

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

typescript
const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

最佳实践: 是仅使用你声明对象的代理版本

保证代理一致性

typescript
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

reactive局限性

  1. 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型
  2. 不能替换整个对象:引用的响应式连接将丢失
typescript
let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
  1. 对解构操作不友好
typescript
const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

ref解包细节

ref作为reactive对象属性

一个ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说,它的行为就像一个普通的属性:

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

console.log(state.count) // 0

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

ref赋值给reactive的ref属性,将会替换

typescript
const otherCount = ref(2)

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

ref在reactive数组集合中不会解包

typescript
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)

模板中,顶层ref才解包

count 和 object 是顶级属性,但 object.id 不是

typescript

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

// 1. 顶层才自动解包
{{ count + 1 }} // ok 

{{ object.id + 1 }} // not 
// 解决=> 将带有ref属性值的id解构
const {id} = object
{{ id + 1 }}  // ok

// 2. 在{{ }} 中,如果ref是最终计算值,自动解包
{{ object.id }}  // 没有多余的加1

类型标注

ref

泛型参数

typescript
// 在调用 ref() 时传入一个泛型参数
const year = ref<string | number>('2020')
// 得到的类型:Ref<string | number>
year.value = 2020 // 成功!

// 如果指定泛型参数但是没给初始值
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()

reactive

隐式推导


typescript
import { reactive } from 'vue'

// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })

接口

不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

typescript
import { reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

const book: Book = reactive({ title: 'Vue 3 指引' })
vue
<script lang="ts">
  import { defineComponent, ref, reactive } from 'vue'

  // 为user标注类型
  interface Person {
    name: string
    age: number
  }

  export default defineComponent({
    name: 'App',
    setup() {
      // ref标注类型
      const count = ref<string | number>(0)
      // user:Interface
      const user: Person = reactive({
        name: 'sunflow',
        age: 18
      })
      const increase = () => {
        // 因为为ref标注了类型可能为string,所以这里就需要判断了
        if (typeof count.value === 'number') {
          count.value++
        }
        user.age++
      }

      return {
        count,
        user,
        increase
      }
    }
  })
</script>

<template>
  <h1 @click="increase">{{ count }}</h1>
</template>

ref 和 reactive 区别

  1. ref返回一个包含 .value 属性的响应式对象,常用于基本数据类型(number,string),当然对象也行。
  2. reactive 函数用于创建对象类型的响应式引用。它会递归地将对象的所有属性转换为响应式属性。可以直接访问属性,不需要额外的.value

  1. reactive 参数只能是 object, ref都可以的(包括原始数据类型 string, boolean,number.)
  2. ref需要使用 value 来访问其中的值,reactive 不需要。
  3. ref 在 Vue 源代码内部是一种特殊的 reactive
typescript
// ref相当于这样一个模型
const myRef = reactive({
  value:"I'm ref!"
})
myRef.value // "I'm ref!"
myRef.value = "Changed"
myRef.value // "Changed"

怎么选择? 没有特殊的规则,按照个人喜好。 个人喜欢原始类型使用 refobject 使用 reactive

BugFix:


  • VScode在DOM中打入花括号会出现{ }}三个标签的错误

cmd + shift + p 找到用户设置 找到 autoClosingBrackets image.png