大家好,我是兔兔,今天来给大家分享关于图鸟UI中tabbar的基本用法。
看到有的同学在图鸟群中反馈,对tabbar组件如何使用,以及子父组件之间的各种事件通信,不知道如何使用,今天我就来给大家分享一下。如涉及到不正确的地方,欢迎大家指正,同时也欢迎大家留言反馈日常遇到的问题和经验。
开篇还是闲聊一下,我个人关于组件的一些观点。
什么是组件
在前端开发中,组件(Component)是一种代码复用和模块化设计的方法。组件是自包含的代码片段,具有特定的功能和样式,可以在整个应用程序中重复使用:
- 复用性:组件可以被多次使用,减少了代码重复,提高了开发效率。
- 封装性:组件封装了特定的功能和样式,隐藏了内部实现细节,使得代码更加清晰和易于维护。
- 可维护性:当组件需要更新或修复时,只需修改一次,所有使用该组件的地方都会自动更新。
- 可测试性:组件可以独立测试,确保其功能正常,提高了代码的稳定性和可靠性。
- 灵活性:组件可以根据需要进行配置和扩展,适应不同的使用场景。
组件通常用于以下场景:
- UI元素:按钮、输入框、下拉菜单等基本的界面元素。
- 布局组件:用于构建页面布局的组件,如头部、侧边栏、页脚等。
- 功能组件:具有特定功能的组件,如表单验证、数据展示等。
- 页面组件:构成页面的大块组件,如商品列表、用户评论等。
在现代前端框架中,如React、Vue和Angular,组件是核心概念之一。这些框架提供了丰富的工具和API来创建和管理组件,使得开发大型、复杂的前端应用程序变得更加容易。
说人话就是:将重复的代码给单独抽离出来,封装成独立的一块,在需要的时候,直接调用即可。
前端组件开发,并不是前端所独有的。说白了,前端所谓的组件开发,就和后端的面向对象的开发一样,就是将重复的代码抽离出来,封装成独立的一块,在需要的时候,直接调用即可。
如何去设计组件
对于组件的设计,需要根据业务而定,不同的业务有不同的组件设计方式,同时我们也要保证其具备扩展性和可维护性。例如一个常见的用户信息表单,里面可能有姓名、性别、年龄、地址等字段,我们可以将这些字段封装成一个组件,然后通过props的方式传入数据,然后组件内部根据props的数据进行渲染。但另外的一个用户信息表单,可能有手机号、性别、头像等字段。如果我们按照表单级别来进行封装,就会出信息表单无法复用的,所谓的组件就没实际价值。因此在组件封装时,我们尽可能按照小颗粒度来进行封装。就像element ui一样,按钮是一个组件、复选框是一个组件、输入框是一个组件,这些组件都是可以复用的。
这里总结几点组件封装的设计原则,组件的设计应当遵循一些基本原则,以确保它们是可维护的、可扩展的、并且易于使用:
- 单一职责原则:每个组件应该只负责一件事情。这有助于减少组件的复杂性,使其更容易理解和维护。
- 封装性:组件应该封装其内部状态和行为,只通过定义好的接口与外部交互。这有助于隐藏实现细节,减少组件间的依赖。
- 可复用性:设计组件时,应考虑它们在不同场景下的复用性。避免为特定场景定制组件,除非这种定制是不可避免的。
- 可配置性:允许组件通过属性(props)或插槽(slots)等机制接受外部配置,以适应不同的使用场景。
- 独立性:组件应尽可能独立于其他组件,避免紧密耦合。这有助于在不影响其他组件的情况下修改或替换单个组件。
- 可测试性:设计组件时,应确保它们易于进行单元测试。这通常意味着避免依赖全局状态或外部服务。
- 性能:组件的设计应考虑性能影响,避免不必要的渲染或计算,特别是在大型应用程序中。
- 可访问性:组件应遵循无障碍设计原则,确保所有用户都能使用,包括那些使用辅助技术的用户。
- 样式一致性:在设计组件时,应保持应用程序内样式的一致性,以提供统一的用户体验。
- 文档和示例:为组件提供清晰的文档和使用示例,帮助开发者理解如何使用和集成这些组件。
- 版本控制:为组件维护版本号,确保在更新时向后兼容,避免破坏现有功能。
- 国际化和本地化:如果应用程序面向多语言用户,组件设计应支持国际化和本地化。
图鸟组件
在小程序开发中,可以直接配置原生的tabbar菜单,但原生的tabbar在UI效果、事件处理等方面没有对应的接口支持,因此要避开这些问题,就需要自定义tabbar。图鸟UI中tabbar组件,是一个简单的tabbar组件,它由一个父组件和一个子组件组成,父组件负责管理tabbar的样式和状态,子组件负责管理tabbar的点击事件。 下面我以图鸟UI会员模版举例,对该模版感兴趣的同学也可以咨询一下该套模版。
在代码目录中,目录结构中是这样的(为了省略一些篇幅,下面实际代码就只展示A和B两个页面):
home # 入口目录
home.vue # tabbar页面,也就是父组件和子组件构成的页面
components # 子组件目录
PageA.vue # 子组件页面A,程序的首页
PageB.vue # 子组件页面B,视频分类页
PageC.vue # 子组件页面C,热门视频页
PageD.vue # 子组件页面D,个人主页面
就是上面几个简单的文件,就构成了一个tabbar+四个业务页面的模版。效果图如下:
主页代码结构
下面是home.vue文件的部分代码结构,为了方面展示,就只显示核心的部分代码:
{/* 加载子组件开始 */}
页面A
<view v-if="tabberPageLoadFlag[0]":style="{ display: currentTabbarIndex === 0 ? '' : 'none'}">
<scroll-view
class="custom-tabbar-page"
scroll-y
enable-back-to-top
:lower-threshold="100"
@scrolltolower="tabbarPageScrollLower"
>
<page-a ref="pageA" :bannerList="bannerList"></page-a>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[0]":style="{ display: currentTabbarIndex === 0 ? '' : 'none'}">
<scroll-view
class="custom-tabbar-page"
scroll-y
enable-back-to-top
:lower-threshold="100"
@scrolltolower="tabbarPageScrollLower"
>
<page-b ref="pageB" :bannerList="bannerList"></page-b>
</scroll-view>
</view>
{/* 加载子组件结束 */}
{/* 底部导航栏开始 */}
<view class="tabbar">
<view class="action" @tap.stop="changeTabbar(0)">
<view class="bar-icon">
<view class="" :class="[currentTabbarIndex === 0 ? 'tn-icon-home-love-fill' : 'tn-icon-home-love']">
</view>
</view>
<view class="" :class="[currentTabbarIndex === 0 ? '' : '']">首页</view>
</view>
<view class="action" @tap.stop="changeTabbar(1)">
<view class="bar-icon">
<view class="" :class="[currentTabbarIndex === 1 ? 'tn-icon-reload-planet-fill' : 'tn-icon-reload-planet']">
</view>
</view>
<view class="" :class="[currentTabbarIndex === 1 ? '' : '']">发现</view>
</view>
</view>
{/* 底部导航栏结束 */}
下面是home.vue中JavaScript部分代码,主要的作用就是引入子组件文件,并根据tabbar的点击事件动态切换子组件:
import PageA from './component/PageA.vue'
import PageB from './component/PageB.vue'
export default {
components: {
PageA,
PageB
},
data: {
return {
// 用来显示当前被点击的tabbar的索引,从而会展示不同的子组件页面
currentTabbarIndex: 0,
tabberPageLoadFlag: [],
}
},
// 修改当前选中的tabbar
changeTabbar(index) {
if (this.currentTabbarIndex === index) return
this._switchTabbarPage(index)
this.currentTabbarIndex = index
},
// 根据选中的tabbar索引,切换子组件页面
_switchTabbarPage(index) {
const selectPageFlag = this.tabberPageLoadFlag[index]
if (selectPageFlag === undefined) {
return
}
if (selectPageFlag === false) {
this.tabberPageLoadFlag[index] = true
}
},
}
子组件页面
子组件页面就跟我们平常的页面一样,正常编写自己的业务代码即可。
<template>
<view class="pages-a">
<!-- 观看记录,一般显示三个够了,别整那种左右滑动的,产品体验很差 start-->
<view class="tn-flex tn-flex-wrap" style="margin: 0 20rpx;">
<block v-for="(item, index) in historyCourseList" :key="index">
<view class="" style="width: 50%;">
<view class="product-content">
<view class="image-pic" :style="'background-image:url(' + item.cover + ')'">
<view class="image-product">
<view class="tn-flex tn-flex-col-center tn-text-center"
style="height: 38rpx;position: absolute;top: 10rpx;left:10rpx;background-color: #00000080;border-radius: 10rpx;">
<view class="tn-margin-xs tn-color-white">{{item.category.title}}</view>
</view>
<view class="tn-text-df"
style="width: 100%;height: 40rpx;background: linear-gradient(0deg, rgba(0,0,0,0.6), rgba(0,0,0,0));position: absolute;bottom: 0;">
<view class="tn-padding-left-xs tn-padding-right-xs tn-color-white clamp-text-1">
<text class="tn-icon-play-fill"></text>
<text class="tn-padding-left-xs">{{ item.play }}</text>
</view>
</view>
</view>
</view>
<view class="tn-text-justify tn-padding-top-sm">
<text class="clamp-text-1">{{ item.title }}</text>
</view>
<view class="tn-text-justify tn-padding-top-xs">
<text class="tn-text-sm tn-color-gray">完成{{ item.course_number }}课时</text>
</view>
</view>
</view>
</block>
</view>
<!-- 观看记录 end-->
</view>
</template>
<script>
export default {
name: 'PagesA',
props: {
bannerList: {
type: Array,
default: []
}
},
data() {
return {
historyCourseList: [],
}
},
created() {
// 请求后端数据
this.fetchRecommendCourse()
},
methods: {
fetchRecommendCourse() {
uni.showLoading({
title: '努力加载中',
mask: true
})
courseList({
is_recommend: 1,
page: 1,
size: 20
}).then(res => {
if (res.code === 100) {
this.courseList = res.data.items
return
}
this.$func.showToast(res.msg)
}).finally(res => {
uni.hideLoading()
})
},
}
}
</script>
常见问题
上面总结了自定义tabbar组件如何使用,很多同学在使用子组件和父组件之间相互通信,可能会遇到问题,这里也简单的做几个总结。
子组件传递给父组件
子组件传递给父组件指的是,当子组件中发生事件操作,需要将数据传递给父组件,此时改如何做。解决的方法是通过$emit()
实现通信。具体更多的细节也可以参考一下vue的官方文档。
// 在子组件方法中,通过$emit()触发事件,并将数据传递给父组件
this.$emit('changeData', index)
// 在父组件中,调用子组件的地方,进行事件处理,需要注意的是这里的中划线
<page-a ref="pageA" :bannerList="bannerList" @change-data="父组件对应的方法"></page-a>
<script>
function 父组件对应的方法(index) {
console.log(index, "接收子组件传递过来的值")
}
</script>
父组件传递给子组件
父组件传递给子组件指的是,当父组件中发生事件操作,需要将数据传递给子组件,此时改如何做。解决的方法是通过$refs
获取子组件,然后调用子组件的方法。具体更多的细节也可以参考一下vue的官方文档。
这里用页面滚动加载数据为例,当页面向下滚动时,需要向后端加载新的数据。如果把这个事件放在子组件中去处理,你会发现不会生效。因为该组件需要再父组件中去监听。
<template>
<page-a ref="pageA"></page-a>
</template>
export default {
methods: {
// 在父组件监听页面滚动事件
onPageScroll(e) {
// 这里的pageA是调用子组件定义的ref命令
this.$ref.pageA.onPageScroll(e)
}
}
}
接着在子组件中定义方法,处理对应的业务逻辑。
export default {
methods: {
onPageScroll(e) {
// 在这里去加载新的数据,或者处理业务逻辑
}
}
}
父组件传参
父组件传参指的是,父组件向子组件传递数据,子组件接收到数据后,进行业务逻辑处理。这应该是组件开发,最基础的知识点,要实现数据的传递,就需要使用到props,具体细节可以阅读官方文档地址。 在父组件调用子组件时,添加props属性,并指定对应的值。
<template>
<page-a ref="pageA" :bannerList="bannerList"></page-a>
</template>
<script>
export default {
bannerList: [{
title: "轮播图标题",
image: "https://www.baidu.com/img/bd_logo1.png?where=super",
},{
title: "轮播图标题",
image: "https://www.baidu.com/img/bd_logo1.png?where=super",
},]
}
</script>
然后在子组件中定义props属性,接收父组件传递过来的数据。
<swiper class="card-swiper" :circular="true" :autoplay="true" duration="500" interval="8000">
<swiper-item v-for="(item,index) in bannerList" :key="index" :class="cardCur==index?'cur':''">
<view class="swiper-item image-banner"
:style="'background-image:url('+ item.image + ');background-size: cover;border-radius: 15rpx;'">
</view>
<view class="swiper-item-text">
<view class="tn-text-bold tn-color-white" style="font-size: 50rpx;">{{item.first_title}}</view>
<view class="tn-color-white tn-padding-top" style="font-size: 30rpx;">{{item.second_title}}</view>
</view>
</swiper-item>
</swiper>
export default {
name: 'PagesA',
props: {
bannerList: {
type: Array,
default: []
}
},
}
自此关于图鸟UI在使用自定义tabbar,组件调用以及组件之间数据、事件的通信都介绍完了。希望本文的分享能够对大家使用图鸟UI有所帮助。同时也欢迎大家提出意见,欢迎大家提issue。大家在开发过程中,遇到有价值的问题也欢迎投稿,帮助更多的同学。