Reactive判断的API
| API | 说明 |
|---|---|
| isProxy | 检查对象是否是由 reactive 或 readonly创建的 proxy |
| isReactive | 检查对象是否是由 reactive创建的响应式代理 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true |
| isReadonly | 检查对象是否是由 readonly 创建的只读代理 |
| toRaw | 返回 reactive 或 readonly 代理的原始对象 可以看做是将proxy对象转回raw对象 不建议保留对原始对象的持久引用。请谨慎使用 |
| shallowReactive | 创建一个浅层的响应式代理,它跟踪其自身 property 的响应性 但不执行嵌套对象的深层响应式转换 (深层还是原生对象) |
| shallowReadonly | 创建一个 proxy,使其自身的 property 为只读 但不执行嵌套对象的深度只读转换(深层还是可读、可写的) 浅层只读 |
isProxy
import { isProxy, reactive, ref, readonly } from 'vue'
export default {
name: 'Home',
setup() {
const info = { name: 'Klaus' }
console.log(isProxy(info)) // => false
// ----------------------------------------------
const info = reactive({ name: 'Klaus' })
console.log(isProxy(info)) // => true
// ---------------------------------------------
// ref方法返回的是一个RefImpl对象(RefImplement)
// 其值类似于
/*
{
_rawValue: 10,
_shallow: false,
_value: 10,
value: 10
}
*/
const info = ref(10)
console.log(isProxy(info)) // => false
console.log(isProxy(info.value)) // => false
// ---------------------------------------------
// ref方法返回的是一个RefImpl对象(RefImplement)
// 其值类似于
/*
{
_rawValue: { name: 'Klaus' },
_shallow: false,
_value: Proxy({ name: 'Klaus' }),
value: Proxy({ name: 'Klaus' })
}
*/
const info = ref({ name: 'Klaus' })
console.log(isProxy(info)) // => false
console.log(isProxy(info.value)) // => true
// ---------------------------------------------
const info = readonly(reactive({ name: 'Klaus' }))
// => Proxy(reactive({ name: 'Klaus' })) --- 对reactiive对象进行了二次代理
console.log(info)
console.log(isProxy(info)) // => true
// ---------------------------------------------
const info = readonly(ref({ name: 'Klaus' }))
// => Proxy(ref({ name: 'Klaus' })) --- 对ref对象进行了再次代理
console.log(info)
console.log(isProxy(info)) // => true
}
}
isReactive
import { isReactive, reactive, ref, readonly } from 'vue'
export default {
name: 'Home',
setup() {
const info = ref({ name: 'Klaus' })
console.log(isReactive(info)) // => true
// ---------------------------------------------
const info = ref({ name: 'Klaus' })
console.log(isReactive(info)) // => false
// ---------------------------------------------
const info = readonly(reactive({ name: 'Klaus' }))
console.log(isReactive(info)) // => true
}
}
isReadonly
const info = readonly(reactive({ name: 'Klaus' }))
console.log(isReadonly(info)) // => true
// ---------------------------------------------
const info = readonly(ref({ name: 'Klaus' }))
console.log(isReadonly(info)) // => true
toRaw
// 如果参数是ref对象,那么ref对象会原封不动的返回
const info = toRaw(reactive({ name: 'Klaus' }))
shallowReactive
<template>
<div>
<!-- <h2>{{ info.foo }}</h2> -->
<h2>{{ info.nested.bar }}</h2>
<h2>{{ info.nested.demo.baz }}</h2>
<button @click="changeState">change state</button>
</div>
</template>
<script>
import { shallowReactive, isReactive } from 'vue'
export default {
name: 'Foo',
setup() {
let info = shallowReactive({
foo: 1,
nested: {
bar: 2,
demo: {
baz: 3
}
}
})
const changeState = () => {
// info.foo++
info.nested.bar++
info.nested.demo.baz++
}
return {
info,
changeState
}
}
}
</script>
shallowReadonly
<template>
<div>
<h2>{{ state.foo }}</h2>
<h2>{{ state.nested.bar }}</h2>
<button @click="changeState">change state</button>
</div>
</template>
<script>
import { shallowReadonly, isReadonly, reactive } from 'vue'
export default {
name: 'Foo',
setup() {
const state = shallowReadonly(reactive({
foo: 1,
nested: {
bar: 2
}
}))
const changeState = () => {
state.foo++ // state.foo是只读的,会报警告
console.log(isReadonly(state.nested)) // false
state.nested.bar++ // state.nested.bar不是只读的,界面中的值依旧可以正常修改
}
return {
state,
changeState
}
}
}
</script>
toRefs
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值
解构后的值将不再是响应式的,因为proxy劫持的是reactive返回的对象
但是直接从中取值相当于是值的赋值,所以此时对解构后的值的改变依旧不会被劫持到
为此 Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化
<template>
<div>
{{ name }} --- {{ age }}
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'Home',
setup() {
let { name, age } = reactive({ name: 'Klaus', age: 23 })
// 此时修改age属性不是响应式的
const changeAge = () => age++
return {
name,
age,
changeAge
}
}
}
</script>
<template>
<div>
{{ name }} --- {{ age }}
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'Home',
setup() {
/*
1. 此时name和age在被解构后依旧是响应式的
2. name 和 age 本质上都是一个ref对象
3. info.name 和 name.value info.age 和 age.value 指向的是同一个对象
所以当修改name的值的是,info.name的值也会相应发生改变,其余同理
*/
const info = reactive({ name: 'Klaus', age: 23 })
// 此时的age 和 name 是响应式的
let { name, age } = toRefs(info)
// 解构出的age是一个ref对象,所以修改值的时候需要修改它的value属性
const changeAge = () => age.value++
return {
name,
age,
changeAge
}
}
}
</script>
<template>
<div>
{{ name }} --- {{ info.age }}
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'Home',
setup() {
let { name, info } = reactive({ name: 'Klaus', info: { age: 23 } })
// 需要注意的是:此时的age是可以响应式的,因为此时info是一个对象,是一个引用地址
// 所以实际修改的还是原本的那个reactive对象的属性值
const changeAge = () => info.age++
return {
name,
info,
changeAge
}
}
}
</script>
toRef
在上述案例中,我们需要修改的是age的值,也就是只有age属性的值是需要变成响应式的,name是不需要变成响应式的,
但是toRefs函数,会将name和age的值全部转换为ref对象,这其实是有损性能的
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法
<template>
<div>
{{ name }} --- {{ age }}
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
name: 'Home',
setup() {
const info = reactive({ name: 'Klaus', age: 23 })
const { name } = info
/*
1. toRef(对象, 属性名)
2. toRef函数的返回值就是一个Ref对象
*/
let age = toRef(info, 'age')
const changeAge = () => age.value++
return {
name,
age,
changeAge
}
}
}
</script>
ref其他的API
| API | 说明 |
|---|---|
| unref | 如果参数是一个 ref,则返回内部值,否则返回参数本身 这是 val = isRef(val) ? val.value : val 的语法糖函数 |
| isRef | 判断值是否是一个ref对象 |
| shallowRef | 创建一个浅层的ref对象 |
| triggerRef | 手动触发和 shallowRef 相关联的副作用 相对于手动触发shallowRef的更新操作 |
isRef
import { ref, isRef } from 'vue'
setup() {
const age = ref(23)
console.log(isRef(age)) // => true
}
unRef
// 注意 unref的r是小写的
import { ref, unref } from 'vue'
setup() {
const age = ref(23)
console.log(unref(age) === age.value) // => true
}
shallowRef
<template>
<div>
<h2>{{ user.name }}</h2>
<button @click="change">change name</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Home',
setup() {
const user = ref({ name: 'Klaus' })
// 界面实时发送了更新
const change = () => user.value.name = 'Alex'
return {
user,
change
}
}
}
</script>
<template>
<div>
<h2>{{ user.name }}</h2>
<button @click="change">change name</button>
</div>
</template>
<script>
import { shallowRef } from 'vue'
export default {
name: 'Home',
setup() {
const user = shallowRef({ name: 'Klaus' })
// 界面不会实时发送了更新 --- 但是直接修改user.value 界面依旧是响应式的
const change = () => user.value.name = 'Alex'
return {
user,
change
}
}
}
</script>
triggerRef
<template>
<div>
<h2>{{ user.name }}</h2>
<button @click="change">change name</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from 'vue'
export default {
name: 'Home',
setup() {
const user = shallowRef({ name: 'Klaus' })
const change = () => {
user.value.name = 'Alex'
// 强制更新ref对象 --- 参数为需要更新的那个ref对象
triggerRef(user)
}
return {
user,
change
}
}
}
</script>
customRef
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
- 参数是一个工厂函数,应该返回一个带有 get 和 set 的对象
- 该工厂函数接受 track 和 trigger 函数作为参数
- track === 收集依赖
- trigger === 触发更新
模拟ref
src/hooks/useCustomRef.js --- 自定义ref
import { customRef } from 'vue'
export default function(value) {
return customRef((track, trigger) => ({
set(newValue) {
// 设置值
value = newValue // 新值覆盖旧值
trigger() // 触发更新
},
get() {
// 取值的时候,就将调用track函数,vue会自动将使用的部分记录为value的对应依赖
// 在vue中所有使用某一个值的地方(这里以value为例),就被称之为value的依赖,或者说是value的副作用
// 或可以被称之为vue的dependencies(简写为 deps)
track()
return value
}
}))
}
Home.vue --- 使用自定义ref
<template>
<div>
<h2>{{ num }}</h2>
<button @click="changeNum">change num</button>
</div>
</template>
<script>
import useCustomRef from '@/hooks/useCustomRef.js'
export default {
name: 'Home',
setup() {
let num = useCustomRef(10)
const changeNum = () => {
// 返回的值是ref,所以修改值的时候需要使用value属性来取值
num.value++
}
return {
num,
changeNum
}
}
}
</script>
自定义debounce(节流)ref
src/hooks/useDebounceRef.js
import { customRef } from 'vue'
export default function(value, delay = 2000) {
return customRef((track, trigger) => {
let timer = null
return {
set(newValue) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
value = newValue
trigger()
}, delay)
},
get() {
track()
return value
}
}
})
}
Home.vue
<template>
<div>
<input type="text" v-model="info">
<p>{{ info }}</p>
</div>
</template>
<script>
import useDebounceRef from '@/hooks/useDebounceRef.js'
export default {
name: 'Home',
setup() {
const msg = 'Hello'
let info = useDebounceRef(msg, 1000)
return {
info
}
}
}
</script>
computed
计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理
在前面的Options API中,我们是使用computed选项来完成的
在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性
如何使用computed呢?
方式一: 接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
方式二: 接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象
<template>
<div>
<h2>{{ fullName }}</h2>
<button @click="change">change</button>
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'Home',
setup() {
const firstName = ref('Klaus')
const lastName = ref('Wang')
// 计算属性fullName本身也是一个ref对象
// 计算属性的依赖项本身也必须是ref对象,只有这样,才可以监听到依赖项的改变
const fullName = computed(() => firstName.value + ' - ' + lastName.value)
const change = () => firstName.value = 'Alex'
return {
fullName,
change
}
}
}
</script>
同样,computed的本质是一个有set方法和get方法的对象
<template>
<div>
<h2>{{ fullName }}</h2>
<button @click="change">change</button>
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'Home',
setup() {
const firstName = ref('Klaus')
const lastName = ref('Wang')
let fullName = computed({
get() { return firstName.value + ' - ' + lastName.value },
set(v) { console.log(v) }
})
const change = () => fullName.value = 'Alex - Li'
return {
fullName,
change
}
}
}
</script>
watch
我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些 操作
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听
- watchEffect用于自动收集响应式数据的依赖
- watch需要手动指定侦听的数据源
watchEffect
<template>
<div>
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="changeName">change name</button>
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: 'Home',
setup() {
const name = ref('Klaus')
const age = ref(23)
const changeName = () => name.value = 'Alex'
const changeAge = () => age.value++
/*
1. 参数为一个回调函数
2. 这个方法会在初始的时候被执行 --- immediate是强制开启的
这么做的目的是因为,这个函数在初始化被执行的时候,会收集其中所有的响应式数据,并将该函数作为响应式数据的依赖
例如: 在这里,name是响应式数据,所以该回调函数变成了响应式数据name的依赖(副作用)
当name的值发生改变的时候,该回调函数会作为name的依赖也一起被重新执行
而此处age并不在该回调函数中,所以age的值的改变并不会被watchEffect监听到
*/
watchEffect(() => {
console.log(name.value)
})
return {
name,
age,
changeName,
changeAge
}
}
}
</script>
停止侦听
在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可
<template>
<div>
<h2>{{ age }}</h2>
<button @click="changeAge">change age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: 'Home',
setup() {
const age = ref(23)
// watchEffect有一个返回值,是一个函数,可以用来取消监听
const stop = watchEffect(() => {
// 当age的值大于30的时候,且age的值发生改变的时候,界面依旧会更新
// 但是监听函数将不会再被执行
console.log(age.value)
})
const changeAge = () => {
age.value++
if (age.value > 30) {
// 取消监听
stop()
}
}
return {
age,
changeAge
}
}
}
</script>
清除副作用
什么是清除副作用呢?
比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
-
当副作用即将重新执行 或者 侦听器被停止(停止或组件被销毁的时候,onInvalidate就会被执行) 时会执行该函数传入的回调函数
-
我们可以在传入的回调函数中,执行一些清除工作
<template>
<div>
<h2>{{ num }}</h2>
<button @click="handleClick">click me</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: 'Home',
setup() {
let timer = null
const num = ref(0)
const handleClick = () => {
num.value++
}
// watchEffect有一个参数onInvalidate,其本身也是一个函数
// onInvalidate函数的参数也是一个函数
watchEffect(onInvalidate => {
if (num.value > 10) {
// 建立对num的监听
console.log('num is bigger then 10')
}
timer = setTimeout(() => {
console.log('num被改变了~')
}, 2000)
// 当取消监听或者依赖项发生改变的时候
// 这个函数会有优先于所有的监听逻辑执行
// 也就是初始化的时候,这个函数是不会被执行的
onInvalidate(() => {
console.log('onInvalidate')
clearTimeout(timer)
})
})
return {
num,
handleClick
}
}
}
</script>
执行时机
<template>
<div>
<h2 ref="numElem">{{ num }}</h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
name: 'Home',
setup() {
const num = ref(0)
// 如果参数是null或者undefined或者不传的时候
// vue会认为你想获取dom,类似于vue的this.$refs
// 如果你要获取的是dom元素的话,一般推荐参数设置为null
// 返回的ref对象要被设置到对应元素的ref属性中
const numElem = ref(null)
watchEffect(() => {
console.log(numElem.value)
// 第一次输出 null -- 可以是null,也可以是undefined,取决于你之前传递的值,这里是ref(null),所以第一次打印null
// 第二次输出 <h2>0</h2> -- dom挂载完毕,所以此时的结果编程了dom元素
})
return {
num,
numElem
}
}
}
</script>
在上述案例中,我们会发现打印结果打印了两次
- 这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null
- 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素
如果我们希望在第一次的时候就打印出来对应的元素呢?
-
使用if函数进行判断
watchEffect(() => { if (numElem.value) { console.log(numElem.value) } }) -
修改副作用监听的执行时机
// watchEffect的第一个参数是回调函数,第二个参数是一个配置对象 // 在配置对象中,我们可以改变副作用函数的执行时机 // 属性flush的默认值为pre,表示的是函数在创建完毕或者更新之前执行 // flush的值修改为post后,表示的是函数在挂载完毕或者更新之前执行 // flush还支持一个属性 sync,官方不推荐使用,了解即可 watchEffect(() => { console.log(numElem.value) }, { flush: 'post' })
watch
watch的API完全等同于组件watch选项的Property
- watch需要侦听特定的数据源,并在回调函数中执行副作用
- 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调
与watchEffect的比较,watch允许我们:
- 懒执行副作用(第一次不会直接执行)
- 更具体的说明当哪些状态发生变化时,触发侦听器的执行
- 访问侦听状态变化前后的值
单个数据源
watch侦听函数的数据源有两种类型:
- 一个getter函数: 但是该getter函数必须引用可响应式的对象(比如reactive或者ref)
- 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)
监听一个具体的属性
<template>
<div>
<h2>{{ info.name }}</h2>
<h2>{{ info.age }}</h2>
<button @click="change">click me</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = reactive({ name: 'Klaus', age: 18 })
// 监听某一个具体的属性
// watch(参数1, 参数2)
// 参数1 --- 一个get函数,返回需要监听的属性
// 参数2 ---- handler函数
watch(() => info.age, (newV, oldV) => {
console.log('newV', newV)
console.log('olfV', oldV)
})
const change = () => info.age++
return {
info,
change
}
}
}
</script>
监听一个reactive对象
<template>
<div>
<h2>{{ info.name }}</h2>
<h2>{{ info.age }}</h2>
<button @click="change">click me</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = reactive({ name: 'Klaus', age: 18 })
// 监听一个reactive对象
// newV 和 oldV 执行的是同一个对象 所以此时oldV是没有意义的
// newV 和 oldV 的值都是 reactive对象
watch(info, (newV, oldV) => {
console.log('newV', newV)
console.log('olfV', oldV)
})
const change = () => info.age++
return {
info,
change
}
}
}
</script>
<template>
<div>
<h2>{{ info.name }}</h2>
<h2>{{ info.age }}</h2>
<button @click="change">click me</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = reactive({ name: 'Klaus', age: 18 })
// 监听reactive对象,但是将newV和oldV的值转变为原生对象
// 执行get函数,将reactive对象进行解构后再重新拼成一个新的对象
// 因为默认情况下,解构出来的数据不是响应式的数据,是原生的数据
// 所以重新组合成的对象也是一个原生对象
watch(() => ({...info}), (newV, oldV) => {
console.log('newV', newV)
console.log('olfV', oldV)
})
const change = () => info.age++
return {
info,
change
}
}
}
</script>
监听一个ref对象
<template>
<div>
<h2>{{ info.name }}</h2>
<h2>{{ info.age }}</h2>
<button @click="change">click me</button>
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = { name: 'Klaus', age: 18 }
const age = ref(info.age)
// 第一个参数也可以是ref对象
// 如果是ref对象,那么newV和oldV是ref对象的value属性值
// 即返回的是ref.value 不是ref对象
watch(age, (newV, oldV) => {
console.log('newV', newV)
console.log('olfV', oldV)
})
const change = () => age.value++
return {
info,
change
}
}
}
</script>
多个数据源
const name = ref('Klaus')
const age = ref(23)
// newV 和 oldV的值是一个数组
watch([name, age], (newV, oldV) => {
console.log('newV', newV)
console.log('olfV', oldV)
})
// 可以使用解构的方式来获取到具体的值
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
console.log('newName', newName)
console.log('newAge', newAge)
console.log('oldName', oldName)
console.log('oldAge', oldAge)
})
配置参数
immediate
// watch有第三个参数,可以用来传递配置项,选项有immediate和deep
watch(age, () => {
console.log('监听被触发了')
}, {
immediate: true // 组件创建完毕,立即执行监听 --- 此时oldValue的值是undefined
})
deep
reactive对象默认会开启深度监听
<template>
<div>
<h2>{{ info.friend.name }}</h2>
<button @click="change">change</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = reactive({ name: 'Klaus', friend: { name: 'Alex' } })
// reactive对象默认会开启深度监听
watch(info, () => {
console.log('reactive对象默认会开启深度监听')
})
const change = () => info.friend.name = 'Jhon'
return {
info,
change
}
}
}
</script>
ref对象需要手动设置深度监听
<template>
<div>
<h2>{{ info.friend.name }}</h2>
<button @click="change">change</button>
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
name: 'Home',
setup() {
const info = ref({ name: 'Klaus', friend: { name: 'Alex' } })
// ref对象需要手动设置深度监听
watch(info, () => {
console.log('ref对象需要手动设置深度监听')
}, {
deep: true
})
const change = () => info.value.friend.name = 'Jhon'
return {
info,
change
}
}
}
</script>