【Vue3】实现 el-tab 的基本功能

16 篇文章 0 订阅

这次我们要实现两个组件:Tab.vue 和 TabItem.vue,其中 TabItem 是 Tab 的插槽内容。实际上不用封装组件也能达到类似的效果,但是考虑到语义化和可复用性等要求,封装一个 Tabs 组件还是有必要的。

根据 Vue 的风格指南,组件名需为两个以上的单词,以避免和原生 HTML 的标签冲突,这里为了简单起见不实现这个要求。

在线体验

Tabs 的基本功能

<Tab :active-index="activeIndex">
    <TabItem 
      v-for="(tab, idx) in tabs" 
      :key="tab" 
      @tabItemClick="handleClick" 
      v-slot="{ index }"
    >{{ tab }} - {{ index }}</TabItem>
  </Tab>
  1. Tab 组件可以指定默认 active 的 TabItem
  2. TabItem 被点击后会 emit 一个 tabItemClick 事件通知自己被点击
  3. 能够使用 v-slot 获取 TabItem 的一些内容,比如该 Item 的 index
  4. 被点击的 TabItem 自动添加 active 样式

了解 h 函数

h 函数用来创建 VNode,所谓 VNode 实际上就是个原生 JS 对象,用来保存一些真实 HTML 元素的信息,如元素标签、属性、子元素等。操作 VNode 的性能远超操作原生 DOM。

h 函数的参数

h 函数的参数位置非常灵活,这里介绍两个最常用的

  1. h(元素类型, 元素的属性, 子元素)
  2. h(元素类型, 子元素)

例子

// <div class="title">Cap1</div>
h('div', {class: 'title'}, 'Cap1')

// <div class="title"><span>hello world</span></div>
h('div', {class: 'title'}, [
  h('span', 'hello world')
])

// <span>lalalal</span>
h('span', 'lalalal')

在 setup 中使用 h 函数

使用渲染函数时无需写 template 标签,组件会被渲染成渲染函数实现的样式

export default {
  setup(props, { emit, slots }) {
    return () => h('div', 'h function') 
  }
}

// 等同于模板写法
// <template>
//   <div>h function</div>
// </template>

需要注意的是,h函数是箭头函数的返回值,因为 setup 在每个组件创建时只调用一次

实现

Tab.vue

Tab 组件作为 TabItems 的包裹组件,应该提供一个插槽用来渲染 TabItems,同时需要保存一些状态(如当前活跃 tab 的下标)。

<script>
import { h } from 'vue';

export default {
  // 需要声明 props,否则 setup 的 props 为 {}
  // 因为 setup 中的 props 等同于选项式 api 的 props
  props: {
    activeIndex: Number
  },
  setup(props, { emit, slots }) {
    // children 是插槽的所有子元素,也就是所有的 TabItems
    const tabItems = slots.default ?
          slots.default()[0].children :
    			[]
         
    return () => h('div', tabItems.map((item, idx) => {
      // 这里 item 实际也是个 VNode,使用 h 修改这些 VNode 的 props
      return h(item, {index: idx, class: {
        active: props.activeIndex === idx
      }})
    }))
  }
}
</script>

TabItem.vue

<script>
import { h } from 'vue'
export default {
  props: {
    index: Number
  },
  emits: ['tabItemClick'],
  setup(props, { emit, slots }) {
    // 这里的插槽的内容是 TabItems 的内容 
    const slotItems = slots.default ?
          slots.default({ index: props.index }) :
    			[]
    
    return () => h('div', {
      onClick: () => {
        emit('tabItemClick', props.index)
      }
    }, slotItems)
  }
}
</script>

<style scoped>
  .active {
    color:red;
  }
</style>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值