vue如何便捷的判断vnode的类型?

我有一个组件,使用方式如下:

<TabContainer v-model="activeTab">
    <TabHeader tab-id="1"> tab1 </TabHeader>
    <TabHeader tab-id="2"> tab2 </TabHeader>
    <TabHeader tab-id="3"> tab3 </TabHeader>
    <TabContent tab-id="1"> content 1 </TabContent>
    <TabContent tab-id="2"> content 2 </TabContent>
    <TabContent tab-id="3"> content 3 </TabContent>
</TabContainer>

按照预期工作。

实现如下:

import { h } from 'vue'
import './TabContainer.scss'

const TabContainer = {
  name: 'TabContainer',
  props: {
    modelValue: {
      type: String,
      required: true,
    },
  },
  render() {
    const slots = this.$slots.default()
    console.log(slots)
    // 检查子组件类型
    const existInValidSubCom = slots.some(slot => ![TabHeader, TabContent].includes(slot.type))
    if (existInValidSubCom) {
      const message = 'TabContainer的子组件必须是 TabHeader 和 TabContent'
      // throw new Error(message)
      return h('div', message)
    }
    const Tabs = slots
      .filter(item => item.type === TabHeader)
      .map(Tab =>
        h(Tab, {
          class: {
            tab: true,
            active: Tab.props['tab-id'] === this.modelValue,
          },
          onClick: () => {
            this.$emit('update:modelValue', Tab.props['tab-id'])
          },
        }),
      )
    const content = slots.find(
      slot => slot.type === TabContent && slot.props['tab-id'] === this.modelValue,
    )
    return [h('div', { class: 'tab-container' }, Tabs), h('div', content)]
  },
}

export default TabContainer
export const TabHeader = TabItem({ name: 'TabHeader' })
export const TabContent = TabItem({ name: 'TabContent' })

function TabItem(options) {
  return {
    ...options,
    props: {
      tabId: {
        type: String,
        required: true,
      },
    },
    render() {
      return h('div', null, this.$slots.default())
    },
  }
}

当插槽里有注释时:

<TabContainer v-model="activeTab">
    <TabHeader tab-id="1"> tab1 </TabHeader>
    <TabHeader tab-id="2"> tab2 </TabHeader>
    <TabHeader tab-id="3"> tab3 </TabHeader>
    <!-- 注释 -->
    <TabContent tab-id="1"> content 1 </TabContent>
    <TabContent tab-id="2"> content 2 </TabContent>
    <TabContent tab-id="3"> content 3 </TabContent>
</TabContainer>

slots 多了一个注释的 vnode:

image.png

下面的判断条件结果为 true,组件不能按照预期工作:

 // 检查子组件类型
const existInValidSubCom = slots.some(slot => ![TabHeader, TabContent].includes(slot.type))

当有很多TabHeader和 TabContent 时,希望使用v-for

<TabContainer v-model="activeTab">
    <TabHeader :tab-id="item" v-for="(item, index) in ['1', '2', '3']" :key="index">
      tab{{ item }}
    </TabHeader>
    <TabContent tab-id="1"> content 1 </TabContent>
    <TabContent tab-id="2"> content 2 </TabContent>
    <TabContent tab-id="3"> content 3 </TabContent>
</TabContainer>

slots 里多了一个 Symbol(Fragement)

image.png

也导致刚才的判断结果为 true。

为何需要限制子组件的类型?table 内只能使用 thead、tbody、tr、td等有限的标签,希望组件之间也有这样的特性。

vue 版本:^3.2.41

这种情况该如何处理比较好?或者说,有什么快捷的函数可判断一个vnode的类型呢?

阅读 3.8k
4 个回答

没有简便的办法可检查类型,只能这样检查了。

const vnodeList = this.$slots.default()
const childList = []
let existNonValidSubCom = false
let i = 0
do {
  const vnode = vnodeList[i]
  // html tag
  if (typeof vnode.type === 'string') {
    existNonValidSubCom = true
    break
  } else if ([TabHeader, TabContent].includes(vnode.type)) {
    childList.push(vnode)
  }
  // Symbol(Fragment)
  else if (typeof vnode.type === 'symbol' && Array.isArray(vnode.children)) {
    // childList.push(h(vnode.type, null, vnode.children))
    vnode.children.forEach(child => {
      if ([TabHeader, TabContent].includes(child.type)) {
        childList.push(child)
      }
    })
  }
  // Symbol(Comment)
  else if (typeof vnode.type === 'symbol' && typeof vnode.children === 'string') {
    // 跳过
  } else {
    existNonValidSubCom = true
    break
  }
} while (++i < vnodeList.length && !existNonValidSubCom)

这种在生产环境判断的方法有点浪费资源了。可以换个思路,从vue的config入手,去掉所有的注释 看看会不会产生好的效果。

module.exports = {
  chainWebpack: config => {
    config.optimization.minimizer('terser').tap((args) => {
      args[0].terserOptions.output = {
        ...args[0].terserOptions.output,
        comments: false  // 跳过所有的注释
      }
      return args
    })
  }
}

不是很理解为什么要去判断 slotVNode类型。直接使用 具名插槽 不就好了???

有点搞不懂你的做法。

  1. 你的每个组件都是手写的
  2. 然后你手动插入一个注释
  3. 然后你说你检查组件会出错

??

换个逻辑,如果你这里不手写,那判断传入的数据就行了,也不需要管注释。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题