ived types into the component's prop signature.
Compilation Mapping Strategy
- Default Slot: Mapped to
children?: React.ReactNode. This aligns with React's convention for primary content injection.
- Named Slots: Mapped to optional function props. The function signature matches the slot's payload type, returning
React.ReactNode.
- Scoped Slots: Mapped to function props that accept the scope object as an argument. The compiler preserves the generic constraints of the payload.
New Code Example
Consider a Vue 3 component defining a layout with a header slot and a sidebar slot that receives theme configuration.
Vue 3 Source:
<script setup lang="ts">
import type { VNode } from 'vue';
// Define slot contract with typed payloads
const slots = defineSlots<{
default?(): VNode[];
header?(): VNode;
sidebar(props: { theme: 'light' | 'dark'; collapsed: boolean }): VNode;
}>();
</script>
VuReact Compiled Output:
The compiler generates the following React component definition. Note that defineSlots is removed, and the types are applied to the props interface.
import React from 'react';
// Generated interface derived from defineSlots generic
interface LayoutProps {
// Default slot maps to children
children?: React.ReactNode;
// Named slot maps to function prop
header?: () => React.ReactNode;
// Scoped slot maps to function prop with payload
sidebar?: (props: { theme: 'light' | 'dark'; collapsed: boolean }) => React.ReactNode;
}
export const Layout: React.FC<LayoutProps> = ({ children, header, sidebar }) => {
return (
<div className="layout">
{header && <header>{header()}</header>}
<main>{children}</main>
{sidebar && (
<aside>
{sidebar({ theme: 'dark', collapsed: false })}
</aside>
)}
</div>
);
};
Architecture Decisions and Rationale
- Type-Only Transformation: The compiler does not generate a
useSlots hook or a slots object. React's ecosystem expects content to be passed via props. Generating a runtime abstraction would create a non-idiomatic API that confuses consumers and hinders interoperability with React libraries.
- Function Props for Named Slots: Vue's named slots are conceptually similar to React's render props. Mapping them to function props allows the parent to control rendering and pass data back to the child, preserving the scoped slot capability without runtime slot resolution.
React.ReactNode Return Type: The compiler enforces React.ReactNode as the return type for all slot functions. This ensures compatibility with React's rendering engine, which accepts strings, numbers, elements, and fragments. Vue's VNode or any types are normalized to this standard.
- Optional Modifiers: Slots in Vue can be optional. The compiler respects this by marking the corresponding props as optional (
?), allowing consumers to omit slots without type errors.
Pitfall Guide
When working with slot compilation or manual migration, developers encounter specific traps. The following pitfalls highlight common mistakes and their resolutions based on production experience.
-
Runtime Slot Access Attempts
- Mistake: Attempting to access
slots.default or slots.header inside the component body as if they were objects.
- Explanation: React does not expose a
slots collection. Slots are compiled to props.
- Fix: Access slots via the destructured props. Use
children for the default slot and props.header() for named slots. Ensure you invoke function props to render content.
-
Type Leakage in Scoped Slots
- Mistake: Using
any for slot payloads in Vue, resulting in untyped function props in React.
- Explanation: If the Vue source uses loose types, the compiled React props will also be loose, defeating the purpose of type safety.
- Fix: Enforce strict typing in
defineSlots. Use explicit interfaces for payload objects. The compiler will propagate these types accurately to React.
-
Re-render Loops with Function Props
- Mistake: Passing inline arrow functions to slot props without memoization, causing unnecessary re-renders of the child component.
- Explanation: In React, function props are compared by reference. A new function on every render breaks referential equality checks.
- Fix: Memoize slot functions in the parent component using
useCallback or define them outside the render function. This is a React best practice that applies to slot props just as it does to event handlers.
-
Ignoring children Opacity
- Mistake: Assuming
children is an array and trying to iterate over it directly.
- Explanation: React's
children prop is opaque; it can be a single element, an array, a string, or null.
- Fix: Use
React.Children utilities (React.Children.map, React.Children.toArray) when you need to manipulate or iterate over children. The compiler maps the default slot to children, so standard React patterns apply.
-
Missing Return Type Annotations
- Mistake: Defining slot functions without return types, leading to implicit
any or inference failures.
- Explanation: TypeScript may fail to infer the correct return type for slot functions, especially in complex generic scenarios.
- Fix: Ensure
defineSlots specifies the return type explicitly (e.g., (): VNode). The compiler uses this to generate React.ReactNode in the output.
-
Conflicting Default and Named Slots
- Mistake: Expecting named slots to behave exactly like Vue's
<slot name="..."> in terms of fallback content.
- Explanation: React function props do not have built-in fallback mechanisms. If a prop is undefined, the slot renders nothing.
- Fix: Implement fallback logic explicitly in the component. Check if the prop exists before invocation, or provide a default value in the prop destructuring.
-
Generic Constraint Mismatch
- Mistake: Using generic types in
defineSlots that do not map cleanly to React generics.
- Explanation: Complex Vue generics may require adjustments to work with React's type system, particularly around variance.
- Fix: Simplify generic constraints where possible. Ensure that generic parameters in slot payloads are covariant to match React's expectations. The compiler may require explicit type parameters on the component to resolve these correctly.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Large Vue Codebase Migration | VuReact Compilation | Automates type-safe slot mapping; reduces manual effort; preserves API contracts. | Low (Tooling setup) |
| Greenfield React Project | Native React Props | No migration overhead; idiomatic React patterns; no compiler dependency. | None |
| Hybrid Team (Vue/React Skills) | VuReact Compilation | Leverages Vue expertise; lowers learning curve; accelerates onboarding. | Medium (Learning compiler config) |
| Performance-Critical UI | VuReact Compilation | Zero runtime overhead for slot definitions; generates optimized prop interfaces. | Low |
| Complex Generic Slots | Manual Refinement | Compiler may struggle with edge-case generics; manual types offer precise control. | High (Development time) |
Configuration Template
Use this configuration to enable slot compilation in your VuReact setup. Ensure TypeScript is configured to support the generated types.
// vureact.config.ts
import { defineConfig } from '@vureact/core';
export default defineConfig({
// Enable slot compilation
slots: {
// Map defineSlots to React prop interfaces
strategy: 'type-only',
// Default slot mapping
defaultSlot: {
target: 'children',
type: 'React.ReactNode',
},
// Named slot mapping
namedSlots: {
target: 'function-props',
returnType: 'React.ReactNode',
},
// Scoped slot handling
scopedSlots: {
preservePayloadTypes: true,
generateInterfaces: true,
},
},
// TypeScript output settings
typescript: {
strict: true,
jsx: 'react-jsx',
declaration: true,
},
});
Quick Start Guide
- Install VuReact: Add the core package and compiler dependencies to your project.
npm install @vureact/core @vureact/compiler
- Configure Build: Add the VuReact plugin to your build tool (Vite, Webpack, or Rollup). Reference the configuration template above.
- Write Vue Syntax: Create components using
<script setup> and defineSlots(). Focus on defining clear slot contracts.
- Compile and Verify: Run the build process. Inspect the output directory for generated React components and type definitions.
- Consume in React: Import the compiled components into your React application. Use them with standard React props and children. Validate type checking in your IDE.