Vue3 学习记录-02 响应式基础

93 阅读5分钟

选项式API: 在Vue2.x项目中使用的都是选项式API

组合式API:在Vue3中使用的就是组合式API,代码竖着写在setup中

声明响应式状态

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

import { reactive } from 'vue' 
const state = reactive({ count: 0 })

响应式对象是JavaScript Proxy,其行为表现与一般对象相似。不同之处在于Vue能够给跟踪对响应式对象属性的访问与更改操作。

为reactive()标注类型 reactive() 也会隐式地从它的参数中推导类型:

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

要显示地标注一个reactive变量的类型,可以是使用接口/Type:

import { reactive } from 'vue' 
interface Book { 
    title: string 
    year?: number 
} 
const book: Book = reactive({ title: 'Vue 3 指引' })

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

import { reactive } from 'vue' 
export default { 
    // `setup` 是一个专门用于组合式 API 的特殊钩子函数 setup() 
    { 
        const state = reactive({ count: 0 })
        // 暴露 state 到模板 
        return {
        state 
        }
     }
}

//template
<div>{{ state.count }}</div>

也可以在同一个作用域下定义一个更新响应式状态的函数,并作为一个方法与状态一起暴露出去:

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>

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

<script setup\>中的顶层的导入和变量声明可在同一组件的模板中直接使用。相当于模板中的表达式和<script setup\>中的代码处于同一个作用域中。

DOM更新时机

当更改响应式状态后,DOM会自动更新。然而,DOM的更新不是同步的,相反。Vue将缓冲它们直到更新周期的“下个时机”,以确保无论进行了 多少次状态更改,每个组件都只需要更新洗一次。 若想等待一个状态改变后的DOM更新完成,可以使用nextTick()这个全局API:

import { nextTick } from 'vue'
function btnClick() {
  btn.count++;
  const btnCls = document.querySelector('.btnCls');
  nextTick(() => {
  // 访问更新后的 DOM
    console.log(btnCls?.innerHTML);
  });
}
//Template
<div class="btnCls">Count: {{ btn.count }}</div>

深层响应性

在Vue中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组(例如,改动对象的内部属性),改动也会被检测到。

import { reactive } from 'vue'

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

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

响应式代理与原始对象

reactive() 返回的是一个原始对象的Proxy,与原始对象不相等。 只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用Vue的响应式系统的最佳实践是仅使用你声明对象的代理版本。 为保证访问代理的一致性,对同一个原始对象调用reactive()会总是返回同样的代理对象,而对一个已存在的代理对象调用reactive()会返回其自身。

// 在同一个对象上调用 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. 仅对对象类型有效(对象、数组和MapSet这样的集合类型),而对stringnumberboolean这样的原始类型无效。
  2. 因为Vue的响应式系统是通过属性访问进行追踪的,因此必须保持对该响应式对象的相同引用。这意味着不可以随意的替换一个响应式对象。会导致对初始引用的响应性链接丢失:
let state = reactive({ count: 0 }) 
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)

state = reactive({ count: 1 })

同时如果将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,会失去响应性: 也就是说,n或count 只会得到最初的属性值,不会因为响应式对象的属性变化而变化

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)

ref()定义响应式变量

为补足reactive()的限制,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) // 0 count.value++ console.log(count.value) // 1

为ref标注类型

import { Ref, ref } from "vue";
let counts: Ref<number> = ref(0);

一个包含对象类型值的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在模板中的解包

当ref在模板中作为顶层属性被访问时,它们会被自动"解包"