Vue 3 中的新的响应式 API

这几天整理的一下过往的文章和笔记,备份到了 Github 上,地址👉 blog

如果我的内容帮助到了您,欢迎点个 Star 🎉🎉🎉 鼓励鼓励 :) ~~ 👆


Vue3 使用基于 ES6 Proxy 的新的响应性系统。

详细内容可查阅 Reactivity FundamentalsReactivity API: Utilities

ref()

Vue 有一个全局 ref() 方法,它在 JavaScript 原始类型创建一个响应式包装器。它通常只能用于基本类型:numberstringbooleanbigintsymbol

例如,下面是如何创建一个响应式计数器对象。

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

++count.value

console.log(count) // 1

使用 Vue 的全局 watchEffect() 方法,您可以监视到引用的更新。

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(function handler() {
  console.log(count.value)
})

// 打印 1,因为 Vue 知道在计数更改时调用 handler()
++count.value

reactive()

Vue3 还引入了一个 reactive() 方法,其行为类似于 ref(),但用于对象。

reactive() 方法的作用是:为对象的属性增加响应性。在对象上调用 reactive(),就会得到一个代理对象,可以使用 watchEffect() 监听变化。

例如,在下面的例子中,因为 user 是响应式的,所以 watchEffect() 将在每次 user 更改时打印出 user.name 的值。

import { reactive, watchEffect } from 'vue'

const user = reactive({ name: 'O.O' })

watchEffect(() => {
  console.log(user.name) // 'D.O'
}) 

user.name = 'D.O'

相对于 Vue2 的 data 属性,reactive() 的最大改进是,当您添加新属性时,reactive() 可以监听到,而不仅仅是访问现有属性。

在下面的例子中,watchEffect() 足够智能,当您在 user 上添加一个新的属性 age 时,监听到行的变化:

import { reactive, watchEffect } from 'vue'

const user = reactive({ name: 'O.O' })

watchEffect(() => {
  console.log(user.age) // 18
})

user.age = 18

reactive() 消除了在事件循环的同一事件上发生的变化。下面的代码将打印 61 和 62,但不会打印 59 或 60,因为这些更改同步发生在 61之前。

import { reactive, watchEffect } from 'vue'

const user= reactive({ name: 'O.O' })

watchEffect(() => {
  console.log(user.age) // 61
})

user.age = 59
user.age = 60
user.age = 61

setImmediate(() => { user.age = 62 }) // 62

isRef()

isRef() 检查一个值是否是一个 ref 对象。

const open = ref(true)

console.log(isRef(open)) // true

unref()

如果参数是一個 ref,则返回内部值,否则返回参数本身。

它是 val = isRef(val) ? val.value : val 的语法糖函数。

const open = ref(true)

console.log(unref(open)) // true

toRef()

可用于为源 reactive 对象上的属性创建 ref。创建的 ref 与其源属性同步:改变源属性将更新 ref,反之亦然。

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

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2
console.log(unref(fooRef)) // 2
state.foo++
console.log(unref(fooRef)) // 3

toRefs()

将响应式对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

它可以在不丢失响应性的情况下对返回的对象进行解构/展开

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

const stateAsRefs = toRefs(state)

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

isProxy()

isProxy() 检查对象是否是由 reactive()readonly()shalloreactive()shalloreadonly() 创建的代理。

const data = readonly({
  key: 123456
})

const user = reactive({
  name: 'O.O'
})

console.log(isProxy(user)) // true
console.log(isProxy(data)) // true

isReactive()

检查对象是否是由 reactive()shallowReactive() 创建的代理。

如果该代理是 readonly 创建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true

const data = readonly({
  pass: 123456
})
const user = reactive({
  name: 'O.O'
})
const userCopy = readonly(user)

console.log(isReactive(user)) // true
console.log(isReactive(userCopy)) // true

isReadonly()

检查对象是否是由 readonly()shalloreadonly() 创建的代理。

const data = readonly({
  pass: 123456
})
const user = reactive({
  name: 'O.O'
})
const userCopy = readonly(user)

console.log(isReadonly(data)) // true
console.log(isReadonly(userCopy)) // true

shallowRef()

浅版本的 ref。如果是基本数据类型,refshallowRef 的效果一样。

const state = shallowRef({ count: 1 })

state.value.count = 2
state.value = { count: 2 }

triggerRef()

triggerRef() 配合 shallowRef 一起使用。

const user = shallowRef({
  name: 'O.O'
})

watchEffect(() => {
  console.log(user.value.name) // O.O
})

user.value.name = 'D.O'

triggerRef(user) // D.O

customRef()

创建自定义的 ref,对其依赖项跟踪和更新触发进行显式控制。

<template>
  <div>name:{{ name }}</div>
  <input v-model="name" />
</template>

<script setup>
  let value = 'front-refined'

  const name = customRef((track, trigger) => {
    return {
      get() {
        // 收集依赖它的 effect
        track()
        return value
      },
      set(newValue) {
        value = newValue
        // 触发更新依赖它的所有 effect
        trigger()
      }
    }
  })
</script>

shallowReactive

reactive 的浅版本。

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

state.foo++
console.log(state.foo) // 2
console.log(isReactive(state.nested)) // false

// 它是非响应性大,但值会被修改
state.nested.bar++
console.log(state.nested.bar) // 3

shallowReadonly

readonly() 的浅版本。

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

// 不会报错,只会警告
state.foo++
// 使用 shallowReadonly 代理的对象不能被修改
console.log(state.foo) // 1

// 嵌套对象的属性可以被修改,但它是非响应式的!
state.nested.bar++
console.log(state.nested.bar) // 3

toRaw()

返回 Vue 创建的代理的原始对象。

const open = ref(true)
const user = reactive({
  name: 'O.O'
})

console.log(toRaw(open)) // RefImpl {_shallow: false, dep: undefined, __v_isRef: true, _rawValue: true, _value: true}
console.log(toRaw(user)) // {name: 'O.O'}

markRaw()

标记一个对象,使其永远不会转换为代理。返回对象本身。

const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

effectScope()

创建一个效果范围对象,该对象可以捕获在其中创建的响应性效果(即 computedwatch),以便将这些效果一起处理。

const scope = effectScope()
const counter = ref(0)
scope.run(() => {
  const doubled = computed(() => counter.value * 2)

  watch(doubled, () => console.log(doubled.value))

  watchEffect(() => console.log('Count: ', doubled.value))
})

scope.stop() // 'Count: 0'

其他的还有:

  • getCurrentScope() 返回当前活动的 effect 作用域(如果有)。
  • onScopeDispose() 在当前活动的 effect 作用域上注册 dispose 回调。当关联的 effect 作用域停止时,将调用回调。

详细查阅 Reactivity API: Advanced

关于 watchcomputedwatchEffect 可以查看 Vue Computed — 计算属性watch 与 watchEffect 的区别

https://juejin.cn/post/6926776849066360846#heading-12
https://www.sitepoint.com/vue-3-reactivity-system/

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

推荐阅读更多精彩内容