美文网首页
Vue3 组合式API及响应式原理

Vue3 组合式API及响应式原理

作者: 李霖弢 | 来源:发表于2024-06-06 16:37 被阅读0次

组合式API可以让逻辑关注点更为集中
建议总是使用 ref 而非 reactive

Vue3响应式原理

  • 对于基本数据类型(使用 getter / setter),只能使用 ref,并赋值给结果的value
  • 对于引用数据类型(使用Proxy),可以使用 ref 也可以使用 reactive。
    reactive会将目标深层递归并返回Proxy代理。
    ref对目标reactive后赋值给结果的value。

对选项式API的影响

因Vue3中的响应性是通过Proxy代理实现的,因此与Vue2不同,以下this.someObjectnewObject不是同一个东西,修改其中一项也不会影响到另一项:

export default {
  data() {
    return {
      someObject: {}
    }
  },
  mounted() {
    const newObject = {}
    this.someObject = newObject

    console.log(newObject === this.someObject) // false
  }
}

组合式API

ref(推荐使用)

  • 如果传入值为基本数据类型,则通过getter/setter赋值给 value。如果传入值为对象,则通过 reactive(Proxy代理) 深层响应式处理后,再赋值给 value。
    综上:返回一个具有 value 属性的响应式对象
  • 深拷贝,与原始数据不保持联系
伪代码实现ref
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

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

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      // 在 JavaScript 中需要 .value
      count.value++
    }

    // 不要忘记同时暴露 increment 函数
    return {
      count,
      increment
    }
  }
}
</script>
ref 整个对象和仅 ref 某个对象属性的区别
const a = ref({foo:1,bar:2})
则 a.foo 和 a.bar 变化都有效果

const b = {foo:1,bar:ref(2)}
则 b.foo 变化没有效果
ref 自动解包(即不需要使用value
  1. 在模板中引用时会自动解包
const count = ref(0)//解包,可直接用 {{count}}
const object = { id: ref(1) }//不解包,需要用 {{object.id.value}}
  1. 作为响应式对象属性时自动解包(即 ref({age:ref(18)})中,内层的ref(18)是没有意义的,会直接变成数字18),但作为响应式数组/Map属性时不会自动解包:
const count = ref(0)
const state = reactive({
  count
})
// 无需 .value
console.log(state.count) // 0


const books = reactive([ref('Vue 3 Guide')])
// 需要 .value
console.log(books[0].value)

reactive

  • 传入一个引用类型(对象/数组等,但不能为基本数据类型)进行 深层递归 Proxy代理,返回 Proxy 实例(而非原始对象)。
  • 深拷贝,与原始数据不保持联系
  • 这是 Vue3 的响应式根基,data() 返回的内容即通过 reactive() 处理成为响应式对象
伪代码实现reactive
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}
  • 深层递归时会解包其中的ref属性,同时保持相应性
const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3


const count2= ref(1)

obj.count2 = count2

console.log(obj.count2) // 1
console.log(obj.count2 === count2.value) // true
  • 为保证一致性,对同一对象进行reactive返回的结果全等,对已reactive的结果再次reactive返回结果等于自身。
const raw = {}
const proxy = reactive(raw)

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

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

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

// 对一个对象ref,即将其reactive并赋值给value
console.log(reactive(proxy) === ref(raw).value) // true

  • reactive对象整体重新赋值时会失去响应性
shallowRef 和 shallowReactive

分别是refreactive的浅层版本,用于优化性能。放弃对深层内容的响应,只有首层具有响应性。
shallowRef只有.value层具有响应性:

const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }

shallowReactive只有第一层属性具有响应性:

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

// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++

toValue

将值、refs 或 getters 规范化为值(比unref多个 getters 的处理)。
reactive对象(Proxy)不会被转化,保留原内容。

readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
可修改原值影响新对象,但不能直接修改新对象。
相对的,还有浅层版本shallowReadonly

import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)

// 通过 original 修改 count,将会触发依赖 copy 的侦听器
original.count++

// 通过 copy 修改 count,将导致失败并出现警告
copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."

isRef

判断入参是否为 ref 实例

unref

isRef(val) ? val.value : val 的语法糖,将值、refs规范化为值。

toRef

  • 可以将值、ref 实例、getters 规范化为 ref 实例
// 按原样返回现有的 ref
toRef(existingRef)

// 创建一个只读的 ref,当访问 .value 时会调用此 getter 函数
toRef(() => props.foo)

// 等同于 ref(1)
toRef(1)
  • 或基于响应式对象上的一个属性,创建一个对应的 ref,并与原内容保持响应式连接。
import { reactive, ref, isRef, unref, toRef, toRefs } from "vue";

const state = reactive({});
const fooRef = toRef(state, "age");//fooRef.value 和 state.age 保持响应式连接
fooRef.value = 100;
console.log(state.age); // 100
state.age++;
console.log(fooRef.value); // 101

toRefs

传入一个响应式对象(Proxy),将该对象每个属性经过 toRef 处理后传给一个新的普通对象并返回(因此该普通对象的每一个属性都是一个 ref 实例)。
常用于解决ref、reactive对象解构赋值后,赋值目标失去相应性

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

const stateAsRefs = toRefs(state)

state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

//可用于解构 props 防止响应性丢失
setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

相关文章

网友评论

      本文标题:Vue3 组合式API及响应式原理

      本文链接:https://www.haomeiwen.com/subject/qtpyqjtx.html