Composition API(组合API)
ref
-
作用: 定义一个响应式的数据
-
语法:
const xxx = ref(initValue)- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value - 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
-
备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive函数。
-
代码👇👇👇👇👇👇
<template>
<el-card>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h3>工作种类:{{ job.type }}</h3>
<h3>工作薪水:{{ job.salary }}</h3>
<button @click="changeInfo">修改人的信息</button>
</el-card>
</template>
<script setup>
import { ref } from "vue";
let name = ref("张三");
let age = ref(18);
let job = ref({
type: "前端工程师",
salary: "30K",
});
function changeInfo() {
// 修改响应式数据
name.value = "李四";
age.value = 48;
console.log(job.value);
job.value.type = "UI设计师";
job.value.salary = "60K";
console.log(name, age, job);
}
</script>
通常用于定义基本数据类型
reactive
-
作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref函数) -
语法:
const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) -
reactive定义的响应式数据是“深层次的”。
-
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
-
代码👇👇👇👇👇👇
<template>
<el-card>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h3>工作种类:{{ person.job.type }}</h3>
<h3>工作薪水:{{ person.job.salary }}</h3>
<h3>爱好:{{ person.hobby }}</h3>
<h3>测试的数据c:{{ person.job.a.b.c }}</h3>
<button @click="changeInfo">修改人的信息</button>
</el-card>
</template>
<script setup>
import { reactive } from "vue";
let person = reactive({
name: "张三",
age: 18,
job: {
type: "前端工程师",
salary: "30K",
a: {
b: {
c: 666,
},
},
},
hobby: ["抽烟", "喝酒", "烫头"],
});
// 修改响应式数据
function changeInfo() {
person.name = "李四";
person.age = 48;
person.job.type = "UI设计师";
person.job.salary = "60K";
person.job.a.b.c = 999;
person.hobby[0] = "学习";
}
</script>
通过api接口请求来的数据需要重新赋值可以通过
Object.assign()合并对象来重新赋值
Object.assign()批量将后台返回的数据变为响应式
- 概念:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
- 用法👇👇👇👇👇👇
Object.assign(target, ...sources)
参数: target--->目标对象
source--->源对象
返回值:target,即目标对象
-
说人话就是:将source这个对象的键值都赋给target(合并对象)
-
代码👇👇👇👇👇👇
let detail = reactive({});
// 后台请求来的数据
const { data } = await detailApi({ contributor_id: 1 });
// 将后台数据的键值都赋给detail
detail = Object.assign(detail, data);
computed计算属性
- 用法几乎与vue2一致
- 代码👇👇👇👇👇👇
<template>
<el-card>
姓:<input type="text" v-model="person.firstName" />
<br />
名:<input type="text" v-model="person.lastName" />
<br />
<span>全名:{{ person.fullName }}</span>
<br />
全名:<input type="text" v-model="person.fullName" />
<br />
<div>计算ref的值{{ myFullName }}</div>
</el-card>
</template>
<script setup>
import { ref, reactive, computed } from "vue";
let person = reactive({
firstName: "张",
lastName: "三",
});
let myFullName = ref();
//计算属性——简写(没有考虑计算属性被修改的情况)
/* person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
}) */
//计算属性——完整写法(考虑读和写)
person.fullName = computed({
get() {
return person.firstName + "-" + person.lastName;
},
set(value) {
const nameArr = value.split("-");
person.firstName = nameArr[0];
person.lastName = nameArr[1];
},
});
// 计算ref的值
myFullName = computed({
get() {
return person.firstName + person.lastName;
},
set(value) {
person.firstName = nameArr[0];
person.lastName = nameArr[1];
},
});
</script>
computed函数的回调什么时候执行?
初始化的时候执行一次和所依赖的数据发生变化的时候执行
watch
- watch的套路是:既要指明监视的属性,也要指明监视的回调。
情况一:监视ref所定义的一个响应式数据
情况二:监视ref所定义的多个响应式数据
情况三:监视ref定义的响应式对象
情况四:监视reactive所定义的一个响应式数据的全部属性
情况五:监视reactive所定义的一个响应式数据中的某个属性
情况六:监视reactive所定义的一个响应式数据中的某些属性
对应代码👇👇👇👇👇👇
<template>
<el-card>
<h2>当前求和为:{{ sum }}</h2>
<button @click="sum++">点我+1</button>
<hr />
<h2>当前的信息为:{{ msg }}</h2>
<button @click="msg += '!'">修改信息</button>
<hr />
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>薪资:{{ person.job.j1.salary }}K</h2>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
<br />
<h2>ref定义的响应式对象情况</h2>
<h2>姓名:{{ personRef.name }}</h2>
<h2>年龄:{{ personRef.age }}</h2>
<h2>薪资:{{ personRef.job.j1.salary }}K</h2>
<button @click="personRef.name += '~'">修改姓名</button>
<button @click="personRef.age++">增长年龄</button>
<button @click="personRef.job.j1.salary++">涨薪</button>
</el-card>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
let sum = ref(0);
let msg = ref("你好啊");
let personRef = ref({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
//情况一:监视ref所定义的一个响应式数据
watch(
sum,
(newValue, oldValue) => {
console.log("sum变了", newValue, oldValue);
},
{ immediate: true }
);
//情况二:监视ref所定义的多个响应式数据
watch(
[sum, msg],
(newValue, oldValue) => {
console.log("sum或msg变了", newValue, oldValue);
},
{ immediate: true }
);
/*
情况三:监视ref定义的响应式对象
1.注意:由于person是通过ref定义的响应式对象,所以监听它的属性变化的时候需要使用.value才能监听到数据的变化
2.注意:由于person是引用类型,在ref内部默认其value是reactive生成的一个Proxy对象,因此还可以添加深度监听来监听数据的变化
*/
// 以下两种写法任选其一
watch(personRef.value, (newValue, oldValue) => {
console.log("person的值变化了", newValue, oldValue);
});
watch(
personRef,
(newValue, oldValue) => {
console.log("person的值变化了", newValue, oldValue);
},
{ deep: true } //此处的deep配置有效,但是oldValue还是获取不对
);
/*
情况四:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)
*/
watch(
person,
(newValue, oldValue) => {
console.log("person变化了", newValue, oldValue);
},
{ deep: false }
); //此处的deep配置无效
//情况五:监视reactive所定义的一个响应式数据中的某个属性
watch(
() => person.name,
(newValue, oldValue) => {
console.log("person的name变化了", newValue, oldValue);
}
);
//情况六:监视reactive所定义的一个响应式数据中的某些属性
watch([() => person.name, () => person.age], (newValue, oldValue) => {
console.log("person的name或age变化了", newValue, oldValue);
});
//特殊情况
watch(
() => person.job,
(newValue, oldValue) => {
console.log("person的job变化了", newValue, oldValue);
},
{ deep: true }
); //此处由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效
</script>
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取(也变成了新数据)、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
watchEffect
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
-
代码👇👇👇👇👇👇
<template>
<el-card>
<h2>当前求和为:{{ sum }}</h2>
<button @click="sum++">点我+1</button>
<hr />
<h2>当前的信息为:{{ msg }}</h2>
<button @click="msg += '!'">修改信息</button>
<hr />
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>薪资:{{ person.job.j1.salary }}K</h2>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</el-card>
</template>
<script setup>
import { ref, reactive, watchEffect } from "vue";
let sum = ref(0);
let msg = ref("你好啊");
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
watchEffect(() => {
const x1 = sum.value;
const x2 = person.job.j1.salary;
console.log("watchEffect所指定的回调执行了");
});
</script>
toRef与toRefs
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name') -
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
vue3写法
- 代码👇👇👇👇👇👇
<template>
<h2>姓名:{{ name }}</h2>
<h2>姓名:{{ name2 }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>薪资:{{ job.j1.salary }}K</h2>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import { ref, reactive, toRef, toRefs } from "vue";
export default {
name: "Demo",
setup() {
//数据
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
const name2 = toRef(person,'name')
//返回一个对象(常用)
return {
name2,
/*
下面三行代码等同于 ...toRefs(person)
name:toRef(person,'name'),//将person对象中的name变为响应式属性
age:toRef(person,'age'),
salary:toRef(person.job.j1,'salary'),
*/
...toRefs(person),
};
},
};
</script>
setup形式写法
- 代码👇👇👇👇👇👇
<template>
<el-card>
<h2>姓名:{{ name }}</h2>
<h2>姓名:{{ name2 }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>job:{{ job }}</h2>
<h2>薪资:{{ job.j1.salary }}K</h2>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</el-card>
</template>
<script setup>
import { toRef, reactive, toRefs } from "vue";
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
const name2 = toRef(person, "name");
const { name, age, job } = toRefs(person);
</script>
shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
- 代码👇👇👇👇👇👇
<template>
<el-card>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>job:{{ job }}</h2>
<h2>薪资:{{ job.j1.salary }}K</h2>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</el-card>
</template>
<script setup>
import { reactive, ref, readonly, toRefs } from "vue";
let sum = ref(0);
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
person = readonly(person);
sum = readonly(sum);
// person = shallowReadonly(person)
// sum = shallowReadonly(sum)
const { name, age, job } = toRefs(person);
</script>
toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
- 代码👇👇👇👇👇👇
<template>
<h4>当前求和为:{{ sum }}</h4>
<button @click="sum++">点我++</button>
<hr />
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>薪资:{{ job.j1.salary }}K</h2>
<h3 v-show="person.car">座驾信息:{{ person.car }}</h3>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
<button @click="showRawPerson">输出最原始的person</button>
<button @click="addCar">给人添加一台车</button>
<button @click="person.car.name += '!'">换车名</button>
<button @click="changePrice">换价格</button>
</template>
<script setup>
import { ref, reactive, toRefs, toRaw, markRaw } from "vue";
let sum = ref(0);
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
const showRawPerson = () => {
const p = toRaw(person);
p.age++;
console.log(p);
};
const addCar = () => {
let car = { name: "奔驰", price: 40 };
person.car = markRaw(car);
};
const changePrice = () => {
person.car.price++;
console.log(person.car.price);
};
const { name, age, job } = toRefs(person);
</script>
customRef
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果
-
代码👇👇👇👇👇👇
<template>
<input type="text" v-model="keyword" />
<h3>{{ keyword }}</h3>
</template>
<script setup>
import { ref, customRef } from "vue";
//自定义一个ref——名为:myRef
const myRef = (value, delay) => {
let timer;
// 一定要返回
return customRef((track, trigger) => {
// 一定要返回一个对象
return {
get() {
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`);
track(); //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value;
},
set(newValue) {
console.log(`有人把myRef这个容器中数据改为了:${newValue}`);
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger(); //通知Vue去重新解析模板
}, delay);
},
};
});
};
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef("hello", 500); //使用程序员自定义的ref
console.log(keyWord);
console.log(ref);
</script>
响应式数据的判断
-
isRef: 检查一个值是否为一个 ref 对象
-
isReactive: 检查一个对象是否是由
reactive创建的响应式代理 -
isReadonly: 检查一个对象是否是由
readonly创建的只读代理 -
isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理 -
代码👇👇👇👇👇👇
<script setup>
import {
ref,
reactive,
toRefs,
readonly,
isRef,
isReactive,
isReadonly,
isProxy,
} from "vue";
let car = reactive({ name: "奔驰", price: "40W" });
let sum = ref(0);
let car2 = readonly(car);
const { name, price } = toRefs(car);
console.log(isRef(sum)); //true
console.log(isReactive(car)); //true
console.log(isReadonly(car2)); //true
console.log(isProxy(car)); //true
console.log(isProxy(sum)); //false
</script>