1、ref
ref
接受一个内部值并返回一个响应式且可变的 ref 对象,ref 对象仅有一个 .value
属性,指向该内部值;
<script setup>
const refValue = ref('refValue')
console.log(refValue) // ref 对象
console.log(refValue.value) // refValue
</script>
ref
声明的变量,我们可以在html
代码中直接使用变量,但在 js 代码中,我们需要通过.value
来访问,因为ref
定义的变量返回的是一个响应式的ref 对象
,对象需要通过.
的形式来访问;ref
一般用来声明基本数据类型,当然也可以声明一个对象,但需要通过.value.xxx
来访问,比较繁琐,并且它将被reactive
函数处理为深层的响应式对象;ref
也可以用于获取 DOM,具体用法如下;
<script setup>
const inputRef = ref(null)
// setup 是包含了 beforeCreate 和 created,这个阶段还没挂载实例,所以需要在 mounted 中获取
onMounted(() => {
if (inputRef && inputRef.value) {
inputRef.value.focus()
}
})
</script>
<template>
<input type="text" ref="inputRef">
</template>
2、reactive
reactive
是返回一个对象的响应式副本,即 proxy
对象;
<script setup>
const reactiveObj = { name: 'reactiveValue' }
const reactiveValue = reactive(reactiveObj)
console.log(reactiveValue, reactiveValue.name) // proxy 代理对象 reactiveValue
const reactiveRef = ref(0)
const reactiveRefValue = reactive(reactiveRef)
reactiveRef.value++
console.log(reactiveRef.value === reactiveRefValue.value) // true
reactiveRefValue.value++
console.log(reactiveRef.value === reactiveRefValue.value) // true
</script>
reactive
声明的变量,返回的是一个proxy
对象,如果我们修改了变量,目标对象也会随之改变;reactive
一般用来声明复杂的对象类型,不能用来声明基本数据类型,如果声明了基本数据类型,它返回的是一个基本数据类型的副本,不是proxy
对象,修改它的值会报错;- 如果传入的值是一个
ref
对象,reactive
将解包所有深层的refs
,同时维持 ref 的响应性,也就是修改reactive
的值,对应的ref
对象也会随之变化;
3、readonly
readonly
接受一个对象 (响应式或纯对象) 或 ref
并返回原始对象的只读代理,只读代理是深层的:任何被访问的嵌套 属性也是只读的;
<script setup>
const readObj = { count: 0 }
const readRef = ref(0)
const readReac = reactive({ count: 0 })
const readRefValue1 = readonly(readObj)
const readRefValue2 = readonly(readRef)
const readRefValue3 = readonly(readReac)
const updateReadOnly = () => {
readObj.count++
readRef.value++
readReac.count++
// 修改失败并会在控制台发出警告
readRefValue1.count++
readRefValue2.value++
readRefValue3.count++
}
</script>
4、shallowReactive
shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
<script setup>
const shallowObj = {
name: 'shallowObj',
age: 10,
nested: {
name: 'nested',
bar: 2
}
}
const shallowReactiveValue = shallowReactive(shallowObj)
const updateShallowReactive = () => {
// shallowReactiveValue.age++
shallowReactiveValue.nested.bar++
}
</script>
- 当修改
shallowReactiveValue
的外层属性,页面会跟着变化,也就是外层属性是响应式的,但是改变嵌套对象nested
时,页面不会更新,也就是这个对象不是响应式的; - 要是同时改变两个属性,则视图都会更新,因为响应式的属性会让视图更新,从而拿到了最新的值;
- 与
reactive
不同,任何使用ref
的 property 都不会被代理自动解包; - 如果传入的对象是响应式的,那么它的深层嵌套属性也是响应式的;
结论:shallowReactive 与 reactive 不同的是它只会让外层的属性拥有响应式,嵌套对象(普通对象)的属性没有响应式;
5、shallowReadonly
shallowReadonly
创建一个 proxy
,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。
<script setup>
// 传入普通对象
const readOnlyObj = {
age: 10,
nested: {
age: 2
}
}
const shallowReadonlyValue0 = shallowReadonly(readOnlyObj)
const updateShallowReadonly0 = () => {
// 修改失败并会在控制台发出警告
// shallowReadonlyValue.age++
shallowReadonlyValue0.nested.age++
}
// 传入 reactive 声明的变量
const readOnlyReactive = reactive({
age: 10,
nested: {
age: 2
}
})
const shallowReadonlyValue1 = shallowReadonly(readOnlyReactive)
const updateShallowReadonly1 = () => {
// 修改失败并会在控制台发出警告
// shallowReadonlyValue.age++
shallowReadonlyValue1.nested.age++
}
</script>
- 当传入的对象是普通对象,不能改变对象外层属性,可以改变嵌套对象的属性,但不是响应式的,目标对象会改变;
- 当传入的对象是用
reactive
对象时,不能改变对象外层属性,但是修改对象的嵌套属性仍然是响应式的,目标对象也会随之改变; - 与
readonly
不同,任何使用ref
的 property 都不会被代理自动解包;
6、shallowRef
shallowRef
创建一个跟踪自身 .value
变化的 ref
,但不会使其值也变成响应式的;
<script setup>
const shallowRefReac = {
name: 'shallowRefReac',
nested: {
name: 'nested'
}
}
const shallowRefValue = shallowRef(shallowRefReac)
const updateShallowRefValue = () => {
// 值会改变,但页面不会改变,说明 .value 的值不是一个响应式的属性
shallowRefValue.value.name += '+'
// shallowRefValue.value.nested.name += '+'
// shallowRefValue自身是响应式,修改 .value 页面会变化
// shallowRefValue.value = '111'
console.log(shallowRefValue) // RefImpl
}
</script>
7、toRef
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref
,然后,ref
可以被传递,它会保持对其源 property 的响应式连接;
<script setup>
const toRefReac = reactive({
name: 'toRef'
})
const toRefValue = toRef(toRefReac, 'name')
const updateToRefValue = () => {
toRefValue.value += '+'
console.log(toRefValue.value === toRefReac.name) // true
}
</script>
toRef
第一个参数传入的目标对象需要是一个reactive
声明的变量,第二个参数是目标对象中的某个属性名,修改其值时,原目标对象对应的属性值也会随之改变,如果第二个参数是目标对象中没有的属性,则声明变量的值是undefined
;toRef
的返回值是一个ObjectRefImpl
,与ref
不同,ref
返回的是一个RefImpl
;
8、toRefs
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref
;
<script setup>
const toRefsReac = reactive({
name: 'toRefsReac',
nested: {
name: 'nested'
}
})
const toRefsValue = toRefs(toRefsReac)
console.log(toRefsValue) // { name: ObjectRefImpl, nested: ObjectRefImpl }
const updateToRefsValue = () => {
toRefsValue.name.value += '+'
console.log(toRefsValue.name.value === toRefsReac.name) // true
}
</script>
toRefs
接收的是一个reactive
的值,它会返回一个普通对象,对象的每个属性是一个ObjectRefImpl
值,指向目标对象相应的属性;- 当从组合式函数返回响应式对象时,
toRefs
非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开;
<script setup>
// const updateUseState = () => {
// name += '+'
// age += 1
// // 值变化了,但是页面不会改变,说明解构出来的变量是不具有响应式的
// console.log(name, age)
// }
// function useState() {
// const state = reactive({
// name: 'stateName',
// age: 10
// })
// return state
// }
// 使用 toRefs
let { name, age } = useState()
const updateUseState = () => {
name.value += '+'
age.value += 1
// 值变化了,页面也会随之改变
console.log(name, age)
}
function useState() {
const state = reactive({
name: 'stateName',
age: 10
})
return toRefs(state)
}
</script>
9、computed
computed
计算属性在 Vue3 中有两种声明方式,一种是接受一个 getter
函数,并根据 getter
的返回值返回一个不可变的响应式 ref
对象;另一种是接受一个具有 get
和 set
函数的对象,用来创建可写的 ref
对象;
<script setup>
// 方式一
const reactiveValue1 = reactive({
name: 'reactiveValue1',
age: 10
})
const computedValue1 = computed(() => reactiveValue1.age + 1)
const updateComputed1 = () => {
// 修改失败,并在控制台发出警告
computedValue1.value++
console.log(computedValue1) // ComputedRefImpl
}
// 方式二
const reactiveValue2 = reactive({
name: 'reactiveValue2',
age: 10
})
const computedValue2 = computed({
get: () => reactiveValue2.age + 1,
set: (val) => reactiveValue2.age = val - 1
})
const updateReactive2 = () => {
reactiveValue2.age++
}
const updateComputed2 = () => {
// 修改成功,页面也随之变化
computedValue2.value++
console.log(computedValue2) // ComputedRefImpl
}
</script>
在 Vue3 中,computed
声明变量是通过 API 的形式来声明,分别两种形式,一种是 getter
,一种是有 get
和 set
的对象,同时,computed
的 API 还可以传入第二个参数 debuggerOptions
,它是一个对象,包含两个方法 onTrack
和 onTrigger
,他们可以用来调试;
10、watch
watch
API 与选项式 API this.$watch
完全等效,watch
需要侦听特定的数据源,并在单独的回调函数中执行副作用,默认情况下,它是惰性的——即回调仅在侦听源发生变化时被调用;
(1)侦听单一数据源
<script setup>
const watchReac = reactive({
name: 'watchReac',
nested: {
name: 'nested',
hobbies: ['篮球', '游戏']
}
})
const watchValue = ref<any>('')
// 侦听对象某一属性
watch(() => watchReac.name, (val, preVal) => {
console.log("watch: ", val, preVal)
watchValue.value = val
})
// 侦听对象的嵌套对象
// watch(() => watchReac.nested, (val, preVal) => {
// // 不会执行,需要传入第三个参数,{ deep: true }
// console.log("watch: ", val, preVal)
// watchValue.value = val
// })
// 侦听整个对象
// watch(watchReac, (val, preVal) => {
// console.log("watch: ", val, preVal)
// watchValue.value = val
// })
const updateWatchValue = () => {
watchReac.nested.name += '+'
}
</script>
侦听单一数据源时,传入的参数有侦听对象、回调函数、配置选项;
- 侦听对象:两种形式,一种是箭头函数返回一个对象,另一种是直接传入侦听对象;
- 回调函数:两个参数,分别是修改后的值和修改前的值;
- 配置选项:
deep
是否深入侦听,immediate
是否立即执行;
(2)侦听多个数据源
<script setup>
const watchReac = reactive({
name: 'watchReac',
nested: {
name: 'nested',
hobbies: ['篮球', '游戏']
}
})
const watchRef = ref(0)
const watchReacValue = ref<any>('')
const watchRefValue = ref<number>()
watch([watchReac, watchRef], ([reacVal, refVal], [preReacVal, preRefVal]) => {
watchReacValue.value = reacVal
watchRefValue.value = refVal
})
const updateWatchValue = () => {
watchReac.nested.name += '+'
watchRef.value++
}
</script>
侦听多个多数据源时,传入的参数有侦听对象、回调函数、配置选项;
- 侦听对象:需要传入一个数组对象,数组对象包括要侦听的数据;
- 回调函数:两个参数,参数分别为数组类型,数组的值分别是侦听对象修改前后的值,顺序按照侦听对象的顺序;
- 配置选项:
deep
是否深入侦听,immediate
是否立即执行;
11、watchEffect
为了根据响应式状态自动应用和重新应用副作用,我们可以使用 watchEffect
函数,它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数;
<script setup>
const watchEffectRef = ref(0)
const watchEffectReac = reactive({
count: 0
})
watchEffect(() => {
console.log("watchEffect: ", watchEffectRef.value)
})
const updateWatchEffect = () => {
watchEffectReac.count++
// watchEffectRef.value++
}
</script>
watchEffect
会立即执行一次回调函数,之后它追踪的依赖发生变化才会执行,也就是说,当我们改变watchEffectReac
的值时,并不会执行watchEffect
的回调;watchEffect
可以看作是配置了immediate
参数的watch
;watchEffect
默认是在组件更新前执行,可以通过配置flush
选项来设置执行时机;watchEffect
可以传入第二个参数,包括配置项flush
、onTrack
、onTrigger
;flush
:有三个值pre
(默认值)、post
、sync
;pre
:默认值,在组件更新前执行;post
:在组件更新后执行;sync
: 强制效果始终同步触发;
onTrack
: 响应式 property 或 ref 作为依赖项被追踪时被调用;onTrigger
: 依赖项变更导致副作用被触发时被调用;
Vue3 中还提供了 watchPostEffect
和 watchSyncEffect
两个 API,这两个是 watchEffect
的别名,只是在 watcher Effect
的基础上配置了 flush
的值,分别为 post
和 sync
;