Vue3学习笔记2 reactive、shallowReactive、readonly、computed、watchEffect、watch、toRef

寒假学的时候属于是有点过于敷衍了,现在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()getset来实现响应式(数据劫持)
  • 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

  1. 侦听单个数据源
// 直接侦听ref
const msg = ref<string>('')
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue);
})
msg.value = 'poem'  // 输出:poem ""
  1. 侦听多个数据源
    使用数组侦听多个数据源
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

  1. 侦听单个响应式数据
    这里获取不到正确的oldValue!!
    (获取到的newValueoldValue会是一样的
    并且开不开启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>
  1. 侦听单个响应式数据中的某个属性
    要把侦听的属性放在函数的回调中
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>
  1. 侦听单个响应式数据中的多个属性
    把侦听的多个属性放在数组中,数组写在函数回调里
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>
  1. 侦听单个响应式数据中的某个对象属性
    与第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可以为特定的属性创建ref
  • toRefs是为源响应式对象中每个属性创建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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值