DEV Community

loading...
Cover image for How I improve my skills in Typescript

How I improve my skills in Typescript

codeozz profile image CodeOzz ・3 min read

I will share with us some tips that improved my skill in Typescript !

Typeguard

Typeguard allow you to validate the type of an object within a conditional block.

interface Fish {
  swim: () => void
}

interface Bird {
  fly: () => void
}

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined
}
Enter fullscreen mode Exit fullscreen mode

We can be sure thanks to the condition, that pet object is a Fish.

Why and where use this ?

It's very useful when you need to check the type of an object among other type. In the example above, the typeguard isFish can be use like that.

function toto(pet: Fish | Bird) {
  if (isFish(pet)) {
     pet.swim() // At this moment, TS know that pet is `Fish` and no a `Bird`
  }
}


function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined
}
Enter fullscreen mode Exit fullscreen mode

Mapped Type

I use it a lot when I want to defined possible key of an object !

type PossibleKeys = 'a' | 'b' | 'c'

type Toto = {
   // This is a mapped type
  [key in keyof PossibleKeys]: string
}

const toto: Toto = { ... } // Only key allowed are a, b or c !
Enter fullscreen mode Exit fullscreen mode

Type this as argument in function

A small tips, you can type this object in a function like this

function toto(this: { a: string }, arg: number) {
  console.log(this.a, arg) // "toto",  55 
}

toto.bind({ a: 'toto' })(55) // Don't forget to bind `this`

Enter fullscreen mode Exit fullscreen mode

Infer Type

With generic type, you can use condition in order to infer the type ! What does mean infer ? Infer type is the capability of Typescript to find the correct type of your object.

type NonNull<T> = T extends (null | undefined) ? never : T

const toto: NonNull<undefined> = undefined // TS Error since object type (toto) is never, it's not possible to create this
const toto: NonNull<string> = 'tt' // No error since object type (toto) is string !
Enter fullscreen mode Exit fullscreen mode

Utility Types

Typescript allow us to use utility type, it's a very usefull feature ! you have the full list at https://www.typescriptlang.org/docs/handbook/utility-types.html

I will show you the common utility type that I used !

Partial :

Constructs a type with all properties of Type set to optional.

interface Toto { a: string, b: string }
// Partial type equal to -> interface Partial<Toto> { a?: string, b?: string }

const partialToto: Partial<Toto> = { a: 'a' }
Enter fullscreen mode Exit fullscreen mode

Pick & Omit :

Pick is used to extract some keys from a type in order to create a new type.

interface Toto { a: string, b: string }
// Pick type equal to -> interface Pick<Toto, 'a'> { a: string }

const pickToto: Pick<Toto, 'a'> = { a: 'a' }
Enter fullscreen mode Exit fullscreen mode

Omit is used to remove some keys from a type in order to create a new type.

interface Toto { a: string, b: string }
// Pick type equal to -> interface Omit<Toto, 'a'> { a: string }

const omitToto: Omit<Toto, 'a'> = { b: 'b' }
Enter fullscreen mode Exit fullscreen mode

With three utility types, you can create new very smart type ! And very useful to understand for other developers.

Record :

You can construct an object with typed keys and type and make useful type like this

type TotoKeys = 'a' | 'b' | 'c'
interface Toto { name: string, age: number }

const toto: Record<TotoKeys, Toto> = {
   a: { name: 'a', age: 55 },
   b: { name: 'b', age: 44 },
   c: { name: 'c', age: 33 },
}
Enter fullscreen mode Exit fullscreen mode

I love records since you can use enum for typing keys !

enum TotoEnum { 
  A = 'A',
  B = 'B',
  C = 'C'
}
interface Toto { name: string, age: number }

const toto: Record<TotoEnum, Toto> = {
   [TotoEnum.A]: { name: 'a', age: 55 },
   [TotoEnum.B]: { name: 'b', age: 44 },
   [TotoEnum.C]: { name: 'c', age: 33 },
}
Enter fullscreen mode Exit fullscreen mode

I hope you learn and improves your skills during this article !

If you have other suggestions or questions, don't hesitate to put it in comment bellow !

Discussion (14)

pic
Editor guide
Collapse
thorstenhirsch profile image
Thorsten Hirsch

Hi CodeOzz, I might be totally wrong here, because I'm a Typescript noob, but your typeguard example doesn't look like the "Typescript way" to me. Today a friend of mine twittered some Typescript wisdom so now I want to propose the following typeguard:

type Fish = {
    kind: "fish",
    swim: () => void
}

type Bird = {
    kind: "bird",
    fly: () => void
}

