Level Up Your JavaScript Skills: A Comprehensive Guide to TypeScript

Level Up Your JavaScript Skills: A Comprehensive Guide to TypeScript

Welcome to this detailed guide on TypeScript, a powerful superset of JavaScript that adds static typing and other advanced features to the language you already know and love. If you're a JavaScript developer looking to elevate your code quality, increase productivity, and manage large projects with greater ease, TypeScript is the tool for you.

Introduction to TypeScript

What Is TypeScript?

TypeScript is an open-source programming language developed by Microsoft that extends JavaScript by adding static types. TypeScript code compiles down to plain JavaScript, meaning it can run anywhere JavaScript runs—in web browsers, Node.js, and any other JavaScript runtime environments.

Why Use TypeScript?

  • Static Typing: Helps prevent runtime errors by catching issues during development.
  • Enhanced Code Completion: Editors can provide more accurate suggestions thanks to type information.
  • Easier Refactoring: Modifying code becomes safer and simpler.
  • Latest JavaScript Features: TypeScript supports the latest ECMAScript features and transpiles them for older environments.
  • Community and Tools: Large support community and integration with popular development tools like Visual Studio Code.

Static Typing vs. Dynamic Typing

Understanding the difference between static and dynamic typing is crucial when transitioning from JavaScript to TypeScript.

  • Static Typing: In statically typed languages like TypeScript, the type of a variable is known at compile-time. Variables, function parameters, and object properties have types that cannot change, which allows the compiler to catch type-related errors before the code runs.
  • Dynamic Typing: In dynamically typed languages like JavaScript, types are known at runtime. Variables can hold values of any type, and their types can change, which can lead to runtime errors if not carefully managed.

Advantages of Static Typing:

  • Error Detection: Catch type-related errors during development rather than at runtime.
  • Improved IDE Support: Enhanced autocomplete and type-checking in code editors.
  • Self-Documentation: Code becomes more readable and self-explanatory due to explicit type annotations.

Installation and Configuration

Installing TypeScript

To install TypeScript, you need Node.js and npm installed on your system.

npm install -g typescript        

Project Configuration

Create a tsconfig.json file to configure the TypeScript compiler.

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}        

Project Structure

Organize your TypeScript code in a src folder and compile it into the distfolder by running:

tsc        

Basic Types

Primitive Types

TypeScript supports the same primitive types as JavaScript, along with some additional ones.

  • boolean
  • number
  • string
  • null
  • undefined
  • any
  • void
  • never
  • unknown

Example

let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";        

Advanced Types

Arrays

let list: number[] = [1, 2, 3];        

Tuples

let x: [string, number];
x = ["hello", 10];        

Enums

enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;        

Any

let notSure: any = 4;
notSure = "maybe a string instead";        

Void

Typically used for functions that do not return a value.

function warnUser(): void {
  console.log("This is a warning message");
}        

Functions

Function Typing

You can specify the types of parameters and the return value.

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

Optional and Default Parameters

Optional Parameters

function buildName(firstName: string, lastName?: string) {
  return lastName ? `${firstName} ${lastName}` : firstName;
}        

Default Parameters

function buildName(firstName: string, lastName = "Smith") {
  return `${firstName} ${lastName}`;
}        

Arrow Functions (Lambda Functions)

let add = (x: number, y: number): number => x + y;        

Interfaces

Interfaces define contracts in your code, specifying the structure that classes or objects should adhere to. They are a fundamental aspect of TypeScript's type system, allowing for robust type-checking and clearer code architecture.

What Are Interfaces and Contracts?

An interface in TypeScript is a way to define the shape of an object. It specifies what properties and methods an object should have, along with their types. This serves as a contract that any implementing class or object must fulfill. By using interfaces, you can ensure consistency across different parts of your application, making your code more predictable and easier to maintain.

Interfaces for Objects

interface LabelledValue {
  label: string;
}

function printLabel(labeledObj: LabelledValue) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);        

Explanation:

  • Interface LabelledValue: Defines a contract that requires a labelproperty of type string.
  • Function printLabel: Accepts any object that implements the LabelledValue interface.
  • Object myObj: Has both size and label properties, but only label is required by the interface.

Interfaces for Functions

Interfaces can define the types of function signatures, enforcing that any function assigned to the interface adheres to the specified parameters and return type.

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (src, sub) {
  return src.search(sub) > -1;
};        

