寒假学的时候属于是有点过于敷衍了,现在update一下😮💨
最近是跟ts一起学的,所以里面会有ts相关的内容(无伤大雅无伤大雅
内容
1. reactive
- 定义一个
对象
类型的响应式
数据(基本类型不要用它,用ref
) - 响应式是深层的,影响所有嵌套的属性
- 内部基于es6的
Proxy
实现 reactive
会自动解包ref
,也就是无需使用.value
来访问
<script setup lang="ts">
const obj = reactive<{ name: string, age: number}>({
name: 'AIpoem',
age: 20
});
const arr = reactive<number[]>([1,2,3,4]);
const changeInfo = () => {
// 这样的更改也是具有响应性的
obj.name = 'change AIpoem';
arr[0] = 2;
}
</script>
1.1 数组异步赋值问题
reactive的数组直接赋值失去响应式
解决方法:
使用push
,不要直接赋值
<script setup lang="ts">
import { reactive } from "vue";
interface IList {
id: number;
name: string;
}
let list = reactive<IList[]>([]);
setTimeout(() => {
// 定时器模拟异步
let data = [
{
id: 1,
name: "11",
},
{
id: 2,
name: "22",
},
];
// 如果data是我们拿到的网络请求的结果,这样直接赋值会破坏list的响应式
// list = data;
list.push(...data);
}, 1000);
</script>
<template>
<div>
<div v-for="(item, index) in list" :key="index">
{{ item.id }} {{ item.name }}
</div>
</div>
</template>
另一种方法,reactive再包一层
interface IList {
list: { id: number; name: string }[];
}
let container = reactive<IList>({
list: [],
});
setTimeout(() => {
// 定时器模拟异步
let data = [
{
id: 1,
name: "11",
},
{
id: 2,
name: "22",
},
];
// 如果data是我们拿到的网络请求的结果,这样直接赋值会破坏list的响应式
// list = data;
container.list = data;
console.log(listF);
}, 1000);
<template>
<div>
<div v-for="(item, index) in container.list" :key="index">
{{ item.id }} {{ item.name }}
</div>
</div>
</template>
1.2 reactive对比ref
从定义数据角度对比:
ref
用来定义:基本
类型数据reactive
用来定义:对象/数组
类型数据
⚠️:ref
也可以用来定义对象/数组类型数据,它的内部会自动通过reactive
转为代理对象
从原理角度对比:
ref
通过Object.defineProperty()
的get
和set
来实现响应式(数据劫持)reactive
通过使用Proxy
来实现响应式(数据劫持),并通过Reflect
来操作源对象内部的数据
从使用角度对比:
ref
定义的数据:操作数据需要.value
,模板中可以直接读取不需要.value
reactive
定义的数据:操作数据与读取数据:均不需要.value
2. shallowReactive
shallowReactive
只保证第一层属性
的响应性,不保证更深层次的属性的响应性
也就是说这个reactive
是浅层的
<script setup lang="ts">
import { shallowReactive } from "vue";
let message = shallowReactive({
a: "poem",
b: {
c: "poemm",
},
});
const changeMsg1 = () => { // 可以实现响应式
message.a = "change poem";
};
const changeMsg2 = () => { // 无法实现响应式
message.b.c = "change poemm"
}
</script>
3. readonly
接收一个对象
(响应式对象/纯对象)或ref
,并返回原始对象的只读代理
import { ref, reactive, readonly } from "vue";
let obj1 = reactive({ count: 0 });
let copy = readonly(obj1);
obj1.count++; // 正常
copy.count++; // 报错
let obj2 = {
count: ref(0),
};
let copy2 = readonly(obj2);
obj2.count.value++; // 正常
copy2.count++; // 报错
// ⚠️:与reactive一样,任何使用了ref的属性通过代理访问时都会自动解包(readonly返回的count属性使用了ref,所以不用.value
4. computed
4.1 接受一个getter函数
返回一个不可变的响应式的ref
对象
<script setup lang="ts">
import { ref, computed } from "vue";
const count = ref(1);
const count2 = computed(() => ++count.value);
count2.value++; // 报错,只读属性
</script>
4.2 接受一个具有get和set函数的对象
可以创建可写的ref
对象
<script setup lang="ts">
import { ref, computed } from "vue";
const count = ref(1);
const count2 = computed({
get: () => count.value,
set: val => {
count.value = val;
},
});
// 有了set函数之后可以改值了
count2.value = 2;
console.log(count.value, count2.value); // 2 2
</script>
5. watchEffect高级侦听器
watchEffect
函数会立即执行
传入的一个函数,同时响应式追踪其依赖,并在依赖改变
的时候再次运行该函数
<script setup lang="ts">
import { ref, watchEffect } from "vue";
let msg1 = ref<string>("hello");
let msg2 = ref<string>("world");
// 用到了msg1,msg2,就会监听这两个
// 非惰性,一开始就会立即执行一次这个函数,之后改变了才执行
watchEffect(() => {
console.log(msg1.value, msg2.value);
});
</script>
5.1 停止侦听
watchEffect
所在组件被卸载时会自动停止侦听
也可以显式停止侦听:通过调用watchEffect
的返回值
const stop = watchEffect(() => {})
stop();
5.2 清除副作用
触发侦听
之前/侦听停止
之前,可以调用一个函数处理一些逻辑
(如果没有显式停止侦听,当watchEffect
所在组件被卸载时也能触发onInvalidate()
函数)
⚠️:onInvalidate()
不管写在watchEffect
内的后面还是前面,都是会最先执行的
let msg = ref<string>("hello");
const stop = watchEffect(onInvalidate => {
console.log(msg.value);
onInvalidate(() => {
console.log(1)
});
});
const change = () => {
msg.value = "change";
};
未点击按钮之前,控制台输出:hello
点击改变按钮,控制台输出:1 change
点击停止按钮,控制台输出:1
<template>
<button @click="change">改变</button>
<button @click="stop">停止</button>
</template>
5.2.1 清除副作用的应用
平时我们定义一个定时器,或者监听一个事件,会在mounted
钩子内定义或者注册,然后组件销毁之前在beforeUnmount
钩子中清除定时器或取消监听,但这样我们的逻辑会被分散,不利于维护
可以利用watchEffect
把创建和销毁的逻辑都放在一起
watchEffect(onInvalidate => {
// 定时器的创建和销毁
const timer = setInterval(() => {
//...
}, 1000);
onInvalidate(() => {
clearInterval(timer);
});
// dom的监听和取消监听
onMounted(()=>{
watchEffect((onInvalidate) => {
document.querySelector('.btn').addEventListener('click', handleClick, false)
onInvalidate(() => document.querySelector('.btn').removeEventListener('click', handleClick))
})
})
});
6. watch侦听器
watch
需要侦听特定的数据源
默认情况下是惰性
的,即只有当被侦听的源发生变化时才执行回调
三个参数:1.监听源 2.回调函数 3.配置项对象(immediate:true deep:true)
6.1 侦听ref
- 侦听单个数据源
// 直接侦听ref
const msg = ref<string>('')
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue);
})
msg.value = 'poem' // 输出:poem ""
- 侦听多个数据源
使用数组侦听多个数据源
const msg1 = ref<string>('')
const msg2 = ref<string>('')
watch([msg1, msg2], (newValue, oldValue) => {
console.log(newValue, oldValue);
})
msg1.value = 'poem1' // 输出 ["poem1", ""] ["", ""]
msg2.value = 'poem2' // logs: ["poem1", "poem2"] ["poem1", ""]
6.2 侦听reactive
- 侦听单个响应式数据
这里获取不到正确的oldValue
!!
(获取到的newValue
和oldValue
会是一样的
并且开不开启deep:true
效果都一样
(因为reactive
默认开启深度侦听,多深都能侦听到
let obj = reactive({
name: "poem",
});
watch(obj, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
const change = () => {
obj.name = "change poem";
};
<template>
<button @click="change">改变</button>
<!-- 点击 输出:Proxy {name: 'change poem'} Proxy {name: 'change poem'} -->
</template>
- 侦听单个响应式数据中的某个属性
要把侦听的属性放在函数的回调中
let obj = reactive({
name: "poem",
});
watch(() => obj.name, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
const change = () => {
obj.name = "change poem";
};
<template>
<button @click="change">改变</button>
<!-- 点击 输出:change poem AIpoem-->
</template>
- 侦听单个响应式数据中的多个属性
把侦听的多个属性放在数组中,数组写在函数回调里
let obj = reactive({
name: "AIpoem",
age: 20,
});
watch(
() => [obj.name, obj.age],
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
);
const change = () => {
obj.name = "change poem";
obj.age = 100;
};
<template>
<button @click="change">改变</button>
<!-- 点击 输出:['change poem', 100] (2) ['AIpoem', 20] -->
</template>
- 侦听单个响应式数据中的某个对象属性
与第2点的差别就在于,这个是对象属性,是有更深层次的属性的
因为呢,obj.info
它也不是由reactive
直接定义的,它本身就不会默认开启深度侦听
let obj = reactive({
info: {
name: "poem",
age: 20,
},
});
// 这个就侦听不到下面的obj.info.name的变化
watch(
() => obj.info,
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
);
const change = () => {
obj.info.name = "change poem";
};
所以我们在这里就要开启deep: true
了!
同样也获取不到正确的oldValue
let obj = reactive({
info: {
name: "poem",
age: 20,
},
});
// 这个就侦听不到下面的obj.info.name的变化
watch(
() => obj.info,
(newValue, oldValue) => {
console.log(newValue, oldValue);
},
{ deep: true }
);
const change = () => {
obj.info.name = "change poem";
};
<template>
<button @click="change">改变</button>
<!-- 点击 输出:
Proxy {name: 'change poem', age: 20} Proxy {name: 'change poem', age: 20} -->
</template>
7. toRef
为响应式对象上的某个属性新创建一个ref
可以用在:你要将响应式对象中的某个属性单独提供给外部使用并不想丢失响应式
<script setup lang="ts">
import { reactive, toRef } from "vue";
const obj = reactive({
name: "poem",
age: 20,
});
// 想要单独使用name这个属性
const name = toRef(obj, "name");
// 如果这样拿出来name就会丢失响应式
// let { name } = obj;
const change = () => {
name.value = "change poem";
console.log(name.value); // change poem
// 源对象中的name属性也会一起变
console.log(obj.name); // change poem
};
</script>
<template>
<button @click="change">改变</button>
</template>
应用场景:
常用在取prop
时:
setup(props) {
const name = toRef(props, "name")
// name会丢失响应式
const { name } = props
}
8. toRefs
批量创建多个ref
对象
⚠️:和toRef
的区别是:
toRef
可以为特定的属性
创建reftoRefs
是为源响应式对象中
的每个属性
创建ref
<script setup lang="ts">
import { reactive, toRefs } from "vue";
const obj = reactive({
name: "poem",
age: 20,
});
// 'poem' 20
// 取出的两个属性都不是响应式的
console.log(obj.name, obj.age);
let obj2 = toRefs(obj);
// ObjectRefImpl ObjectRefImpl
// 用toRefs包裹的对象,单独取属性也会是响应式的
console.log(obj2.name, obj2.age);
</script>
主要是方便我们解构使用:
// hook
function useX() {
const obj = reactive({
name: 'poem',
age: 20
})
// 这样导入拿到的时候就可以直接解构,并且不丢失name和age的响应式
return toRefs(obj);
}
<script setup lang="ts">
const { name, age } = useX()
</script>
参考博文:
watchEffect的应用:
https://juejin.cn/post/7093089634694987783