vue封装一个公共组件

4,639 阅读3分钟

为什么要封装

1. 组件(Component)是Vue.js最强大的功能之一

2. 组件是可复用的Vue 实例,封装了可重用的代码。

3. 组件可以提升整个项目的开发效率,能够把页面抽象成多个相对独立的模块,让开发者使用小型、独立和通常可复用的组件构建大型应用,解决了我们传统项目开发:效率低难维护复用性低等问题。能让web前端代码实现“高内聚”和“低耦合”,使得前端开发的过程变成搭积木的过程。

场景引入

最近在搞一个房车迷的项目,项目中有很多图片在上文字描述在下的版块,它们的区别不过是图片和文字不同而已(下图红框部分),为了避免重复搬砖,就简单封装了一个丐版的组件

image.png

vue组件三要素

  1. props参数
  2. slot定制插槽
  3. 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
        }
    }
}

现在 超级迷你乞丐版本 已经升级到了 迷你乞丐版本 (字体大小和颜色也可以按照上面的方式去封装),到此,如行文开始的那张图上面红框部分就可以完全解决了,新的问题也出现了,当用户想要这样的效果时:(下图红框)

image.png

此时就要折行显示了,考虑到业务需要,折行之后可能每行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
        }
    }
}

完美解决折行问题,可以放心使用了,但是还是差了一点点,比如这样的:

image.png

在我们的常规内容之外,头上还多了一些东西,此时我们只需要给模板添加一个插槽就好了

<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>

=============结束=============