act pattern generation. Each phase applies a specific compilation strategy based on the slot variant detected in the Vue template.
Step 1: Default Slot Translation
Vue's implicit or explicit default slot maps directly to React's children prop. The compiler strips the <template #default> wrapper and promotes child nodes to the component's direct children.
Vue Source:
<AnalyticsDashboard>
<template #default>
<MetricCard title="Revenue" value="$42k" />
</template>
</AnalyticsDashboard>
Compiled React:
<AnalyticsDashboard>
<MetricCard title="Revenue" value="$42k" />
</AnalyticsDashboard>
Rationale: React's children is the idiomatic composition primitive. Forcing named props for default content violates React conventions and adds unnecessary prop drilling. The compiler preserves Vue's default slot semantics by treating it as implicit child injection.
Step 2: Named Slot Translation
Named slots require explicit injection points. The compiler converts each #name directive into a corresponding prop, assigning the slot content as a JSX expression.
Vue Source:
<ReportLayout>
<template #title>
<h2>Q3 Financial Summary</h2>
</template>
<template #actions>
<button onClick={exportCSV}>Export</button>
</template>
</ReportLayout>
Compiled React:
<ReportLayout
title={<h2>Q3 Financial Summary</h2>}
actions={<button onClick={exportCSV}>Export</button>}
/>
Rationale: React lacks a native named slot mechanism. Converting to props maintains explicit injection points while keeping the component API predictable. The compiler generates static JSX props to avoid unnecessary function invocations during render.
Step 3: Scoped Slot Translation
Scoped slots pass data from child to parent for dynamic rendering. The compiler converts these into function props that accept a payload and return JSX.
Vue Source:
<UserTable :records="employeeList">
<template #row="scope">
<tr>
<td>{scope.record.name}</td>
<td>{scope.record.role}</td>
</tr>
</template>
</UserTable>
Compiled React:
<UserTable
records={employeeList}
row={(scope) => (
<tr>
<td>{scope.record.name}</td>
<td>{scope.record.role}</td>
</tr>
)}
/>
Child Component Implementation:
function UserTable({ records, row }) {
return (
<table>
<tbody>
{records.map((emp) => (
<tr key={emp.id}>{row({ record: emp })}</tr>
))}
</tbody>
</table>
);
}
Rationale: React's render props pattern is the direct equivalent of Vue's scoped slots. Converting to function props preserves data flow directionality and enables parent-controlled rendering. The compiler generates inline arrow functions, but production builds should apply useCallback or React.memo to prevent identity changes on re-renders.
Step 4: Dynamic Slot Translation
Dynamic slot names require runtime evaluation. The compiler uses computed property syntax within a spread object to assign the slot content to a dynamically resolved prop key.
Vue Source:
<WidgetContainer>
<template #[activePanel]>
<PanelContent data={panelData} />
</template>
</WidgetContainer>
Compiled React:
<WidgetContainer
{...{ [activePanel]: <PanelContent data={panelData} /> }}
/>
Rationale: JSX does not support dynamic prop names natively. The spread-computed pattern {...{ [key]: value }} is the standard workaround. The compiler isolates dynamic slots into a single spread object to prevent prop collision with static props.
Step 5: Fallback Content Handling
Vue slots support default content when no parent injection is provided. The compiler translates this into a conditional render that checks for children or specific prop existence.
Vue Source:
<!-- ModalWrapper.vue -->
<template>
<div class="modal">
<slot>
<p>No content provided</p>
</slot>
</div>
</template>
Compiled React:
function ModalWrapper({ children }) {
return (
<div className="modal">
{children ?? <p>No content provided</p>}
</div>
);
}
Rationale: React's conditional rendering handles fallback semantics naturally. The compiler uses nullish coalescing (??) instead of logical OR (||) to prevent falsy values like 0 or false from triggering fallback rendering, which aligns with Vue's slot evaluation rules.
Pitfall Guide
1. Collapsing Named Slots into Children
Explanation: Developers often merge multiple named slots into a single children prop to simplify migration. This breaks explicit injection points and forces child components to parse and route content manually.
Fix: Maintain the props compilation strategy for named slots. Explicit prop names preserve component contracts and enable TypeScript validation.
2. Ignoring Scoped Slot Function Identity
Explanation: Inline arrow functions in scoped slots create new references on every render. This triggers unnecessary child re-renders and breaks React.memo optimizations.
Fix: Extract scoped slot functions outside the render cycle or wrap them in useCallback. When using the compiler, enable the memoizeScopedSlots flag to auto-generate stable function references.
3. Dynamic Slot Prop Collision
Explanation: Using spread syntax for dynamic slots can overwrite static props if the dynamic key matches an existing prop name.
Fix: Prefix dynamic slot keys or validate them against the component's prop interface before compilation. The compiler should emit a warning when dynamic keys conflict with static props.
4. Fallback Evaluation with Falsy Values
Explanation: Using || for fallback content treats 0, false, and "" as missing content, triggering unintended fallback rendering.
Fix: Use nullish coalescing (??) or explicit children !== undefined checks. This matches Vue's slot fallback behavior, which only triggers when no content is explicitly provided.
5. Mixing Vue Slot Inheritance with React Prop Drilling
Explanation: Vue slots implicitly inherit parent scope. React requires explicit prop passing. Developers sometimes rely on context or global state to bridge this gap, breaking component isolation.
Fix: Keep scoped slot data flow explicit. Pass required context through the function prop payload rather than relying on React Context or module-level state.
6. Over-Compiling Static Slots
Explanation: Wrapping static named slots in functions or complex prop structures adds unnecessary indirection and hurts tree-shaking.
Fix: The compiler should detect static content and generate direct JSX props instead of function wrappers. Reserve function props exclusively for scoped slots that require data binding.
7. Ignoring TypeScript Slot Contracts
Explanation: Migrated components often lose type safety when slot props are inferred as any. This defeats React's static analysis benefits.
Fix: Generate explicit TypeScript interfaces for slot props. The compiler should emit interface ComponentSlots { header?: ReactNode; row?: (data: RowData) => ReactNode; } to maintain compile-time validation.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Simple content injection | Default slot → children | Native React pattern; zero overhead | Low |
| Multiple injection points | Named slots → explicit props | Maintains explicit API contract | Medium |
| Data-driven rendering | Scoped slots → function props | Preserves parent-controlled rendering | Medium-High (requires memoization) |
| Runtime panel switching | Dynamic slots → computed spread | Enables flexible composition | High (runtime evaluation cost) |
| Legacy Vue migration | Compiler-assisted translation | Preserves semantics; reduces manual rewrite | Low (initial setup) |
Configuration Template
// vureact.config.ts
import { defineConfig } from 'vureact/compiler';
export default defineConfig({
input: './src/vue-components',
output: './src/react-components',
slotCompilation: {
defaultSlot: 'children',
namedSlot: 'prop',
scopedSlot: 'functionProp',
dynamicSlot: 'computedSpread',
fallbackStrategy: 'nullishCoalesce',
memoizeScopedSlots: true,
generateTypeInterfaces: true,
warnOnPropCollision: true,
},
reactVersion: '18.x',
strictMode: true,
});
Quick Start Guide
- Install the compiler: Run
npm install vureact --save-dev and initialize the configuration file with npx vureact init.
- Map your components: Place Vue files in the configured input directory. The compiler will parse
v-slot directives and classify them by variant.
- Generate React output: Execute
npx vureact compile. The tool will emit TSX files with translated slot patterns, TypeScript interfaces, and memoization hooks.
- Validate and integrate: Run TypeScript checks against the generated output. Replace Vue imports with React equivalents and verify fallback behavior in test environments.
- Optimize for production: Enable tree-shaking, verify scoped slot identity stability, and run performance profiling to confirm zero regression in composition rendering.