文章目录
ref 系列
ref()
深层响应式
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value
property,指向该内部值
ref()
可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如Map
。
用ref()
去绑定对象 或者 数组 等复杂的数据类型,看源码其实也是去调用reactive
使用
<template>
<!-- 在模板中使用 ref 时,我们不需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包 -->
<p>{{ count }}</p>
<button @click="onAdd">add</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
// ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回
console.log(count) // { value: 0 }
console.log(count.value) // 0
const onAdd = () => {
// 注意:被ref包装之后需要 .value 来进行赋值
count.value = count.value + 1
}
</script>
结合 TS
const str = ref('') // 会自动识别为 string 类似
const str:Ref<string> = ref('')
// 或
const str = ref<string>('')
自动解包场景
作为 文本插值
<template><p>{{ count }}</p></template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
作为顶级属性
const object = { id: ref(1) }
// object.id => [object Object] ref 对象
const { id } = object
id + 1 // => 2
作为 reactive 对象的属性
一个 ref 会在作为响应式对象的属性被访问或修改时自动解包:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
// 注意:修改 state.count 会影响 count
state.count = 1
console.log(count.value) // 1
如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:
const otherCount = ref(2)
state.count = otherCount // 将一个新的 ref 赋值给一个关联了已有 ref 的属性
console.log(state.count) // 2
console.log(count.value) // 1,原始 ref count 与 state.count 失去联系
当 ref 作为响应式数组或原生集合类型(如 Map
) 中的元素被访问时,它不会被解包:
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
Ref
TS对应的接口
interface Ref<T> {
value: T
}
import { ref, Ref } from 'vue'
let message: Ref<string | number> = ref("我是message")
isRef()
判断是不是一个ref对象
<template>
<!-- template 中 count 自动识别为 count.value -->
<p>{{count}}</p>
</template>
<script setup lang="ts">
import { ref, isRef } from 'vue'
const count = ref(0)
const num = 1
console.log(isRef(count)) // true
console.log(isRef(num)) // false
</script>
shallowRef()
浅层响应式
浅响应。创建一个跟踪自身 .value
变化的 ref,但不会使其值变成响应式的。
修改值不会触发视图更新
修改数据只会修改值得变更,不会影响视图。
<template>
<p>{{ info.name }}--{{ info.age }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
const info = shallowRef({
name: 'www',
age: 18,
})
const onChange = () => {
info.value.name = 'abc' // 不会进行响应式变更
console.log(info)
}
</script>
但是 重新赋值会触发更新
<template>
<p>{{ info.name }}--{{ info.age }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
const info = shallowRef({
name: 'www',
age: 18,
})
const onChange = () => {
info.value = { name: 'abc', age: 10 }
console.log(info)
}
</script>
shallowRef()
和 ref()
同时使用会强制更新视图
由于 ref()
底层会调用 triggerRef()
,triggerRef()
会强制更新视图。所以 shallowRef()
和 ref
同时使用也会更新视图。
<template>
<p>{{ info.name }}--{{ info.age }} --- {{ count }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { shallowRef, ref } from 'vue'
const info = shallowRef({
name: 'www',
age: 18,
})
const count = ref(0)
const onChange = () => {
info.value.name = 'abc' // 不会进行响应式变更
count.value++ // 响应式变更
console.log(info)
}
</script>
triggerRef()
强制更新页面DOM
<template>
<p>{{info.name}}--{{info.age}}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { shallowRef, triggerRef } from 'vue'
const info = shallowRef({
name: 'www',
age: 18
})
const onChange = () => {
info.value.name = 'abc' // 不会进行响应式变更
triggerRef(info)
}
</script>
customRef()
自定义ref
customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的
<template>
<p>{{ message }}</p>
<input type="text" v-model="message" />
</template>
<script setup lang="ts">
import { customRef } from 'vue'
function myRef<T>(value: T) {
let timer: ReturnType<typeof setTimeout> | undefined
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newVal) {
// 采用防抖赋值的结果有问题,以下采用节流
timer && clearTimeout(timer)
timer = setTimeout(() => {
trigger()
value = newVal
clearTimeout(timer)
timer = undefined
}, 1000)
},
}
})
}
const message = myRef('123')
</script>
toRef()
应用场景:有个函数需要接收 ref 响应式数据,但是 值 却是 reactive 。这个时候就可将 reactive 转成 ref
将数据变成 Ref 响应式数据
<template>
<p>info: {{ info.name }}--{{ info.age }}</p>
<p>person: {{ personName }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const info = reactive({
name: 'www',
age: 18,
})
const personName = toRef(info, 'name')
const onChange = () => {
personName.value = 'abc'
console.log(info, personName)
}
</script>
注意:如果原始对象是非响应式的就不会更新视图 数据是会变的
<template>
<p>info: {{ info.name }}--{{ info.age }}</p>
<p>person: {{ personName }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { toRef } from 'vue'
const info = {
name: 'www',
age: 18,
}
const personName = toRef(info, 'name')
const onChange = () => {
personName.value = 'abc'
console.log(info, personName)
}
</script>
toRefs()
批量将数据变成 Ref 响应式数据
<template>
<p>info: {{ info.name }}--{{ info.age }}</p>
<p>person: {{ personName }}---{{ personAge }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive, toRefs } from 'vue'
const info = reactive({
name: 'www',
age: 18,
})
const { name: personName, age: personAge } = toRefs(info)
const onChange = () => {
personName.value = 'abc'
personAge.value = 20
console.log(info, personName)
}
</script>
注意:如果原始对象是非响应式的就不会更新视图 数据是会变的
<template>
<p>info: {{ info.name }}--{{ info.age }}</p>
<p>person: {{ personName }}---{{ personAge }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { toRefs } from 'vue'
const info = {
name: 'www',
age: 18,
}
const { name: personName, age: personAge } = toRefs(info)
const onChange = () => {
personName.value = 'abc'
personAge.value = 20
console.log(info, personName)
}
</script>
reactive 系列
reactive()
用来绑定复杂的数据类型,例如 对象 数组。
使用reactive 去修改值无须 .value
使用
<template>
<button @click="state.count++">
{{ state.count }}
</button>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
// reactive() 将对象本身具有响应性
const state = reactive({ count: 0 })
</script>
响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
结合 TS
interface Book {
title: string
year?: number
}
const str:Book = reactive({ title: '' })
// 或
const str = reactive<Book>({ title: '' })
局限性
不可以绑定普通的数据类型
它只能用于对象类型 (对象、数组和如 Map
、Set
这样的集合类型)。它不能持有如 string
、number
或 boolean
这样的原始类型。
<template>
<p>{{ count }}</p>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const count = reactive(2)
</script>
查看源码: reactive 继承与对象
不能重新赋值
reactive 数据 重新赋值会丢失响应式
由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地 “替换” 响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
<template>
<p>{{ info.name }}--{{ info.age }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
let info = reactive({
name: 'www',
age: 18,
})
const onChange = () => {
// 响应性连接已丢失!
info = { name: 'abc', age: 20 }
console.log(info)
}
</script>
解决方法:
数组:
- 使用数组方法
- 将数组放到对象中,作为对象的属性去赋值
<template>
<p>{{ arr }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
let arr = reactive<number[]>([])
const onChange = () => {
// arr = [1, 2, 3] // 不会进行响应式变更
console.log(arr)
// 解决方法:
// 使用数组方法
// arr.push(1, 2, 3)
arr.splice(0, 0, 1, 2, 3)
}
</script>
对象:
- 一个个属性赋值
- 在外层再包裹一层对象
<template>
<p>{{ info.name }}--{{ info.age }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
let info = reactive({
name: 'www',
age: 18,
})
const onChange = () => {
info = { name: 'abc', age: 20 } // 不会进行响应式变更
console.log(info)
// 解决方法
info.name = 'abc'
info.age = 20
}
</script>
对解构操作不友好
当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
由于这些限制,我们建议使用 ref()
作为声明响应式状态的主要 API。
注意
const raw = {} // 原始对象
const proxy = reactive(raw) // 代理对象:返回的是一个原始对象的 Proxy
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 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
shallowReactive
只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图
注意:shallowReactive 不能和 reactive 同时使用,否则 shallowReactive 也会更新视图。
官方回复:https://github.com/vuejs/core/issues/5623
<template>
<p>{{ info.name }}--{{ info.score }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { shallowReactive } from 'vue'
let info = shallowReactive({
name: 'www',
score: {
math: 90,
english: 80,
chinese: 95,
},
})
const onChange = () => {
// 不会进行响应式变更
info.score.math = 10
console.log(info)
}
</script>
readonly
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
类型:
function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>>
详细信息:
只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive()
相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 shallowReadonly()
作替代。
示例:
<template>
<p>{{ info.name }}--{{ info.age }}</p>
</template>
<script setup lang="ts">
import { reactive, readonly } from 'vue'
const info = reactive({
name: 'www',
age: 18,
})
const copy = readonly(info)
// 用来做响应性追踪
watchEffect(() => {
console.log(copy.name)
})
// 更改源属性会触发其依赖的侦听器
info.age = 20
// 更改该只读副本将会失败,并会得到一个警告
copy.name = 'abc'
</script>
toRaw
将响应式对象转化为普通对象
<template>
<p>info: {{ info.name }}--{{ info.age }}</p>
<p>person: {{ person }}</p>
<button @click="onChange">change</button>
</template>
<script setup lang="ts">
import { reactive, toRaw } from 'vue'
const info = reactive({
name: 'www',
age: 18,
})
const person = toRaw(info)
const onChange = () => {
person.name = 'abc'
person.age = 20
console.log(info, person)
}
</script>