组件通信
在实际开发中,父子组件通信是十分常见的 所谓通信就是数据和事件的相互传递 有父组件向子组件传递数据,子组件向父组件发出事件,还有父子组件的数据双向绑定
在vue中,我们推崇单向数据流,但这不意味着双向数据流有着何种劣势,在特定的场景下,我们也十分建议使用双向数据绑定
双向绑定
v-model
<template>
<label>我是输入框: </label>
<input type="text" v-model="content">
<div class="mt-5">
我是content: {{ content }}
</div>
</template>
<script setup>
import { ref } from 'vue'
const content = ref("")
</script>
实现效果
可以使用v-model的元素
<input>
<textarea>
<select>
对于组件,可以使用
model
选项来定制v-model
的作用,下面的defineModel
中我们会说到
defineModel
defineModel() 返回的值是一个
ref
它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用
参数介绍 第一个参数是绑定名称 第二个参数是一个配置对象,用于指定绑定值的类型、是否必填及默认值
//defineModel每次只能定义一个模型
//如果有多个要申明的model要写就要写多个
const modelName = defineModel("modelName",{
type:xx, //值类型
required:xx, //bool 值是否必传 如果为true 但不传控制台会抛出警告
default:xx, //默认值
})
父组件代码展示
<!--parent-->
<template>
<div>
<label>父亲组件</label>
<input v-model="ctx">
</div>
<div>
<label>孩子组件</label>
<Child v-model:content="ctx" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from "./Child.vue"
const ctx = ref("")
</script>
子组件代码展示
<!--child-->
<template>
<input v-model="ctx1" />
</template>
<script setup>
const ctx1= defineModel("content", {
type: String,
required: false,
default: "xxoo"
})
//这里为了更好理解,故使用了不一样的名字,以免造成误会
</script>
实现效果
代码解释 父组件声明了一个响应式ctx,并使用v-model与父组件中的input绑定 子组件中使用defineModel自定义了一个v-model,名字为content,并赋值给ctx1(上文说过了这个事一个响应式ref,这里再强调一下) 然后将ctx1与子组件的input绑定 关键点就在于父组件中的
<Child v-model:content="ctx" />
这里的意思是将该组件(父组件)中的ctx于子组件中定义的 content(defineModel的第一个参数值)进行双向绑定
由此,我们就知道了内在逻辑,父组件input绑定了ctx,子组件自定义为content的v-model绑定了也ctx,所以我们无论修改哪个输入框,另外一个输入框都会同步改变(非拼字阶段) 具体参考官方文档
更多v-model详细 具体参考官方文档
父传子
prop介绍
prop 指的是组件的属性(property)
prop和attribute的不同
"prop"全称是"property"(属性)的缩写,专指在Vue组件中定义可以从外部传入的属性值
而"attribute"一词更广义,指HTML标签上的属性。在Vue模板渲染过程中,prop实际上会被编译为相应HTML元素的attribute
在 Vue.js 中,prop 用于父组件向子组件传递数据。子组件通过声明所需要的 prop,就可以像数据属性一样访问由父组件传递的值
prop的特点
prop 有以下几个主要特点:
- 单向数据流:prop 是单向绑定的,当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意修改父组件的状态数据。
"单向数据流"指的是prop的数据只能由父组件传递给子组件,子组件不能直接修改prop。但这并不意味着prop的值就是静态不变的
- 类型验证:我们可以为每个 prop 指定类型,如果传入的数据不符合要求,Vue 会在浏览器控制台发出警告
- 必传与默认值:可以指定 prop 是否必传,对于非必传的prop,我们也可以指定默认值
defineProps
defineProps用于子组件,申明一些prop,由父组件传递,支持js表达式
参数介绍
//props默认是仅仅可读的
//不同意 defineModel 的是 defineProps 可以一次声明多个prop,它接收一个对象作为参数,对象的每个键值对代表一个 prop 以及它的选项 这
const props = defineProps({
props1: {
type: xx, //值类型
required: xx, //bool 值是否必传 如果为true 但不传控制台会抛出警告
default: xx, //默认值
},
props2: {
// ...
}
})
父组件
<!-- parent-->
<template>
<Child title="essay.title" content="essay.content" :page="1" />
</template>
<script setup>
import { ref } from "vue";
import Child from "./Child.vue"
const essay = ref({
title: "这是一篇文章",
content: "这是文章内容",
})
</script>
子组件
<!-- child-->
<template>
<div> 题目 {{ props.title }}</div>
<div> 内容 {{ props.content }}</div>
<div> 页码 {{ props.page }}</div>
<div> 作者 {{ props.author }}</div>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
page: {
type: Number,
required: true
},
author: {
type: String,
default: "罹景偓佺"
}
})
</script>
实现效果
我们会看见结果不符合预期,并且控制台报警提示我们的page类型错误(期待一个Number类型但传递的是一个String类型)
js表达式解决数据类型问题
在Vue的模板语法中,我们可以在指令或者一些特殊的属性前面使用 : 前缀,这样该属性后面的值就会被当作一个JavaScript表达式而不是字符串
具体参考官方文档
使用JavaScript表达式后的代码
<!-- parent-->
<template>
<Child :title="essay.title" :content="essay.content" :page="1" />
</template>
<script setup>
import { ref } from "vue";
import Child from "./Child.vue"
const essay = ref({
title: "这是一篇文章",
content: "这是文章内容",
})
</script>
实现效果
defineProps的单向数据流是支持动态绑定的,也就是说JavaScript表达式子里面的响应式数据能被正确追踪,即父组件修改响应式数据的值,子组件对应的值也会修改
更多defineProps详细 具体参考官方文档
子传父
defineExpose
<!--parent-->
<template>
<child ref="childRef"></child>
</template>
<script setup>
import { ref, onMounted } from "vue";
import child from "./child.vue";
const childRef = ref(null);
onMounted(() => {
console.log(childRef.value.childText); //我是子组件的内容
childRef.value.childFunc(); //触发子组件事件
});
</script>
子组件
<!--child-->
<template>
<div>我是子组件</div>
</template>
<script setup>
const childText = "我是子组件的内容";
const childFunc = () => {
console.log("触发子组件事件");
};
defineExpose({
childText,
childFunc,
});
</script>
defineExpose用于以定义对象的形式实现导出子组件的数据或方法 父组件中得到子组件的ref即可调用 tips:要注意生命周期导致ref是否为null的问题
defineEmits
defineEmits 是 Vue3 组合式 API 中用于定义组件触发事件的方式。它允许组件通过触发自定义事件,将数据传递给父组件,从而实现组件之间的通信
在子组件中,我们可以使用 defineEmits 声明将要触发的事件名称,并使用 emits 函数来触发事件并传递数据。在父组件中,可以使用 v-on 或 @ 监听子组件触发的事件,并接收传递的数据
举个例子,点击子组件中的一个按钮触发一个事件,该事件触发emits,实现子组件向父组件发送一个事件和数据,从而实现组件通信,这个是作者认为比较直观的写法,当然因人而异,只要能理解和运用就是好的
父组件
<!--parent-->
<template>
<Child @foo="handelFoo"></Child>
</template>
<script setup>
import Child from "./Child.vue"
const handelFoo = (parms) => {
console.log(parms) //这是子组件传过来的参数
}
</script>
- 子组件
<!--child-->
<template>
<button @click="funcFoo">子组件emit发送事件</button>
</template>
<script setup>
const emits = defineEmits(["foo"])
const funcFoo = () => {
emits("foo", "这是子组件传过来的参数")
}
</script>
在这个例子中,我们点击子组件触发funcFoo函数,然后funcFoo函数中调用emits向父组件发送一个事件foo并传递相应数据,同时父组件监听这个foo事件,已经获得其传递的数据
!!!说到这里,你可能还没觉得这一功能有多么强大,但当我们实际使用之后,你一定会被该功能的强大和便利所震惊的
emits不同的使用方法
1.数组形式
defineEmits(['eventName', 'eventName2'])
- 对象形式
defineEmits({
eventName: null, // 没有验证函数
eventName2(payload) { // 验证函数
return payload instanceof Object
}
})
- 对象形式,同时定义 emits 选项
const emits = defineEmits({
_,
eventName: null,
eventWithValidation(payload) {
// 做一些验证
return true
}
})
我的建议 1.在大多数情况下,使用数组形式的 defineEmits 就已经足够了,它简单直接,易于理解和维护 2.如果需要对事件参数进行验证,我们完全可以在触发事件的地方或事件处理函数中进行验证,而不需要将验证逻辑放在 defineEmits 中。这样可以更好地遵循关注点分离的原则 3.使用对象形式定义验证函数的方式,主要是为了在极端情况下防止意外触发事件。但是在实际开发中,我们通常更倾向于信任开发人员不会错误地触发事件。过度使用验证函数可能会导致代码过于臃肿和难以维护
更多define详细 具体参考官方文档
使用props和emit实现v-model
接下来我们要使用props和emit来实现一个伪双向绑定的功能
以下是代码演示
父组件
<!-- parent-->
<template>
<div>实现逻辑 父组件内容更新 传递给子组件 实现同步</div>
<div>实现逻辑 子组件内容更新 传递给父组件 实现同步</div>
<div>
<label>父组件</label>
<input v-model="content">
</div>
<div>
<label>子组件</label>
<Child :modelValue="content" @update:modelValue="handelUpdate" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from "./Child.vue"
const content = ref("")
const handelUpdate = (ret) => {
content.value = ret
}
</script>
子组件
<!-- child-->
<template>
<input @input="update" :value="props.modelValue" />
</template>
<script setup>
const emits = defineEmits(["update:modelValue"])
const props = defineProps({
modelValue: {
type: String,
required: true,
}
})
const update = (event) => {
emits("update:modelValue", event.target.value)
}
</script>
实现效果
- 代码解释
我们在父组件申明了一个content响应式数据,并使用prop传递的子组件,实现单向绑定,现在修改父组件的内容子组件能做出同步变化
子组件中定义了emits,同时监听了input元素的input事件,触发input事件时update函数会调用emits向父组件发送一个update:modelValue事件和当前输入框内容的值
父组件中监听子组件的update:modelValue事件,接收到子组件中输入框的值,然后修改content的值,实现子组件和父组件的单向绑定,从而实现伪造的双向绑定
注意的点,为什么不在子组件修改prop呢?
原因很简单,prop默认是只读的,vue官方的设计理念也是让prop作为一个单向的数据流,所以我们在子组件的update函数中不是之间修改modelValue的值(虽然我们可以将该props设为可写,但这种做法是不推荐的,也是不专业的)
参考文档: 组件 v-model