Tabs组件使用方式
<template><h1>示例1</h1><Tabs><Tab title="导航1">内容1</Tab><Tab title="导航2">内容2</Tab></Tabs></template>
如何判断子组件的类型?
将context.slots打印出来,context.slots.default()得到的就是子组件数组
<template><div>Tabs组件</div><component :is="defaults[0]"/><component :is="defaults[1]"/></template><script lang="ts">import Tab from './Tab.vue'export default {setup(props,context){const defaults = context.slots.default()defaults.forEach(tag => {if(tag.type !== Tab) {throw new Error('Tabs的子标签必须为Tab')}})return {defaults}}}</script>
渲染嵌套的插槽
props属性中可以获取到tab组件的props
<template><div>Tabs组件</div><div v-for="title in titles" :key="title">{{title}}</div><component v-for="(tab,index) of defaults" :is="tab" :key="index"/></template><script lang="ts">import Tab from './Tab.vue'export default {setup(props,context){const defaults = context.slots.default()defaults.forEach(tag => {if(tag.type !== Tab) {throw new Error('Tabs的子标签必须为Tab')}})const titles = defaults.map(tab => tab.props.title)return {defaults,titles}}}</script>
实现tab切换
<template><h1>示例1</h1><Tabs v-model:selected="title"><Tab title="导航1">内容1</Tab><Tab title="导航2">内容2</Tab></Tabs></template><script lang="ts">import { ref } from 'vue'import Tab from '../lib/Tab.vue'import Tabs from '../lib/Tabs.vue'export default {components: {Tab, Tabs},setup(){const title = ref('导航1')return {title}}}</script>
component需要绑定key才能感知到数据更新重新渲染
<template><div class="pika-tabs"><div class="pika-tabs-nav" ><div class="pika-tabs-nav-item" :class="{selected: title === selected}" v-for="title in titles" :key="title" @click="changeTab(title)">{{title}}</div></div><div class="pika-tabs-content"><component class="pika-tabs-content-item" :is="current" :key="selected"/></div></div></template><script lang="ts">import { computed } from 'vue'import Tab from './Tab.vue'export default {props:{selected: String},setup(props,context){const defaults = context.slots.default()defaults.forEach(tag => {if(tag.type !== Tab) {throw new Error('Tabs的子标签必须为Tab')}})const changeTab = (value) => {context.emit('update:selected', value)}const titles = defaults.map(tab => tab.props.title)const current = computed(() => defaults.find(item => item.props.title === props.selected))return {defaults,titles,changeTab,current}}}</script>
title下面添加动态指示条
在title下面添加指示条
添加一个div, 绝对定位
<div class="pika-tabs-nav" >// ...<div class="pika-tabs-nav-indicator"></div></div>
获取宽度
要获取到title的宽度,需要先用ref获取到title所在的DOM
参考文档
<template><div class="pika-tabs"><div class="pika-tabs-nav" ><div class="pika-tabs-nav-item":ref="el => { if (el) divs[index] = el }":class="{selected: title === selected}"v-for="(title,index) in titles":key="index"@click="changeTab(title)">{{title}}</div><div class="pika-tabs-nav-indicator"></div></div>// ...</div></template><script lang="ts">import { computed,onMounted,ref } from 'vue'// ...export default {// ...setup(props,context){const divs = ref([])onMounted(()=>{console.log(...divs.value)})// ...return {// ...divs}}}</script>
获取DOM节点的宽度, 使用Node.getBoundingClientRect()方法
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置
<template><div class="pika-tabs">// ...<div ref="indicator" class="pika-tabs-nav-indicator"></div></div>// ...</div></template><script lang="ts">import { computed,onMounted,onUpdated, ref } from 'vue'export default {props:{selected: String},setup(props,context){const defaults = context.slots.default()const navItems = ref([])const indicator = ref(null)onMounted(()=>{const divs = navItems.valueconst result = divs.filter(item => item.classList.contains('selected'))[0]const {width} = result.getBoundingClientRect()indicator.value.style.width = width + 'px'})onUpdated(()=> {// 同上})// ...}}</script>
计算left
setup(props,context){const defaults = context.slots.default()const navItems = ref([])const indicator = ref(null)const container = ref(null)const x = () => {const divs = navItems.valueconst result = divs.filter(item => item.classList.contains('selected'))[0]const {width, left: left2} = result.getBoundingClientRect()indicator.value.style.width = width + 'px'const {left: left1} = container.value.getBoundingClientRect()indicator.value.style.left = (left2 - left1) + 'px'}onMounted(x)onUpdated(x)}
优化
onMounted和onUpdated可以使用watchEffect代替,注意watchEffect会在mounted前执行,导致获取不到DOM节点,所以需要放在onMounted里
onMounted(()=>{watchEffect(() => {console.log(selectedItem.value)const {width, left: left2} = selectedItem.value.getBoundingClientRect()indicator.value.style.width = width + 'px'const {left: left1} = container.value.getBoundingClientRect()indicator.value.style.left = (left2 - left1) + 'px'})})
