SFC (single file component) is one of best vue invention. Compared with jsx, it's simple, easy to use and maintain.

In some cases however, react jsx offers flexibility and power that SFC cannot offer. eg. You're making a complex table component, a custom layout manager. You'll then appreciate the expressiveness of jsx. You can leverage all the power of javascript.

With vue3, jsx support is superb.

Before you begin, notice that jsx syntax in vue3 is different from vue2.

Setup

If you're using vite, the default vue template does not support jsx. You need to install @vitejs/plugin-vue-jsx plugin, and use it inside vite.config.js file.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx';

export default defineConfig({
  plugins: [vueJsx(), vue()],
  build: {
    minify: false,
  },
})

In other build environments, you may need to import h and Fragment from vue if your build library does not do it for you.

Get started

Here's a simple jsx component to help you getting started.

A simple component with option API:

import { defineComponent } from 'vue';

export default defineComponent({
  data() {
    return {
      name: 'Tom',
    };
  },
  render() {
    return <h1>hello, {this.name}</h1>;
  },
})

With composition API:

import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    let name = ref('Sam');
    return () => {
      return <h1>hello, { name.value }</h1>;
    };
  }
});

In composition API , you return a render function in setup function, instead of an object of render variables. Render function is called to render jsx using vue reactive constructs.

Or the simplest functional jsx component

export default () => <h1>Hello</h1>;

Events

For an event emitted as evt, you should use onEvt to listen

// emit an event
<button onClick={() => emit('msg', 'hello') }>Emit</button>

// listen for an event
<HelloWorld msg="vue3+jsx" onMsg={ onMsg } ></HelloWorld>

v-model

In jsx, you can use vModel or v-model attribute to do a two-way binding. The model's value should be a raw variable instead of a reactive construct.

export default defineComponent({
  setup: () => {
    let msg = ref('hello');
    return () => {
      return (
        <input v-model={msg.value} />
      );
    };
  }
});

To pass multiple models in jsx, pass an array. The second argument is the model's name.

<Counter
  v-model={[count.value, 'count']}
  v-model={[name.value, 'name']} />

Or use v-modes

<Counter
  v-models={[[count.value, 'count'], [name.value, 'name']]} />

Slots

Define a component with slots

export default defineComponent({
  setup(_, { slots }) {
    return () => {
      return (
        <div>
          <h1>default slot:</h1>
          { slots.default!() }
          <h1>abc slot:</h1>
          { slots.abc!() }
          // jsx style also works: <slots.default />
        </div>
      );
    };
  },
});

Here we have a component with two slots, a default and one named abc

To fill a component with slot content

<CompWithSlot>
  <HelloWorld></HelloWorld>
</CompWithSlot>

Or with a render function

<CompWithSlot>
  {() => {
    return <HelloWorld></HelloWorld>
  }}
</CompWithSlot>

If you need to fill in multiple slots, provide a render object

<CompWithSlot>
  {{
    default() { return <h1>default slot content</h1> },
    abc() { return <h1>abc slot content</h1> },
  }}
</CompWithSlot>

Or you can use v-slots directive

let slots = {
  default() { return <h1>default slot content</h1> },
  abc() { return <h1>abc slot content</h1> },
};
<CompWithSlot v-slots={ slots } />

Directives

To use custom directives with jsx, you need to use withDirectives and resolveDirective APIs.

withDirectives provide a piece of jsx with directive support.
resolveDirective find a registered directive implementation using string.

// ripple
import { withDirectives } from 'vue';
import ripple from './ripple';

withDirectives(
    <div class="box"></div>,
    [[ripple]],
)

The second argument is a bit complex. Refer this doc for full enlightment.

To use builtin directives, you can use dash-cased attribute name or camel cased attribute name.

<div v-html={"<h1>Hello</h1>"}>
<div vHtml={"<h1>Hello</h1>"}>
<div v-show={false}>
<div vShow={false}>

Conclusion

With these code snippets, I hope you can get started writing vue jsx and improve your vue skills to the next level.

References: https://github.com/vuejs/jsx-next#installation