DEV Community

Cover image for Vue slot to React: How does VuReact handle it?
Ryan John
Ryan John

Posted on • Originally published at vureact.top

Vue slot to React: How does VuReact handle it?

VuReact is a compiler toolchain for migrating from Vue to React — and for writing React with Vue 3 syntax. In this article, we dive straight into the core: how Vue's common <slot> mechanism is compiled into React code by VuReact.

Before We Start

To keep the examples easy to read, this article follows two simple conventions:

  1. All Vue and React snippets focus on core logic only, with full component wrappers and unrelated configuration omitted.
  2. The discussion assumes you are already familiar with Vue 3's slot usage.

Compilation Mapping

Default slot: <slot>

The default slot is Vue's most basic slot form, used to receive default content passed from the parent component.

  • Vue
<!-- Child component Child.vue -->
<template>
  <div class="container">
    <slot></slot>
  </div>
</template>

<!-- Parent usage -->
<Child>
  <p>This is slot content</p>
</Child>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component Child.jsx
function Child(props) {
  return (
    <div className="container">
      {props.children}
    </div>
  );
}

// Parent usage
<Child>
  <p>This is slot content</p>
</Child>
Enter fullscreen mode Exit fullscreen mode

As the example shows, Vue's <slot> element is compiled into React's children prop. VuReact adopts a children compilation strategy, converting the slot outlet into React's standard children receiving mechanism. This fully preserves Vue's default slot semantics — receiving child content passed from the parent component and rendering it.

The key characteristics of this compilation approach are:

  1. Semantic consistency: Fully simulates Vue's default slot behavior by implementing content distribution
  2. React-native support: Uses React's standard children mechanism with no additional adaptation needed
  3. Clean syntax: Vue's <slot> is simplified to a {children} expression
  4. Performance optimization: Uses React's native mechanism directly with zero runtime overhead

Named slots: <slot name="xxx">

Named slots allow a component to define multiple slot outlets, and the parent component can specify where content should be inserted by name.

  • Vue
<!-- Child component Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- Parent usage -->
<Layout>
  <template #header>
    <h1>Page Title</h1>
  </template>

  <p>Main content</p>

  <template #footer>
    <p>Copyright information</p>
  </template>
</Layout>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component Layout.jsx
function Layout(props) {
  return (
    <div className="layout">
      <header>{props.header}</header>
      <main>{props.children}</main>
      <footer>{props.footer}</footer>
    </div>
  );
}

// Parent usage
<Layout
  header={<h1>Page Title</h1>}
  footer={<p>Copyright information</p>}
>
  <p>Main content</p>
</Layout>
Enter fullscreen mode Exit fullscreen mode

As the example shows, Vue's named slots <slot name="xxx"> are compiled into React props. VuReact adopts a props compilation strategy, converting named slot outlets into component props. This fully preserves Vue's named slot semantics — distinguishing different slot content through different prop names.

Compilation rules:

  1. Slot name mapping: <slot name="header">header prop
  2. Default slot: <slot>children prop
  3. Props receiving: Destructure all slot props in the component function parameters

Scoped slots: <slot :prop="value">

Scoped slots allow child components to pass data to slot content, enabling more flexible render control.

  • Vue
<!-- Child component List.vue -->
<template>
  <ul>
    <li v-for="(item, i) in props.items" :key="item.id">
      <slot :item="item" :index="i"></slot>
    </li>
  </ul>
</template>

<!-- Parent usage -->
<List :items="users">
  <template v-slot="slotProps">
    <div class="user-item">
      {{ slotProps.index + 1 }}. {{ slotProps.item.name }}
    </div>
  </template>
</List>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component List.jsx
function List(props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <li key={item.id}>
          {props.children?.({ item, index })}
        </li>
      ))}
    </ul>
  );
}

// Parent usage
<List 
  items={users}
  children={(slotProps) => (
    <div className="user-item">
      {slotProps.index + 1}. {slotProps.item.name}
    </div>
  )}
/>
Enter fullscreen mode Exit fullscreen mode

As the example shows, Vue's scoped slots are compiled into React function children. VuReact adopts a function children compilation strategy, converting scoped slot outlets into functions that receive parameters. This fully preserves Vue's scoped slot semantics — the child component passes data to the parent through function calls, and the parent receives data and renders via function parameters.