Explanation:

  • Interface SearchFunc: Describes a function that takes two stringparameters and returns a boolean.
  • Function mySearch: Implements the SearchFunc interface, ensuring it matches the required signature.

Interfaces for Classes

Interfaces can enforce the structure of classes, specifying properties and methods that must be implemented.

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}        

Explanation:

  • Interface ClockInterface: Requires a currentTime property and a setTime method.
  • Class Clock: Implements the ClockInterface, adhering to the contract by providing the required properties and methods.

Classes

TypeScript extends JavaScript classes with typing and other features, enhancing object-oriented programming capabilities.

Access Modifiers

  • public (default): Accessible from anywhere.
  • private: Accessible only within the class.
  • protected: Accessible within the class and its subclasses.
  • readonly: Indicates that a property cannot be reassigned after initialization.

Example

class Animal {
  private name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}

class Rhino extends Animal {
  constructor() {
    super("Rhino");
  }
}        

Explanation:

  • Private Property name: Cannot be accessed outside the Animal class.
  • Protected Constructor: Animal cannot be instantiated directly but can be extended.
  • Class Rhino: Extends Animal and calls the parent constructor.

Inheritance

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  department: string;
  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}        

Explanation:

  • Class Person: Base class with a name property.
  • Class Employee: Extends Person, adding a department property and a method.
  • Method getElevatorPitch: Uses inherited name property.

Generics

Generics allow you to create reusable components that work with any data type, providing flexibility and type safety.

Generic Functions

function identity<T>(arg: T): T {
  return arg;
}        

Usage:

let output = identity<string>("myString");
let output2 = identity<number>(100);        

Generic Classes

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};        

Modules and Namespaces

Modules

Modules are based on the ECMAScript module system, enabling code organization and reuse.

Exporting

export class Calculator {
  // ...
}        

Importing

import { Calculator } from "./Calculator";        

Namespaces

Namespaces group related code, helping to organize large codebases and prevent naming collisions.

namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  export const numberRegexp = /^[0-9]+$/;
}        

Usage:

let validator: Validation.StringValidator;        

Decorators

Decorators are an experimental feature that allows you to annotate classes and their members, adding metadata or modifying behavior.

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}        

Explanation:

  • Decorator @sealed: Applies the sealed function to the Greeterclass, sealing its constructor and prototype.
  • Class Greeter: Now has a sealed constructor and prototype.

Advanced Types

Union and Intersection Types

Union Types

Allows a variable to be one of several types.

let value: string | number;
value = "hello";
value = 42;        

Explanation:

  • Variable value: Can be either a string or a number.

Intersection Types

Combines multiple types into one.

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

type ArtworksResponse = ArtworksData & ErrorHandling;        

Explanation:

  • Type ArtworksResponse: Must include all properties from both ArtworksData and ErrorHandling.

Type Guards

Type Guards help in narrowing down types at runtime, enhancing type safety.

function isNumber(x: any): x is number {
  return typeof x === "number";
}

