文章目录
一、概述
在Vue的框架开发的项目过程中,经常会用到组件来管理不同的功能,有一些公共的组件会被提取出来。这时必然会产生一些疑问和需求?比如一个组件调用另一个组件作为自己的子组件,那么我们如何进行给子组件进行传值呢?如果是电商网站系统的开发,还会涉及到购物车的选项,这时候就会涉及到非父子组件传值的情况。
父子通信是Vue项目开发过程中最常见使用最频繁的通信方式,可以解决大部分开发中的数据通信问题,简单易操作。此文章主要介绍父组件传递数据给子组件,以及子组件传递数据给父组件、子组件暴露树形给父组件三个部分的内容。
数字化管理平台
Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus
权限系统-商城
个人博客地址
Vue 开发中常见的三种数据传递情况:父传子、子传父、非父子组件间值的传递
二、父组件向子组件传值
父传子:父组件直接把要传递的数据写在子组件标签身上,子组件通过 defineProps 接收父组件传递过来的数据,之后该数据即可在子组件内部使用。
2.1 父组件属性绑定传递数据
父组件 App.vue 中传递 type 和 loading 两个属性值给子组件 Ogbutton
<script setup>
import OgButton from "@c/ogbutton/Ogbutton.vue"
import { reactive } from "vue"
let state = reactive({
loading: false
})
</script>
<template>
<og-button type="warning" :loading="state.loading">Warning</og-button>
<og-button :type="'success'">Success</og-button>
</template>
2.2 子组件接收数据
子组件 OgButton.vue 中通过 defineProps 宏(宏无需引入,直接使用即可)接收传递过来的值
<script setup>
import { computed, toRefs } from "vue"
const props = defineProps({
type: {
type: String,
default: "default"
},
loading: {
type: Boolean,
default: false
}
})
const { type,loading } = toRefs(props);
const btnType = computed(() => {
return type.value ? `btn-${type.value}` : ""
})
</script>
<template>
<button class="btn" :class="btnType">
<i class="iconfont icon-loading" v-if="loading" style="margin-right:6px"></i>
</button>
</template>
注:vue 设计是单向数据流,数据的流动方向只能是自上往下。为了防止从子组件意外变更父组件的状态,导致应用的数据流向难以理解,规定子组件中不能修改父组件中的数据。
三、瀑布流案例
<script setup>
import { ref,reactive, onMounted } from 'vue'
// 定义 ref 引用
let waterfall = ref();
// 接收 list 数据
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
// 定义一个空数组,存储处理后的数据
let waterList = reactive([]);
let heightList = reactive([])
// 初始化函数
const init = () => {
// 定义每一个部分的宽度(大于元素宽度,用于使元素之间拉开间隔,美观)
const width = 160;
// 获取当前浏览器的有效宽度
const aw = document.documentElement.clientWidth;
console.log("当前浏览器的有效宽度aw:", aw)
// 通过上面两个参数,求出一行可以摆放的列数
const column = Math.trunc(aw / width); // Math.trunc() 对数字取整
console.log(`共${column}列!`)
// 遍历接收的 list 数据
props.list.forEach((val, idx) => {
if (idx < column) { //展示第一行数据(列数是固定的,heightList元素数量是固定的,改变的是高度)
val.left = width * idx;
val.top = 20;
waterList.push(val)
heightList.push(val.height + 10)
} else { // 其它行数据,新的数据放到上一行最短的那一个的下面
// 假设第一列高度为最短的
let cutIdx = 0;
let cutHeight = heightList[0];
// 遍历 heightList 当前所有列的高度,找出最低的那一列
heightList.forEach((h,i) => {
if(cutHeight > h){
cutHeight = h;
cutIdx = i;
}
})
// console.log("最低的一列:" + (cutIdx + 1) + "列,高度为:" + cutHeight)
val.top = cutHeight + 20
val.left = width * cutIdx;
heightList[cutIdx] = heightList[cutIdx] + val.height + 10
waterList.push(val)
// 清除决定定位造成的塌陷,动态修改父元素高度为最大的那个
// console.log(waterfall,waterfall.value)
waterfall.value.style.height = heightList[cutIdx] + "px"
waterfall.value.style.width = width * column + "px";
// 设置容器居中显式(可放到css中实现)
// waterfall.value.style.marginLeft = "auto";
// waterfall.value.style.marginRight = "auto";
}
})
}
// 页面挂载完成的生命周期钩子
onMounted(() => {
init()
// 监听页面变化,重新对色块排列
window.onresize = function () {
console.log("onresize...")
// 清空原数组
waterList.splice(0, waterList.length)
heightList.splice(0, heightList.length)
init()
}
})
</script>
<template>
<div class="wrap" ref="waterfall">
<div v-for="item in waterList"
:style="{ height: item.height + 'px', background: item.background, top: item.top + 'px', left: item.left + 'px' }"
class="item">{{ item }}</div>
</div>
</template>
<style scoped lang="scss">
// 通过定位摆放元素位置
.wrap {
position: relative;
margin:0 auto;
.item {
position: absolute;
width: 150px;
transform: translateX(5px);
}
}
</style>
四、子组件向父组件传值
在父组件中,也就是子组件的标签身上定义自定义事件,该事件的作用就是接收子组件传递过来的值,然后进行一些操作;在子组件中,通过defineEmits方法,参数是一个数组或者对象,该方法返回一个箭头函数,这个函数需要传递参数,第一个参数是事件类型(事件名称),第二个及以后的参数都表示要传递给父组件的数据。
template 模板中直接使用 $emit() 触发这个事件,script 模块中通过defineEmits宏返回的函数触发这个事件
两个触发事件的函数参数:
- 第一个参数为自定义的事件名称
- 第二个参数为需要传递的数据
4.1 子组件派发事件并传递数据
<script setup>
const emits = defineEmits(['formSubmit', 'formBack'])
const submitForm = (formEle) => {
console.log(formEle)
if (!formEle) return
emits('formSubmit', formEle)
}
const resetForm = (formEle) => {
if (!formEle) return
formEle.resetFields()
}
const backFn = () => {
// history.back()
emits('formBack','返回操作传递的数据....')
}
</script>
<template>
<div>
<el-form ref="formRef" scroll-to-error>
<el-form-item>
...省略表单控件渲染代码
</el-form-item>
<el-form-item class="btns">
<el-button type="primary" @click="submitForm(formRef)">提交</el-button>
<el-button type="warning" @click="resetForm(formRef)">重置</el-button>
<el-button type="danger" @click="backFn">返回</el-button>
</el-form-item>
</el-form>
</div>
</template>
4.2 父组件注册并接收参数
父组件监听子组件中定义的事件,并接收传递过来的数据
<script setup>
import OgForm from "@c/ogform/Ogform.vue"
const submit = (formEle) => {
formEle.validate((valid, fields) => {
....省略代码
})
}
const back = (arg) => {
....省略代码
}
</script>
<template>
<div>
<og-form @form-submit="submit" @form-back="back"></og-form>
</div>
</template>
五、子组件暴露内部属性给父组件
用了 <script setup>
的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup>
的子组件中的任何东西,除非子组件在其中通过 defineExpose
编译器宏显式暴露。
Child.vue 子组件通过 defineExpose
宏暴露私有属性:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
父组件 ref 模板引用:
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const child = ref(null)
onMounted(() => {
console.log(child.value)
})
</script>
<template>
<Child ref="child" />
</template>
六、涉及到的混合宏介绍
为了在声明 props 和 emits 选项时获得完整的类型推导支持,我们可以使用 defineProps 和 defineEmits API,它们将自动地在 <script setup>
中可用:
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
- defineProps 和 defineEmits 都是只能在
<script setup>
中使用的编译器宏。他们不需要导入,且会随着<script setup>
的处理过程一同被编译掉。 - defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
- defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推导。
- 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。