大家好,我是兔兔,今天来给大家分享关于图鸟UI中tabbar的基本用法。

看到有的同学在图鸟群中反馈,对tabbar组件如何使用,以及子父组件之间的各种事件通信,不知道如何使用,今天我就来给大家分享一下。如涉及到不正确的地方,欢迎大家指正,同时也欢迎大家留言反馈日常遇到的问题和经验。

开篇还是闲聊一下,我个人关于组件的一些观点。

什么是组件

在前端开发中,组件(Component)是一种代码复用和模块化设计的方法。组件是自包含的代码片段,具有特定的功能和样式,可以在整个应用程序中重复使用:

  1. 复用性:组件可以被多次使用,减少了代码重复,提高了开发效率。
  2. 封装性:组件封装了特定的功能和样式,隐藏了内部实现细节,使得代码更加清晰和易于维护。
  3. 可维护性:当组件需要更新或修复时,只需修改一次,所有使用该组件的地方都会自动更新。
  4. 可测试性:组件可以独立测试,确保其功能正常,提高了代码的稳定性和可靠性。
  5. 灵活性:组件可以根据需要进行配置和扩展,适应不同的使用场景。

组件通常用于以下场景:

  • UI元素:按钮、输入框、下拉菜单等基本的界面元素。
  • 布局组件:用于构建页面布局的组件,如头部、侧边栏、页脚等。
  • 功能组件:具有特定功能的组件,如表单验证、数据展示等。
  • 页面组件:构成页面的大块组件,如商品列表、用户评论等。

在现代前端框架中,如React、Vue和Angular,组件是核心概念之一。这些框架提供了丰富的工具和API来创建和管理组件,使得开发大型、复杂的前端应用程序变得更加容易。

说人话就是:将重复的代码给单独抽离出来,封装成独立的一块,在需要的时候,直接调用即可。

前端组件开发,并不是前端所独有的。说白了,前端所谓的组件开发,就和后端的面向对象的开发一样,就是将重复的代码抽离出来,封装成独立的一块,在需要的时候,直接调用即可。

uniapp自定义tabbar组件使用总结_数据

如何去设计组件

对于组件的设计,需要根据业务而定,不同的业务有不同的组件设计方式,同时我们也要保证其具备扩展性和可维护性。例如一个常见的用户信息表单,里面可能有姓名、性别、年龄、地址等字段,我们可以将这些字段封装成一个组件,然后通过props的方式传入数据,然后组件内部根据props的数据进行渲染。但另外的一个用户信息表单,可能有手机号、性别、头像等字段。如果我们按照表单级别来进行封装,就会出信息表单无法复用的,所谓的组件就没实际价值。因此在组件封装时,我们尽可能按照小颗粒度来进行封装。就像element ui一样,按钮是一个组件、复选框是一个组件、输入框是一个组件,这些组件都是可以复用的。

这里总结几点组件封装的设计原则,组件的设计应当遵循一些基本原则,以确保它们是可维护的、可扩展的、并且易于使用:

  1. 单一职责原则:每个组件应该只负责一件事情。这有助于减少组件的复杂性,使其更容易理解和维护。
  2. 封装性:组件应该封装其内部状态和行为,只通过定义好的接口与外部交互。这有助于隐藏实现细节,减少组件间的依赖。
  3. 可复用性:设计组件时,应考虑它们在不同场景下的复用性。避免为特定场景定制组件,除非这种定制是不可避免的。
  4. 可配置性:允许组件通过属性(props)或插槽(slots)等机制接受外部配置,以适应不同的使用场景。
  5. 独立性:组件应尽可能独立于其他组件,避免紧密耦合。这有助于在不影响其他组件的情况下修改或替换单个组件。
  6. 可测试性:设计组件时,应确保它们易于进行单元测试。这通常意味着避免依赖全局状态或外部服务。
  7. 性能:组件的设计应考虑性能影响,避免不必要的渲染或计算,特别是在大型应用程序中。
  8. 可访问性:组件应遵循无障碍设计原则,确保所有用户都能使用,包括那些使用辅助技术的用户。
  9. 样式一致性:在设计组件时,应保持应用程序内样式的一致性,以提供统一的用户体验。
  10. 文档和示例:为组件提供清晰的文档和使用示例,帮助开发者理解如何使用和集成这些组件。
  11. 版本控制:为组件维护版本号,确保在更新时向后兼容,避免破坏现有功能。
  12. 国际化和本地化:如果应用程序面向多语言用户,组件设计应支持国际化和本地化。

图鸟组件

在小程序开发中,可以直接配置原生的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,个人主页面
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

就是上面几个简单的文件,就构成了一个tabbar+四个业务页面的模版。效果图如下:

uniapp自定义tabbar组件使用总结_前端_02

主页代码结构

下面是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>
{/* 底部导航栏结束 */}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.

下面是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
        }
      },
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

子组件页面

子组件页面就跟我们平常的页面一样,正常编写自己的业务代码即可。

<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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.

常见问题

上面总结了自定义tabbar组件如何使用,很多同学在使用子组件和父组件之间相互通信,可能会遇到问题,这里也简单的做几个总结。

子组件传递给父组件

子组件传递给父组件指的是,当子组件中发生事件操作,需要将数据传递给父组件,此时改如何做。解决的方法是通过$emit()实现通信。具体更多的细节也可以参考一下vue的官方文档。

// 在子组件方法中,通过$emit()触发事件,并将数据传递给父组件
this.$emit('changeData', index)
  • 1.
  • 2.
// 在父组件中,调用子组件的地方,进行事件处理,需要注意的是这里的中划线
<page-a ref="pageA" :bannerList="bannerList" @change-data="父组件对应的方法"></page-a>
<script>
    function 父组件对应的方法(index) {
        console.log(index, "接收子组件传递过来的值")
    }
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

父组件传递给子组件

父组件传递给子组件指的是,当父组件中发生事件操作,需要将数据传递给子组件,此时改如何做。解决的方法是通过$refs获取子组件,然后调用子组件的方法。具体更多的细节也可以参考一下vue的官方文档。 这里用页面滚动加载数据为例,当页面向下滚动时,需要向后端加载新的数据。如果把这个事件放在子组件中去处理,你会发现不会生效。因为该组件需要再父组件中去监听。

<template>
    <page-a ref="pageA"></page-a>
</template>
export default {
    methods: {
        // 在父组件监听页面滚动事件
        onPageScroll(e) {
            // 这里的pageA是调用子组件定义的ref命令
            this.$ref.pageA.onPageScroll(e)
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

接着在子组件中定义方法,处理对应的业务逻辑。

export default {
    methods: {
        onPageScroll(e) {
            // 在这里去加载新的数据,或者处理业务逻辑
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

父组件传参

父组件传参指的是,父组件向子组件传递数据,子组件接收到数据后,进行业务逻辑处理。这应该是组件开发,最基础的知识点,要实现数据的传递,就需要使用到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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

然后在子组件中定义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: []
        }
    },
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

自此关于图鸟UI在使用自定义tabbar,组件调用以及组件之间数据、事件的通信都介绍完了。希望本文的分享能够对大家使用图鸟UI有所帮助。同时也欢迎大家提出意见,欢迎大家提issue。大家在开发过程中,遇到有价值的问题也欢迎投稿,帮助更多的同学。