Skip to main content

TypeScript

TypeScript - Basics

  • TypeScript is strongly typed programming language (https://www.typescriptlang.org/) that is based on JavaScript.
  • Benefits of using TypeScript:
    • Catch errors before pushing your code to production.
    • Code is easier to understand and refactor/maintain.
    • Provides more information to developer (Self documented)
    • More popular nowadays (Great skill)
  • You can try TypeScript in your browser: https://www.typescriptlang.org/play
  • You can install TypeScript into your project using the npm:
npm install typescript --save-dev
  • Then you can run TypeScript compiler using the following command:
npx tsc

Type inference

  • TypeScript will automatically define the type when you initialize a variable. In the following example, we declare variable helloworld and assign it to a string value. If we try to re-assign it with other type, we get an error.


  • You can also set explicit type to a variable:
let name: string;
let age: number;
  • You can check the type using the typeof:
typeof name === "string" // true
typeof age === "number" // true
  • If the type is not known when you write code, you can use unknown type.
let a: unknown = 10;
a = "It should be string";
  • If you want to exlude type checking, you can use any type.
let a: any = 10;
  • The unknown type is a safer alternative to any. It represents any value, but you cannot perform operations on it without first asserting its type. The code below demonstrates the difference between any and unknown.

Using unknown:

let value: unknown;

value = 42; // This is OK
value = "hello"; // This is OK
value = true; // This is OK

// Type checking is required before using the value
if (typeof value === "number") {
let num: number = value;
}

Using any:

let value: any;

value = 42; // This is OK
value = "hello"; // This is OK
value = true; // This is OK

// Type checking is not required
// This is OK, but potentially unsafe
let num: number = value;

Functions

  • You can define the type of parameters and return value
function calc(x: number, y: number): number {
return x * y;
}
  • In JavaScript function parameters are optional by default.
  • In TypeScript, you will get an error if you don't pass all function parameters. To make function parameter optional, you can use ? after the parameter name.
function sayHello(name: string, age?: number): string {
if (!age)
return `Hello ${name}`;

return `Hello ${name}, you are ${age} year(s) old`
}
  • Now, you can call function in the following ways:
sayHello("John");
// or
sayHello("John", 20);
  • If there is no return value, you can use the void keyword:
function logMessage(message: string): void {
console.log(message);
}

Arrays

  • Arrays are declared with the following syntax:
const arr: type[];
  • For example:
let names: string[];
names = ["John", "Lisa", "Mike"];

// You can also use Array() constructor
let nums: number[] = new Array(10);
nums[0] = 4;
  • You can also combibe more complex types
  • Unions:
type MyStates = "awake" | "sleep" | "eating";
  • Generics:
type NumArray = Array<number>

Structural types

  • Structural types (define entities) are used a lot with React. For example, typing component props.
  • You can use interface or type keyword.
interface Person {
name: string;
age: number;
}

type Point = {
x: number;
y: number;
}

  • If you have nested objects in your entities, for example:
interface Person {
name: string;
age: number;
address: {
street: string;
number: number;
zip: number;
}
}
  • Instead of using nested object, you can create Address interface
interface Address {
street: string;
number: number;
zip: number;
}

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

React & TypeScript

  • You can use TypeScript in your Vite React project by selecting TypeScript variant when creating a Vite project:
npm create vite@latest
  • React provides @types/react and @types/react-dom packages that offer useful types for React. If you create Vite project using TypeScript, these packages are installed as development dependencies. There is also a configuration file tsconfig.app.json created in the Vite project for TypeScript, and it specifies the compiler options required to compile the project.

Component props

  • You can define the type for the function component props (=function arguments)
interface HelloProps {
name: string;
age: number;
}

function Hello({name, age}: HelloProps) {
return(
<div>
Hello {name}, you are {age} years old!
</div>
)
}

export default Hello;
  • Now, if you pass value that doesn't match to defined type, you will get an error.

  • You can define optional props using the ?, for example:
interface HelloProps {
name: string;
age?: number;
}

function Hello({name, age}: HelloProps) {
return(
<div>
Hello {name}
{ age && <>, you are {age} years old!</>}
</div>
)
}

export default Hello;
  • If the props value is a function, the definition is following:
interface HelloProps {
name: string;
age?: number;
myFunc: () => void; // no parmeters and return value
}
interface HelloProps {
name: string;
age?: number;
myFunc: (msg: string) => void; // function parmeters
}

Function components

  • You can also define the return type for your component and you will get an error if something else is returned. The JSX.Element is a type that represents a React element. It is the return type of a functional component in React. This type is used to ensure that the component returns a valid JSX element.
interface HelloProps {
name: string;
age: number;
}

function Hello({name, age}: HelloProps): JSX.Element {
return(
<div>
Hello {name}, you are {age} years old!
</div>
)
}

export default Hello;

useState hook

  • Type interference works with useState hooks function. For example, if you declare following state:
const [isReady, setReady] = useState(false);
  • Now, if you update the state using wrong type, you will get an error:
setReady(10);

  • If you have complex state, you can use interface, for example:
// declare interface
interface IUser {
firstname: string,
lastname: string,
age: number
}

// useState hook
const [user, setUser] = useState<IUser | null>(null);
// or if nullish values are not accepted
const [user, setUser] = useState<IUser>({} as IUser);

  • You can also explicitly define types. For example, if you want to initialize state with null or undefined value.
const [value, setValue] = useState<string | undefined>(undefined);
  • Specifying type for array state
const [values, setValues] = useState<Array<string>>([]);


Forms & Events

  • In the example below, the inputChanged function is used to handle input element change events. In TypeScript, you have to define event types, otherwise you will the following error.

  • The type for input element change event is React.ChangeEvent
  • The type for form submit event is React.FormEvent, like shown in the following code.
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPerson({...person, [event.target.id]: event.target.value});
}

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Do something with data
}
// interfaces.ts 
export interface Address {
street: string;
number: number;
zip: number;
}

export interface Person {
name: string;
age: number;
address: Address;
}
  • Then you can import interfaces to modules where these are used:
import { Person } from './interfaces';

Further reading