为什么要封装
1. 组件(Component)是Vue.js最强大的功能之一
2. 组件是可复用的Vue 实例,封装了可重用的代码。
3. 组件可以提升整个项目的开发效率,能够把页面抽象成多个相对独立的模块,让开发者使用小型、独立和通常可复用的组件构建大型应用,解决了我们传统项目开发:效率低
、难维护
、复用性低
等问题。能让web前端代码实现“高内聚”和“低耦合”,使得前端开发的过程变成搭积木的过程。
场景引入
最近在搞一个房车迷
的项目,项目中有很多图片在上文字描述在下
的版块,它们的区别不过是图片和文字不同而已(下图红框部分)
,为了避免重复搬砖,就简单封装了一个丐版
的组件
vue组件三要素
props参数
slot定制插槽
event自定义事件
思路
1. 既然结构一样,那么我们只需要在封装的模板中写好相对应的HTML
结构和CSS
样式
2. 具体数据使用props
传参的方式由父级注入,标签被点击时触发自定义事件
让父级得到响应
实现
我们通常用组件的方式如下,那我们就照着这个实现
// LyItem 为封装的组件
// list 为传入的数据
// @change 为自定义事件
<LyItem :list="item1" @change="itemChange1" />
1. 完成模板HTML及基础样式(先用假数据写好,再写成下面的结构)
<template>
<ul>
<li v-for="(t, i) in list" :key="i" @click="handClick(i)">
<img :src="t.img" alt="">
<span>{{ t.txt }}</span>
</li>
</ul>
</template>
// 样式略...
2. 接收参数并写好回调
export default {
props: {
// 展示的数据,包含img路径和图标下部分的名称
list: {
type: Array,
default: []
},
},
// 要使用emit先解构,而且必须接收props
setup(props, { emit }) {
// 用户点击回调,将点击的item下标返回
const handClick = i => {
emit("change", i)
}
return {
handClick
}
}
}
超级迷你乞丐版本
就诞生了,但是这时候每张图片大小都是一样的,这并不符合业务需要,所以需要继续改进,让用户可以自行设置图片的大小
export default {
props: {
// 展示的数据,包含img路径和图标下部分的名称
list: {
type: Array,
default: []
},
// 设置 img 的高度
imgHeight: {
type: String,
default: ""
},
// 设置 img 的宽度
imgWidth: {
type: String,
default: "27px"
},
},
setup(props, { emit }) {
// 用户点击回调,将点击的item下标返回
const handClick = i => {
emit("change", i)
}
// 设置img样式,可以单传宽度,那么高度与宽度相同。若都不传则默认宽高为27px
const imgStyle = {
width: props.imgWidth,
height: props.imgHeight || props.imgWidth
}
return {
handClick,imgStyle,liStyle
}
}
}
现在
超级迷你乞丐版本
已经升级到了迷你乞丐版本
(字体大小和颜色也可以按照上面的方式去封装),到此,如行文开始的那张图上面红框部分就可以完全解决了,新的问题也出现了,当用户想要这样的效果时:(下图红框)
此时就要折行显示了,考虑到业务需要,折行之后可能每行3个5个的,这些并不确定,所以需要用户通过
props
传入配置
export default {
props: {
// 展示的数据,包含img路径和图标下部分的名称
list: {
type: Array,
default: []
},
// 设置 img 的高度
imgHeight: {
type: String,
default: ""
},
// 设置 img 的宽度
imgWidth: {
type: String,
default: "27px"
},
// 如果需要折行显示,那么每一行显示多少个
wrap:{
type:[Number,String],
default:0
}
},
setup(props, { emit }) {
// 用户点击回调,将点击的item下标返回
const handClick = i => {
console.log(props.wrap);
emit("change", i)
}
// 设置li样式,看 item 有多少项来分配占比,如5项那每个占 20%
// 如果wrap存在说明需要折行显示,那么根据用户想要一行显示多少个来配置占比
const width = props.wrap ? Math.floor(100/props.wrap) : Math.floor(100/props.list.length)
const liStyle = {
width: `${width}%`
}
// 设置img样式,可以单传宽度,那么高度与宽度相同。若都不传则默认宽高为27px
const imgStyle = {
width: props.imgWidth,
height: props.imgHeight || props.imgWidth
}
return {
handClick,imgStyle,liStyle
}
}
}
完美解决折行问题,可以放心使用了,但是还是差了一点点,比如这样的:
在我们的常规内容之外,头上还多了一些东西,此时我们只需要给模板添加一个插槽就好了
<template>
// 插槽让用户自己传入想要的内容,如果有多个可以用具名插槽,我就偷懒了
<slot />
<ul>
<li v-for="(t, i) in list" :key="i" :style="liStyle" @click="handClick(i)">
<img :src="t.img" alt="" :style="imgStyle">
<span>{{ t.txt }}</span>
</li>
</ul>
</template>
麻雀虽小,五脏差不多全了
props参数
slot定制插槽
event自定义事件
组件该有的基本都有了
完整代码
组件
<template>
<slot />
<ul>
<li v-for="(t, i) in list" :key="i" :style="liStyle" @click="handClick(i)">
<img :src="t.img" alt="" :style="imgStyle">
<span>{{ t.txt }}</span>
</li>
</ul>
</template>
<script>
export default {
props: {
// 展示的数据,包含img路径和图标下部分的名称
list: {
type: Array,
default: []
},
// 设置 img 的高度
imgHeight: {
type: String,
default: ""
},
// 设置 img 的宽度
imgWidth: {
type: String,
default: "27px"
},
// 如果需要折行显示,那么每一行显示多少个
wrap:{
type:[Number,String],
default:0
}
},
setup(props, { emit }) {
// 用户点击回调,将点击的item下标返回
const handClick = i => {
console.log(props.wrap);
emit("change", i)
}
// 设置li样式,看 item 有多少项来分配占比,如5项那每个占 20%
// 如果wrap存在说明需要折行显示,那么根据用户想要一行显示多少个来配置占比
const width = props.wrap ? Math.floor(100/props.wrap) : Math.floor(100/props.list.length)
const liStyle = {
width: `${width}%`
}
// 设置img样式,可以单传宽度,那么高度与宽度相同。若都不传则默认宽高为27px
const imgStyle = {
width: props.imgWidth,
height: props.imgHeight || props.imgWidth
}
return {
handClick,imgStyle,liStyle
}
}
}
</script>
<style scoped>
ul {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
border-radius: 10px;
padding: 10px 0;
background-color: #fff;
}
li {
display: flex;
flex-direction: column;
align-items: center;
}
li>span {
font-size: 14px;
color: #666;
padding: 5px 0;
}
</style>
使用
// 引入
import LyItem from '../../components/common/LyItem.vue';
//使用
<LyItem :wrap="3" imgHeight="70px" imgWidth="92px" :list="item3" @change="itemChange3">
<div class="flex">
<p>推荐车型</p>
<div class="img"><img src="../../assets/刷新.png" alt=""> 换一换</div>
</div>
</LyItem>
=============结束=============