How I do Vue in 2019: TypeScript and TSX

Banner by Lily Jang

In this article, I will talk about how I have been using Vue, and plan to do so in 2019. The main new technologies I’ve found useful over the last year have been TypeScript and .tsx instead of .vue files.

The main benefit of using .tsx over .vue files is typechecking in the render function - as far as I know, .vue files to not have typechecking in the <template> section, or at least it is not very well supported.

The app we are building will look like this:

The source code for this article is available here.

You can select a sign, and it will perform the calculation.

Setup

Install the Vue CLI v3 if you haven’t already, then create a new app by running vue create tsx_adder.

For each of prompts

  • The only feature we want is TypeScript and Babel, so select those two.
  • We do not want the class-style component syntax
  • We do want Babel
  • select “in dedicated files” for the config

Once that has finished installing, cd tsx_adder. We need one dependency to be able to use TSX. That is vue-tsx-support. Install in by running vue add tsx-support. We will also demonstrate Vuex with TypeScript, so add it with yarn add vuex.

Structure

The application will have two components: App.tsx and Adder.tsx. The App.tsx will connect to the Vuex store and pass props to the Adder.tsx, which is the presentation component that will handle the layout and UI.

In a large app, you would probably have an AdderContainer.tsx, but for simplicity I am just using App.tsx to interface with the store. Adder.tsx receives data from the store via props, and communicates with the parent by emitting events. This will let us demonstrate:

  • typesafe props, including complex types like Enums and Objects
  • typechecked events between the parent/child component
  • how to get type inference and type safety with Vuex State in components

Start off by converting App.vue to App.tsx. Update it to contain the following:

Since we haven’t created Adder.tsx yet, create it: components/Adder.tsx. Inside components/Adder.tsx, add the following bare-bones component:

Now import it in App.tsx: import { Adder } from './components/Adder'. Lastly, head to main.ts and change import App from './App.vue to import { App } from './App'. Run yarn serve (or npm run serve). localhost:8080 should show the following:

Typesafe Props

The first thing we will demonstrate is typesafe props, including both primitives (like Number and Boolean) as well as complex types, like Enum. In Adder.tsx, add the following:

One caveat is you need to type true as true to get TypeScript to check the props at compile time. It is discussed breifly here. One you add that, if your editor supports TypeScript (for example VS Code), head back to App.tsx, and you should see an error, and <Adder > has a red line under it. Towards the end of the error message, it says Type '{}' is missing the following properties from type '{ left: number; right: number; }': left, right. Let's provide left and right in App.tsx:

Try passing a string instead — TypeScript will warn us the prop type is incorrect.

Next, let’s add a more complex type — an enum. Create a directory called types under src, and inside it a sign.ts file with the following:

Next update Adder.tsx:

Another unforunately hack, which shows some of the limits of Vue’s TS support is String as () => Sign. Since our Sign enum is just Strings, we do String as () => .... If it was an enum of Object or Array, we would type Array as () => MyComplexArrayType[]. More information about this is found here.

Head back to App.tsx, and you'll see another error around <Adder />. Fix it by adding the following:

Typesafe Events

Now let’s see how to have typechecked events. We want the adder to emit a changeSign event when any of the four signs are clicked. This can be achieved using componentFactoryOf, documented here. Start by updating App.tsx:

<Adder /> has an error again: Property 'onChangeSign' does not exist on type '({ props: .... That's because we are passing a prop that Adder doesn't expect.

Add the following to Adder.tsx:

Now the error is gone. Try changing the signature of changeSign(sign: Sign) to changeSign(sign: Number) - TS warns you the parameter has the incorrect type, very cool. Read more about componentFactoryOf here.

Two last things to complete the Adder.tsx component. First, add the following interface at the top, and data function:

Lastly, let’s add the render function for Adder.tsx. It isn't anything special, so I won't go into detail.

One small caveat is we define the event interface as onChangeSign, but we emit changeSign.

To make the app look a bit better, here is some css. Create components/adder.css and insert to following:

Then do import './adder.css' at the top of Adder.tsx. The page now looks like this:

Let’s add Vuex and make the buttons work now.

Adding a Typesafe Vuex store

The next step for the app is adding a (somewhat) typsafe Vuex store. Make a store folder inside of src, then inside of store create index.ts and calculation.ts. Inside store/index.ts, add the following:

Nothing especially exciting — we just define a new Veux store, and pass a calculation module, which we are going to make now. In calculation.ts add the following:

We define a calculation module, with the left and right values in the state. Import it in main.ts:

Let’s use these values in the app now. Update App.tsx:

We need to type (this.$store.state as IState) to get typechecking on the store modules. There are other alternatives that will let you get type checking without casting this.$state to IState, but I've been using this pattern and found it pretty good so far.

Adding a Mutation

Let’s add a mutation. The goal will be to save the selectedSign in the state, and update it with a mutation. Update calculation.ts:

We added a SET_SIGN mutation. The type of state is interferred, since we passed in ICalculationState to Module when we declared the calculation module. We can use the new mutation in App.tsx:

We do not get any typechecking on the commit handler or payload. This is still a problem I'm exploring solutions to. There are a few solutions out there, but none of which feel clean enough, or require a level of abstraction I'm not happy with. I hope Vuex itself can evolve to provide a better TS experience out of the box in the future. I will propose my solution in a follow up article.

Let’s finish the app off. Update App.tsx with the final code, which includes a computer property, result, to calculate the value based on the sign:

Now the app looks like this:

Clicking the signs updates the result based on the calculation.

Improvements and Conclusion

This article demonstrates:

  • creating Vue components using tsx
  • Typesafe props and events
  • Typesafety for this.$store by using an interface

Some improvements I’d like to cover in a future article include:

  • Typesafe getters, commit(mutation) and dispatch(action)

The source code for this article is available here. Originally published on my personal blog.