vue.js响应式API——响应式基础

356 阅读4分钟

staging-cn.vuejs.org/guide/essen…

声明响应式状态,reactive()函数创建一个响应式对象或数组

import {reactive} from 'vue'

const state=reactive({count:0})

要在组件模板中使用响应式状态,请在setup函数中定义并返回。

<script>
import {reactive} from 'vue'

export default{
    //setup 是一个专门用于组合式API的特殊钩子
    setup(){
        const state=reactive({count:0})
        function increment() {          state.count++
        }            //暴露state到模板,
        return{
            state,
            increment//暴露方法可以作为事件监听器
        }
    }
}
</script>
<template>
    <button @click="increment">
        {{ state.count }}
    </button>
</template>

简化大量模板代码,不使用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>

DOM更新时机

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存到“next tick”以确保每个组件无论发生多少状态改变,都仅执行一次更新。

若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

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

const count = ref(0)

async function increment() {
  count.value++

  // DOM 还未更新
  console.log(document.getElementById('counter').textContent) // 0

  await nextTick()
  // DOM 此时已经更新
  console.log(document.getElementById('counter').textContent) // 1
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

深层响应性

就是你声明的对象数组,在深层次,也可以检测到改动

import { reactive } from 'vue'

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

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

响应式代理 vs. 原始对象

reactive就是进行代理,返回的是一个原始对象的Proxy(代理会将所有应用到它的操作转发到这个对象上)代理返回的原始对象和原始对象是不对等的。

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

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

只有代理是响应式的,更改原始对象raw不会触发更新,需要更改proxy,

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

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

响应式对象内的嵌套对象依然是代理.

const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false,前面是代理返回的原始对象,后面是原始对象

reactive()的局限性

1、只对对象类型有效(对象哎、数组、map、set的集合类型),对string、number、Boolean原始类型无效。

2、不能随便替换一个响应式对象,这会导致第一次应用的响应性连接丢失。也不能将对象的属性赋值或解构给本地变量,或者是将属性作为参数传入函数,也会失去响应性。

let state = reactive({ count: 0 })

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

ref()定义响应式变量

ref()允许创建可以使用任何值类型的响应式ref,一个包含对象类型值的 ref 可以响应式地替换整个对象,ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性。

import {ref} from 'vue'

//number类型
const count=ref(0);//返回一个ref对象

console.log(count)//{value:0}
console.log(count.value)//0,通过.value能够获取到具体的数值

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

//对象类型,当值为对象类型时,会用 reactive() 自动转换它的 .value。
//一个包含对象类型值的 ref 可以响应式地替换整个对象:
const objRef=ref({count:0})

objRef.value={count:1}//响应式替换
const obj = {
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const { foo, bar } = obj

ref在模板中的解包

ref在模板中作为顶级property被访问时,自动解包,不需要使用.value。

就是在标签中使用时,需要将你结构出来的那一项是ref的那一层,不需要使用.value。

const object = { foo: ref(1) }
const { foo } = object<button>
    {{ foo + 1}} <!-- 无需 .value ,不能使用object.foo+1--></button>

ref在响应式对象中的解包

当一个 ref 作为一个响应式对象的 property 被访问或更改时,它会自动解包,因此会表现得和一般的 property 一样:

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

console.log(state.count) // 0

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

如果将一个新的 ref 赋值给一个关联了已有 ref 的 property,那么它会替换掉旧的 ref:

const otherCount = ref(2)

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

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

数组和集合类型的 ref 解包

当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。