免费查看本文章可前往我的网站:PiQiu
目录
一、组合式 API 的使用
1.1、watch 函数
与 vue2.x 中的 watch 配置功能一致,但是多了一些坑:
这是我当前的 vue 版本
- 监视 reactive 定义的响应式数据时:oldValue 无法正确获取
- 监视 reactive 定义的响应式数据中某个属性时:deep 配置有效.
Ps:我目前使用的 vue3 的版本没有以上问题!!!
这里我来列出几种情况:
a)监视 ref 所定义的响应式数据
<template>
<h2>当前和为: {{ sum }}</h2>
<button @click="sum++">点我 + 1</button>
</template>
<script>
import { ref, watch } from "vue";
export default {
name: "App",
setup() {
let sum = ref(0);
watch(sum, (newValue, ordValue) => {
console.log("sum变了", newValue, ordValue);
});
return {
sum,
};
},
};
</script>
监视没有问题,如下:
b)监视 ref 定义的多个响应式数据(只要其中一个变,就会触发监视事件)
<template>
<h2>当前和为: {{ sum }}</h2>
<h2>{{ msg }}</h2>
<button @click="sum++">点我 + 1</button>
<button @click="msg += '!'">点我 + !</button>
</template>
<script>
import { ref, watch } from "vue";
export default {
name: "App",
setup() {
let sum = ref(0);
let msg = ref("你好呀");
//情况二
watch([sum, msg], (newValue, oldValue) => {
console.log("sum 或 msg 变了", newValue, oldValue);
});
return {
sum,
msg,
};
},
};
</script>
先点三下 “点我 + 1”、再点三下 “点我 + !”,效果如下:
c)监视 reactive 所定义的一个响应式数据的全部属性,无法正确的获取 oldValue
若没有配置 deep,默认开启深度监视(老版本的 vue3 强制开启深度监视,无法修改,我当前使用的版本已修改该问题).
<template>
<h2>一个人的信息</h2>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>{{ person.a.b.c }}</div>
<button @click="person.name = 'lyj'">修改姓名</button>
<button @click="person.age += 1">修改年龄</button>
<button @click="person.a.b.c += 100">修改a.b.c</button>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
name: "App",
setup() {
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
//情况三
watch(
person,
(newValue, oldValue) => {
console.log("person变化了", newValue, oldValue);
},
);
return {
person,
};
},
};
</script>
依次点击按钮,效果如下:
若设置 deep 为 false ,则点击 “修改 a.b.c ” 无效,如下:
<template>
<h2>一个人的信息</h2>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>{{ person.a.b.c }}</div>
<button @click="person.name = 'lyj'">修改姓名</button>
<button @click="person.age += 1">修改年龄</button>
<button @click="person.a.b.c += 100">修改a.b.c</button>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
name: "App",
setup() {
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
//情况三
watch(
person,
(newValue, oldValue) => {
console.log("person变化了", newValue, oldValue);
},
{ deep: false } //此处 deep 配置有效!!版本更新,修复了之前无效的问题
);
return {
person,
};
},
};
</script>
d)监视 reactive 所定义的一个响应式数据中的某个属性,这样可以解决上述请款三无法正确显示 oldValue 的问题
这里写法上和之前有点差异,第一个参数返回的是一个箭头函数.
<template>
<h2>一个人的信息</h2>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>{{ person.a.b.c }}</div>
<button @click="person.name = 'lyj'">修改姓名</button>
<button @click="person.age += 1">修改年龄</button>
<button @click="person.a.b.c += 100">修改a.b.c</button>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
name: "App",
setup() {
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
//情况四
watch(
() => person.name,
(newValue, oldValue) => {
console.log("person 的 name 变化了", newValue, oldValue);
}
);
return {
person,
};
},
};
</script>
点就 修改姓名 按钮,效果如下:
e)监视 reactive 所定义的一个响应式数据中的某些属性(这种方式可以用来解决监视 reactive 后 oldValue 变化的问题).
<template>
<h2>一个人的信息</h2>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>{{ person.a.b.c }}</div>
<button @click="person.name = 'lyj'">修改姓名</button>
<button @click="person.age += 1">修改年龄</button>
<button @click="person.a.b.c += 100">修改a.b.c</button>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
name: "App",
setup() {
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
//情况五
watch([() => person.name, () => person.age], (newValue, oldValue) => {
console.log("person 的 name 或 age 变化了", newValue, oldValue);
});
return {
person,
};
},
};
</script>
依次点击 修改姓名 和 修改年龄,效果如下:
f)特殊情况:当监视的是 reactive 定义的对象中的某个属性对象或者当前对象,并且第一个参数返回的是 回调方法,deep也会生效,默认非深层监视.
1.2、watchEffect 函数
watchEffect 的参数就是一个回调.
不用指明监视的是哪个属性,只要监视的回调中用到哪个属性发生变化了,就会重新执行回调(挂载的时候也会执行一次).
<template>
<h2>一个人的信息</h2>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>{{ person.a.b.c }}</div>
<button @click="person.name = 'lyj'">修改姓名</button>
<button @click="person.age += 1">修改年龄</button>
<button @click="person.a.b.c += 100">修改a.b.c</button>
</template>
<script>
import { reactive, watchEffect } from "vue";
export default {
name: "App",
setup() {
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
watchEffect(() => {
const test1 = person.name;
const test2 = person.a.b.c;
console.log("watchEffect 回调执行了");
});
return {
person,
};
},
};
</script>
刚开始组件挂载的时候会执行一次,之后分别点击 修改姓名 和 修改 a.b.c ,又会执行两次,效果如下:
Ps:默认 deep: false,因此如果 watchEffect 中使用 person.a 的话,修改 person.a.b.c 是不会重新执行回调的.
1.3、toRef 和 toRefs
1.3.1、toRef
将指定的属性通过 toRef 就可以创建一个 ref 对象.
通俗来讲,是对源对象的属性的浅拷贝,并且将源对象对应的属性改成响应式的.
为什么要使用它?例如以下场景:
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
a)要给模板中展示 person 的所有属性很麻烦,每次都要 person. 的方式才能获取到对应的属性,因此可能就有人会这样操作:
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
return {
person,
name: person.name, //模板中可以直接使用 name 表示 person.name
age: person.age, //同理
};
b)但是上述这种方式返回的数据不是响应式的呀,因此,就有人想到了以下方式
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
return {
person,
name: ref(person.name), //模板中可以直接使用 name 表示 person.name
age: ref(person.age), //同理
};
c)虽然实现了响应式,但是这相当于是重新创建了一个新的 ref 对象,对这些数据的修改不会影响源数据,因此就需要使用 toRef 了~
let person = reactive({
name: "cyk",
age: 18,
a: {
b: {
c: 666,
},
},
});
return {
person,
name: toRef(person, "name"), //模板中可以直接使用 name 表示 person.name
age: toRef(person, "age"), //同理
};
1.3.2、toRefs
和 toRef 的功能一致,但是它可以直接将一个对象中的所有属性都变成响应式的,语法:toRefs(person).
return {
...toRefs(person),
};
1.4、vue3 的声明周期
a)vue3 中可以继续使用 vue2 中的生命周期钩子,但有两个被更名.
- beforeDestory 改名为 beforeUnmount.
- destroyed 改名为 unmounted.
b)vue3 中也提供了 Composition API 形式的生命周期钩子,与 vue2 中的钩子对应关系如下:
案例如下:
App 父组件定义如下
<template>
<button @click="isShowUser = !isShowUser">切换隐藏/显示</button>
<User v-if="isShowUser" />
</template>
<script>
import { ref } from "vue";
import User from "./views/User.vue";
export default {
name: "App",
components: { User },
setup() {
let isShowUser = ref(true);
return {
isShowUser,
};
},
};
</script>
User 子组件如下:
<template>
<h2>求和: {{ sum }}</h2>
<button @click="sum++">点我 + 1</button>
</template>
<script>
import {
ref,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
} from "vue";
export default {
name: "User",
setup() {
let sum = ref(0);
// vue3 中可以通过组合式 API 的形式取使用声明周期的钩子
onBeforeMount(() => {
console.log("-------onBeforeMount-------");
});
onMounted(() => {
console.log("-------onMounted-------");
});
onBeforeUpdate(() => {
console.log("-------onBeforeUpdate-------");
});
onUpdated(() => {
console.log("-------onUpdated-------");
});
onBeforeUnmount(() => {
console.log("--------onBeforeUnmount------");
});
onUnmounted(() => {
console.log("--------onUnmounted------");
});
return {
sum,
};
},
};
</script>
使用效果如下:
1.5、setup 语法糖
1.5.1、语法糖介绍
直接在 script 标签中添加 setup 属性就可以直接使用 setup 语法糖了.
使用setup 语法糖之后:
- 不用写 setup 函数.
- 组件只需要引入不需要注册.
- 属性和方法也不需要返回,可以直接在 template 模板中使用.
<script setup>
const count = ref(1)
const countInc = () => count.value++
</script>
<template>
<div>计数器</div>
<el-button @click="countInc">{{ count }}</el-button>
</template>
1.5.2、新增API:defineProps
子组件接收父组件中传来的 props.
a)父组件
<template>
<div>计数器</div>
<el-button @click="countInc">{{ count }}</el-button>
<Children :count="count"></Children>
</template>
<script setup>
import Children from "./Children.vue";
const count = ref(1)
const countInc = () => count.value++
</script>
b)子组件
<template>
<div>父组件传入的数据: {{count}}</div>
</template>
<script setup>
defineProps({
count: {
type: Number,
default: NaN
}
})
</script>
1.5.3、新增API:defineEmits
子组件调用父组件中的方法.
a)父组件
<template>
<div>计数器</div>
<el-button @click="countInc">{{ count }}</el-button>
<Children @countIncr="countInc" :count="count"></Children>
</template>
<script setup>
import Children from "./Children.vue";
const count = ref(1)
const countInc = () => count.value++
</script>
b)子组件
<template>
<div>父组件传入的数据: {{count}}</div>
<n-button @click="incr">{{count}}</n-button>
</template>
<script setup>
defineProps({
count: {
type: Number,
default: NaN
}
})
const emit = defineEmits(['countIncr'])
const incr = () => {
//emit(父组件中的自定义方法, 参数1, 参数2....)
emit("countIncr")
}
</script>
1.5.4、新增API:defineExpose
子组件暴露属性,可以在父组件中拿到.
a)父组件
<template>
<Children ref="children"></Children>
<el-button :plain="true" @click="getChildrenNum">获取子组件中暴露的值</el-button>
</template>
<script setup>
import Children from "./Children.vue";
import {ElMessage} from "element-plus";
//注册 ref,获取组件(这里的变量名必须和 上面引入子组件时的 ref 中的值一样)
let children = ref()
const getChildrenNum = () => {
ElMessage({
message: `子组件的值为: ${children.value.num}`,
type: 'success',
})
}
</script>
b)子组件
<template>
<n-button @click="childrenIncr">点我 +1: {{num}}</n-button>
</template>
<script setup>
let num = ref(100)
function childrenIncr() {
num.value++
}
//暴露子组件中的属性
defineExpose({
num
})
</script>
1.6 组件通信
props
1)父给子传递数据
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
const toy = ref<string>('奥特曼')
</script>
<template>
<div>
<Child :toy />
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
// 方式一: 无类型(不推荐)
// defineProps(['toy'])
// 方式二: 有类型(推荐,传递时会进行类型检查)
defineProps<{
toy: string
}>()
</script>
<template>
<div>
父亲给孩子的玩具: {{ toy }}
</div>
</template>
2)父给子传递方法
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
const toy = ref<string>('奥特曼')
// 给子组件传递的方法
const sendToy = (): string => {
return "买一个玩具车"
}
</script>
<template>
<div>
<!-- 两种写法都可以(:后表示子组件通过 props 接受的名称)-->
<Child
:toy
:buyToy="sendToy"
/>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
// 方式一: 无类型(不推荐)
// defineProps(['toy', 'buyToy'])
// 方式二: 有类型(推荐,传递时会进行类型检查)
import {ref} from "vue";
const props = defineProps<{
toy: string,
// 接受父组件的方法
buyToy: () => string,
}>()
const myToy = ref<string>()
const buy = () => {
// 调用父组件提供的方法
myToy.value = props.buyToy()
}
</script>
<template>
<div>
父亲给孩子的玩具: {{ toy }}
<input type="text" v-model="myToy">
<button @click="buy">让父亲给我买玩具</button>
</div>
</template>
3)子组件给父组件传递数据:此处需要通过方法传递
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
// 子组件给父组件传递的数据放在这里,先声明出来
const childToy = ref<string>('')
// 子组件需要通过这里的方法给父组件传递数据
const sendToy = (toy: string) => {
// 将来子组件调用这个方法,就会成功把 toy 值传递给 childToy
childToy.value = toy
}
</script>
<template>
<div>
<!-- 两种写法都可以(:后表示子组件通过 props 接受的名称)-->
<Child
:buyToy="sendToy"
/>
<div>孩子想让我给他买玩具: {{ childToy }}</div>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
defineProps<{
buyToy: (toy: string) => void,
}>()
</script>
<template>
<div>
<!-- 调用父亲的方法,并把 "芭比娃娃" 这个值传递给父组件 -->
<button @click="buyToy('芭比娃娃')">我要买芭比娃娃</button>
</div>
</template>
自定义事件($emit)
自定义事件主要用于 子组件 给 父组件 传递数据.
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
// 将来接受子传递的数据
const t = ref('')
// 给子的方法
const saveToy = (toy: string) => {
t.value = toy
}
</script>
<template>
<div>
<!-- 对于自定义事件名,官方推荐使用 类似于 a-b-c 这种的肉串"命名" -->
<Child
@send-toy="saveToy"
/>
<div>子组件给我的玩具: {{ t }}</div>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
const emit = defineEmits(['send-toy'])
</script>
<template>
<div>
<!-- emit(事件名, 多个事件参数) -->
<button @click="emit('send-toy', '遥控飞机')">给父组件一个遥控飞机</button>
</div>
</template>
v-model
Ps:Vue 3.4+ 引入了 defineModel 语法.
双向绑定,任意一方修改数据都是响应式的
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
const title = ref('这是标题')
const content = ref('这是内容')
</script>
<template>
<div>
<Child
v-model:title="title"
v-model:content="content"
/>
<div>
父组件中 显示/修改 数据
<input type="text" v-model="title">
<input type="text" v-model="content">
</div>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
const title = defineModel<string>('title')
const content = defineModel<string>('content')
</script>
<template>
<div>
子组件中 显示/修改 数据
<input type="text" v-model="title">
<input type="text" v-model="content">
</div>
</template>
$attrs
attrs 适用于父组件给孙子组件传递数据.
当父组件给子组件通过 v-bind 传递多个数据,并且子组件通过 props 没有没有全部接收父组件传递的数据,那么没有被接收的数据就会被放到 attrs 中.
<!-- Father.vue -->
<script setup lang="ts">
import Child from "./Child.vue";
import {ref} from "vue";
const a = ref(1)
const b = ref(2)
</script>
<template>
<div>
<Child
:a="a"
:b="b"
/>
</div>
</template>
<!-- Child.vue -->
<script setup lang="ts">
import GChild from "./GChild.vue";
defineProps<{
a: number,
// 此处没有接收b,因此 b 就会被放到 attrs 中(任何没有被 props 接收的数据,都会被放到的 attrs 中)
}>()
</script>
<template>
<div>
<!-- 这里由于当前 Child 组件已经通过 props 接收了a,因此 attrs 中只有 b,此处就把 b 传递给孙子 -->
<GChild
v-bind="$attrs"
/>
</div>
</template>
<!-- GChild.vue(孙子j) -->
<script setup lang="ts">
</script>
<template>
<div>我是孙子,这是爷爷给我的东西: {{ $attrs.b }}</div>
</template>