DEV Community

Cover image for Tips and Tricks for Typescript to daily use
DaniAcu
DaniAcu

Posted on • Updated on

Tips and Tricks for Typescript to daily use

I often see that JS devs struggle to create good types in Typescript. Some people use the famous any and other use unspecific types.

First of all, I'd like to remark that good typing helps you to think less and reduce to time to check the implementation. In Functional Programming, the function definition is so important for the same reason. Your types should be thrust and strictly define which is the structure of your data.

Today, we'll explore some tips about how to use some utils types and some extra cases that will help you on a daily basis.

Pick and Omit

These two utils are part of the utils that comes with Typescript. These are helpful for preventing rewrite interfaces every time that we need something similar. Let's see in action in a real example.

Imagine that we are creating a store to be used in some components.

interface Stores {
  user: User,
  theme: Theme
  // ... some others
}
Enter fullscreen mode Exit fullscreen mode

If we want to define the props of our component that also comes with some of these stores we don't need to replicate it like that:

interface AvatarProps {
  user: User,
  rounded: boolean
  // ... some others
}
Enter fullscreen mode Exit fullscreen mode

Instead, we could use these utils types to prevent repeat these types, and reduce some mistakes like add another type for the user prop.

interface AvatarProps extends Pick<Stores, "user">{
  rounded: boolean
  // ... some others
}
Enter fullscreen mode Exit fullscreen mode

Pick util just create a new type only with the keys that match with the second type that we passed. Imagine this like a function with 2 parameters, the first one is the whole type and the second one is a union with the names that we need to "pick". Remember a union is a conjunction of 2 or more types, in this case, we use a fixed string to match with each key.

interface Foo {
  key1: number,
  key2: number,
  key3: number
}

type FooPicked = Pick<Foo , "key1" | "key2">

/*

This will result in a type like that:

interface FooPicked {
  key1: number,
  key2: number
}

*/
Enter fullscreen mode Exit fullscreen mode

Omit util does the same thing but in inverse order. I mean instead of taking every key that matches with the union it'll "omit" every key that matches with the union.

interface Foo {
  key1: number,
  key2: number,
  key3: number
}

type FooOmited = Omit<Foo , "key1" | "key2">

/*

This will result in a type like that:

interface FooOmited {
  key3: number
}

*/
Enter fullscreen mode Exit fullscreen mode

Partial

We were talking about the store so let continue with that. In this case let think about action, mutation, or anything that will do an update. For example, let use the old setState that React uses in classes as an example.

// state
this.state = {
  foo: "foo",
  bar: "bar"
}

// mutation
this.setState({
  foo: "foo"
})
Enter fullscreen mode Exit fullscreen mode

The method setState needs to receive just a part of the whole state, but we can't use Pick or Omit, because we don't know which will be the key that will be omitted. So, for these cases, we need to send a "partial interface" that will be merged with the whole interface.

// state
interface State {
  foo: string,
  bar: string
}

// mutation
type SetState = (value: Partial<State>) => State;
Enter fullscreen mode Exit fullscreen mode

But what is doing this Partial behind the scene, well it's not so complicated. It's just adding optional to each first-level property.

// state

interface State {
  foo: string,
  bar: string
}

type PartialState = Partial<State>;

/*

This will result in a type like that:

interface PatialState {
  foo?: string,
  bar?: string
}

*/
Enter fullscreen mode Exit fullscreen mode

You could find another case where you need to use it. Just remember that only put optional to first level properties, if you have nested object the child properties will not be impacted by this util.

readonly

If you like to work with immutable data maybe you'll love this keyword. Typescript allows you to determine which properties of your object could modify or not. Continue with the stores, if you will use the Flux architecture you don't want to allow that the state to be modified, you just want to recreate the state in each action.

So for these cases are helpful to put these properties as readonly because it will throw an error if anyone tries to modify it.

interface Stores {
  readonly user: User,
  readonly theme: Theme
  // ... some others
}
Enter fullscreen mode Exit fullscreen mode

Also, you could use the Readonly util

type ReadonlyStores = Readonly<Stores>
Enter fullscreen mode Exit fullscreen mode

When you try to modify any value, you will see an error message.

const store: ReadonlyStores = {
  user: new User(),
  theme: new Theme(),
  // ... some others
}

stores.user = new User()
// Error: Cannot assign to 'user' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

IMPORTANT

This check will throw an error in compile-time but not during runtime as const does. It means that if you have a code that typescript is not tracking, it will easily modify your property in runtime. Just prevent skipping typescript rules from your files.

Smart use of infer typing

Typescript has a really powerful inference algorithm. This means that sometimes we don't need to be explicit with the type of a variable because it will directly be typed for you.

let a = "a" // Typescript infer that it will be a "string"
a = 3 // It'll throw an error

// Just need to specify the type if you are not passing a value to the variable
let a: string;
a = "a"

// In another way it will be typed as any
let a; // typescript typed as any (some config will prevent this automatic any type)
a = "a"
a = 3 // it will NOT throw an error
Enter fullscreen mode Exit fullscreen mode

We could use this superpower for our benefit. Continue with our store, instead of creating the interface like that...

interface Stores {
  user: User,
  theme: Theme
  // ... some others
}

const stores: Stores = {
  user: new User(),
  theme: new Theme()
}
Enter fullscreen mode Exit fullscreen mode

... we could give the responsability to typescript to create it automatically.

const stores = {
  user: new User(),
  theme: new Theme()
}

type Stores = typeof stores;
Enter fullscreen mode Exit fullscreen mode

The common typeof keyword takes a new power in typescript. It will return the type that typescript infers of the declaration of the variable. So both codes are doing the same thing.

I love this feature because in these cases the type is completely dependent on the declaration. If you add a new field you just need to add it in the declaration and it will propagate to type immediately. Instead in the manual interface creation, you need to propagate this by your self which could come with some mistakes.

Conclusion

Typescript is fabulous, but as you could see with the difference between readonly and const, typescript just creates a layer for the developer to make the code more secure for all. But the JS code that is generated will not follow the rules. So it could modify the readonly property or have access to private attributes because it just a layer while you are coding.

Also if you are using classes to privatize some methods or attributes, it will be just "private" before compilation. If you really want to use a private value you could use a closure factory, also this could reduce just a little bit the bundle size of your compiled code because there now any necesity to compile anything like when you are using a class. If you are looking for a example of that let check this rewrite that Mark Erikson did in the Subscription of react-redux.

Remember this when you are working with typescript it will help you understand what is happening behind the scenes.

Thank you for reading this post. I hope this helps you in your daily work.

If you would like to learn more, I highly recommend the Typescript documentation for utils.

https://www.typescriptlang.org/docs/handbook/utility-types.html

Discussion (0)