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>
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 };
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 };
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>
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.
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)
great post!
Hey Thanks! Have you tried using Vue with JSX or TSX?
i haven't acc started learning vue, definitely on my list though.
do you know any good places to learn vue from?
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. 🤔
oh okay thanks!
This seems even more confusing than SFC, in fact this is exactly why I left React, does this really lead to better DX?