Skip to content

Ermegilius/ReactAndTypeScript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReactAndTypeScript

TS is invented and is being implemented to control types.

How to use TS

If YS was installed globaly, you can compile ts file to js file by running the following command:

tsc <filename>.ts

Basic types:

  • string;
  • number;
  • boolean.

Combined types

let userId: string | number = "abc1";

Array types

Array of strings

let hobbies: Array<string>;
hobbies = ["Sports", "Cooking", "Reading"];

Function types

function add(a: number, b: number): void {
	// void for a function with no return value
	const result = a + b;
	console.log(result);
}

function add2(a: number, b: number): number {
	// and here we return number
	const result = a + b;
	return result;
}

Callback types

function calculate(a: number, b: number, calcFn: (a: number, b: number) => number) {
	calcFn(a, b);
}

calculate(2, 5, add2); //call it with add2 function

Custom types

type StringOrNumber = string | number;
let userId: StringOrNumber = "abc1";
userId = 123;
type User = {
	name: string;
	age: number;
	isAdmin: boolean;
	id: StringOrNumber;
};
let user: User;

Interface

Usially is used to define object types.

interface Credentials {
	user: string;
	pass: string;
}

It's easelly extendeble (declaration merging). Other devs can extend your interface for their projects.

interface Credentials {
	user: string;
	password: string;
	email: string;
}
let creds: Credentials;
interface Credentials {
	mode: string;
}

Useful for projects where classes are used.

class AuthCredentials implements Credentials {
	user: string;
	password: string;
	email: string;
}
function login(credentials: Credentials) {}

Merging types

We can combine several types into one.

type Admin = {
	permissions: string[];
};
type AppUser = {
	userName: string;
};
type AppAdmin = Admin & AppUser;

Merge interfaces

interface Admin {
	permissions: string[];
}
interface AppUser {
	userName: string;
}
interface AppAdmin extends Admin, AppUser {}

Literal types

let role: "admin" | "user" | "editor"; //'admin' or 'user', 'editor'
role = "admin";
role = "user";
role = "editor";
//role = "abc"; // error

Type guards

type Role = "admin" | "user" | "editor";

function performAction(action: string | number, role: Role) {
	if (role === "admin" && typeof action === "string") {
		// do something
	}
}

When using "Type Guards" (i.e., if statements that check which concrete type is being used), TypeScript performs so-called "Type Narrowing".

function combine(a: number | string, b: number | string) {
	if (typeof a === "number" && typeof b === "number") {
		return a + b;
	}
	return `${a} ${b}`;
}

Inside if statement TS narrows a and to numbers only. You can NOT check if a value meets the definition of a custom type (type alias) or interface type. Becouse a custom type does not exist once the code is compiled to JavaScript.

Geberic type feature

Build in generics

let roles: Array<Role>;
roles = ["admin", "user", "editor"];

Custom generics

//T is a common placeholder for the type
type DataStorage<T> = {
	storage: T[];
	add: (data: T) => void;
};

//replacing T with string
const textStorage: DataStorage<string> = {
	storage: [],
	add(data) {
		this.storage.push(data);
	},
};

Generic functions

function merge<T, U>(a: T, b: U) {
	return {
		...a,
		...b,
	};
}
const newUser = merge<{ name: string }, { age: number }>({ name: "Max" }, { age: 30 });

TS & REACT

npm create vite@latest <project name>
<chose react and TS>
cd <project name>
npm i
npm run dev

Components and props

import { type FC } from "react";
type CourseGoalProps = PropsWithChildren<{ title: string }>;
const CourseGoal: FC<CourseGoalProps> = ({ title, children }) => {
	return (
		<article>
			<div>
				<h2>{title}</h2>
				{children}
			</div>
			<button>Delete</button>
		</article>
	);
};

export default CourseGoal;

Or this one:

import { type ReactNode } from "react";

type HeaderProps = {
	image: {
		src: string;
		alt: string;
	};
	children: ReactNode;
};

export default function Header({ image, children }: HeaderProps) {
	return (
		<header>
			<img {...image} />
			{children}
		</header>
	);
}

The last one will work with App.tsx:

import Header from "./components/Header.tsx";
import goalsImg from "./assets/goals.jpg";

<Header image={{ src: goalsImg, alt: "A list of goals" }}>
	<h1>Your course goals</h1>
</Header>;

useState

import { useState } from "react";
import CourseGoal from "./components/CourseGoal.tsx";

type CourseGoal = {
	title: string;
	description: string;
	id: number;
};

export default function App() {
	const [goals, setGoals] = useState<CourseGoal[]>([]);
	function handleAddGoal() {
		setGoals((prevGoals) => {
			const newGoal: CourseGoal = {
				id: Math.random(),
				title: "Learn React, step 1",
				description: "Learn the first steps in React TS",
			};
			return [...prevGoals, newGoal];
		});
	}

	return (
		<main>
			<button onClick={handleAddGoal}>Add Goal</button>
			<ul>
				{goals.map((goal) => (
					<li key={goal.id}>
						<CourseGoal title={goal.title}>
							<p>{goal.description}</p>
						</CourseGoal>
					</li>
				))}
			</ul>
		</main>
	);
}