Vue3 组合式API及响应式原理

组合式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)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容