The source code for this blog is available on GitHub.
Note
Top

Basic Usage in TypeScript

Cover Image for Basic Usage in TypeScript
Han Chen
Han Chen

Basic usage within Typescript

✅ If you're a web developer with little or no experience in TypeScript, this guide is perfect for you. We'll start with the basics, such as syntax and language concepts, in the "Basic usage within TypeScript" section. From there, we'll dive into more advanced topics, like ensuring type safety with the "Type check with Hash pattern object" and "Extract Type to Interface" sections. You'll learn how to optimize your code with the "Extract Type to Interface with Curry Function" and "How to define function with TypeScript" sections, which are geared towards more experienced users. This guide also covers other essential topics, such as "Type assertion with angle brackets," "Array with generic type T," "How can I exclude key or keys in TypeScript," and "TypeScript with type cast." By learning these key concepts, you'll be able to write robust and efficient TypeScript code that's perfect for your web development projects.

Type check with Hash pattern object

If direction is undefined, it might cause error. How to deal with that in typescript?

const directionStyle = {
    up: "rotate-[-90deg]",
    right: null,
    down: "rotate-[90deg]",
    left: "rotate-[180deg]",
  };

directionStyle[direction]

If direction is undefined, accessing directionStyle[direction] will throw a TypeError because undefined is not a property of directionStyle. To avoid the error, you can check if direction is truthy before accessing the property:

I use the following approach instead. I add applyDirectionClass to prevent from wrong type

const directionStyle = {
  up: "rotate-[-90deg]",
  right: null,
  down: "rotate-[90deg]",
  left: "rotate-[180deg]",
};

let applyDirectionClass: string | null = null;
if(direction) {
  applyDirectionClass = directionStyle[direction];
}

Extract Type to Interface

Extract them into isolated place?

type ConsoleMethod = (...args: any[]) => void;

Here's the corrected code:

declare namespace $projects {
  export interface ConsoleMethod {
    (...args: any[]): void;
  }
}

The corrected code defines a namespace called $projects that contains an interface called ConsoleMethod. The ConsoleMethod interface describes a function type that takes in any number of arguments of any type (...args: any[]) and returns void.

Note that the correct syntax for defining a function type is using the () to specify the function's arguments and return type, rather than using = to assign a value to the interface. Additionally, the return type of the ConsoleMethod interface should be void, as the type describes a function that doesn't return a value.

Extract Type to Interface with Curry Function

// Define original function
export const consoleType = (type: keyof typeof console) => (env: "production" | "development" | "test") => {
    if (env === "production") {
        return () => {}
    }
    return (...args: any[]) => {
        (console[type] as $utility.ConsoleMethod)(...args);
    }
};

Extract inline function to type

type ConsoleType = (type: keyof typeof console) => (env: "production" | "development" | "test") => void;

It is OK too if you want to go type with declare namespace

declare namespace $utility {
  type ConsoleType = (type: keyof typeof console) => (env: "production" | "development" | "test") => void;

  const consoleType: ConsoleType;
}

Use declare namespace to define types and functions

declare namespace $utility {
  export interface ConsoleMethod {
    (...args: any[]): void
  };

  interface ConsoleType {
    (type: keyof typeof console): (env: "production" | "development" | "test") => void;
  }
}

Note that the ConsoleType type is defined outside the namespace as it is used both inside and outside the namespace. The ConsoleType type is then used to define the ConsoleType type inside the namespace, as well as the consoleType function. The ConsoleMethod interface is also defined inside the namespace.

How to define function with typescript

To define a function with TypeScript, you can use the following syntax:

function functionName(parameter1: type1, parameter2: type2, ...): returnType {
  // function body
}