function padLeft(value: string, padding: string | number) {
  if (isNumber(padding)) {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}        

Mapped Types

Allows you to create new types based on existing ones, transforming each property.

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

type ReadonlyPerson = Readonly<Person>;        

Explanation:

  • Type ReadonlyPerson: All properties of Person are set to readonly.

Integrating with Existing JavaScript

TypeScript can be gradually adopted in an existing JavaScript project, allowing for incremental type safety improvements.

Declaration Files

To use untyped JavaScript libraries, you can create .d.ts declaration files, which provide type information.

declare module "my-library" {
  export function myFunction(): void;
}        

Usage:

import { myFunction } from "my-library";
myFunction();        

Tools and Advanced Configuration

Linting with ESLint

To keep your code clean and consistent.

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin        

ESLint Configuration (.eslintrc.js):

module.exports = {
  parser: "@typescript-eslint/parser",
  plugins: ["@typescript-eslint"],
  extends: ["plugin:@typescript-eslint/recommended"],
};        

Integration with Build Tools

TypeScript integrates with Webpack, Gulp, Grunt, and other build tools.

Example with Webpack

npm install --save-dev ts-loader        

Configure webpack.config.js:

module.exports = {
  // ...
  module: {
    rules: [{ test: /\.tsx?$/, loader: "ts-loader" }],
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
};        

TypeScript is a powerful tool that can significantly improve the quality and maintainability of your JavaScript code. With static typing, advanced tooling, and support for the latest ECMAScript features, TypeScript allows you to write more robust and scalable code.

By understanding interfaces and contracts, you can enforce consistent structures in your codebase, making it easier to collaborate and maintain. Embracing the principles of static typing can lead to fewer runtime errors and a more efficient development process.


Using TypeScript with React Native (Expo)

TypeScript can greatly enhance your React Native development experience by providing static typing, improved code completion, and better tooling support. In this guide, we'll focus on setting up and using TypeScript in a React Native project using Expo, specifically with its latest version via npx create-expo-app@latest.

Setting Up a New React Native Project with TypeScript Using Expo

Expo makes it straightforward to create React Native apps with TypeScript support. Using the latest version of Expo ensures you have access to the most recent features and improvements.

Using npx create-expo-app@latest

  1. Install Node.js (if you haven't already):
  2. Create a New Project with the TypeScript Template:

npx create-expo-app@latest MyTypeScriptApp -t expo-template-blank-typescript        

This command uses the latest version of create-expo-app to initialize a new Expo project named MyTypeScriptApp with the TypeScript template.

  • Navigate to Your Project Directory:

cd MyTypeScriptApp        

  • Start the Development Server:

npm start        

This will start the Expo development server. You can now open the Expo Go app on your phone or an emulator to view your app.

Project Structure

The created project comes with the following structure:

  • App.tsx: The main entry point of your application.
  • tsconfig.json: Configuration file for TypeScript compiler options.
  • package.json: Contains project dependencies and scripts.
  • node_modules/: Directory where all your installed packages reside.

Writing React Native Components with TypeScript

Here's how you can define functional components using TypeScript in your Expo project.

Functional Components

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

interface GreetingProps {
  name: string;
}

const Greeting: React.FC<GreetingProps> = ({ name }) => (
  <View style={styles.container}>
    <Text>Hello, {name}!</Text>
  </View>
);

const styles = StyleSheet.create({
  container: {
    padding: 10,
  },
});

export default Greeting;        

Using the Component in App.tsx

import React from 'react';
import { SafeAreaView } from 'react-native';
import Greeting from './Greeting';

export default function App() {
  return (
    <SafeAreaView>
      <Greeting name="Expo User" />
    </SafeAreaView>
  );
}        


Article content

Using Type Definitions

TypeScript uses declaration files (.d.ts) to understand the types of modules. With Expo, many of the necessary types are already included, but if you add new dependencies, you may need to install their type definitions.

  • Install Type Definitions for New Packages:

npm install --save-dev @types/package-name        

Typing Props and State

When defining components, you can create interfaces or types for Props and State.

Example with Interfaces

interface MyButtonProps {
  title: string;
  onPress: () => void;
}

const MyButton: React.FC<MyButtonProps> = ({ title, onPress }) => (
  <TouchableOpacity onPress={onPress}>
    <Text>{title}</Text>
  </TouchableOpacity>
);        

Example with Types

type MyButtonProps = {
  title: string;
  onPress: () => void;
};

const MyButton: React.FC<MyButtonProps> = ({ title, onPress }) => (
  <TouchableOpacity onPress={onPress}>
    <Text>{title}</Text>
  </TouchableOpacity>
);        

Handling External Libraries

For external libraries, you might need to install type definitions or create them if they don't exist.

  • Install Type Definitions:

npm install --save-dev @types/react-navigation        

  • Creating Custom Type Definitions:

declare module 'my-untyped-library';        

Using React Navigation with TypeScript

React Navigation is a commonly used library for routing and navigation in React Native apps. Here's how you can use it with TypeScript in an Expo project.

Install React Navigation

  • Install Dependencies:

npm install @react-navigation/native
npm install @react-navigation/native-stack        

  • Install Required Peer Dependencies:

npx expo install react-native-screens react-native-safe-area-context        

  • Install Type Definitions:

npm install --save-dev @types/react-navigation        

Set Up Navigation with TypeScript

// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; otherParam?: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

// HomeScreen.tsx
import React from 'react';
import { View, Text, Button } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';

type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; otherParam?: string };
};

type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;

function HomeScreen({ navigation }: Props) {
  return (
    <View>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() =>
          navigation.navigate('Details', { itemId: 86, otherParam: 'anything you want here' })
        }
      />
    </View>
  );
}