Compilation rules:

  1. Slot attribute conversion: <slot :item="item" :index="i"> → function parameter { item, index }
  2. Function invocation: Calls the children() function at the render location and passes data
  3. Optional chaining guard: Uses ?. to prevent errors when no slot content is provided

Named scoped slots: <slot name="xxx" :prop="value">

Named scoped slots combine the features of both named slots and scoped slots.

  • Vue
<!-- Child component Table.vue -->
<template>
  <table>
    <thead>
      <tr>
        <slot name="header" :columns="props.columns"></slot>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in props.data" :key="row.id">
        <slot name="body" :row="row" :columns="props.columns"></slot>
      </tr>
    </tbody>
  </table>
</template>

<!-- Parent usage -->
<Table :columns="tableColumns" :data="tableData">
  <template #header="headerProps">
    <th v-for="col in headerProps.columns" :key="col.id">
      {{ col.title }}
    </th>
  </template>

  <template #body="bodyProps">
    <td v-for="col in bodyProps.columns" :key="col.id">
      {{ bodyProps.row[col.field] }}
    </td>
  </template>
</Table>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component Table.jsx
function Table(props) {
  return (
    <table>
      <thead>
        <tr>
          {props.header?.({ columns: props.columns })}
        </tr>
      </thead>
      <tbody>
        {props.data.map((row) => (
          <tr key={row.id}>
            {props.body?.({ row: row, columns: props.columns })}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// Parent usage
<Table
  columns={tableColumns}
  data={tableData}
  header={(headerProps) => (
    <>
      {headerProps.columns.map((col) => (
        <th key={col.id}>{col.title}</th>
      ))}
    </>
  )}
  body={(bodyProps) => (
    <>
      {bodyProps.columns.map((col) => (
        <td key={col.id}>{bodyProps.row[col.field]}</td>
      ))}
    </>
  )}
/>
Enter fullscreen mode Exit fullscreen mode

Compilation strategy:

  1. Named function props: Named scoped slots are converted into function props
  2. Parameter passing: Correctly passes scoped parameters
  3. Fragment wrapping: Multiple elements are wrapped in a Fragment
  4. Type safety: Preserves TypeScript type definition integrity

Slot fallback content

Vue supports providing default content in slot definitions, displayed when the parent component does not provide slot content.

  • Vue
<!-- Child component Button.vue -->
<template>
  <button class="btn">
    <slot>
      <span class="default-text">Click me</span>
    </slot>
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component Button.jsx
function Button(props) {
  return (
    <button className="btn">
      {props.children || <span className="default-text">Click me</span>}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Fallback content handling rules:

  1. Conditional rendering: Uses the || operator to check if children exists
  2. Default value provision: Renders fallback content when children is falsy
  3. React pattern: Uses standard React conditional rendering patterns

Dynamic slot names

Vue supports dynamic slot names for more flexible slot selection.

  • Vue
<!-- Child component DynamicSlot.vue -->
<template>
  <div>
    <slot :name="dynamicSlotName"></slot>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
// Child component DynamicSlot.jsx
function DynamicSlot(props) {
  return (
    <div>
      {props[dynamicSlotName]}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Dynamic slot handling:

  1. Computed property name: Uses object computed property syntax to receive dynamic slots
  2. Runtime determination: The slot name is determined at runtime

Compilation strategy summary

VuReact's <slot> compilation strategy demonstrates a complete slot system conversion capability:

  1. Default slots: Converted to React's children
  2. Named slots: Converted to component props
  3. Scoped slots: Converted to function children or function props
  4. Fallback content: Supports slot default content
  5. Dynamic slots: Supports dynamic slot names

Slot type mapping table:

Vue slot type React equivalent Description
<slot> children Default slot, as child elements of the component
<slot name="xxx"> xxx prop Named slot, as a property of the component
<slot :prop="value"> Function children Scoped slot, as a function receiving parameters
<slot name="xxx" :prop="value"> Function xxx prop Named scoped slot, as a function prop

Performance optimization strategy:

  1. Static slot optimization: Static slot content is compiled into static JSX
  2. Function caching: For scoped slots, render functions are intelligently cached
  3. On-demand generation: The most minimal code is generated based on actual usage
  4. Type inference: Supports intelligent inference of slot type definitions in TypeScript

VuReact's compilation strategy ensures a smooth migration from Vue to React. Developers do not need to manually rewrite slot logic. The compiled code preserves Vue's semantics and flexibility while following React's component design patterns, keeping the migrated application fully capable of content distribution.

Related Links

Top comments (0)