Vue 3 provides comprehensive JSX/TSX support with full TypeScript integration, allowing you to write components using JSX syntax while maintaining Vue's reactivity and component features.
Vue provides JSX transformation runtime functions for modern JSX compilation.
/**
* JSX runtime function for creating elements
* @param type - Element type or component
* @param props - Properties object
* @param key - Element key
* @returns VNode
*/
function jsx(
type: any,
props: any,
key?: any
): VNode;
/**
* JSX runtime function for creating elements with children
* @param type - Element type or component
* @param props - Properties object
* @param key - Element key
* @returns VNode
*/
function jsxs(
type: any,
props: any,
key?: any
): VNode;
/**
* JSX Fragment component
*/
const Fragment: unique symbol;import { defineComponent, ref } from "vue";
// Functional component with JSX
const Counter = defineComponent({
setup() {
const count = ref(0);
const increment = () => count.value++;
return () => (
<div class="counter">
<p>Count: {count.value}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
});
// With props
const Button = defineComponent({
props: {
label: String,
disabled: Boolean,
variant: {
type: String as PropType<'primary' | 'secondary'>,
default: 'primary'
}
},
emits: ['click'],
setup(props, { emit }) {
const handleClick = () => emit('click');
return () => (
<button
class={['btn', `btn-${props.variant}`]}
disabled={props.disabled}
onClick={handleClick}
>
{props.label}
</button>
);
}
});const Layout = defineComponent({
setup(props, { slots }) {
return () => (
<div class="layout">
<header class="header">
{slots.header?.()}
</header>
<main class="main">
{slots.default?.()}
</main>
<footer class="footer">
{slots.footer?.()}
</footer>
</div>
);
}
});
// Usage
const App = defineComponent({
setup() {
return () => (
<Layout>
{{
header: () => <h1>My App</h1>,
default: () => <p>Main content</p>,
footer: () => <small>© 2024</small>
}}
</Layout>
);
}
});import { withDirectives, vShow, vModel } from "vue";
const Form = defineComponent({
setup() {
const visible = ref(true);
const inputValue = ref('');
const isLoading = ref(false);
return () => (
<div>
{withDirectives(
<input
type="text"
placeholder="Enter text..."
/>,
[[vModel, inputValue.value]]
)}
{withDirectives(
<div class="message">
Form is visible
</div>,
[[vShow, visible.value]]
)}
<button disabled={isLoading.value}>
{isLoading.value ? 'Loading...' : 'Submit'}
</button>
</div>
);
}
});const EventExample = defineComponent({
setup() {
const handleClick = (event: MouseEvent) => {
console.log('Clicked:', event);
};
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
console.log('Enter pressed');
}
};
return () => (
<div>
{/* Basic event handler */}
<button onClick={handleClick}>
Click me
</button>
{/* Event with modifiers */}
<input
onKeydown={withKeys(handleKeydown, ['enter'])}
onKeydownStop={(e: KeyboardEvent) => e.stopPropagation()}
/>
{/* Multiple event handlers */}
<button
onClick={[handleClick, () => console.log('Second handler')]}
>
Multiple handlers
</button>
</div>
);
}
});const CustomEventComponent = defineComponent({
emits: ['customEvent', 'dataChange'],
setup(props, { emit }) {
const handleCustom = () => {
emit('customEvent', { timestamp: Date.now() });
};
const handleDataChange = (newData: any) => {
emit('dataChange', newData);
};
return () => (
<div>
<button onClick={handleCustom}>
Emit Custom Event
</button>
</div>
);
}
});const ConditionalExample = defineComponent({
setup() {
const isLoggedIn = ref(false);
const userRole = ref<'admin' | 'user' | 'guest'>('guest');
const items = ref([1, 2, 3, 4, 5]);
return () => (
<div>
{/* Conditional rendering */}
{isLoggedIn.value ? (
<div>Welcome back!</div>
) : (
<div>Please log in</div>
)}
{/* Multiple conditions */}
{userRole.value === 'admin' && (
<button>Admin Panel</button>
)}
{/* List rendering */}
<ul>
{items.value.map(item => (
<li key={item}>Item {item}</li>
))}
</ul>
{/* Fragment for multiple elements */}
<>
<h2>Title</h2>
<p>Content</p>
</>
</div>
);
}
});const ReactiveExample = defineComponent({
setup() {
const count = ref(0);
const user = reactive({
name: 'John',
email: 'john@example.com'
});
const doubledCount = computed(() => count.value * 2);
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
return () => (
<div>
<h1>Count: {count.value}</h1>
<h2>Doubled: {doubledCount.value}</h2>
<div>
<label>
Name:
<input
type="text"
value={user.name}
onInput={(e) => user.name = (e.target as HTMLInputElement).value}
/>
</label>
</div>
<button onClick={() => count.value++}>
Increment
</button>
</div>
);
}
});const TemplateRefExample = defineComponent({
setup() {
const inputRef = ref<HTMLInputElement>();
const componentRef = ref<InstanceType<typeof SomeComponent>>();
const focusInput = () => {
inputRef.value?.focus();
};
onMounted(() => {
console.log('Input element:', inputRef.value);
console.log('Component instance:', componentRef.value);
});
return () => (
<div>
<input
ref={inputRef}
type="text"
placeholder="Focus me"
/>
<SomeComponent ref={componentRef} />
<button onClick={focusInput}>
Focus Input
</button>
</div>
);
}
});interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
size?: 'small' | 'medium' | 'large';
}
const TypedButton = defineComponent<ButtonProps>({
props: {
label: {
type: String,
required: true
},
variant: {
type: String as PropType<ButtonProps['variant']>,
default: 'primary'
},
disabled: Boolean,
size: {
type: String as PropType<ButtonProps['size']>,
default: 'medium'
}
},
emits: {
click: (event: MouseEvent) => true,
focus: () => true
},
setup(props, { emit }) {
const handleClick = (event: MouseEvent) => {
if (!props.disabled) {
emit('click', event);
}
};
return () => (
<button
class={[
'btn',
`btn-${props.variant}`,
`btn-${props.size}`
]}
disabled={props.disabled}
onClick={handleClick}
onFocus={() => emit('focus')}
>
{props.label}
</button>
);
}
});function defineGenericComponent<T>() {
return defineComponent({
props: {
items: {
type: Array as PropType<T[]>,
required: true
},
keyExtractor: {
type: Function as PropType<(item: T) => string | number>,
required: true
}
},
setup(props, { slots }) {
return () => (
<div class="list">
{props.items.map(item => (
<div key={props.keyExtractor(item)} class="list-item">
{slots.default?.({ item })}
</div>
))}
</div>
);
}
});
}
// Usage
const StringList = defineGenericComponent<string>();
const UserList = defineGenericComponent<{ id: number; name: string }>();// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
plugins: [
vue(),
vueJsx({
// Options
transformOn: true,
mergeProps: true
})
]
});// tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue"
}
}/**
* JSX namespace declarations
*/
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass {
$props: {};
}
interface ElementAttributesProperty {
$props: {};
}
interface IntrinsicElements {
[elem: string]: any;
}
interface IntrinsicAttributes {
key?: string | number;
}
}
}
/**
* JSX component props type
*/
type JSXComponent<Props = any> = (props: Props) => VNode;
/**
* JSX element props
*/
interface JSXProps {
key?: string | number;
ref?: VNodeRef;
}
/**
* Event handler types for JSX
*/
interface JSXEventHandlers {
onClick?: (event: MouseEvent) => void;
onInput?: (event: Event) => void;
onChange?: (event: Event) => void;
onFocus?: (event: FocusEvent) => void;
onBlur?: (event: FocusEvent) => void;
onKeydown?: (event: KeyboardEvent) => void;
onKeyup?: (event: KeyboardEvent) => void;
onSubmit?: (event: SubmitEvent) => void;
}