function isFish(pet: Fish | Bird): Boolean {
    switch (pet.kind) {
        case "fish": { return true; }
        default:     { return false; }
    }
}
Enter fullscreen mode Exit fullscreen mode

As I understand it my (type based) version does type-checking at compile time while your (interface based) version does type checking only at runtime? Oh, and as you can see I also changed the return type of isFish to Boolean, because I don't understand the "pet is Fish" return type.

So what do you think?

Collapse
codeozz profile image
CodeOzz Author

Hi Thorsten how are you ! Thank you for your comment !

I will try to explain you what is the pet is Fish. This none common synthax is called Type predicate in TS, it allow us to tell to TS that your param is typed as a Fish only if your function return true.

In my case I check if my object has swim methods, if it's the case I tell to TS -> Typescript, trust me, this pet is a Fish, not a Bird.

In you code, you put an interest thing, you create a new property in order to determine the type of your object. It should be a great idea but in your case you should delete this since you have already a way to determine the type of your object, thanks to the methods fly or swin, so you don't really need to add this new property !

About your function, it's not a really typeguard, and why? it's because your are correctly checking the type but the typescript checker is again doubting about the type of this object since you are not using predicated type !

Why it can be not really usefull to use the function like this? I will show you why of course !

const toto = function (pet: Fish | Bird) {
    if (isFish(pet)) {
        // pet is again a Fish or a Bird for TS
        pet.swim() // error TS Property 'swim' does not exist on type 'Fish | Bird'.
        pet.fly() // error TS Property 'fly' does not exist on type 'Fish | Bird'.
    }
}
Enter fullscreen mode Exit fullscreen mode

So if you want to use pet in the if block, you cannot really use Fish property since for TS, pet is again a Fish or a Bird.

With the Type predicate, you don't have this issue:

const toto = function (pet: Fish | Bird) {
    if (isFish(pet)) {
        // pet is a fish, since we tell to TS that pet is a Fish thanks to Typeguard
        pet.swim() // work
        pet.fly() // error TS Property 'fly' does not exist on type 'Fish', since pet is not a Bird !
    }
}
Enter fullscreen mode Exit fullscreen mode

If you have any question ask me ! ;D

Collapse
dobromyslov profile image
Viacheslav Dobromyslov • Edited

But when you add new type Duck you will not be able to differentiate pets and fishes from Duck just checking available methods because a Duck can both swim and fly. In that case you will have to turn to Thorsten's explicit kind of an animal.

Thread Thread
codeozz profile image
CodeOzz Author

In the case of your interface growth you will need to use another way to put in the type guard yes. But in this exemple it's overkill to use more properties

Collapse
thorstenhirsch profile image
Thorsten Hirsch

Thank you. This predicate thingy seems pretty cool indeed.  👍

Collapse
blindfish3 profile image
Ben Calder • Edited

I find your example of Typeguard a little problematic precisely because it is subject to fail in the case of "interface growth". Determining type based on the presence of a method looks really flaky to me. As already suggested a duck would be identified as a fish. In your example code it makes much more sense to simply check for the presence of the swim method (i.e. canSwim()); rather than checking against the type*.

I appreciate it's sometimes hard to find simple real-world examples; but in this case you either need to find a better one or add a health warning: your isFish method is very likely to introduce hard to trace bugs in future and defeats the very purpose of using TS.

Apart from that I found the article useful ;)


* In the same way you should prefer 'feature detection' over 'browser detection'.

Collapse
codeozz profile image
CodeOzz Author

Hello ! As I said in another comment the aim of this example is to understand what is a Typeguard ! My example doesn't reflect the reality since as you said, Duck can be identificated as Fish !

In a real project you should use another way to make the difference between each class :)

Thank you for your comment a lot !

Collapse
blindfish3 profile image
Ben Calder • Edited

In a real project you should use another way to make the difference between each class

Indeed; that was precisely my point: it's an ill-conceived example and demonstrates how not to use Typescript. There's little point demonstrating that functionality exists if you don't also demonstrate the thought processes required to use it properly.

Collapse
merichard123 profile image
Richard

This is awesome!! Thank you so much I've always wondered what some of this does in other peoples code. Now I can use it myself!

Collapse
codeozz profile image
CodeOzz Author

Thank you Richard ! I appreciate your comment ! If you have any question about some TS code that you don't understand, you can ask me on my twitter :)

Collapse
merichard123 profile image
Richard • Edited

Thank you so much!!

Collapse
thatanjan profile image
Anjan Shomodder

Great Article

Collapse
codeozz profile image
CodeOzz Author

Thank you a lot Anjan, it's very motivating to see comment like this :)

Collapse
andrewbaisden profile image
Andrew Baisden

Good article.