Reactive判断的API
isProxy
检查对象是否是由 reactive 或 readonly创建的 proxy。
isReactive
检查对象是否是由 reactive创建的响应式代理:
如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
检查对象是否是由 readonly 创建的只读代理。
toRaw
返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
toRefs
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的:
那么有没有办法让我们解构出来的属性是响应式的呢?
Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
那么我们再次进行结构出来的 name 和 age 本身都是 ref的
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
toRef
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
//不是说每个地方都会用到,info也是响应式的,取值也可以直接用{{info.name}}
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { reactive, toRefs, toRef } from 'vue';
export default {
setup() {
const info = reactive({name: "why", age: 18});
// 1.toRefs: 将reactive对象中的所有属性都转成ref, 建立链接
// let { name, age } = toRefs(info);
// 2.toRef: 对其中一个属性进行转换ref, 建立链接
let { name } = info;
let age = toRef(info, "age");
const changeAge = () => {
age.value++;
}
return {
name,
age,
changeAge
}
}
}
</script>
ref其他的API(了解)
unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
如果参数是一个 ref,则返回内部值,否则返回参数本身;
这是 val = isRef(val) ? val.value : val 的语法糖函数;
isRef
判断值是否是一个ref对象。
shallowRef
创建一个浅层的ref对象;
triggerRef
手动触发和 shallowRef 相关联的副作用
customRef(用的比较少,写一些第三方库时可能会用到)
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:
它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数;
并且应该返回一个带有 get 和 set 的对象;
这里我们使用一个的案例:
对双向绑定的属性进行debounce(节流)的操作;
//下面案例为输入框简单防抖案例,输入延时0.3s显示
//hook下useDebounceRef.js
import { customRef } from 'vue';
// 自定义ref
export default function(value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
}
})
}
//customeRef.vue文件
<template>
<div>
<input v-model="message"/>
<h2>{{message}}</h2>
</div>
</template>
<script>
import debounceRef from './hook/useDebounceRef';
export default {
setup() {
const message = debounceRef("Hello World");
return {
message
}
}
}
</script>
<style scoped>
</style>
computed
在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理
在前面的Options API中,我们是使用computed选项来完成的;
在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性;
如何使用computed呢?
方式一:接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象;
方式二:接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象;
<template>
<div>
<h2>{{fullName}}</h2>
<button @click="changeName">修改firstName</button>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref("Kobe");
const lastName = ref("Bryant");
// 1.用法一: 传入一个getter函数
// computed的返回值是一个ref对象
const fullName = computed(() => firstName.value + " " + lastName.value);
// 2.用法二: 传入一个对象, 对象包含getter/setter
const fullName = computed({
get: () => firstName.value + " " + lastName.value,
set(newValue) {
const names = newValue.split(" ");
firstName.value = names[0];
lastName.value = names[1];
}
});
const changeName = () => {
// firstName.value = "James"
fullName.value = "coder why";
}
return {
fullName,
changeName
}
}
}
</script>
侦听数据的变化
在前面的Options API中,我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些操作。
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听;
watchEffect用于自动收集响应式数据的依赖;
watch需要手动指定侦听的数据源;
watchEffect
当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect。
我们来看一个案例:
首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const changeName = () => name.value = "kobe"
const changeAge = () => age.value++
watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
watchEffect的停止侦听
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。
比如在上面的案例中,我们age达到20的时候就停止侦听:
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const stop = watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
watchEffect清除副作用
什么是清除副作用呢?
比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器, 或者侦听器侦听函数被再次执行了。
那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;
我们可以在传入的回调函数中,执行一些清楚工作;
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const stop = watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log("网络请求成功~");
}, 2000)
// 根据name和age两个变量发送网络请求
onInvalidate(() => {
// 在这个函数中清除额外的副作用
// request.cancel()
clearTimeout(timer);
console.log("onInvalidate");
})
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
}
★ setup中使用ref
在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?
其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;
调整watchEffect的执行时机
如果我们希望在第一次的时候就打印出来对应的元素呢?
这个时候我们需要改变副作用函数的执行时机;
它的默认值是pre,它会在元素 挂载 或者 更新 之前执行;
所以我们会先打印出来一个空的,当依赖的title发生改变时,就会再次执行一次,打印出元素;
我们可以设置副作用函数的执行时机:
export default {
setup() {
const title = ref(null);
watchEffect(() => {
console.log(title.value);
}, {
flush: "post"
})
return {
title
}
}
}
Watch的使用
watch的API完全等同于组件watch选项的Property:
watch需要侦听特定的数据源,并在回调函数中执行副作用;
默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
与watchEffect的比较,watch允许我们:
懒执行副作用(第一次不会直接执行);
更具体的说明当哪些状态发生变化时,触发侦听器的执行;
访问侦听状态变化前后的值;
watch侦听函数的数据源有两种类型:
一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref);
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
const info = reactive({name: "why", age: 18});
// 1.侦听watch时,传入一个getter函数
watch(() => info.name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 2.传入一个可响应式对象: reactive对象/ref对象
// 情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象
watch(info, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 如果希望newValue和oldValue是一个普通的对象,对对象进行解构,获取到的就是普通对象
watch(() => {
return {...info}
}, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 情况二: ref对象获取newValue和oldValue是value值的本身
const name = ref("why");
watch(name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</script>
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
// 1.定义可响应式的对象
const info = reactive({name: "why", age: 18});
const name = ref("why");
// 2.侦听器watch
watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
console.log(newInfo, newName, oldInfo, oldName);
})
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</script>
const names = reactive(["aaa", "bbb", "ccc"]);
watch(()=>[...names],(newValue, oldValue) => {
console.log(newName);
})
const changeArr = () => {
names.push('ddd');
}
如果我们希望侦听一个深层的侦听,那么依然需要设置 deep 为true:
也可以传入 immediate 立即执行;
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
// 1.定义可响应式的对象
const info = reactive({
name: "why",
age: 18,
friend: {
name: "kobe"
}
});
// 2.侦听器watch
// () => ({...info})是将info进行ES6的展开运算符进行解构,拿到每个属性后,放到一个新的对象中,这时就不能进行深度监听
watch(() => ({...info}), (newInfo, oldInfo) => {
console.log(newInfo, oldInfo);
}, {
deep: true,
immediate: true//默认第一次进来是不会进行上边监听打印的,设为true,第一次运行也会监听打印
})
const changeData = () => {
info.friend.name = "james";
}
return {
changeData,
info
}
}
}
</script>