📌前言
关于一次性总结的文章比较少 , 加上最近写代码发现了父子双向绑定defineModel()
,因此便写下这一篇。
和我其他的文章一样 , 我会根据我的理解把经常使用的简单的描述出来 , 比较冷门的可能不会写, 如有问题和缺少 , 还请大佬补充。
搭建父子组件
要进行父子组件的操作 , 首先先搭建起来 , 下面是简单搭建。
目录结构
父组件/parent/index.vue
html
<template>
<div>父组件</div>
<Child> </Child>
</template>
<script setup lang="ts">
// 导出子组件
import Child from "./components/Child.vue";
</script>
<style lang="scss" scoped></style>
子组件/parent/components/Child.vue
html
<template>
<div>
<div>子组件</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>
这样最简单的搭建就完成了
一、🍉通信
这是最基础的操作了 , 相信大家都会了 , 就简单描写过一遍。
1、父传子
- 和使用内置组件一样 , 只需要在子组件的标签上打
:冒号
- 左侧子组件接收名称 , 右侧父组件传入的值就行。
父组件
html
<template>
<div>父组件</div>
<hr />
<!-- childValue子组件接收的名称 可以写成child-value -->
<Child :child-value="parentValue"></Child>
</template>
<script setup lang="ts">
// 导出子组件
import { ref } from "vue";
import Child from "./components/Child.vue";
// 定义一个父组件值
let parentValue = ref<string>("父组件值");
</script>
子组件
代码都是ts
的写法 , 如果要js
的需要自行转换一下
withDefaults
是ts
特有的写法 , 使用js
的话不需要写
?
的意思是非必传项- 如果没有传并且没有给默认值打印出来是
undefined
,页面渲染不会显示 - 在
html
渲染的时候props.
可以不写 , 不过建议还是写上
html
<template>
<div>
<div>子组件</div>
<div>没有设置默认值: {{ props.notDefaultValue }}</div>
<div>设置了默认值: {{ props.defaultValue }}</div>
<div>接收到的父组件值: {{ props.childValue }}</div>
<div>接收到的父组件值(不写props.): {{ childValue }}</div>
</div>
</template>
<script setup lang="ts">
// 接收父组件值
const props = withDefaults(
defineProps<{
defaultValue?: string;
notDefaultValue?: string;
childValue: string;
}>(),
{
defaultValue: "默认值"
}
);
</script>
效果
2、子传父(自定义事件)
- 因为是事件所以不是使用
:冒号
而是@符号
了 - 左侧子组件事件名称 , 右侧父组件接收函数。
父组件
使用childEmit
接收子组件事件
html
<template>
<div>父组件</div>
<hr />
<!-- childEmit可以写成child-emit -->
<Child @child-emit="childEmit"></Child>
</template>
<script setup lang="ts">
// 导出子组件
import Child from "./components/Child.vue";
// 子组件事件
const childEmit = (value: string) => {
console.log(value);
};
</script>
子组件
- 使用
defineEmits
泛型定义一个对象 ,参数1:事件名
, 其他参数:事件参数 , 有几个传几个,可以不传 - 使用事件的方式是调用
emit
并传参告诉它调用的事件和事件参数。 - 可以设置事件返回值 , 一般没有 , 写
void
html
<template>
<div>
<div>子组件</div>
<!-- 参数1:事件名 , 参数2....:传输数据 -->
<button @click="emit('childEmit', '哈哈')">发送子组件定义事件</button>
</div>
</template>
<script setup lang="ts">
// 设置事件 参数1:事件名 , 参数2....:传输数据
const emit = defineEmits<{
(e: "childEmit", value: string): void;
(e: "childEmit2", value: string, value2: number): void;
(e: "childEmit3"): void;
}>();
</script>
效果
点击按钮
3、defineExpose(暴露)
将子组件的属性和函数暴露给父组件调用 , 如果使用的灵活是非常方便的。
注意:vue3 3.2+新增
父组件
- 拿到子组件的DOM元素
- 直接点出暴露属性和函数
注意: 不能在setup函数下直接调用,DOM元素还未挂载
html
<template>
<div>父组件</div>
子组件属性: {{ ChildRef?.msg }}
<hr />
<Child ref="ChildRef"></Child>
</template>
<script setup lang="ts">
// 导出子组件
import { onMounted, ref } from "vue";
import Child from "./components/Child.vue";
// 获取子组件DOM元素
let ChildRef = ref<InstanceType<typeof Child> | null>(null);
onMounted(() => {
// 调用子组件方法
ChildRef.value?.childFn();
});
</script>
子组件
html
<template>
<div>
<div>子组件</div>
</div>
</template>
<script setup lang="ts">
function childFn() {
console.log("我是子组件函数");
}
const msg = "子组件msg";
defineExpose({
msg,
childFn
});
</script>
效果
二、🍈插槽
有时候组件会在不同的页面上产生不同的变化 , 为了组件的兼容性 , 插槽就不可或缺了
父组件
- 写在子标签内部
- 默认 : 直接写
- 支持给具体名称,具体调用
- 支持调用多次
html
<template>
<div>父组件</div>
<hr />
<Child>
默认数据
<template #title>title</template>
<template #body>body</template>
</Child>
</template>
<script setup lang="ts">
// 导出子组件
import Child from "./components/Child.vue";
</script>
子组件
html
<template>
<div>
<div>子组件</div>
<div><slot></slot></div>
<div><slot name="title"></slot></div>
<div><slot name="body"></slot></div>
</div>
</template>
效果
三、🍇双向数据绑定
这也是我最近写项目时发现的
因为使用普通的父子通信 , 我感到非常的麻烦
所以我在想能不能像input
一样绑定一个值呢 , 通过查询资料也是成功发现并学会了。
注意:vue3 3.4+新增
1、基础使用
父组件
可以看到我使用v-model
将值传给了子组件
html
<template>
<div>父组件</div>
{{ value }}
<hr />
<Child v-model="value"> </Child>
</template>
<script setup lang="ts">
// 导出子组件
import { ref } from "vue";
import Child from "./components/Child.vue";
// 定义值
let value = ref<string>("默认值");
</script>
子组件
可以使用默认直接接收 , 也可以对接收值进行处理。
html
<template>
<div>
<div>子组件</div>
<input v-model="value" />
</div>
</template>
<script setup lang="ts">
//接收父组件v-model的值
// 正常接收
let value = defineModel();
// 配置接收
let value = defineModel({
// 是否必传
required: true,
// 默认值
default: "默认内容",
// 数据类型
type: String,
// 校验器 val-父组件传来的值
validator: (val: any) => {
// 返回false否则会发出警告
return val;
}
})
</script>
效果
接收到之后我使用input改变value的值 , 可以看到父组件的值也被改变了
2、进阶使用
我们已经可以基本使用双向绑定了 , 但是它还有更多的功能
- 绑定多个数据
- 数据初始化
父组件
- 需要绑定多个数据
v-model:[名称]
直接写就行 - 接收时第一个参数是
[名称]
html
<template>
<div>父组件</div>
<div>{{ title }}</div>
<div>{{ content }}</div>
<hr />
<Child v-model:title="title" v-model:content="content"> </Child>
</template>
<script setup lang="ts">
// 导出子组件
import { ref } from "vue";
import Child from "./components/Child.vue";
// 定义值
let title = ref<string>("标题");
let content = ref<string>("内容");
</script>
子组件
html
<template>
<div>
<div>子组件</div>
<input v-model="title" />
<input v-model="content" />
</div>
</template>
<script setup lang="ts">
let title = defineModel("title");
let content = defineModel("content", {
// 是否必传
required: true,
// 默认值
default: "默认内容",
// 数据类型
type: String,
// 校验器 val-父组件传来的值
validator: (val: any) => {
// 返回false否则会发出警告
return val;
}
});
</script>
效果
📚总结
组件的应用还是很重要的 , 一个好的组件能不仅能加快开发时间 , 还能增强可维护性。