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 vs. Dynamic Typing
Understanding the difference between static and dynamic typing is crucial when transitioning from JavaScript to TypeScript.
Advantages of Static Typing:
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.
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:
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:
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:
Classes
TypeScript extends JavaScript classes with typing and other features, enhancing object-oriented programming capabilities.
Access Modifiers
Example
class Animal {
private name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
class Rhino extends Animal {
constructor() {
super("Rhino");
}
}
Explanation:
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:
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:
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:
Intersection Types
Combines multiple types into one.
interface ErrorHandling {
success: boolean;
error?: { message: string };
}
interface ArtworksData {
artworks: { title: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling;
Recommended by LinkedIn
Explanation:
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:
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
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.
cd MyTypeScriptApp
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:
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>
);
}
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.
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.
npm install --save-dev @types/react-navigation
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
npm install @react-navigation/native
npm install @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
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;
Benefits of Using TypeScript with React Native (Expo)
Tips and Best Practices
{
"compilerOptions": {
"strict": true,
// other options...
}
}
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:
npx expo install expo-location
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;
Debugging TypeScript in Expo
Expo and TypeScript work well together with debugging tools.
Testing with TypeScript
You can use testing frameworks like Jest with TypeScript in your Expo project.
npm install --save-dev jest @types/jest ts-jest
module.exports = {
preset: 'jest-expo',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testPathIgnorePatterns: ['/node_modules/', '/android/', '/ios/'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
};
// 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();
});
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.