一、父传子
1、父组件传递
父: App.vue, 通过使用组件 <导入的组件名 :属性名1=“” :属性名2=“”></导入的组件名>,传递给子组件
传递了一个t字符串类型是不需要v-bind,也就是不需要冒号,非字符串类型的必须加 v-bind(😃
<template><div>父集</div><hr><!--v-bind:属性名 或者 简写 :属性名--><waterFall :title="name" :arr="[1,2,3]"></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
let name = "lvmanba"
</script>
<style lang="scss">#app{@include bfc;}
</style>
2、子组件接收
通过defineProps来接收父传递过来的属性。defineProps是无须引入的直接使用即可
<template><div>子集</div><div>接收的字符串值为: {{ title }}</div><div>接收的数组值为: {{ arr }}</div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
//方法1:接收父组件传递过来的值使用 defineProps函数。
/*
defineProps({title:{type: String,default: "默认值"}
})
*/
//上面可以完成在模版中使用,如果想在ts中使用,则赋值给一个变量即可以在模版,也可以在ts中使用
// 注意,接收的变量不能和里面的变量名相同,如果相同,模版中的title显示的内容就为了:{ "title": "lvmanba" }
/*
const props = defineProps({title:{type: String,default: "默认值"}
})
console.log(props.title) //这样就可以读取到了。
*/
//-----------------------------------------------------------------------------------------------------
// 方法2:有了ts 可以直接使用泛型自变量的形式来接收
//接受一个泛型,里面是对象
/*
defineProps<{title:string
}>()
*/
// 赋值给变量,就可以在ts中使用了。
/*
const props = defineProps<{title: string,arr: number[]
}>()
*/
//-------------------------------------------------------------------------------------------------
/*
方法3, 如果父类没有传递过来参数,就会报错,需要设置默认值。
可以在方法2的基础上修改,使用ts特有的 withDefaults
它是一个函数,两个参数
1、第一个参数只能接受defineProps的返回值。
2、第二个参数是一个对象,用来设置默认值的, 如果类型复杂,需要使用一个函数来返回的。
如 arr:()=>{}*/
const props = withDefaults(defineProps<{title: string,arr: number[]
}>(),{title: "我是默认值",arr: ()=>[0]
})上面的写法也可以发开写,单独定义类型,然后直接使用defineProps
type prop = {title: string,arr: number[]
}
const props = withDefaults(defineProps<prop>(),{title: "我是默认值",arr: ()=>[0]
})</script>
二、子传父
1、方法一
子组件传值给父组件主要是子组件通过defineEmits注册一个自定义事件,而后触发emit去调用该自定义事件,并传递参数给父组件。defineEmits 是一个组合式 API,用于在组件中定义可以触发的事件。
子组件代码
<template><div>子集</div><button @click="send">给父组件传值</button>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
/*
第一种方式:
1、使用defineEmits 注册一个自定义事件,帮助你定义组件能触发的事件。
格式为: const emit = defineEmits(['方法1','方法2'...])
*/
const emit = defineEmits(['mymethod'])
//2、通过上面的点击事件,去调用我们注册的自定义事件,并传递值
const send = () =>{emit('mymethod','lvmanba1','birds2')
}
</script>
父文件代码
<template><div>父集</div><hr><!--v-on: 简写为"@""。为HTML标签绑定事件v-bind: 简写为":"。 为HTML标签绑定属性--><waterFall @mymethod="getName" ></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
const getName = (name:string) =>{ //传递的参数,传递几个,在这里就写几个参数,参数的名称不变。console.log(name)
}
</script>
2、方法二
defineEmits 接收一个泛型,接受一个对象,传递的是一个方法。可以写多个方法。
格式为:(方法名,参数名):返回值
<template><div>子集</div><button @click="send">给父组件传值</button>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
/*
第二种方式:defineEmits 接收一个泛型,类面接受一个对象
传递的是一个方法。可以写多个方法。
格式为:(方法名,参数名):返回值
*/const emit = defineEmits<{(e:"mymethod",name:string):void(e:"mymethod2",name:string):void(e:"mymethod3",name:string):void
}>()
const send = () =>{emit('mymethod','lvmanba1')emit('mymethod2','lvmanba2')emit('mymethod3','lvmanba3')
}
</script>
父类通过 v-on 来接收子类的方法。
<template><div>父集</div><hr><waterFall @mymethod="getName"></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
const getName = (name:string) =>{console.log(name)
}
</script>
父类也可以接受子类的多个方法
<template><div>父集</div><hr><!--v-on: 简写为"@""。为HTML标签绑定事件v-bind: 简写为":"。 为HTML标签绑定属性这里接收子类的两个方法,既mymethod 和 mymethod2,他们可以在父类中对应一个方法,也可以对应多个方法,这里对应的都是getName--><waterFall @mymethod="getName" @mymethod2="getName"></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
const getName = (name:string) =>{console.log(name)
}
</script>
3、方法三
子组件暴露方法或属性给父组件 defineExpose, 适合场景,表单验证等。父类使用ref来接收。
1、现在子类中保护方法或者属性
<template><div>子集</div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
// 这个是子类中要暴露的方法和属性
defineExpose({name:"lvmanba2024",open:()=>console.log("我是子类的方法")
})</script>
2、在父类中接收
<template><div>父集</div><hr><button @click="getSon">获取子类传递的信息</button><!--2、在引入组件中使用ref绑定下面定义组件类型的ref变量。当你在模板中使用 ref 时,可以通过这个引用来访问对应的组件或 DOM 元素的属性和方法。--><waterFall ref="water"></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
//InstanceType 是一个内置的工具类型,用于获取一个类的实例类型。
//在TS中用于获取变量或值的类型 typeof waterFall 获取waterFall的类型
//1、定义一个空的ref类型变量,变量名随意,我这里叫water,类型为引入子类的类型。
const water = ref<InstanceType<typeof waterFall>>()
const getSon=()=>{//3、通过空的ref变量来访问子类的属性和方法。console.log(water.value?.name)console.log(water.value?.open)
}
</script>
三、案例封装瀑布流组件
App.vue
<template><waterFall :list="list"></waterFall>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import waterFall from './components/water-fall.vue';
const list = [{height: 300,background: 'red'},{height: 400,background: 'pink'},{height: 500,background: 'blue'},{height: 200,background: 'green'},{height: 300,background: 'gray'},{height: 400,background: '#CC00FF'},{height: 200,background: 'black'},{height: 100,background: '#996666'},{height: 500,background: 'skyblue'},{height: 300,background: '#993366'},{height: 100,background: '#33FF33'},{height: 400,background: 'skyblue'},{height: 200,background: '#6633CC'},{height: 300,background: '#666699'},{height: 300,background: '#66CCFF'},{height: 300,background: 'skyblue'},{height: 200,background: '#CC3366'},{height: 200,background: '#CC9966'},{height: 200,background: '#FF00FF'},{height: 500,background: '#990000'},{height: 400,background: 'red'},{height: 100,background: '#999966'},{height: 200,background: '#CCCC66'},{height: 300,background: '#FF33FF'},{height: 400,background: '#FFFF66'},{height: 200,background: 'red'},{height: 100,background: 'skyblue'},{height: 200,background: '#33CC00'},{height: 300,background: '#330033'},{height: 100,background: '#0066CC'},{height: 200,background: 'skyblue'},{height: 100,background: '#006666'},{height: 200,background: 'yellow'},{height: 300,background: 'yellow'},{height: 100,background: '#33CCFF'},{height: 400,background: 'yellow'},{height: 400,background: 'yellow'},{height: 200,background: '#33FF00'},{height: 300,background: 'yellow'},{height: 100,background: 'green'}]
</script>
<style lang="scss">
#app,
html,
body {height: 100%;
}
*{padding: 0;margin: 0;
}
</style>
water-fall.vue 代码
<template><div class="wraps"><div :style="{height:item.height+'px',background: item.background,left:item.left+'px',top:item.top+'px'}" class="items" v-for="item in waterList"></div></div>
</template>
<script setup lang='ts'>
import { List } from '@element-plus/icons-vue';
import { ref,reactive,onMounted } from 'vue'
//接收父组件传递过来的数组
const props = defineProps<{list:any[]
}>()
//里面有高度和背景,又新增了左侧的绝对距离 和 顶部的高度。
const waterList = reactive<any[]>([])
const heightList:number[]=[]const init = () =>{const width = 130;const x = document.body.clientWidth; //可视区的宽度//console.log(x)//计算能显示多少列//console.log(x/width) 向下取整const column = Math.floor(x / width)//开始遍历for(let i=0; i<props.list.length;i++){if(i<column){ //第一行props.list[i].left = i * widthprops.list[i].top = 10waterList.push(props.list[i])heightList.push(props.list[i].height+10) //收集第一行的高度}else{//从第二行开始,就依次找高度最小的,放置在下面,为维持整体高度的一致性,形成瀑布流let current = heightList[0]; //假定第一个高度是最小的。let index = 0; //假定第一个高度是最小的,那么索引就为0//开始遍历,找真正高度最小的heightList.forEach((h,i)=>{if(current > h){current = h;index = i}})props.list[i].top = current + 20props.list[i].left = index * width//重新维护高度heightList[index] = heightList[index]+props.list[i].height + 20waterList.push(props.list[i])//console.log(heightList)//console.log(current)}}}
onMounted(()=>{window.onresize = () => init()init()
})
</script>
<style lang="scss" scoped>
.wraps{position: relative;height: 100%;.items{position: absolute;width: 120px;}
}
</style>