In addition to template syntax, Vue components can also be written using JSX . JSX, originally developed by Facebook for React, has gained popularity due to its ease of adoption, leading many web frameworks to support this JavaScript dialect. JSX also offers another advantage: seamless type integration (TSX). Any frontend framework that compiles templates into createElement() -like functions should inherently support JSX, and Vue is no exception. Vue has supported JSX since version 2.x. The template options syntax in Vue 2 was cumbersome for flexible scenarios, often necessitating JSX or render function implementations (e.g., ant-design-vue's Modal.method()).

Vue 3 introduced the setup function, allowing JSX to be used not only in render functions but also directly in the return value of setup. Render functions tend to isolate component state, logic, lifecycle hooks, and structure, whereas the Composition API aims to bring these elements closer together. Thus, when using JSX in Vue 3, it is recommended to implement it within setup rather than a render function. A simple comparison:

// vue2
Vue.component('Hello', {
  render: function (h) {
    return h(
      'h' + this.level,
      this.title
  props: {
    level: {
      type: Number,
      required: true
    title: {
      type: String,
      required: true
    Enter fullscreen mode
    Exit fullscreen mode

Vue 2 had limited TypeScript support, making both template and render function approaches cumbersome for large-scale projects. Vue 3 significantly enhances TypeScript integration, offering robust type inference for both the new <script setup> syntax and traditional Options API. Previously, props validation was purely runtime-based. For example, defining a component's props:

props: {
  title: {
    type: String
    Enter fullscreen mode
    Exit fullscreen mode
import { defineComponent, h, PropType } from 'vue';
export const HelloProps = {
  level: {
    type: Number as PropType<1 | 2 | 3 | 4 | 5>,
    required: true
  title: {
    type: String as PropType<string>,
    required: true
} as const;
export default defineComponent({
  name: 'Hello',
  props: HelloProps,
  setup(props) {
    return () => h(
      `h${props.level}`,
      props.title,
    Enter fullscreen mode
    Exit fullscreen mode
<script setup lang="ts">
const slots = defineSlots<{
  default(props: { msg: string }): any
</script>
    Enter fullscreen mode
    Exit fullscreen mode
  slots: Object as SlotsType<{
    default: () => JSX.Element;
    footer: () => JSX.Element;
  setup(props, { slots }) {
    return () => <>
      {slots.default?.()}
    Enter fullscreen mode
    Exit fullscreen mode

JSX transforms elements into createElement-like calls. In React, children are treated as a props.children attribute, but Vue handles slots differently. Vue’s JSX runtime does not map slots to props, and its type definitions (jsx-runtime.d.ts) lack ElementChildrenAttribute declarations. React’s approach:

interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
interface ElementChildrenAttribute {
  children: {};
    Enter fullscreen mode
    Exit fullscreen mode
export function defineComponent<
  // ... existing generics
  ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
    ? TypeEmitsToOptions<TypeEmits>
    : RuntimeEmitsOptions,
  // 注入发生在这里,给 InferredProps 增加一个 children 属性,指向 传入的 Slots 定义,当然放到最后面会更好,不会被覆盖
  InferredProps = {children?: UnwrapSlotsType<Slots>} & (IsKeyValues<TypeProps> extends true
    ? TypeProps
    : string extends RuntimePropsKeys
      ? ComponentObjectPropsOptions extends RuntimePropsOptions
        ? {}
        : ExtractPropTypes<RuntimePropsOptions>
      : { [key in RuntimePropsKeys]?: any }),
  TypeRefs extends Record<string, unknown> = {},
  // ... remaining generics
      

Built on Forem — the open source software that powers DEV and other inclusive communities.

Made with love and Ruby on Rails. DEV Community © 2016 - 2025.