Here's a breakdown of the different parts of the function definition:

  • function: The function keyword is used to define a function.
  • functionName: This is the name of the function. You can use any valid identifier as the function name.
  • (parameter1: type1, parameter2: type2, ...): This is the parameter list for the function. Each parameter is separated by a comma, and consists of a name and a type annotation.
  • : returnType: This is the return type of the function. The return type is specified after the parameter list, separated by a colon.
  • { // function body }: This is the body of the function. It consists of one or more statements that are executed when the function is called.

Here's an example of a function that takes two parameters, x and y, both of type number, and returns their sum:

function addNumbers(x: number, y: number): number {
  return x + y;
}

How to define function with type and with interface separately?

interface Person {
  firstName: string;
  lastName: string;
}

type GreetingFunction = (person: Person) => string;

const sayHello: GreetingFunction = (person) => {
  return `Hello, ${person.firstName} ${person.lastName}!`;
};

In this example, we define an interface Person that specifies the shape of an object with a firstName and lastName property. Then we define a type GreetingFunction that describes a function that takes a Person as a parameter and returns a string.

Finally, we define a function sayHello that matches the GreetingFunction type, and takes a Person object as a parameter. The function returns a string greeting the person by their first and last name.

Type assertion with angle brackets

The <string> syntax in the code is a type assertion. A type assertion is a way to tell TypeScript that a value of a certain type should be treated as another type. In this case, it's asserting that addAllNumber(...props) should be treated as a string, even if it's actually a number. This is necessary because the toFixed method is only available on strings, and not on numbers.

By using a type assertion, the code is able to use the toFixed method and fix the decimal places to 2. The result of the toFixed method is then a string, which is the final return type of the function.

// I need to change this
export const addAllPrice = (...props: (number | string)[]): string =>
  parseFloat(addAllNumber(...props)).toFixed(2);

// to this
export const addAllPrice = (...props: (number | string)[]): string =>
  parseFloat(<string>addAllNumber(...props)).toFixed(2);

Why wrong?

const a = <string>[1, 2, 3];

The syntax <string> is called type assertion, and it is used to inform TypeScript that a value has a specific type. In this case, it is trying to assert that the array [1, 2, 3] is of type string, but arrays can only be of a single type, and the elements of this array are of type number, not string. So this code will result in a type error.

Array with generic type T

How can I declare with T[]

export const popLast = (arr: any[]) => {
  let result = arr;
  result.pop();

  return result;
};

You can declare the popLast function to take an array of generic type T as an argument and return an array of the same type T[] as follows:

export const popLast = <T>(arr: T[]): T[] => {
  let result = arr.slice(); // Create a copy of the array to avoid modifying the original array.
  result.pop();
  return result;
};

In this implementation, the popLast function takes an array of type T[] as input and returns an array of the same type T[]. The generic type T allows you to use this function with arrays of any type. The slice method is used to create a copy of the input array to avoid modifying the original array. The pop method is then called on the copied array, and the modified array is returned as the result.

In TypeScript, adding <T> before a function name is how you declare that the function is a generic function.

By doing this, you are telling TypeScript that the function can take in an argument of any type, and it will return a value of the same type.

When you add <T> to the function declaration, it allows you to use the type T as a placeholder for a type that will be specified later, when the function is called.

For example, if you call the function popLast and pass in an array of strings, TypeScript will infer the type string[] for T and use that type for the array arr and the return value.

Using generics can make your code more reusable and more type-safe.

How can I exclude key or keys in Typescript

You can exclude one or more keys from a TypeScript type by using the Omit utility type. Omit creates a new type that includes all properties of the original type except for the specified keys.

Here's an example of how to use Omit:

interface Person {
  name: string;
  age: number;
  address: string;
}

// create a new type that excludes the "address" key from the Person interface
type PersonWithoutAddress = Omit<Person, 'address'>;

// usage
const person: PersonWithoutAddress = {
  name: 'John',
  age: 30,
};

In this example, we define an interface called Person with three properties: name, age, and address. We then create a new type called PersonWithoutAddress that excludes the address property from the Person interface using the Omit utility type.

You can use Omit with any TypeScript type that has named properties, including object types, interface types, and class types.

If you want to delete multiple keys, you can do it like the following.

type TransformOrderBillingAddressProp = Omit<Order.BillingAddress, "address_2" | "email" | "phone" | "company" | "country_iso2">;

The syntax for Omit type in TypeScript is to specify the type to be extended as the first argument, followed by a union of the keys to be omitted as the remaining arguments. Not this:

type TransformOrderBillingAddressProp = Omit<Order.BillingAddress, "address_2", "email", "phone", "company", "country_iso2”>;

Typescript with type cast

Operator '+' cannot be applied to types 'string | number' and 'number'.

The + operator can't be applied to a value of type string | number because JavaScript cannot automatically convert a string to a number. You need to handle both cases separately. You can use the Number() function to convert a string to a number, or use a type guard to check the type of each value before adding it to the result. Here's one way to do that:

// I need to change this
export const addAllNumber = (...props: (string | number)[]) =>
  props.reduce((a, b) => {
    return a + naNToZero(+b);
  }, 0);

// to this
export const addAllNumbers = (...props: (string | number)[]) => {
  let sum = 0;
  for (const prop of props) {
    if (typeof prop === "string") {
      sum += Number(prop) || 0;
    } else {
      sum += prop;
    }
  }
  return sum;
};

We need to understand why addAllNumber is incorrect. addAllNumber is a function that takes a variable number of arguments of either string or number type, and returns their sum. The function uses the reduce method to iterate over the array of arguments and add each element to a running total, represented by a. However, this implementation is incorrect and leads to an error because a may sometimes be of string type, and the + operator cannot be applied to a string and a number.

export const addAllNumber = (...props: (string | number)[]) =>
  props.reduce((a, b) => {
    return a + naNToZero(+b);
  }, 0);

Two simple examples are provided to illustrate the concept. In both examples, using the + operator on either a string or a number results in the expected outcome.

const a = "2";
console.log(+a);

const b = 2;
console.log(+b);

Additionally, using a for loop with the + operator works as expected.

const sumWithPlusSign = function (...props: (string | number)[]) {
  let sum = 0;
  for (const prop of props) {
    sum += +prop;
  }
  return sum;
}

the for loop is used to iterate over the props array, adding each element to a running total (sum). The + operator is used to convert the prop element to a number before it's added to the sum. This ensures that if a string is passed as an argument, it's typecast to a number before being added to the sum.

when i change it to reducer it causes error

const sumWithPlusSign = function (...props: (string | number)[]) {
  return props.reduce((acc, curr) => acc + +curr, 0)        // ❌
}

const sumWithPlusSign = function (...props: (string | number)[]) {
  return props.reduce((acc, curr) => acc + Number(curr), 0) // ❌
}

It is incorrect in the expression acc + +curr and it produces an error that states Operator '+' cannot be applied to types 'string | number' and 'number'.

acc in this case could either be a string or a number

reduce method is used to iterate over the props array, adding each element to a running total (acc). The + operator is used to convert the curr element to a number before it's added to the acc. This is done inside the reduce function, which will accumulate the sum of all elements in the array.

The error you're encountering in the second example is because of the acc + +curr expression. The error message is indicating that the + operator cannot be applied to string | number and number. This is because reduce can sometimes pass a string type for curr, and the + operator cannot be applied to a string and a number. To resolve this, you'll need to make sure that curr is always a number before adding it to acc.

It will be right if I assign acc as number:

const sumWithPlusSign = function (...props: (string | number)[]) {
  return props.reduce((acc: number, curr) => acc + +curr, 0)
}

const sumWithPlusSign = function (...props: (string | number)[]) {
  return props.reduce((acc: number, curr) => acc + Number(curr), 0)
}

so i need to assign a with type number to suit both reduce and the outcome

export const addAllNumber = (...props: (string | number)[]) =>
  props.reduce((a: number, b) => {
    return a + naNToZero(+b);
  }, 0);

Arbitrary Arguments in Function

type ArbitraryFunction = (...args: any[]) => any;

export const executeFunction = (env: "production" | "development" | "test")  =>
  (callback: ArbitraryFunction, ...arg: any)=> {
    if (["development", "test"].includes(env)) {
      callback(...arg);
    }
  }
  
export const executeInDev = executeFunction(process.env.VERCEL_ENV)

In order to define process.env.VERCEL_ENV in TypeScript, you need to declare a global namespace that includes process.env. Here is an example of how to define it:

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      VERCEL_ENV: string;
      // add other environment variables as needed
    }
  }
}

This declares a new global namespace NodeJS that contains the interface ProcessEnv. This interface extends the existing NodeJS.ProcessEnv interface to include the VERCEL_ENV environment variable. You can add other environment variables to this interface as needed.

Note that this declaration must be in a .d.ts file in order for TypeScript to recognize it.

© 2024 WOOTHINK. All Rights Reserved.
Site MapTerms and ConditionsPrivacy PolicyCookie Policy