Skip to main content

Typescript

Typescript is Javascript with type safety.

E2E workflow efficiency with Typescript.

TypeScript doesn't allow extra properties when passing literal objects to functions or assigning them directly to a variable with an explicit type.

tip

Use Execute Program to get the fundamentals in your fingers.

Benefits

Code must be understandable and maintainable by other programmers, including programmers who join the team in the future and don't know the code's history.

Satisfying the type checker is like getting the code to work for the first time. It takes effort to make typing more understandable and maintainable.

  1. We tell the compiler what we believe about our code.
  2. The compiler analyzes those beliefs and checks for contradictions. Those contradictions are type errors.
  3. Stating beliefs in a more precise way, means more precise error messages in response.
  4. That makes debugging and maintenance easier

Best Practices

Keyof with Typeof

An example where any modifications to the icons list will be handled by the compiler.

const icons = {
rightArrow: 'fake right arrow image',
leftArrow: 'fake left arrow image',
billing: 'fake billing image',
};

function Icon(props: { name: keyof typeof icons }) {
return icons[props.name];
}

Get type with typeof icons, which gives us {rightArrow: string, billing: string}.

Use keyof typeof icons to get the type 'rightArrow' | 'billing'

Pitfalls

  • Using Any as a type
  • Using As is dangerous

Assertion Functions

An assertion function throws an error unless some condition is true.

function assertNumber(n: unknown): asserts n is number {
if (typeof n !== 'number') {
throw new Error("Value wasn't a number: " + n);
}
}

Type guards and assertion functions are very closely related. The difference is in the scope of their type narrowing.

With a type guard the type is narrowed inside the code block. With an assertion function the type is narrowed for all code after the assertion call.

warning

Write automated tests to be sure that your assertions functions are working as expected.

Extends vs Intersections

Sometimes intersections are the only tool for a particularly complex job. In those cases, you may find yourself reasoning through the type algebra to find out what's happening.

Generally favour extends to save from getting confusing errors.

Covariance vs Contravariance

The normal rule for assignability, where narrow types are assignable to wider types, is called covariance. But type compatibility is reversed for function parameter types. That's called contravariance.

In most strict static languages, function types are contravariant on the parameter types. Which means the following is OK.

type TakesLiteralString = (s: 'lastLoginDate') => string;

function takesString(s: string): string {
return s;
}

const testFunction: TakesLiteralString = takesString;
testFunction('lastLoginDate');

The testFunction variable only lets us pass the literal string 'lastLoginDate'. But the underlying takesString function can take 'lastLoginDate' or any other string.

By assigning an (s: string) => string function to an (s: 'lastLoginDate') => string variable, allowS a narrower set of arguments, which is safe.

Overloading

Overloading Functions and Options Objects can be useful in situations where there's a lot of code duplication.

type Comment = {
subject: string;
};

interface User {
id: number;
name: string;
}

interface UserWithComments extends User {
comments: Comment[];
}

function findUser(id: number): User;
function findUser(id: number, options: { withComments: true }): UserWithComments;
function findUser(id: number, options: { withComments?: boolean } = {}): User | UserWithComments {
const user = { id: 1, name: 'Amir' };
if (options.withComments) {
return { ...user, comments: [{ subject: "Ms. Fluff's 4th birthday" }] };
} else {
return user;
}
}

Context

Education

Tools and Libraries