export default HomeScreen;

// DetailsScreen.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';

type RootStackParamList = {
  Home: undefined;
  Details: { itemId: number; otherParam?: string };
};

type Props = NativeStackScreenProps<RootStackParamList, 'Details'>;

function DetailsScreen({ route }: Props) {
  const { itemId, otherParam } = route.params;

  return (
    <View>
      <Text>Details Screen</Text>
      <Text>itemId: {itemId}</Text>
      {otherParam && <Text>otherParam: {otherParam}</Text>}
    </View>
  );
}

export default DetailsScreen;        
Article content

Benefits of Using TypeScript with React Native (Expo)

  • Type Safety: Catch errors and bugs during development.
  • Improved Developer Experience: Better IntelliSense and code completion in IDEs.
  • Refactoring: Easier and safer code refactoring.
  • Documentation: Self-documented code through type annotations.
  • Expo Integration: Expo fully supports TypeScript, making it seamless to use.

Tips and Best Practices

  • Enable Strict Mode: In your tsconfig.json, set "strict": true to enable all strict type-checking options.

  {
    "compilerOptions": {
      "strict": true,
      // other options...
    }
  }        

  • Use Functional Components: Prefer functional components with hooks over class components for simpler state management.
  • Leverage Type Inference: TypeScript can often infer types, so you don't need to annotate everything explicitly.
  • Consistent Types: Use either interfaces or types consistently throughout your project.
  • Third-Party Libraries: Always check if type definitions are available for third-party libraries you use.

Additional Expo-Specific Features

Expo provides a range of APIs and modules that you can use in your app. Many of these come with TypeScript definitions.

Using Expo Modules with TypeScript

For example, using the expo-location module:

  • Install the Module:

npx expo install expo-location        

  • Use in Your Code with TypeScript:

import React, { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
import * as Location from 'expo-location';

const LocationExample: React.FC = () => {
    const [location, setLocation] = useState<Location.LocationObject | null>(null);
    const [errorMsg, setErrorMsg] = useState<string | null>(null);

    useEffect(() => {
    (async () => {
        let { status } = await Location.requestForegroundPermissionsAsync();
        if (status !== 'granted') {
        setErrorMsg('Permission to access location was denied');
        return;
        }

        let loc = await Location.getCurrentPositionAsync({});
        setLocation(loc);
    })();
    }, []);

    let text = 'Waiting..';
    if (errorMsg) {
    text = errorMsg;
    } else if (location) {
    text = JSON.stringify(location);
    }

    return (
    <View>
        <Text>{text}</Text>
    </View>
    );
};

export default LocationExample;        


Article content

Debugging TypeScript in Expo

Expo and TypeScript work well together with debugging tools.

  • Source Maps: TypeScript generates source maps, allowing you to debug your TypeScript code directly.
  • Expo Debugging Tools: Use Expo's built-in debugging tools and React Native Debugger.

Testing with TypeScript

You can use testing frameworks like Jest with TypeScript in your Expo project.

  • Install Jest and Required Packages:

npm install --save-dev jest @types/jest ts-jest        

  • Configure Jest:

module.exports = {
    preset: 'jest-expo',
    transform: {
    '^.+\\.tsx?$': 'ts-jest',
    },
    testPathIgnorePatterns: ['/node_modules/', '/android/', '/ios/'],
    globals: {
    'ts-jest': {
        tsconfig: 'tsconfig.json',
    },
    },
};        

  • Write Tests in TypeScript:

// Example.test.ts
import React from 'react';
import renderer from 'react-test-renderer';
import Greeting from '../Greeting';

it('renders correctly', () => {
    const tree = renderer.create(<Greeting name="Expo User" />).toJSON();
    expect(tree).toMatchSnapshot();
});        

  1. Run Tests:

npm test        

at the end...

Integrating TypeScript into your React Native projects using Expo can significantly enhance your development workflow. It provides static typing, which helps in catching errors early, and improves code maintainability and readability. Expo's seamless support for TypeScript and its latest features make it an excellent choice for building robust mobile applications.

By using npx create-expo-app@latest, you ensure that you're starting your project with the most up-to-date tools and configurations.

To view or add a comment, sign in

More articles by Michelangelo Giacomelli

Others also viewed

Explore content categories