오늘은 본가에 가야 해서 온라인으로 수업을 들었다. DOM, 이벤트, 타입스크립트에 대해 배웠는데, 프레임워크를 배우고 나서부터 DOM에 대한 내용을 많이 잊어버려 복습할 수 있어 의미 있었다. 하지만 내용이 방대해서 간략하게만 짚고 넘어가서 지난번에 공부하고 기록해 놓은 블로그 글을 다시 읽어보면서 부족한 부분을 채웠다. 또한, 간단한 문제를 풀었는데 생각보다 쉽지 않아서 충격이었다. 바닐라 자바스크립트를 연습해야 할 이유가 생겼다.
타입스크립트는 여러 프로젝트에서 사용했지만, 다시 공부하고 싶은 부분 중 하나였다. 특히 제네릭 사용이 많이 어려워서 그 부분에 집중해서 들었다. 또한, 강사님은 interface
대신 type
만 사용한다고 하신 부분이 인상깊었는데, 나도 프로젝트 때 마다 관련 주제로 논쟁을 했었는데 강사님의 주장과 탄탄한 근거를 들으니 설득됐다. 배운 내용을 잊어버리지 않도록 정리하려 한다.
querySelector
querySelector
메서드는 CSS 선택자와 동일한 방법으로 HTML 요소를 한 개만 찾을 때 사용
const h1El = document.querySelector('h1');
console.log(h1El); // <h1>가장 먼저 찾아지는 요소를 반환함
querySelectorAll
querySelectorAll
메서드는 CSS 선택자와 동일한 방법으로 여러 HTML 요소를 찾을 때 사용
const h1Els = document.querySelectorAll('body > h1');
console.log(h1Els);
HTML 요소의 내용을 바꾸는 방법
const h1El = document.querySelector('h1');
h1El.innerHTML = '<i>sucoding</i>';
h1El.innerText = '<i>sucoding</i>';
console.log(h1El);
CSS 스타일을 직접 설정할 수 있음
const h1El = document.querySelector('h1');
h1El.style.color = 'red';
h1El.style.fontSize = '130px';
console.log(h1El);
HTML 요소에 클래스를 추가하는 방법
const h1El = document.querySelector('h1');
h1El.classList.add('active'); // 기존 클래스 뒤에 추가
console.log(h1El);
HTML 요소에서 클래스를 제거하는 방법
const h1El = document.querySelector('h1');
h1El.classList.remove('active');
console.log(h1El);
클래스가 있으면 제거하고, 없으면 추가하는 방법
const h1El = document.querySelector('h1');
h1El.classList.toggle('done');
console.log(h1El);
input 요소에 입력된 값을 가져오는 방법
setTimeout(() => {
const inputEl = document.querySelector('input');
console.log(inputEl.value);
}, 3000);
this
가 가리키는 요소는?const buttonEl = document.querySelector('button');
buttonEl.addEventListener('click', function (ev) {
console.log(this); // 클릭된 button 요소
console.log(ev); // 이벤트 객체
console.log('click');
});
this
는 buttonEl
을 가리킴this
는 이벤트가 바인딩된 요소를 참조함this
는 다르게 동작함 this
가 상위 스코프의 this
를 유지함const buttonEl = document.querySelector('button');
buttonEl.addEventListener('click', (ev) => {
console.log(this); // 상위 스코프의 this
console.log(ev); // 이벤트 객체
console.log('click');
});
this
는 상위 스코프의 this
를 유지하므로, 전역 객체(window
)를 가리킬 수 있음타입스크립트는 자바스크립트에 타입 시스템을 추가하여 코드의 안정성과 가독성을 높이는 도구임
str
변수가 문자열 타입임을 추론함str2
변수는 const
로 선언 돼 변수의 타입을 문자열 리터럴 "hello"로 추론함const num: number = 10;
const arr: number[] = [1, 2, 3];
const arr2: [number, string, number] = [1, "A", 3]; // 튜플(tuple)
const arr3: (string | number)[] = [1, "A", 3];
const obj: {} = {};
const obj2: { name: string; age: number } = { name: "kim", age: 20 };
특정 값만 가질 수 있는 타입
let num: 10 | 20 = 10;
let str: "A" | "B" = "A";
let obj: { name: "kim" } = { name: "kim" };
const printName = (name: "kim") => {
console.log(name);
};
printName("kim");
printName(obj.name);
타입을 조작하는 연산자
OR 연산자(|
)를 사용하여 여러 타입 중 하나를 선택할 수 있음
const arr: (number | string)[] = [1, "A", 3];
AND 연산자(&
)를 사용하여 여러 타입을 조합할 수 있음
const obj: { name: string } & { age: number } = { name: "kim", age: 20 };
객체의 구조를 정의하는 방법
interface IUser {
name: string;
age: number;
}
const user: IUser = {
name: "John",
age: 30,
};
console.log(user.name); // "John"
console.log(user.age); // 30
interface IUser {
name: string;
age: number;
height?: number; // 옵셔널 프로퍼티
}
const user1: IUser = {
name: "John",
age: 30,
};
const user2: IUser = {
name: "Jane",
age: 25,
height: 170,
};
console.log(user1.height); // undefined
console.log(user2.height); // 170
readonly
키워드를 사용하여 읽기 전용 속성을 정의함interface IUser {
name: string;
readonly age: number; // 읽기 전용 속성
}
const user: IUser = {
name: "John",
age: 30,
};
user.name = "Jane"; // 가능
user.age = 25; // 오류 발생: age는 읽기 전용 속성입니다.
interface IPerson {
name: string;
age: number;
}
interface IEmployee extends IPerson {
employeeId: number;
}
const employee: IEmployee = {
name: "John",
age: 30,
employeeId: 12345,
};
console.log(employee.name); // "John"
console.log(employee.employeeId); // 12345
interface ICar {
brand: string;
}
interface ICar {
model: string;
}
const car: ICar = {
brand: "Toyota",
model: "Corolla",
};
console.log(car.brand); // "Toyota"
console.log(car.model); // "Corolla"
interface IStringArray {
[index: number]: string;
}
const myArray: IStringArray = ["Alice", "Bob", "Charlie"];
console.log(myArray[0]); // "Alice"
console.log(myArray[1]); // "Bob"
interface IDictionary {
[key: string]: string | number;
}
const dict: IDictionary = {
name: "John",
age: 30,
country: "USA",
};
console.log(dict.name); // "John"
console.log(dict.age); // 30
interface ISearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: ISearchFunc = (src, sub) => {
return src.includes(sub);
};
console.log(mySearch("Hello, world!", "world")); // true
console.log(mySearch("Hello, world!", "typescript")); // false
type
키워드를 사용하여 새로운 타입을 정의하는 방법
type TRainbowColor = "red" | "orange" | "yellow" | "green" | "blue" | "indigo" | "violet";
const phoneColor: TRainbowColor = "indigo";
const wrongColor: TRainbowColor = "pink"; // 오류 발생
TRainbowColor
타입은 무지개의 색상 중 하나의 값만을 가질 수 있는 타입을 정의함phoneColor
변수는 이 타입을 사용하여 정의되었으며, 올바른 색상 값만을 가질 수 있음type TUser = {
name: string;
};
type TJob = {
readonly title?: string;
};
type TUserAndJob = TUser & TJob;
const user1: TUserAndJob = {
name: "kim",
title: "developer",
};
const user2: TUser = {
name: "kim",
};
TUserAndJob
타입에서 처럼 TUser
와 TJob
타입을 결합(&
)하여 두 타입의 속성을 모두 가지는 객체를 정의할 수 있음type TUser = {
name: string;
age: number;
};
const user1: TUser = {
name: "kim",
age: 20,
};
function printUserName({ name }: TUser) {
console.log(name);
}
printUserName(user1); // "kim"
타입 별칭과 인터페이스는 유사하지만 몇 가지 중요한 차이점이 있음
인터페이스: 동일한 이름으로 여러 번 정의되면 자동으로 병합됨
interface Car {
brand: string;
}
interface Car {
model: string;
}
const myCar: Car = {
brand: "Toyota",
model: "Corolla",
};
console.log(myCar.brand); // "Toyota"
console.log(myCar.model); // "Corolla"
타입 별칭: 동일한 이름으로 여러 번 정의할 수 없음
type Car = {
brand: string;
};
// 다음 줄은 오류 발생
// type Car = {
// model: string;
// };
const myCar: Car = {
brand: "Toyota",
// model: "Corolla", // 오류 발생
};
interface IPerson {
name: string;
}
interface IEmployee extends IPerson {
readonly title?: string;
}
const employee: IEmployee = {
name: "John",
title: "developer",
};
&
연산자를 사용하여 여러 타입을 결합할 수 있음type TUser = {
name: string;
};
type TJob = {
readonly title?: string;
};
type TUserAndJob = TUser & TJob;
const user1: TUserAndJob = {
name: "kim",
title: "developer",
};
VS Code에서는 type
으로 정의한 타입은 툴팁으로 확인할 수 있지만, 인터페이스로 정의한 타입은 툴팁에서 바로 확인할 수 없는 경우가 있음
인터페이스:
타입 별칭:
제네릭은 타입 매개변수를 사용하여 여러 타입을 유연하게 처리할 수 있음
T
를 사용하지만, 다른 이름을 사용할 수도 있음const firstElements = <T>(elements: T[]): T => {
return elements[0];
};
console.log(firstElements<number>([1, 2, 3])); // 1
console.log(firstElements<string>(["a", "b", "c"])); // "a"
console.log(firstElements<boolean>([true, false])); // true
firstElements
함수는 배열의 첫 번째 요소를 반환함 <T>
는 타입 매개변수를 선언한 것임firstElements<number>([1, 2, 3])
와 같이 구체적인 타입을 전달하여 사용할 수 있음제네릭은 인터페이스와 클래스에서도 사용할 수 있음
// 예시 1
interface Container<T> {
value: T;
}
const stringContainer: Container<string> = { value: "Hello" };
const numberContainer: Container<number> = { value: 123 };
console.log(stringContainer.value); // "Hello"
console.log(numberContainer.value); // 123
Container
인터페이스는 제네릭 타입 T
를 가지며, value
속성은 T
타입임// 예시 2
type TCar<T> = {
name: string;
options: T;
};
const car1: TCar<string> = {
name: "sonata",
options: "auto",
};
const car2: TCar<string[]> = {
name: "sonata",
options: ["auto", "sunroof"],
};
class Box<T> {
contents: T;
constructor(contents: T) {
this.contents = contents;
}
getContents(): T {
return this.contents;
}
}
const stringBox = new Box<string>("Hello");
console.log(stringBox.getContents()); // "Hello"
const numberBox = new Box<number>(123);
console.log(numberBox.getContents()); // 123
Box
클래스는 제네릭 타입 T
를 가지며, contents
속성과 getContents
메서드는 T
타입임extends
키워드를 사용하여 제네릭 타입 매개변수에 제약을 두는 방식으로 구현됨const getLength = <T extends { length: number }>(item: T): number => {
return item.length;
};
console.log(getLength([1, 2, 3])); // 3
console.log(getLength("Hello")); // 5
console.log(getLength({ length: 10 })); // 10
// console.log(getLength(10)); // 오류 발생: 'number' 형식에는 'length' 속성이 없음
getLength
함수는 length
속성을 가진 객체만 받을 수 있도록 제약을 두었음T extends { length: number }
는 T
타입이 length
속성을 가져야 함을 명시함length
속성을 가진 객체는 사용할 수 있지만, 숫자와 같은 타입은 사용할 수 없음제네릭을 사용할 때, 다중 타입 매개변수를 정의할 수도 있음
function mapPair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const pair1 = mapPair<string, number>("age", 30);
const pair2 = mapPair<number, boolean>(1, true);
console.log(pair1); // ["age", 30]
console.log(pair2); // [1, true]
mapPair
함수는 두 개의 타입 매개변수 K
와 V
를 가지며, key
와 value
의 타입을 각각 K
와 V
로 지정함[K, V]
타입의 튜플임제네릭 인터페이스를 다른 인터페이스가 확장할 수 있음
interface Response<T> {
data: T;
status: number;
error?: string;
}
interface User {
name: string;
age: number;
}
const userResponse: Response<User> = {
data: { name: "John", age: 30 },
status: 200,
};
console.log(userResponse.data.name); // "John"
console.log(userResponse.status); // 200
Response
인터페이스는 제네릭 타입 T
를 가지며, data
속성의 타입을 T
로 지정함