Vue3响应式Api:核心(学习笔记)

文档:https://cn.vuejs.org/api/reactivity-core.html

ref()


接受一个内部值,返回一个响应式的、可更改的 ref 对象,此ref对象只有一个指向其内部值的属性 .value

示例
const count = ref(0) // 初始值为0
console.log(count.value) // 0

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

// 如果将一个对象赋值给 ref,那么这个对象将通过reactive转为具有深层次响应式的对象
const refObj = ref({count: 1})
refObj.value.count++
console.log(refObj.value.count) // 2
访问$refs
// template
<form ref="formRef">...</form>

// script setup
const formRef = ref(null) // 变量名formRef 与template中form绑定的ref属性值一致,会自动识别为对应的dom
const handleSubmit = () => {
  formRef.value.validate(async (valid) => {
    if (valid) {
      ...
      formRef.value.resetFields()
    }
  })
}

computed()


接受一个 getter 函数,返回一个只读的响应式ref对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

示例

创建一个只读的计算属性 ref:

let firstName = ref('hello')
let lastName = ref('world')
let fullName = computed(() => firstName.value+' '+lastName.value) // 返回一个响应式ref对象
console.log(fullName.value) // hello world
firstName.value = 'goodbye'
console.log(fullName.value) // goodbye world

const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2

plusOne.value++ // 错误-->只读

创建一个可写的计算属性 ref:

const count = ref(1)
const plusOne = computed({
    get() {
        return count.value
    },
    set(newVal) {
        count.value = newVal
    }
})
plusOne.value = 2
console.log(count.value) // 2
plusOne.value = 3
console.log(count.value) // 3

reactive()


返回一个对象的响应式代理。

详细信息

  • 响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
  • 值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
示例

创建一个响应式对象:

const reactiveObj = reactive({count: 0})
console.log(reactiveObj.count) // 0
reactiveObj.count++
console.log(reactiveObj.count) // 1

ref解包(不需要.value):

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

注意当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包(需要.value):

const arr = reactive([ref('hello')])
// 这里需要 .value
console.log(arr[0].value)

const map = reactive(new Map([['name', ref('jack')],['sex', ref('man')]]))
// 这里需要 .value
console.log('map', map.get('name').value) // jack

readonly()


接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

示例
const original = reactive({ count: 0 })
const copy = readonly(original)

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

watchEffect()


立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

详细信息

  • 第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
  • 第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
  • 返回值是一个用来停止该副作用的函数。
示例
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 输出 0

count.value++
// -> 输出 1

副作用清除:

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前未完成的请求
  onCleanup(cancel)
  data.value = await response
})

停止侦听器:

const stop = watchEffect(() => {}) // 返回一个停止该副作用的函数
// 当不再需要此侦听器时:
stop()

选项:

watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

watch()


侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

详细信息
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

第一个参数是侦听器的源。
这个来源可以是以下几种:

  • 一个函数,返回一个值
  • 一个 ref
  • 一个响应式对象
  • ...或是由以上类型的值组成的数组

第二个参数是在发生变化时要调用的回调函数。
这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象
支持以下这些选项:

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器一节。
  • flush:调整回调函数的刷新时机。参考回调的刷新时机一节。
  • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器一节。

watchEffect() 相比,watch() 使我们可以:

  • 懒执行副作用(watchEffect是立即执行);
  • 更加明确是应该由哪个状态触发侦听器重新执行;
  • 可以访问所侦听状态的前一个值和当前值。
示例

侦听一个 getter 函数:

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 以下是错误的,用上面的方法监听一个对象的属性
watch(state.count, (count, prevCount) => {
    /* ... */
  }
)

当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

const watchGetter = reactive({count: 0})
// 不加deep: true
watch(() => watchGetter, (newVal, oldVal) => {
    console.log("newVal", newVal.count)
    console.log("oldVal", oldVal.count)
})
watchGetter.count = 1 // 此时不会触发回调

// 加deep: true
watch(() => watchGetter, (newVal, oldVal) => {
    // 新值和旧值将是同一个对象
    console.log("newVal", newVal.count) // 1
    console.log("oldVal", oldVal.count) // 1
},{
  deep: true
})
watchGetter.count = 1 // 此时加上deep:true会触发回调

侦听一个 ref:

const watchRef = ref(0)
watch(watchRef, (newVal) => {
    console.log(newVal) // 1
})
watchRef.value = 1

侦听reactive对象:
当直接侦听一个响应式对象时,侦听器会自动启用深层模式。

const watchReactive = reactive({count: {num: 0}})
watch(watchReactive, (newVal) => {
    console.log("newVal", newVal.count.num) // 1
})
watchReactive.count.num = 1

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

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

推荐阅读更多精彩内容