如何在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
相关产品推荐
相关产品推荐

