5

TypeScript: Enhance Variable Types with Declaration Merging

 2 years ago
source link: https://blog.bitsrc.io/enhancing-the-type-of-a-variable-declaration-merging-9f0ce0af309d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Responses

Also publish to my profile

There are currently no responses for this story.

Be the first to respond.

You have 2 free member-only stories left this month.

TypeScript: Enhance Variable Types with Declaration Merging

This story is about an often applied technique by libraries that offer full TypeScript support. Although as a consumer of these things, one most often does not have to implement such techniques — everything just works like magic. Nevertheless, it is always good to have a basic understanding of what is going on in the hidden layer especially in times where things go wrong.

One of TypeScript’s features is the so called declaration merging (see here). Applied on an interface, this in principle is merging two declarations of the same interface:

interface TheInterface{
a: string;
}interface TheInterface{
b: boolean;
}let t: TheInterface = {
a: 'a',
b: true
};

Typically, when we use external libraries that come with their own type declarations it sometimes is necessary to extend a type with some custom fields.

For instance, let us consider a session store:

interface Session{}

As a consumer we want to store custom properties like authToken, socketId, ... in a user’s session but of course the library doesn’t cope for these fields.

We could use the as any trick and hence making the compiler allowing us to store any values on the session:

(session as any).authToken = ...

Obviously, this is not desirable since we loose compiler checks. Somebody could by accident write (session as any).authTypoToken = ... and no one would recognize this until a runtime error appears.

So, let us assume we are using some external library which types are imported like this:

import { Session } from 'path-to-extern-module'

In case Session is an interface we can enhance its type by adding the following to our source code:

declare module 'path-to-extern-module'{
interface Session{
authToken: string;
socketId: number;
}
}

Alternatively, and this is the cleaner approach, we can store this in a xxx.d.ts file (type declarations) and put it into our source folder.

Let us consider all this in more detail by looking at the following example:

Suppose we have a declaration file named some-module.d.ts:

export interface CustomTypes { };export type Session =
CustomTypes extends {session: infer T} ? T : unknown;export function fn(clb: (sess: Session) => void);

This intends to ‘simulate’ an external library. If you don’t know about the infer keyword you can find basic information here. The ternary in this code is like pattern matching. CustomTypes is checked to match a type that extends the type {session: infer T}. The infer T is just a placeholder for the type of session and assigns the latter to the generic T. People coming from logic programming will appreciate this pattern.

Having only this code, Session obviously would turn out to be the type unknown. But if someone, extends CustomTypes by using declaration merging, things can change:

In our source code we could import these types and use them like so:

import { Session, fn } from './some-module';interface MySession {
authToken: string;
socketId: number;
}declare module './some-module' {
interface CustomTypes {
session: MySession;
}
}
fn(sess => console.log(sess.authToken)); // compiles!

This way sess would have the type MySession, since the declaration of CustomerTypes has been merged.

If we would leave away the declaration merging, that is:

import { fn } from './some-module';fn(sess => console.log(sess.authToken)); // error!

Then this resulted in a compilation error since the variable sess would be of type unknown.

Build modular, reusable components with Bit

Monorepos are a great way to speed up and scale app development, with independent deployments, decoupled codebases, and autonomous teams.

Bit offers a great developer experience for building component-driven monorepos. Build components, collaborate, and compose applications that scale. Our GitHub has over 14.5k stars!

Give Bit a try →

An independently source-controlled and shared “card” component (on the right, its dependency graph, auto-generated by Bit)

Conclusion

There you have it, we’ve answered the age-old question of whether APIs and Microservices were the same things.

As it turns out, all we had to do was to carefully look at the definition of each one. In the end, they’re not the same, but they work incredibly well together.

What do you think? Do you still have questions? Leave them in the comments!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK