DEV Community

Cover image for Vue and TSX ?! 🤯
Vincent
Vincent

Posted on

Vue and TSX ?! 🤯

Many developers appreciate React and its Functional Components (FCs) due to the ease of building "multi-piece" components using JSX (or TSX). While some may assume that JSX is unavailable in Vue, in fact, it is perfectly possible to ditch Vue’s conventional Single File Component pattern (SFC) and use JSX or TSX to build components that share the same context like you would with React. In this article you will learn about the pro’s and con’s of using SFCs or using Vue with JSX and how to build a complex components with a used context by using JSX.

So let’s dive right in!

What are Single File Components (SFCs)?

As the name might already give away, SFCs are components that allow us to write a components by using only a single file for the logic, style and template of the component. This is a pattern that is used by the two frameworks Vue and Svelte, where SFCs are saved with the extensions .vue and .svelte, respectively. The structure of a SFC in Vue might look as follows:

<script setup>
  //component logic goes here
</script>
<template>
  <!-- html goes here -->
</template>
<style>
  //css styling goes here
</style>
Enter fullscreen mode Exit fullscreen mode

This certainly is great, when you are building a component, where all component logic is contained within the same file and exposes the following main benefits on the developer experience (DX):

  • The component has a clear and maintainable structure that is quite forward (very close to standard HTML)

  • Using scoped styles you can make sure that your CSS styling only applies to one single component (however, this is often not used in practice)

  • There is a clear separation of concerns (logic of different components is split to different files)

However, for building components that use a shared context (like for example a dropdown-menu or a accordion), the SFC pattern might seem less convenient. In my opinion, this is where React and functional components (FCs) with JSX perform better. This article might interest you, if you want to read more about why Ryan Carniato is not a fan of SFCs.


What is JSX?

JSX stands for JavaScript XML. It is a way of writing HTML-like code within Javascript code. When used within React, it is also possible to express the logic and html within the same file. An example for a FC component within a JSX file could look as follows:

const MyComponent = () => {  
   //logic is expressed here
   return <div>/* html goes here */</div>;
};

const MyOtherComponent = () => {  
   //logic is expressed here
   return <div>/* html goes here */</div>;
};

export { MyComponent, MyOtherComponent };
Enter fullscreen mode Exit fullscreen mode

As you might notice, with this file it is possible to declare two different components and export them. This can for example come in very handy, when you want to share a state between nested components and do not want to pass it via the component props (which in Vue is called Prop Drilling). If you have been using React before, the hook useContext() might be familiar to you. Using this hook you can access the state defined in a parent component, in any nested Child component. Here you can read more about how to share state between components in React.


But... Also Vue can do the trick!

While the default of using SFCs within .vue files, might in most cases be the right fit, sometimes it might be more beneficial to resort to an alternative solution (for example when you are building a component library). In the following we are going to look at an example where we define a three components Parent, Child and DeepChild in the same .tsx file. The components Parent and DeepChild share the same value for count and by clicking a button in the component grand-child we want to update this variable.

import { defineComponent, provide, inject, Ref, ref } from "vue";

const Parent = defineComponent({
  name: "Parent",
  setup(_, { slots }) {
    // global state count
    const count = ref(0);

    //callback to update count
    const updateCount = () => {
      count.value++;
    };

    // provide count and updateCount to all children
    provide("count", {
      count,
      updateCount,
    });

    return () => (
      <div>
        <span>Parent: {count.value}</span>
        {slots.default?.()}
      </div>
    );
  },
});

const Child = defineComponent({
  name: "Child",
  setup(_, { slots }) {
    // count and updateCount not accessed here
    return () => <div>{slots.default?.()}</div>;
  },
});

const DeepChild = defineComponent({
  name: "DeepChild",
  setup() {
    // count and updateCount injected in DeepChild
    const { count, updateCount } = inject("count") as {
      count: Ref<number>;
      updateCount: () => void;
    };

    return () => (
      <button onClick={() => updateCount()}>DeepChild: {count.value}</button>
    );
  },
});

export { Parent, Child, DeepChild };
Enter fullscreen mode Exit fullscreen mode

Using the functions provide() and inject() we can make the state and an update function globally available to all components by binding them to a key count. When you are coming from React, this might already familiar to you as its quite similar to providing [state, setState] = useState(count) to other components via useContext().

Now we can import the three components Parent, Child and DeepChild where we need them.

<script setup lang="ts">
import {
  Parent, 
  Child,
  DeepChild,
} from "../components/nested-component";
</script>
<template>
 <Parent>
    <Child>
      <DeepChild />
    </Child>
 </Parent>
</template>
Enter fullscreen mode Exit fullscreen mode

And the result looks as follows. We can update count from within the DeepChild component without having to pass it through the intermediate component Child and Prop Drilling.

Clicking button

Using provide/inject is not exclusive to TSX component, however, it seems a lot more intuitive and maintainable having this logic defined in the same file. Here are some ideas, where you might find using TSX useful:

  • Deeply nested components
  • recursive components
  • Headless components and primitives

That's all folks!

What do you think? Do you sometimes use TSX or do you only stick to Vue's SFCs? Where could you imagine could it be applied?

I hope you liked this article and find it useful. Thanks for reading! ❤️

References

P.S.: Thanks to Chat GPT for proofreading this article 😘

Top comments (6)

Collapse
 
vulcanwm profile image
Medea

great post!

Collapse
 
vincentdorian profile image
Vincent

Hey Thanks! Have you tried using Vue with JSX or TSX?

Collapse
 
vulcanwm profile image
Medea

i haven't acc started learning vue, definitely on my list though.
do you know any good places to learn vue from?

Thread Thread
 
vincentdorian profile image
Vincent

I think the docs are a good place to start. I think they are nicely written and quite comprehensible.

If you are willing to pay, there is: Vue Mastery and VueSchool. Maybe they also have some introduction content for the free tier. 🤔

Thread Thread
 
vulcanwm profile image
Medea

oh okay thanks!

Collapse
 
aiktb profile image
aiktb

This seems even more confusing than SFC, in fact this is exactly why I left React, does this really lead to better DX?