响应式|Vue3

 响应式是Vue的核心功能之一,即自动跟踪 JavaScript 状态变化并在改变发生时响应式地更新 DOM。

 

声明响应式状态

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

import { reactive } from 'vue'

const state = reactive({ count: 0 })

响应式对象其实是Javascript代理,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象 property 的访问与更改操作

reactive()API的局限性

  1. 仅对对象类型有效(对象、数组和Map、Set这样的集合类型),对string、number和boolean这样的原始类型无效。
  2. 必须始终保持对该响应式对象的相同引用,因为Vue 的响应式系统是通过 property 访问进行追踪的。

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

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({ count: 0 })

    // 暴露 state 到模板
    return {
      state
    }
  }
}
<div>{{ state.count }}</div>

 接下来我们修改响应式state对象。setup作用域下支持定义和更改响应式对象的函数,并作为一个方法与响应式对象一起暴露出去。

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>

 这里可能会有疑问,如果有很多对象和方法,如此手动暴露状态和方法岂不是非常繁琐?

所以,Vue提供了构建工具来简化该操作。

setup

一个专门用于组合式 API 的特殊钩子

针对单文件模板(SFC)时,可以使用<script setup>简化大量样板代码。以上修改state的操作也可以这样写:

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

const state = reactive({ count: 0 })

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

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

<script setup> 中的顶层的导入和变量声明可在同一组件的模板中自动使用。

 

DOM更新时机

我们知道vue是响应式更新DOM,但是DOM更新不是同步的,Vue首先将缓冲他们直到更新周期的 “下个时机” 以确保无论进行了多少次声明更改,每个组件都只需要更新一次(这里想到了拦截器,TMD语言果然是想通的!)。如果要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:

import { nextTick } from 'vue'

function increment() {
  state.count++
  nextTick(() => {
    // 访问更新后的 DOM
  })
}

 

深层响应性

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

import { reactive } from 'vue'

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

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

当然,也可以通过 shallowReactive() 直接创建一个 浅层响应式 对象。它们仅在顶层具有响应性,一般仅在某些特殊场景中需要。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++

 

“响应式代理”和 “.原始对象”的区别

  1. 响应式代理返回的是一个原始对象的代理,和原始对象不相等。
  2. 只有代理是响应式,更改原始对象不会触发更新。

 

定义响应式变量

为了解决 reactive() 带来的限制,Vue 也提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref

import { ref } from 'vue'

const count = ref(0)

ref() 从参数中获取到值,将其包装为一个带 .value property 的 ref 对象:

const count = ref(0)

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

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

对象类型值的 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在模板中作为顶层 property 被访问时,它们会被自动“解包”,所以不需要使用 .value

注意,仅当 ref 是模板渲染上下文的顶层 property 时才适用自动“解包”。例如, foo 是顶层 property,但 object.foo 不是。

const object = { foo: ref(1) }

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

{{ object.foo + 1 }}

 ref 在响应式对象中的解包

ref 作为一个响应式对象的 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

注意:只有嵌套深层响应式对象内时才会解包,作为浅层响应式对象的property 被访问时不会解包。

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)

 

posted @ 2022-04-22 16:44  七月的枫丶  阅读(540)  评论(0编辑  收藏  举报