๐ TypeScript
These Study Room materials were written and edited by keysub with the assistance of AI.
1. Introduction ๐
Expand / Collapse
1.1 What Is TypeScript? ๐ค
TypeScript is a programming language developed by Microsoft. Put simply, it is JavaScript with types.
1.2 Its Position as a Superset of JavaScript ๐
1.2.1. What Does โSupersetโ Mean?
Think of set inclusion in mathematics. If JavaScript is a small circle, TypeScript is a larger circle that completely contains it.
* Compatibility: All JavaScript code is valid TypeScript code. Simply changing an existing .js extension to .ts works without a problem.
* More on top: It adds a powerful tool called a static type system to JavaScript syntax.
1.2.2. Why Add Types on Top of JavaScript?
JavaScript is very flexible, but as a project grows it becomes difficult to predict where errors may occur. TypeScript addresses this drawback. * Catch errors early: Detects typos and incorrect data-type usage before code runs (during compilation). * Guidance: Specifying what data variables and functions exchange makes code much easier for teammates (or your future self) to understand. * Tooling support: Autocomplete improves dramatically, increasing development speed and accuracy.
1.2.3. How It Works: Browsers Do Not Understand TypeScript โ๏ธ
Browsers cannot read TypeScript directly, so a compile (or transpile) step is required.
1. Write code in a .ts file
2. The compiler checks the types and reports errors
3. If there are no issues, the types are removed and the code is converted to a plain .js file
4. The browser runs the final JavaScript file
1.3 Dynamically Typed (JS) vs. Statically Typed (TS) Languages โ๏ธ
The biggest differences between dynamic typing (JS) and static typing (TS) are when types are determined and when errors are discovered.
| Category | JavaScript (Dynamic Type) | TypeScript (Static Type) |
|---|---|---|
| When types are determined | Automatically when a value is assigned at runtime | Explicitly by the developer while writing code (compile time) |
| When errors are detected | Errors become apparent only when the program runs | Errors are caught during compilation, before the program runs |
| Flexibility/Safety | Very flexible, but unexpected type errors are more likely | Strict, but code is more stable and predictable |
1.3.1. JavaScript (Dynamic Typing) ๐
The same variable can hold a number and later a string.
let value = 10; // initially a number
value = "Hello"; // can suddenly change to a string (no issue is known until runtime)
1.3.2. TypeScript (Static Typing) ๐ก๏ธ
You specify in advance what type a variable will hold when you create it.
let value: number = 10;
value = "Hello"; // Error! "A string cannot be assigned to a number type."
2. Basic Types ๐
Expand / Collapse
2.1 Primitive Types ๐
2.1.1. string: Stores text data
let userName: string = "Alice";
let greeting: string = `Hello, ${userName}`; // Template literals are also supported
2.1.2. number: Stores all numbers, integers or decimals
let age: number = 25;
let pi: number = 3.14;
2.1.3. boolean: True or false
let isLoggedIn: boolean = true;
let hasToken: boolean = false;
2.1.4. null & undefined
They are usually used to explicitly indicate โno value.โ
let empty: null = null;
let notFound: undefined = undefined;
2.2 Arrays and Tuples ๐๏ธ
Arrays and tuples play a key role in structuring data. Arrays hold lists of values of the same type; tuples hold lists with a fixed order and length.
2.2.1 Array Type Declarations: type[] vs Array<type>
// Method 1: type[] (most commonly used)
const productList: string[] = ["iPhone", "Galaxy", "MacBook"];
// Method 2: Array<type> (generic form)
const priceList: Array<number> = [1000, 2000, 3000];
2.2.2 Arrays of Objects ๐ฆ
Common examples include a member list or product list received from a server.
2.2.2.1. Working with an array of objects: [1. Define the object shape] โ [2. Apply it to the array]
- Combining an interface with
[](most common)
// 1. Define the object blueprint (interface)
interface User {
id: number;
name: string;
}
// 2. Apply the type to the array
const users: User[] = [
{ id: 1, name: "Kim" },
{ id: 2, name: "Lee" }
];
users.push({ id: 3, name: "Park" }); // Can be added freely
- Combining a type alias with
Array<>
type User = {
id: number;
name: string;
};
const users: Array<User> = [
{ id: 1, name: "Kim" }
];
- Flexible type definitions (optional properties). Use
?here.
interface User {
id: number;
name: string;
email?: string; // Email is a string when present, otherwise undefined
}
const userList: User[] = [
{ id: 1, name: "Kim", email: "kim@test.com" },
{ id: 2, name: "Lee" } // No error even if email is omitted
];
- Read-only arrays (
Readonly)
const colors: readonly string[] = ["Red", "Blue"];
colors.push("Green"); // Error! (cannot add)
2.2.3 Tuples ๐ฑ
-
A tuple is a mathematical/computer-science term for a group of data with a fixed order and length. In everyday terms, it is similar to a set meal.
A hamburger set has fixed contents and order:[hamburger, cola, fries]. -
In TypeScript, a tuple is a subtype of an array with a fixed length and element types specified in advance.
2.2.3.1 Basic Declaration and Assignment ๐
Specify the type for each index in order.
let person: [string, number];
person = ["Alice", 30]; // โ
OK
// person = [30, "Alice"]; // โ Error: the order is incorrect
// person = ["Alice", 30, true]; // โ Error: exceeds the fixed length
2.2.3.2 Optional Elements โ
Use ? when an element may or may not be present. Optional elements must always come at the end.
let point: [number, number, number?];
point = [10, 20]; // โ
OK
point = [10, 20, 30]; // โ
OK
2.2.3.3 Rest Elements ๐
Use this when you want to type only part of a tuple and handle the rest variably.
let logger: [string, ...number[]];
logger = ["Error", 404, 500, 502]; // โ
The first is a string; the rest are numbers
2.2.3.4 Readonly Tuples ๐
Like regular arrays, tuples allow push and pop, which can break their fixed length. Add readonly to prevent this.
const readOnlyTuple: readonly [number, string] = [1, "Fixed"];
// readOnlyTuple.push(2); // โ Error: The 'readonly' type has no push method
2.2.3.5 Labeled Tuple Elements ๐ท๏ธ [Recommended]
Improve readability by naming what each element represents.
type UserInfo = [id: number, name: string, email: string];
const user: UserInfo = [1, "Kim", "kim@example.com"];
2.2.3.6 ๐ก Tuples vs. Interfaces/Objects โ๏ธ
When choosing between an object ({}) and a tuple ([]), consider these guidelines.
- Object: When key names should explain the meaning of the data (most data structures).
- Tuple: When order itself is meaningful or when passing two or three short values (e.g., coordinates
[x, y], state management[state, setter]).
๐ 2.3 Additional Types (TypeScript-Specific System Types)
๐ฆ 2.3.1 Enums
An enum groups related items into a single set for management.
It improves code readability and restricts values to the predefined set.
๐ฆ 2.3.1.1 Managing Status Codes
Common examples include order statuses, user permissions, and HTTP response handling.
// 1. Declaration: define order statuses
enum OrderStatus {
Pending = "PENDING", // Pending
Shipped = "SHIPPED", // Shipping
Delivered = "DELIVERED", // Completed
Cancelled = "CANCELLED" // Cancelled
}
// 2. Usage: use it as a function parameter type
function updateOrderStatus(id: number, status: OrderStatus) {
console.log(`Changing order ${id} status to ${status}.`);
}
// Usage example
updateOrderStatus(1024, OrderStatus.Shipped);
// Error: an undefined string cannot be used
// updateOrderStatus(1025, "READY");
โก 2.3.1.2 const enum
Adding const prevents an enum object from being created at compile time; values are inlined, making the resulting JavaScript much lighter.
const enum Role {
Admin = "ADMIN",
User = "USER"
}
โ๏ธ 2.3.1.3 Numeric vs. String Enums
The main differences are whether values are assigned automatically and how clear they are during debugging.
1๏ธโฃ Automatic Value Assignment (Auto-incrementing) * Numeric enum: If omitted, values start at 0 and increment by 1 automatically. * String enum: There is no automatic assignment; each memberโs value must be specified manually.
// Numeric: automatically assigned 0, 1, 2
enum Direction { Up, Down, Left }
// String: must be specified explicitly
enum DirectionStr {
Up = "UP",
Down = "DOWN",
Left = "LEFT"
}
2๏ธโฃ Debugging and Logs (Readability)
* Numeric: Logs show numbers like 0 and 1, making it hard for anyone who did not write the code to identify the state.
* String: Logs show meaningful text such as "UP" and "DOWN", which is much more useful when inspecting a database or analyzing error logs.
3๏ธโฃ Reverse Mapping
* Numeric: You can look up a value by name and a name by value (Direction[0] -> "Up").
* String: You can look up values by name only. It does not support reverse mapping, making it safer and stricter.
๐ 2.3.2 Any (Allows Anything)
It disables all type checks. This is convenient but makes the code much like JavaScript, removing the point of TypeScript, so avoid it whenever possible.
let value: any = 10;
value = "Hello"; // No error
value.toUpperCase(); // No error (risk of a runtime error if this were a number)
๐ 2.3.3 Unknown (Safe Any)
- Like
any, it can store a value of any type, but you must check its type (with a type guard) before use. It is much safer thanany. - Use it when you truly do not know what will arrive, such as an API response.
let value: unknown = "Hello";
// value.toUpperCase(); // Error! (cannot use directly because the type is unknown)
if (typeof value === "string") {
value.toUpperCase(); // Now usable because the type has been checked!
}
๐จ 2.3.4 Void (Empty)
- Use it when a function returns nothing.
- Use it for functions without a
returnstatement or functions that only write to the console.
function sayHi(): void {
console.log("Hi!");
// return 1; // Error! There must be no return value
}
๐ซ 2.3.5 Never (Cannot Happen)
Use it when a function never finishes or always throws an error, so a return value cannot exist.
It is used for infinite loops and functions that intentionally throw errors.
function throwError(msg: string): never {
throw new Error(msg); // The function does not finish; it stops here
}
function infiniteLoop(): never {
while (true) {} // Runs forever
}
3. Interfaces and Type Aliases ๐ท๏ธ
Expand / Collapse
3.1 Basic Interface Structure ๐๏ธ
An interface is defined with the interface keyword and specifies the properties and methods an object must have.
interface User {
id: number;
name: string;
email: string;
}
const worker: User = {
id: 1,
name: "Kim",
email: "kim@example.com"
};
3.2 Defining an Object Shape ๐
โDefine an objectโs shapeโ means deciding in advance which keys it has and what type of value each key must contain.
3.2.1 Its Role as a Blueprint ๐
Just as a blueprint is needed to build a structure, code specifies: โThis object must have a numeric id and a string name.โ
// 1. Define the shape with an interface
interface Product {
name: string;
price: number;
}
// 2. Create an object matching the defined shape
const laptop: Product = {
name: "MacBook Pro",
price: 2500000
};
// โ Error: detects an unsupported 'color' property or an incorrect type for Product
const phone: Product = {
name: "iPhone",
price: "1.5 million won", // Error: 'string' is not assignable to 'number'
color: "silver" // Error: 'color' does not exist in type 'Product'
};
3.2.2 โDuck Typingโ and Structural Typing ๐ฆ
TypeScript uses structural typing: values are considered the same type when their internal shapes match, even if their names differ.
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// Point was not explicitly specified, but it passes because the shape matches!
const myObj = { x: 10, y: 20, z: 30 };
printPoint(myObj);
3.3 Optional (?) and Readonly Properties ๐
3.3.1 Optional Properties (?) โ
Add a question mark (?) after the property name when that property may or may not be present on an object.
- Practical use: Often used to distinguish required sign-up information (ID, password) from optional information (bio, website address).
- Behavior: If no value is provided, it is treated as though
undefinedwere assigned.
interface UserProfile {
id: string; // required
nickname: string; // required
bio?: string; // optional (may or may not be present)
website?: string; // optional
}
const user1: UserProfile = {
id: "user01",
nickname: "Taekwon V"
// No error even if bio and website are omitted.
};
3.3.2 Readonly Properties ๐
Add readonly before a property name. A value can be assigned only when the object is created and cannot be changed afterward.
- Practical use: Protects important data that must not change while the program runs, such as API keys, unique IDs, and configuration values.
- Behavior: Errors are caught at compile time, not runtime.
interface Config {
readonly apiKey: string;
readonly domain: string;
}
const myConfig: Config = {
apiKey: "ABC-12345",
domain: "https://example.com"
};
// โ Error: cannot be modified because it is readonly.
myConfig.apiKey = "XYZ-99999";
3.3.3 (Reference) The ReadonlyArray<T> Type ๐
- Use
ReadonlyArray<number>to make the entire array immutable. - It prevents the use of array-mutating methods such as
pushandpop.
const numbers: ReadonlyArray<number> = [1, 2, 3];
// โ Error: 'push' does not exist
numbers.push(4);
3.4 Index Signatures for Dynamic Keys ๐
Use this when defining an interface whose property names are unknown in advance or whose number of properties varies.
3.4.1 Basic Syntax ๐
Define the key and value types inside square brackets ([]).
interface SalaryInfo {
// [key: key type]: value type
[role: string]: number;
}
const teamSalary: SalaryInfo = {
developer: 5000,
designer: 4500,
manager: 5500,
// Any key is accepted as long as its value is a number.
};
3.4.2 Why It Is Used in Practice ๐ก
- Handling dynamic data: Essential for API response data whose keys keep changing, such as user IDs or dates.
- Using an object as a dictionary: Useful for mapping data by category.
interface ErrorMessages {
[code: string]: string;
}
const apiErrors: ErrorMessages = {
"404": "Page not found.",
"500": "A server error occurred.",
"403": "Permission denied.",
};
3.4.3 Notes and Rules โ ๏ธ
- Using it with required properties: Other explicitly declared properties must follow the index signatureโs rule.
typescript interface UserInventory { [itemName: string]: number; gold: number; // โ Allowed (matches the index signature type) // username: string; // โ Error: the index signature requires every value to be a number } - Allowing multiple types: Use a union type if values should allow multiple types.
typescript interface FlexibleConfig { [key: string]: string | number | boolean; version: number; // โ Allowed because it is within the string | number | boolean range } - Key types: Index signature keys are generally limited to
string,number, andsymbol.
3.4.4 (Reference) The Record<K, T> Utility Type ๐ ๏ธ
In practice, instead of writing an index signature directly, you can express it more concisely with TypeScriptโs Record.
interface Obj { [key: string]: number; }type Obj = Record<string, number>;(Same meaning as above)
3.5 Using Type Aliases ๐ท๏ธ
A type alias gives a new name (alias) to a particular type or interface.
3.5.1 Basic Usage ๐
You can name any type, including object, ordinary variable, and union types.
// 1. Add an alias to a basic type
type MyString = string;
const name: MyString = "Kim";
// 2. Define an object type (similar to an interface)
type User = {
id: number;
name: string;
};
const worker: User = { id: 1, name: "Kim" };
3.5.2 Interface vs Type Alias ๐ค
The following features are the decisive reasons to use type instead of interface in practice.
3.5.2.1 Union Types ๐ฅ
Use it to define a type that is โA or B.โ (Not possible with an interface)
type Status = "success" | "error" | "loading";
const currentStatus: Status = "success"; // โ
OK
const wrongStatus: Status = "pending"; // โ Error: only the three specified strings are allowed
3.5.2.2 Tuples ๐ฆ
Use it to fix the array length and the type at each position.
type Point = [number, number];
const coordinates: Point = [10, 20]; // โ
OK
3.5.2.3 Intersection Types โ
Use the & operator to combine two or more types into one.
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age; // equivalent to { name: string, age: number }
3.5.2.4 Managing Complex Types โ๏ธ
It lets you define API results or component state very intuitively.
type ResponseData = {
id: number;
content: string;
};
// Practical pattern combining union and object types
type ApiResponse =
| { status: "success"; data: ResponseData }
| { status: "error"; message: string };
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
console.log(response.data.content);
} else {
console.log(response.message);
}
}
3.5.3 (Reference) Inheritance with extends ๐ฒ
This interface feature is similar to class inheritance.
- How it works: A new interface inherits properties from an existing interface.
- Feature (strictness): A property type conflict between parent and child causes an error, preventing the problem in advance.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// โ Error when a conflict occurs interface Cat extends Animal { name: number; // Error: type does not match 'name(string)' in 'Animal'! }
3.5.4 Which One Should You Use? ๐ค
- Defining object shapes and frequently using inheritance (
extends)? โInterface - Need unions (
|), intersections (&), or primitive type aliases? โType Alias
4. Functions ๐
Expand / Collapse
4.1. Defining Function Types โ๏ธ
4.1.1. Basic Function Type Definitions
This is the most basic, yet essential, form. Specify the parameters and return type.
function add(a: number, b: number): number {
return a + b;
}
// Arrow function form
const multiply = (a: number, b: number): number => a * b;
4.1.2. Optional and Default Parameters โ๏ธ
Function parameters can be made optional with ? or given a default value.
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}`;
}
console.log(greet("Chulsoo")); // "Hello, Chulsoo"
console.log(greet("Younghee", "Hello")); // "Hello, Younghee"
4.2. Function Type Aliases ๐ท๏ธ
This defines a functionโs type in advance for reuse. It is widely used to define callback functions.
type Formatter = (text: string, uppercase: boolean) => string;
const myFormatter: Formatter = (str, isUpper) => {
return isUpper ? str.toUpperCase() : str.toLowerCase();
};
4.3. Function Overloading ๐
Use this when the return type differs depending on the argument type. It is useful for handling API responses or creating utility functions.
// Declaration: define the allowed combinations
function getValue(v: string): string;
function getValue(v: number): number;
// Implementation: actual logic (using any or a union type)
function getValue(v: any): any {
return v;
}
const str = getValue("ABC"); // Inferred as string
const num = getValue(123); // Inferred as number
4.4. Object Destructuring ๐ฆ
This is one of the most common patterns for receiving React component props or complex options objects.
interface UserConfig {
id: string;
isAdmin: boolean;
}
// Define the type directly at the parameter or use an interface
function loginUser({ id, isAdmin }: UserConfig): void {
console.log(`ID: ${id}, Admin: ${isAdmin}`);
}
4.4.1. Why Use This? (Before vs. After) ๐ค
Without destructuring, you must repeat the object name each time, as in user.id and user.isAdmin.
// Without destructuring
function loginUser(user: UserConfig) {
console.log(user.id); // Must prefix with user. every time
console.log(user.isAdmin);
}
// With destructuring
function loginUser({ id, isAdmin }: UserConfig) {
console.log(id); // Can be used directly like a variable!
console.log(isAdmin);
}
4.4.2. Syntax Breakdown ๐
Here is a breakdown of ({ id, isAdmin }: UserConfig):
{ id, isAdmin }: โAn object will be passed; find itsidandisAdminproperties and create variables with the same names!โ: UserConfig: The type definition saying, โThat object must conform to theUserConfigtype!โ
4.5. Arrow Functions ๐น
4.5.1. Arrow Functions
There are two ways to define types for arrow functions in TypeScript.
4.5.1.1. Define the Type Alongside the Function (Most Common)
const add = (a: number, b: number): number => a + b;
4.5.1.2. Assign a Function Type to the Variable in Advance (Recommended!) โจ
type CalculateFn = (a: number, b: number) => number;
const multiply: CalculateFn = (a, b) => a * b; // Argument types are inferred automatically even if omitted
4.6. Rest Parameters ๐๏ธ
4.6.1. Use This When the Number of Arguments Is Not Fixed
The key point is that a rest parameter in TypeScript must be an array type.
// ...numbers: number[] means the parameter receives an array containing only numbers.
// : number means the function returns one number after calculation!
function sum(...numbers: number[]): number {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
4.6.2. API Logger (Rest + Destructuring) ๐ชต
Combine these concepts to handle a fixed argument together with the remaining options.
interface LogOptions {
level: 'info' | 'error' | 'warn';
silent?: boolean;
}
// The first argument is message; the rest are received as an array of option objects.
const logMessage = (message: string, ...options: LogOptions[]) => {
console.log(`[Message]: ${message}`);
console.log(`[Options]:`, options);
};
logMessage(
"Server connected successfully",
{ level: 'info' },
{ level: 'warn', silent: true }
);
4.7. Defining a Callback Functionโs Type Signature ๐
A callback function is a function passed as an argument to another function. In TypeScript, you can define in advance a signature specifying what arguments a callback receives and what it returns.
4.7.1. Basic Definition (Inline)
function processUserInput(name: string, callback: (n: string) => void) {
// 1. Process name
console.log(`User name: ${name}`);
// 2. Run the callback when processing is complete
callback(name);
}
processUserInput("Chulsoo", (userName) => {
console.log(`Welcome, ${userName}!`);
});
4.7.2. Using a Type Alias (Recommended in Practice) ๐ก
Callback types often become complex, so it is preferable to define them separately with a name.
// Define the callback type (blueprint)
type Checker = (id: string, info: { role: string }) => boolean;
// 2. Runner: create an object internally and pass it to the callback
const run = (id: string, callback: Checker) => {
const isOk = callback(id, { role: 'admin' }); // Pass the object!
console.log(isOk ? "Approved" : "Denied");
};
// 3. Actual use: extract the passed object (info) and write the logic
run("user01", (id, info) => {
return info.role === 'admin'; // Return true if admin
});
4.7.3. When a Callback Is Optional (?) โ
If the callback may or may not be provided, add ?. The standard practice is to call it safely using && or a conditional.
function fetchData(url: string, onSuccess?: (data: any) => void) {
// ... logic to fetch data ...
const data = { id: 1, content: "Hello" };
// Run only when onSuccess exists! (using the && operator)
onSuccess && onSuccess(data);
}
4.7.4. Grouping Variable Arguments (...args) into an Array Type ๐๏ธ
// : string[] after ...tags means an array containing only strings.
// It must be written as an array type such as number[] or string[].
// It can be used only once, as the final parameter.
function makeHashTags(...tags: string[]): string {
// Array methods such as map, filter, and reduce can be used directly inside the function.
return tags.map(t => `#${t}`).join(' ');
}
// Whether one or three are passed, they are handled as an array called 'tags'
console.log(makeHashTags("TypeScript")); // "#TypeScript"
console.log(makeHashTags("JS", "TS", "React")); // "#JS #TS #React"
5. Classes and Object-Oriented Programming ๐๏ธ
Expand / Collapse
TypeScript classes extend JavaScript class syntax with type definitions and access modifiers.
5.1 Class Usage Checklist โ
- The
newkeyword: A class is only a blueprint, so create and use an object (instance) withnew ClassName(). - Using
this: Always usethis.to access a variable or method inside the class. - Initialization required: Declaring a property without assigning a value causes an error (or use a definite assignment assertion such as
name!: string).
5.1.1. The Standard Declaration ๐
class Robot {
// 1. Declare member variable types
name: string;
model: string;
// 2. Define the constructor
constructor(name: string, model: string) {
this.name = name;
this.model = model;
}
// 3. Define a method
move(): void {
console.log(`${this.name} (${this.model}) is moving. ๐ค`);
}
}
// Usage (create an instance)
const myRobot = new Robot("Taekwon V", "V1");
myRobot.move(); // "Taekwon V (V1) is moving."
5.1.2. A Practical Shorthand โก
The approach above is cumbersome in practice, so attach access modifiers (public, private, etc.) to constructor parameters to declare and assign properties at once.
class User {
// Adding an access modifier to the constructor automatically declares and assigns the member property!
constructor(public nickname: string, private email: string) {}
showInfo() {
console.log(`Name: ${this.nickname}, email: ${this.email} ๐ค`);
}
}
const user = new User("Tipster", "tip@example.com");
console.log(user.nickname); // โ
OK (public)
// console.log(user.email); // โ Error! (private, so external access is not allowed)
5.2 The Three Access Modifiers ๐
| Modifier | Visibility | Description |
|---|---|---|
public |
Public | Accessible from anywhere (default) |
private |
Private | Accessible only inside that class (not externally or by subclasses) |
protected |
Protected | Accessible inside the class and its subclasses |
class Employee {
public name: string; // Visible to everyone
private salary: number; // Only the class itself can access the salary
protected department: string; // Department info is shared with subclasses only
constructor(name: string, salary: number, department: string) {
this.name = name;
this.salary = salary;
this.department = department;
}
public getSalaryInfo() {
return `${this.name}'s salary is confidential. ๐คซ`; // salary is accessible inside the class
}
}
class Manager extends Employee {
public changeDept(newDept: string) {
this.department = newDept; // โ
Accessible to subclasses because it is protected!
// this.salary = 0; // โ Error! Subclasses cannot access private members either
}
}
const worker = new Employee("Kim Chulsoo", 5000, "Development Team");
console.log(worker.name); // โ
OK
// console.log(worker.salary); // โ Error! (Inaccessible externally)
5.3 private vs. JavaScriptโs #: What Is the Difference? ๐ค
Both prevent external access, but there is a crucial difference.
- 5.3.1
private(TypeScript syntax):- Errors are caught at compile time.
- After compilation to JavaScript, it looks like an ordinary variable (there is no runtime enforcement).
- 5.3.2
#(JavaScript standard syntax):- It remains completely hidden at runtime.
- The browser itself prevents access entirely.
5.4 Abstract Classes ๐งฑ
An abstract class is an unfinished blueprint. It cannot instantiate itself, but passes on common functionality that child classes must have.
5.4.1 Building a Common Foundation with abstract
An abstract class can contain both regular methods (implemented) and abstract methods (name declared only).
abstract class Animal {
// 1. Shared feature (implemented): subclasses use it as-is
breath() {
console.log("Breathing. ๐จ");
}
// 2. Abstract method (declaration only): subclasses must implement it themselves
abstract makeSound(): void;
}
class Dog extends Animal {
// Error if the abstract method is not implemented!
makeSound() {
console.log("Woof! ๐ถ");
}
}
const myDog = new Dog();
myDog.breath(); // "Breathing." (uses the parent implementation)
myDog.makeSound(); // "Woof!" (uses its own implementation)
// const animal = new Animal(); // โ Error! Abstract classes cannot be instantiated directly
5.4.2 Difference in Purpose from Interface Implementation (implements)
| Category | Abstract class (extends) |
Interface (implements) |
|---|---|---|
| Role | Pass down a base implementation (inheritance) | Conform to a specification (implementation) |
| Implementation | Can include actual method code | Cannot include code (declarations only) |
| Purpose | โYou are my child, so use these shared features.โ | โYou must have this feature (method).โ |
| Multiple use | Can extend only one | Can implement several at once |
6. Advanced Types ๐
Expand / Collapse
6.1. Union Types (|) ๐
- It means โA or B.โ Use it when a value can be one of several types.
- Commonly used to handle API responses or state values.
// May be a string or number
let id: string | number;
id = "user_01"; // โ
OK
id = 123; // โ
OK
// Practical example: handling loading states
type Status = "success" | "error" | "loading";
function printStatus(status: Status) {
console.log(`Current status: ${status}.`);
}
printStatus("success"); // โ
OK
// printStatus("pending"); // โ Error! (rejects strings other than those specified)
6.2. Intersection Types (&) ๐ค
- It means โA and B.โ It combines types into one, requiring all their properties.
- Very useful for combining user information with permissions.
interface Person {
name: string;
}
interface Worker {
employeeId: number;
}
// Requires all properties from both Person and Worker
type Employee = Person & Worker;
const newEmployee: Employee = {
name: "Assistant Manager Park",
employeeId: 9901
};
6.3. Type Inference and Assertions ๐
6.3.1. Type Inference โจ
- When you declare a variable and assign a value immediately, TypeScript infers its type from that value.
- When the type is clear, letting inference handle it keeps the code clean.
let name = "Chulsoo"; // inferred as string
let age = 25; // inferred as number
// name = 123; // โ Error! it was already inferred as a string
6.3.2. Type Assertions (as) ๐ ๏ธ
- Use this when the developer knows the type with greater certainty.
- Keyword:
as(the recommended form) - โ ๏ธ If the actual type differs, a runtime error may occur, so be careful.
6.3.2.1. Specify a Concrete Tag with an Assertion ๐ท๏ธ
document.querySelector returns the broad Element type by default. To use a specific tagโs properties (e.g., input value, img src), narrow the type with as.
// TypeScript sees a generic Element, but the developer knows it is an input tag.
const myInput = document.getElementById("user-id") as HTMLInputElement;
// The assertion allows access to the value property
console.log(myInput.value);
6.3.2.2. Null-Checking Pattern (Safe Access) ๐ก๏ธ
Account for the element being absent from the page. In practice, always use optional chaining (?.) or a conditional.
const button = document.querySelector('.submit-btn') as HTMLButtonElement;
// Pattern A: Optional chaining (recommended)
button?.click();
// Pattern B: Conditional (for complex logic)
if (button) {
button.disabled = true;
button.innerText = "Sending...";
}
6.3.2.3. Handling e.target in an Event Listener ๐ฏ
Type assertion is also needed for e.target in an event handler because TypeScript cannot be sure which element it is.
const handleInput = (e: Event) => {
// Assert e.target as the required type before using it
const target = e.target as HTMLInputElement;
console.log(target.value);
};
document.querySelector('#search')?.addEventListener('input', handleInput);
6.3.2.4. Commonly Used HTML Types ๐
HTMLInputElement(input)HTMLButtonElement(button)HTMLAnchorElement(anchor tag)HTMLImageElement(image tag)HTMLFormElement(form tag)
6.4. Type Guards ๐ฎโโ๏ธ
A type guard is a โguardian of types.โ When types are combined, as in a union, it identifies the specific type at runtime so you can write safe code.
6.4.1. typeof: Branching on Primitive Types ๐
This basic approach distinguishes primitive types such as string, number, and boolean.
function printId(id: string | number) {
if (typeof id === "string") {
// Inside this block, id is definitely a string.
console.log(id.toUpperCase());
} else {
// Here, it is automatically narrowed to number.
console.log(id.toFixed(2));
}
}
6.4.2. instanceof: Branching on Class Instances ๐๏ธ
Use it to check whether an object was created from a particular class.
class Dog { bark() { console.log("Woof"); } }
class Cat { meow() { console.log("Meow"); } }
function speak(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // Narrowed to Dog
} else {
animal.meow(); // Narrowed to Cat
}
}
6.4.3. User-Defined Type Guards (the is Keyword) ๐ค
Use this to create a separate function that identifies a complex objectโs type. The key is writing parameter is Type as the return type.
interface User { name: string; role: string; }
interface Guest { name: string; }
// Define a function that checks whether person is a User
function isUser(person: User | Guest): person is User {
return (person as User).role !== undefined;
}
function welcome(person: User | Guest) {
if (isUser(person)) {
// Inside this block, person is treated as a User.
console.log(`Welcome, administrator ${person.name}.`);
} else {
console.log(`Welcome, visitor ${person.name}.`);
}
}
// Practical example calls
const admin = { name: "Team Lead Kang", role: "ADMIN" };
const guest = { name: "Anonymous" };
welcome(admin); // Output: "Welcome, administrator Kang Team Lead."
welcome(guest); // Output: "Welcome, visitor Anonymous."
// Iterating with an array
const people: (User | Guest)[] = [
{ name: "Assistant Manager Lee", role: "EDITOR" },
{ name: "Gildong" }
];
people.forEach(p => welcome(p));
7. Generics ๐ฆ
Expand / Collapse
7.1. The Concept of Generics ๐ก
Generics are type parametersโthey let you use types like variables. Pass a type when using a function or class instead of fixing it in advance to maximize reuse.
7.1.1. Why Do We Need Generics? ๐ค
- [Approach 1] Write a function for every type (inefficient) ๐ญ
typescript function echoNumber(item: number): number { return item; } function echoString(item: string): string { return item; } // You have to keep copying functions as the number of types grows... - [Approach 2] Use
any(risky) โtypescript function echoAny(item: any): any { return item; } const result = echoAny(123); // result becomes any, so you cannot track whether it is a number or a string -
[Approach 3] Use generics โ ```typescript // means a placeholder that can hold any type function echo(item: T): T { return item; }
const r1 = echo(100); // T becomes number const r2 = echo("Hi"); // T becomes string ```
7.1.2. Use Case: Handling API Responses ๐
interface Response<T> {
data: T; // The actual data type varies each time!
status: number;
message: string;
}
// When receiving user data
const userRes: Response<{ name: string }> = {
data: { name: "Chulsoo" },
status: 200,
message: "Success"
};
// When receiving a product list
const itemRes: Response<string[]> = {
data: ["Apple", "Banana"],
status: 200,
message: "Success"
};
7.2. Generic Functions and Interfaces ๐ ๏ธ
7.2.1. Generic Functions โ๏ธ
Use <T> after the function name. T is simply a conventional name.
// <T> before the parentheses declares that this function uses a type variable named T.
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
// Specify the type at the call site or let TypeScript infer it.
const str = getFirstElement<string>(["A", "B"]); // T is string
const num = getFirstElement([1, 2, 3]); // T is number (inferred automatically)
7.2.2. Generic Interfaces as Templates ๐
Add <T> after the interface name. Useful when the data structure stays the same but the content type changes.
// A blueprint for a box that can hold data of any shape (T)
interface Box<T> {
label: string;
content: T;
}
const stringBox: Box<string> = { label: "Letter", content: "Hello!" };
const numberBox: Box<number> = { label: "Age", content: 25 };
7.2.3. HTTP Response Handling Example ๐ก
interface ApiResponse<T> {
success: boolean;
data: T; // Generics determine what data goes here!
error?: string;
}
interface User { id: number; name: string; }
function fetchUser(): ApiResponse<User> {
return { success: true, data: { id: 1, name: "Chulsoo" } };
}
const response = fetchUser();
console.log(response.data.name); // Autocomplete is available because the data is typed as User!
7.2.4. Using Multiple Generic Variables (<T, U, K>) ๐๏ธ
Use this to combine different types or establish a relationship between them.
function merge<T, U>(objA: T, objB: U): T & U {
return { ...objA, ...objB };
}
const combined = merge({ name: 'Chulsoo' }, { age: 25 });
console.log(combined.name); // OK
console.log(combined.age); // OK
7.2.5. Why T, U, and So On? ๐
The following names are commonly used by convention. - T: Type (first type) - U: Usually (the next letter after T) - K: Key - V: Value - E: Element
7.3. Generic Constraints โ
7.3.1. Why Are Constraints Needed? (The Problem) โ ๏ธ
With only <T>, TypeScript does not know what properties exist and reports an error.
function printLength<T>(item: T) {
// โ Error: 'T' type has no 'length' property.
console.log(item.length);
}
7.3.2. Solving It with extends ๐ค
This is a contract stating, โT must be a type with at least a specific property.โ
interface LengthWise { length: number; }
// T must satisfy the LengthWise interface
function printLength<T extends LengthWise>(item: T): void {
console.log(`Length: ${item.length}`); // โ
Now safe to use!
}
printLength("Hello"); // โ
OK
printLength([1, 2, 3]); // โ
OK
// printLength(123); // โ Error! (numbers do not have length)
7.3.3. Selecting a Specific Property (keyof) ๐
This constraint says, โK must be one of Tโs keys.โ
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person = { name: "Chulsoo", age: 25 };
getProperty(person, "name"); // โ
OK
// getProperty(person, "hobby"); // โ Error! (the object has no hobby key)
8. Utilities (Utility Types & Modules) ๐ ๏ธ
Expand / Collapse
Utility types are type-only tools for adapting already-defined types to a particular situation.
8.1. Key Utility Types ๐งฐ
8.1.1. Partial<T>: Make All Properties Optional โ
Create a new type from an existing type. With Partial<User>, you can provide only some of the properties.
interface User {
id: number;
name: string;
age: number;
}
function updateUser(id: number, fieldsToUpdate: Partial<User>) {
// Actual update logic...
}
updateUser(1, { name: "New Name" }); // โ
No error even when age or id are omitted!
8.1.2. Pick<T, K>: Pick Only What You Need ๐
Create a new type by selecting only the properties you want from an existing type.
// When you want to select only name and age from User
type UserPreview = Pick<User, "name" | "age">;
const profile: UserPreview = {
name: "Chulsoo",
age: 20
};
// profile.id; // โ Error! was not included with Pick
8.1.3. Omit<T, K>: Everything Except Specific Properties โ๏ธ
Remove specific properties from an existing type and use the rest.
// When you want everything from User except id
type NewUser = Omit<User, "id">;
const newUser: NewUser = {
name: "Younghee",
age: 27
// Including the id property causes an error!
};
8.1.4. Readonly<T>: Make It Read-Only ๐
It makes all properties immutable.
const frozenUser: Readonly<User> = { id: 1, name: "Chulsoo", age: 20 };
// frozenUser.name = "Gildong"; // โ Error! is read-only
8.1.5. Record<K, T>: Mapping Key-Value Object Types ๐บ๏ธ
Use this to collect specific keys (K) and fix the value type (T); useful for creating an object map or dictionary.
8.1.5.1. Basic Syntax ๐
// Record<key_type, value_type>
const scores: Record<string, number> = {
"math": 100,
"english": 90,
};
8.1.5.2. Practical Example: Managing Fixed Categories ๐๏ธ
It is especially powerful when keys are restricted to a specific set of strings.
type Page = "home" | "about" | "contact";
interface PageInfo {
title: string;
}
// Keys other than "home", "about", and "contact" are not allowed!
const nav: Record<Page, PageInfo> = {
home: { title: "Home" },
about: { title: "About" },
contact: { title: "Contact" },
};
// nav.blog = { title: "Blog" }; // โ Error! The Page type has no 'blog' key
8.1.5.3. Combining Record with Other Utility Types ๐ค
It can be used to define an objectโs shape precisely.
// An object with user IDs (number) as keys and user information (User) as values
const userList: Record<number, User> = {
101: { id: 101, name: "Chulsoo", age: 20 },
102: { id: 102, name: "Younghee", age: 25 },
};
8.2. Module System ๐ฆ
TypeScriptโs module system is a component management system that lets you split code across files and share it.
8.2.1. Exporting ๐ค
This step makes items available for use in other files. - Named export: Export multiple items from one file - Default export: Export one main item from a file
// math.ts
export const PI = 3.14; // Named Export
export default function add(a: number, b: number) { // Default Export
return a + b;
}
8.2.2. Importing ๐ฅ
This step imports components from another file.
// app.ts
import add, { PI } from './math'; // Default goes without braces; named exports go inside braces!
console.log(add(1, 2)); // 3
console.log(PI); // 3.14
8.2.3. Type-Only Modules (import type) ๐๏ธ
Use this when importing only a type blueprint, not runtime data.
// types.ts
export interface User { id: number; name: string; }
// main.ts
import type { User } from './types'; // Explicitly state "I will use only the type"
const user: User = { id: 1, name: "Chulsoo" };
9. Key tsconfig.json Options
Expand / Collapse
tsconfig.json is the key file for configuring the TypeScript compilation environment. The five options most often adjusted in practice are:
9.1. Key Compiler Options
target: Determines which JavaScript version TypeScript is compiled to (e.g.,ES6,ESNext).module: Determines the module system. Modern frontend tools usually useESNext; Node.js usesCommonJS.strict: Enables all type-checking options.trueis strongly recommended in practice.outDir: Specifies the folder for compiled output (.js).paths: Enables project-relative absolute paths (e.g.,@/components/...) to improve readability.
9.2. Recommended Practical Configuration
{
"compilerOptions": {
"target": "ESNext", // Compile to the latest JS version
"module": "ESNext", // Use the latest module system
"strict": true, // Enable strict type checking (required)
"outDir": "./dist", // Save build output in the dist folder
"rootDir": "./src", // Source code starts in the src folder
"baseUrl": ".", // Base for path resolution
"paths": {
"@/*": ["src/*"] // Allow access to the src folder via @/
},
"esModuleInterop": true, // Enable CommonJS library compatibility
"skipLibCheck": true, // Skip library type checking (improves build speed)
"forceConsistentCasingInFileNames": true // Enforce case-sensitive file names
},
"include": ["src/**/*"], // Include all files under the src folder
"exclude": ["node_modules"] // Folder to exclude from checking
}
9.3. Summary
- Build: Set output compatibility with
targetandmodule. - Safety: Prevent runtime errors with
strict. - Structure: Manage the project structure with
rootDir,outDir, andpaths.