You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Vue组件中像React那样创建并渲染内部组件?

Vue实现类似React内联组件的方案

当然可以,Vue(尤其是Vue 3)提供了多种方式实现类似React的内联组件复用逻辑,避免模板代码重复,以下是几种常用方案:

方案一:在setup中定义局部组件(贴近React写法)

你可以在组件的setup函数中用defineComponent或直接通过渲染函数定义内联组件,然后在模板中通过<component>动态渲染:

<template>
  <BsButton v-bind="props">
    <component :is="iconPosition === 'left' ? LeftContent : RightContent" />
  </BsButton>
</template>

<script setup>
import { h, defineComponent, useSlots } from 'vue'
import BsButton from './BsButton.vue'
import FontAwesomeIcon from '@fortawesome/vue-fontawesome'

// 定义组件props
const props = defineProps({
  icon: Boolean,
  iconPosition: {
    type: String,
    default: 'left'
  },
  iconPositionClass: String // 假设你需要传递这个类名
})

const slots = useSlots()

// 定义内联Icon组件,类似React中的内联函数组件
const Icon = defineComponent({
  render() {
    if (!props.icon) return null
    return h(FontAwesomeIcon, {
      icon: ['fal', 'check'],
      class: props.iconPositionClass
    })
  }
})

// 定义左右布局的内容组件
const LeftContent = defineComponent({
  render() {
    return h('span', [h(Icon), slots.default?.()])
  }
})

const RightContent = defineComponent({
  render() {
    return h('span', [slots.default?.(), h(Icon)])
  }
})
</script>

方案二:用渲染函数实现完整组件(完全对标React函数组件)

如果更习惯React那种直接返回VNode的写法,可以直接用Vue的渲染函数h()编写整个组件逻辑,不需要模板:

<script setup>
import { h, defineProps, useSlots } from 'vue'
import BsButton from './BsButton.vue'
import FontAwesomeIcon from '@fortawesome/vue-fontawesome'

const props = defineProps({
  icon: Boolean,
  iconPosition: {
    type: String,
    default: 'left'
  },
  iconPositionClass: String
})

const slots = useSlots()

// 生成Icon的VNode
const getIconVNode = () => {
  if (!props.icon) return null
  return h(FontAwesomeIcon, {
    icon: ['fal', 'check'],
    class: props.iconPositionClass
  })
}

// 渲染整个Button组件
const renderButton = () => {
  // 根据iconPosition拼接内容节点
  const contentNodes = props.iconPosition === 'left'
    ? [getIconVNode(), slots.default?.()]
    : [slots.default?.(), getIconVNode()]
  
  return h(BsButton, props, contentNodes)
}

// 导出组件(如果需要作为独立组件使用)
defineExpose({ renderButton })
// 或者直接作为函数组件导出:
// export default defineComponent({
//   props: {...},
//   setup(props, { slots }) {
//     return () => {
//       // 上述渲染逻辑
//     }
//   }
// })
</script>

方案三:模板中结合局部组件减少重复

如果不想用渲染函数,也可以在setup中定义局部组件,然后在模板中直接复用,避免重复编写内容结构:

<template>
  <BsButton v-bind="props">
    <template v-if="iconPosition === 'left'">
      <IconComponent />
      <slot />
    </template>
    <template v-else>
      <slot />
      <IconComponent />
    </template>
  </BsButton>
</template>

<script setup>
import { h } from 'vue'
import BsButton from './BsButton.vue'
import FontAwesomeIcon from '@fortawesome/vue-fontawesome'

const props = defineProps({
  icon: Boolean,
  iconPosition: {
    type: String,
    default: 'left'
  },
  iconPositionClass: String
})

// 定义局部Icon组件
const IconComponent = () => {
  if (!props.icon) return null
  return h(FontAwesomeIcon, {
    icon: ['fal', 'check'],
    class: props.iconPositionClass
  })
}
</script>

Vue 2的兼容写法

如果还在使用Vue 2,可以通过组件的render选项实现类似逻辑:

import BsButton from './BsButton.vue'
import FontAwesomeIcon from '@fortawesome/vue-fontawesome'

export default {
  props: {
    icon: Boolean,
    iconPosition: {
      type: String,
      default: 'left'
    },
    iconPositionClass: String
  },
  render(h) {
    const renderIcon = () => {
      if (!this.icon) return null
      return h(FontAwesomeIcon, {
        props: { icon: ['fal', 'check'] },
        class: this.iconPositionClass
      })
    }

    const content = this.iconPosition === 'left'
      ? [renderIcon(), this.$slots.default]
      : [this.$slots.default, renderIcon()]

    return h(BsButton, { props: this.$props }, content)
  }
}

内容的提问来源于stack exchange,提问作者happy_story

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 20:15:42