TypeScript is supposed to make your development life easier, but I vividly recall my earliest memories of TypeScript involving a lot of cursing, confusion about errors, and wondering why anyone would ever waste time writing out all types and how interfaces were any better than PropTypes. I used to write type definitions for hours and complain that I definitely didn't feel my productivity getting any better.
My point of view was understandable: I was a total noob (or, in polite parlance, a beginner). And everything is hard and demoralizing when you're a noob. Today, I love TypeScript and usually find projects written in plain old JavaScript to be injurious to my mental health. Looking back, I probably hated TypeScript early on because of all the hurdles I had to go through until I could feel its benefits.
If you've recently joined a TypeScript project, or are thinking of learning it for your own work, then I hope that my little blog post will help make your transition a little easier. These are probably the things that I wish I had known when starting out with TypeScript, but nobody showed me.
Without further ado, here's a list of of cool & useful tricks that make your life way easier when working with TypeScript.
1. Use Ctrl + Space
to see all the available props of a component, or all the available keys of an object.
Don't be a noob. Switching between files or Googling documentation is for people who've got nothing better to do with their time ;) It's much easier to see all available variables immediately, in a drop down menu right under your code.
If you're dealing with popular libraries, 9 times out of 10 the variable name will be descriptive enough for you to know what it's supposed to be used for. If you're confused by the function of a variable in your own code (or your colleague's code!), then you'll see immediately which variables may need to be renamed, or even refactored.
2. Use as
when you know the type of your variable better than your linter.
It happens to all of us. You try to pass in a variable that may be a string, TypeScript screams at you that it may also be undefined, which is unacceptable. But you know for a fact that this variable will never be undefined where you're using it, because you controlled for that ages ago. And perhaps you don't want to write a slew of if-statements.
The easy solution is to just use as
:
interface Props { word?: string }
// let's say that we definitely passed in
// a word to props in another file
const { word } = props;
const logWord = (word: string) => {
console.log(`The word is: ${word}`)
}
// woops, Typescript error! word is required!
logWord(word);
// no error
logWord(word as string)
The example above may make it look like using as
would have to be bad practice. Why make the prop optional if you need to use it later? True, but that's only because I couldn't think of a better example. In fact, this issue pops up quite often, especially when working with 3rd party libraries.
Sooner or later, you will probably encounter a situation in which it's useful to have as
in your mental toolkit, and in which using as
solves the problem 5 times faster than if you insisted on making your code logic absolutely foolproof and TypeScript-friendly.
Using as
is called type-assertion, and it can also be done by putting the type in brackets before the variable:
// type assertion with 'as'
const wordLength = (word as string).length;
// type assertion with brackets
const wordLength = (<string>word).length
3. Use Pick
and Omit
to efficiently create typed sub-components
Writing interfaces for every sub-component is repeating yourself. And as every clever software book will tell you, the first rule of programming is Don't Repeat Yourself!
Try this:
interface PersonProps {
name: string,
age: number,
imageUrl: string,
location: string,
description: string,
}
const PersonContainer = (props: PersonProps) => {
const { name, age, imageUrl, location, description } = props;
return (
<div>
<img src={imageUrl} />
<h2>{name}, {age}</h2>
<h3>{location}</h3>
<p>{description}</p>
</div>
);
}
/**
* Let's say you only want to display some of the
* person's info in a summary card, which can be
* pressed for details
*/
type CardProps = Pick<PersonProps, 'name' | 'age' | 'imageUrl' >;
const PotentialRomanticInterest = (props: CardProps) => {
const { name, age, imageUrl } = props;
return (
<div>
<img src={imageUrl} />
<h2>{name}, {age}</h2>
</div>
);
}
/**
* What if you've got a lot of props, but you want
* to leave some out?
*
* You can make a new type/interface with all
* the same props as your other interface (here,
* PersonProps), but with some omitted.
*/
type BlindDateProfile = Omit<PersonProps, 'imageUrl'>;
const PotentialRomanticInterest = (props: BlindDateProfile) => {
const { name, age, location, description } = props;
return (
<div>
<img src={'images/incognito-image.png'} />
<h2>{name}, {age}</h2>
<h3>{location}</h3>
<p>{description}</p>
</div>
);
}
There's more selectors where that came from. Pick
and Omit
may be the simplest and most common, but there are plenty of other useful ones, like Record
, Partial
, or Parameters
. If you want to dive deeper, check out TypeScript's Utility types documentation.
4. Cmd + click
on things to explore what they expect.
All right, this isn't really a TypeScript trick. But a typical TS developer probably spends a lot of time command-clicking his various 3rd party library functions. With 3rd party libraries, I often find it much easier to figure out what various parameters of a function are for by just clicking around the TypeScript definitions, instead of reading the documentation.
Don't be afraid of type definition files. They're there for the linter, but they're also there for you, if you'll grant them your attention.
Another trick: say you're using a 3rd party library function, and it takes in a complicated parameters object. Well, you can often simply import the parameter object's type, define your variable to be the same type, and get friendly warnings until you fill out every missing property. (And if the arguments' types are not exported, you can always use the built-in Parameters
type to get them yourself).
import { doSomething } from 'cool-library';
// These types are often exported, but if not, don't fear! `Parameters<>` can usually help you out
type DoSomethingArgs = Parameters<typeof doSomething> // <-- an array;
type FirstArg = DoSomethingArgs[0] // <-- the first argument;
const input: FirstArg = { complicatedKey: 'complicatedValue' } // <-- now this object will have autocomplete, and red warning squiggles :)
doSomething(input);
This tip works even better when you couple it with the auto-complete trick from tip #1, and the auto-import trick from tip #6.
5. If you have a list of options, try writing your own type instead of using a generic string
or number
type
Let's say your passing in some names to your function:
// okay
interface Props {
name: string
}
This will work. But what if you know that your name should be one of three, and nothing else? Perhaps you want your TypeScript error to warn you if you pass in and unacceptable name.
// better
type StarterPokemon = 'Bulbasaur' | 'Charmander' | 'Squirtle';
interface Props {
name: StarterPokemon
}
This way, the name
prop can only be one of the three strings specified in the StarterPokemon
type.
Writing your own stricter types is useful because you might forget later which options you have available, or another developer might take over the project and not know which Pokémon are available as starters and break the rule somewhere. Having a strict list of options prevents such a thing from occurring:
const chooseStarterPokemon = (name: StarterPokemon) => {
setPokemon(name);
}
chooseStarterPokemon('Pikachu'); // error!
// Type '"Pikachu"' is not assignable to type 'StarterPokemon'.
Another bonus is that you will later be able to mouse over the StarterPokemon
type wherever you use it to get a reminder of what were the options. You can even add comments:
type StarterPokemon = 'Bulbasaur' // grass
| 'Charmander' // fire
| 'Squirtle' // water
;
/**
* Make sure to add that finaly semi-colon,
* or else the mouse-over info will stop after
* 'Squirtle' and won't show the final comment
*/
As a general tip, you might find it helpful to try to write your types so that you could later autocomplete your way through the whole project by using the control + space
tip from #1. To borrow from Einstein, try to make your types as restrictive as possible, but no more so than needed.
6. Auto-import all missing variables with Cmd + .
Many VS Code users know that you can auto-import individual things by just typing out the first few characters and then selecting the first VS Code dropdown suggestion. But you can also import all missing variables that are already declared in the file.
If you're using TypeScript, all you have to do is click on an undefined variable/function/type and press Cmd + .
. VS Code should be able to suggest to you some options for where to import it from. If you've got several undefined items, it should even offer to 'Add all missing imports'.
This trick works for both 3rd party libraries and your own code, as long as everything is exported correctly and written in TypeScript. And it nearly always works exactly as expected.
In fact, most of the time I don't even check that the imports came from the right place after doing this. Also, you usually learn pretty quickly which things VS Code tends to import from the wrong places. For example, for me it often imports the Lodash 'get' function from the wrong library, so that is one of the few things I import manually.
7. Get optional props the smart way using a ?
operator ("Optional chaining")
Have you ever had to do this?
interface Item {
prop?: {
subprop?: string;
}
}
const value = item && item.prop && item.prop.subprop || 'default';
Seeing several &&
like that makes the code hard to understand at first glance. Wouldn't it be nice if you could tell TypeScript to get that deeply nested value, only if item
and prop
actually exist as well?
Well, it turns out there is! Try this:
const value = item?.prop?.subprop || 'default';
The ?
operator is a sneaky way to tell TypeScript (and newer versions of JavaScript) to only go deeper if the higher-up key exists. In other words, only look for prop
if item
is defined and an is object, and only look for subprop
if prop
is defined and is an object.
The official name for this syntax is "optional chaining". It's easy and intuitive, and it'll probably save you a few lines of code.
8. Work with old JS files by enabling allowJs
in tsconfig.json
This is a quick and easy tip that might not be obvious for the beginners out there.
You can easily mix TypeScript and plain old JavaScript files. All you have to do is make a little edit in your tsconfig.json
file:
{
"compilerOptions": {
"allowJs": true,
// ... other options
}
}
Now your TS compiler won't block your development. But you'll probably have to use a lot of any
types for any JS files you import.
9. Automatically refactor your typed objects into a named interface
Let's say you have a function that takes in a long object:
renderFAQItem = (item: { faqQuestion?: string, faqAnswer?: string }) => {
return (
// component
)
}
The { faqQuestion?: string, faqAnswer?: string }
definition makes this line of code too long. Worse, it also makes your code a bit less maintainable.
Thankfully VS Code lets you turn it into a named interface at the click of a button:
That light bulb is actually quite handy, if you know how to use it. It can also refactor variables into constants, lines into functions, generate get/set methods for classes, and more. There's a whole section on refactoring in the VS Code TypeScript documentation.
10. Combine TypeScript with JSDoc for awesome documentation right in your VS Code.
JSDoc is a pretty awesome skill to have for any JavaScript project. And fortunately it can be combined with type definitions as well.
VS Code automatically recognizes JSDoc comments when you use a multiline comment that starts with two asterisks: /** Insert content here */
. You can also add some keywords (marked by @-symbols) for even more intellisense. And the great thing about JSDoc comments is that, unlike regular //
comments, the things you write down will be visible on-hover, anywhere in your project!
This is a great addition to your codebase when working in teams, when you want to give other developers a clearer explanation of what your code is supposed to do, and how it's supposed to be used.
Many of the best libraries, such as Material UI, annotate their TS definitions with JSDoc comments, so you could see the documentation for that property right in your VSCode tab, without opening any links. Often they even include the links anyway, in case you're hungry for more info :)
These are the main things I wish I knew when starting out with TypeScript.
Keep in mind that TypeScript was created to help you, not to annoy you. As a rule of thumb, I try to write my TS in such a way so that I would later be able to autocomplete my code as much as possible.
I hope these tips could be helpful for you. Good luck, and have fun!
Top comments (0)