! 이 글은 제가 너무 애청하는 유투브 "코딩앙마" 채널의 TypeScript 강좌를 정리한 글입니다.
코딩앙마님 유투브 강의 바로가기
Type Script 기초
유투브 코딩앙마님 강의 정리 by 웅
[1, 2강] 타입스크립트의 기본 타입
let age:number = 30;
let isAdult:boolean = true;
let a:number[] = [1,2,3];
let b:Array<number> = [1,2,3];
let week1:string[] = ['mon', 'tue', 'wed'];
let week2:Array<string> = ['mon', 'tue', 'wed'];
week1.push('ja')
- 리스트 타입은 위와 같이 string[ ] , Array 이렇게 두 가지 정의 방법이 있다.
튜플 : 리스트인데, 자리마다 들어가는 type이 달라지는 경우
let c:[string, number];
c = ['z', 11111];
let d:string = c[1].toLocaleString();
함수 타입 중 void 와 never
- void : 반환하는 값이 없는 함수에게 정의
- never : 에러를 반환하거나 무한 반복 같이 영원히 끝나지 않는 함수의 경우
function sample():void {
console.log('hi');
}
function sample2():never {
while(true) {
console.log('hi')
}
}
function sample3():never {
throw new Error('error');
}
enum : 원래 JS 에는 없다! 비슷한 type끼리 묶어준 객체, 수동으로 값을 주지 않으면 0부터 1씩 증가하면서 할당됨
enum Os {
window,
ios,
android
}
//수동으로 정의하면 해당 값을 가짐, 아래의 경우 android = 11이 들어갈거임
enum Os {
window=3,
ios=10,
android
}
//숫자가 매핑되어 있다는 것은 양방향이 가능하다는 것! 숫자만!
console.log(Os['android']);
console.log(Os[11]);
//문자는 양방향이 불가능 console.log(Os2['win'])하면 에러남!
enum Os2 {
Window = 'win',
Ios = 'ios',
Android = 'android',
}
//특정 값만 입력 가능
let myOs:Os2;
myOs = Os2.Window;
- enum이 숫자인 경우에는 양방향 호출이 가능하지만, 문자인 경우에는 불가
- enum은 주로 특정 값만 입력하도록 제약하고 싶고 그 특정값들이 공통점을 가질때 사용함
null, undefined 타입
//null, undefined도 타입이 있음
let e:null = null;
let f:undefined = undefined
[3강] 인터페이스
let user:object = {}
//이렇게 선언도 하지만 object 에는 property를 정의할 수가 없음
//이럴 때는 인터페이스를 선언!
type Score = 'A' | 'B' | 'C' | 'F';
interface User {
name: string,
age: number,
gender?: string;
readonly birthYear: number;
[grade: number]: Score;
//이 경우는 number 를 키로 하고 score를 발류로 하는 값이 여러개 가능하다는 뜻
}
let user:User = {
name: 'xx',
age: 12,
birthYear: 2000,
1: 'A',
2: 'B',
3: 'F',
}
- [grade: number]: Score 는 number 타입을 key로 하고 Score 값 중 하나를 value로 하는 데이터가 존재할 수도 있고 아닐 수도 있음을 의미
함수 정의도 가능 ( 변수로 함수 정의하는 경우에만 사용 가능 )
interface Add {
// 인자 값 : 리턴 값
(num1: number, num2: number): number;
}
const add: Add = (x, y) => {
return x+y;
}
interface isAdult {
(age: number): boolean;
}
const isAdult:isAdult = (age) => {
return age>19;
}
자바처럼 인터페이스로 Class도 정의 가능 ( Implement라는 키워드 사용 )
interface Car {
color: string;
wheels: number;
start(): void;
}
interface Benz extends Car {
door: number;
stop(): void;
}
class Bmw implements Car {
color;
wheels = 4;
constructor(color: string) {
this.color = color;
}
start() {
console.log('start');
}
}
class myBenz implements Benz {
door = 4;
color;
wheels = 4;
constructor(color: string) {
this.color = color;
}
start() {
console.log('start');
}
stop () {
console.log('benz go...');
}
}
const myCar = new Bmw('green');
console.log(myCar.color);
myCar.start();
Implement 의 사용법
interface Car {
color: string,
wheels: number;
start(): void;
}
interface Brand {
logo: string,
marketing(): void;
}
//이렇게 두 개 상속도 가능
interface Benz extends Car, Brand{
name?: string;
}
const myBenz:Benz = {
color: 'red',
wheels: 4,
start: () => {
console.log('start');
},
logo: 'benz c class',
marketing: () => {
console.log("i'm benz");
},
}
class MyBenz implements Benz {
color;
wheels;
logo;
constructor(color: string, wheels:number, logo:string) {
this.color = color;
this.wheels = wheels;
this.logo = logo;
}
start () {
console.log('start');
}
marketing () {
console.log('marketing');
}
}
[4강] 함수
function hello(name?: string) {
return `Hello, ${name || "world"}`;
}
function hello2(name = "world") {
return `Hello, ${name}`;
}
- 이렇게 두 개는 같은 의미를 갖는다. name이 string 으로 들어오거나, undefined 이거나
주의해야할 점
function hello(name?: string, age: number) {
return `Hello, ${name}, ${age} `;
}
- 위 사례처럼 Optional 한 값을 앞에 두면 안됨!
나머지 매개변수
function add(...nums: number[]) {
return nums.reduce((prev, curr) => prev + curr, 0);
}
add(1,2,3);
add(1,2,3,4,5,5,6,7,7,8,);
함수에서 this
일단 Arrow Function 은 this 라는 값을 못 가진 단다...
interface User {
name: string;
}
const Sam:User = {
name:'Sam'
}
function showName (this:User, age: number, gender: 'm'|'w') {
console.log(this.name, age, gender);
}
const a = showName.bind(Sam);
a(30, 'm');
- this는 매개변수 가장 앞에 선언하고, this 로 선언될 녀석의 타입을 정의해주면 된다.
오버로드
- 함수에 들어오는 매개변수의 Type 에 따라 반환하는 값이 달라질 때, 사용되는 문법
interface User {
name: string;
age: number;
}
function join (name: string, age: string): string;
function join (name: string, age: number): User;
function join (name: string, age: number | string): User | string {
if (typeof age === "number") {
return {
name,
age,
};
} else {
return "나이는 숫자로 입력해주세요.";
}
}
const sam: User = join("Sam", 30);
const jane: string = join("Jane", "30");
- 역시나 화살표 함수로 하니까 const 라서 안된다...
[5강] 리터럴, 유니온/교차 타입
const userName = "Sam";
let userName2 = "Tom";
type Job = "police" | "developer" | "teacher"
interface User {
name : string;
job: Job;
}
const user: User = {
name : "Bob",
job: "police",
}
interface HighSchoolStudent {
name: number | string;
grade: 1 | 2 | 3;
}
- let은 선언된 녀석의 type을 type으로 지정하지만, const 는 선언된 녀석 자체를 type으로 지정한다. ex) 위 예제에서 userName 의 type은 "Sam", userName2의 type은 "string"
- type으로 내가 원하는 요소들만 타입으로 지정할 수 있다.
- number | string 에서 | 는 union이라고 하는데 union type에 대해 더 알아보자
Union 타입
interface Car {
name: "car";
color: string;
start(): void;
}
interface Mobile {
name: "mobile";
color: string;
call(): void;
}
function getGift(gift: Car | Mobile) {
gift.start(); //에러발생
}
이렇게 하면 gift가 Mobile 일 경우 start() 라는 메소드가 없기 때문에 에러가 발생한다.
interface Car {
name: "car";
color: string;
start(): void;
}
interface Mobile {
name: "mobile";
color: string;
call(): void;
}
function getGift(gift: Car | Mobile) {
if(gift.name === "car") {
gift.start();
} else {
gift.call();
}
}
- 그래서 이렇게 식별할 수 있는 값을 기준으로 타입을 나눠서 각자에 맞는 메소드를 적용한다.
- 만약 type이 많아진다면 switch 를 사용하는 것이 좋음. 이렇게 구별이 가능한 type을 식별가능한 union type이라고 한다.
Intersection Types ( 교차 타입 )
interface Car {
name: string;
start(): void;
}
interface Toy {
name: string;
color: string;
price: number;
}
// 교차 타입
const toyCar: Toy & Car = {
name: "타요",
start() {},
color: "blue",
price: 1000,
}
- 교차 타입은 교차되는 인터페이스들의 모든 변수 & 메소드를 정의 해줘야 함
[6강] 클래스
class Car {
color: string; // <= 얘가 없으면 오류 난다. TS 에서는 변수를 미리 정의해줘야함
constructor(color: string) {
this.color = color
}
start() {
console.log("start");
}
}
const bmw = new Car("red");
class Car {
constructor(public color: string) {
this.color = color
}
}
or
class Car {
constructor(readonly color: string) {
this.color = color
}
}
public
이나readonly
와 같은 접근 제한자를 이용하면 에러가 사라진다. 이를 알아보자
Access modifier - 접근제한자 ( JS에는 없지만, TS에는 있음 )
- public ( default ) - 해당 클래스 + 자식 클래스 + 클래스 인스턴스에서도 접근 가능
- private ( # ) - 해당 클래스 내부에서만 사용 가능
- protected - 해당 클래스랑 + 자식 클래스에서만 사용 가능
- readonly - 읽을 수만 있고 수정은 불가능함! 바꾸려면 constructor 에서 this.name = name 으로 바꿔야 함
- static - 정적 멤버 변수나 메소드 앞에 붙여줌. 접근하기 위해서는
class명.변수(or 메소드)이름
이렇게 접근해야 함
Abstract Class 추상 클래스
abstract class Car {
color: string;
constructor(color: string) {
this.color = color;
}
start() {
console.log("start");
}
abstract doSomething(): void;
}
const car = new Car("red"); // 에러 발생
- 추상 클래스는 new 로 인스턴스를 만들 수 없음
- 다른 Class 에 상속해서 써야만 함
- 상속할 때, abstract 가 붙어있는 함수나 변수는 무조건 만들어줘야 함
- 즉, abstract 키워드를 통해 내부 기능은 달라도 같은 골격? 의 클래스를 강요할 수 있음
abstract class Car {
color: string;
constructor(color: string) {
this.color = color;
}
start() {
console.log("start");
}
abstract doSomething(): void;
}
class Bmw extends Car {
constructor(color: string) {
super(color);
}
doSomething() {
console.log(3);
}
}
const z4 = new Bmw("red");
[7강] 제네릭 ( Generic )
function getSize(arr: number[] | string[] | boolean[] | object[]): number {
return arr.length;
}
const arr1= [1,2,3];
getSize(arr1);
const arr2 = ['1', '2', '3'];
getSize(arr2);
const arr3 = [true, false, true]
getSize(arr3);
const arr4 = [{}, {name: 'woong'}];
getSize(arr4);
- 위 상황을 보자, 매개변수에 들어올 수 있는 값이 다 제각각 이기 때문에 이를 모두 union으로 선언한 상황이다 (혹은 함수 오버로드 사용) 이럴 경우, 종류가 계속 많아지면 그걸 다 입력할 수 없으니... 함수를 사용할 때, 사용할 타입을 알려줄 수 있다면 얼마나 좋을까? 대신 리스트 라는 것은 알려줄테니! 약간 이런 생각을 하게 된다. 그게 바로 제네릭이다!
//<여기 오는 애>를 타입 파라미터라고 하는데 보통 T라고 쓴다. 다른애들을 써도 상관없다.
function getSize<T>(arr: T[]): number {
return arr.length;
}
const arr1= [1,2,3];
getSize<number>(arr1);
const arr2 = ['1', '2', '3'];
getSize<string>(arr2);
const arr3 = [true, false, true]
getSize(arr3);
const arr4 = [{}, {name: 'woong'}];
getSize(arr4);
- 아래 두 개처럼, 사실 제네릭을 사용할 때 명시해주지 않아도 TS가 알아서 가장 좁은 범위로 Type을 설정한다. 뭐, 그래도 가능하면 명시해 주는 것이, 내 생각대로 코딩을 하게 해서, 버그를 최소화할 수 있는 방법이겠지?
예제
// option에 뭐가 올 지 모르는 경우!
interface Mobile<T> {
name: string;
price: number;
option: T;
}
const m1: Mobile<{ color: string; coupon: boolean }> = {
name: "s21",
price: 1000,
option: {
color: "red",
coupon: false,
}
}
const m2: Mobile<string> = {
name: "s20",
price: 900,
option: "good",
}
제네릭을 이용하여 매개변수가 객체인 경우, 객체에 꼭 들어가야 하는 값들을 특정할 수도 있다.
interface User {
name: string;
age: number;
}
interface Car {
name: boolean;
color: string;
}
interface Book {
price: number;
}
const user: User= { name: "a", age: 10 };
const car: Car = { name: true, color: "red" };
const book: Book = { price: 3000 };
function showName<T extends {name: string}>(data: T): string {
return data.name
}
showName(user);
showName(car); //에러 발생 name: boolean 이기 때문에
showName(book); //에러 발생 name이 없기 때문에
[8강] 유틸리티 타입
keyof
interface User {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender'
const uk:UserKey = "age";
- keyof 를 사용하면 인터페이스에 정의 된 타입들을 type으로 가져올 수 있다.
Partial ( 프로퍼티를 부분적으로 정의할 수 있도록 해줌, 일일이 '?' 를 안달아도 되겠지!? )
interface User {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
let admin: Partial<User> = {
id: 1,
name: "Bob",
}
Required 는 반대로 '?'로 준 프로퍼티가 있어도 모두 필수로 바꿔준다.
interface User {
id: number;
name: string;
age?: number;
}
let admin: Required<User> = {
id: 1,
name: "Bob",
age: 20 // age? 이지만, Required 때문에 age를 안적으면 에러가 남
}
Readonly : 같은 타입이라도 수정이 불가능!
interface User {
id: number;
name: string;
age?: number;
}
let admin: Readonly<User> = {
id: 1,
name: "Bob",
age: 20
}
admin.id = 4; //에러 발생
Record<K, T> : K는 Key, T는 Type
interface Score {
"1": "A" | "B" | "C" | "D";
"2": "A" | "B" | "C" | "D";
"3": "A" | "B" | "C" | "D";
"4": "A" | "B" | "C" | "D";
}
const score:Score = {
1: 'A',
2: 'B',
3: 'C',
4: 'D',
}
- 이렇게 타이핑하기 번거로운 코드를 아래와 같이 바꿔준다.
type Grade = '1' | '2' | '3' | '4';
type Score = 'A' | 'B' | 'C' | 'D';
const score: Record<Grade, Score> = {
1: 'A',
2: 'B',
3: 'C',
4: 'D'
};
- 훨씬 깔끔해졌다!
레코드 예제 하나 더!
interface User {
id: number;
name: string;
age: number;
}
function isValid(user: User):Record<keyof User, boolean> {
const result: Record<keyof User, boolean> = {
id: user.id > 0,
name: user.name !== "",
age: user.age > 0,
}
return result;
}
Pick<T,K> : Interface 에서 Key로 명시한 애들만 뽑아와서 쓸 수 있음
interface User {
id: number;
name: string;
age: number;
gender: "M" | "W";
}
const admin: Pick<User, "id" | "name"> = {
id: 0,
name: "Bob",
}
Omit<T, K> : Pick이랑 반대로 Key로 명시한 애들만 제외하고 쓸 수 있음
interface User {
id: number;
name: string;
age: number;
gender: "M" | "W";
}
const admin: Omit<User, "age" | "gender"> = {
id: 0,
name: "Bob",
}
Exclude<T1, T2> : Omit과 비슷한데, Omit은 인터페이스에서 K에 해당하는 프로퍼티를 제거하는 거였다면, Exclude는 T1 타입에서 T2 타입과 겹치는 애들을 제거하는 것!
type T1 = string | number | boolean;
type T2 = Exclude<T1, number | string>;
// T2에는 number 와 string 이 제거 되고 boolean 만 남게 됨
NonNullable : null 과 undefined 가 있으면 걔네를 제외한 타입을 생성한다.
type T1 = string | null | undefined | void;
type T2 = NonNullable<T1>;
// T2에는 string 과 void만 남음
'프론트엔드' 카테고리의 다른 글
Docker-Compose Nginx, Certbot 컨테이너로 HTTPS 웹서버 설정하기 (0) | 2022.01.23 |
---|---|
자바스크립트로 브라우저 라우팅 구현하기 (0) | 2022.01.02 |
TypeScript를 활용한, FLUX 패턴 적용하기 (0) | 2022.01.02 |
[부캠위키] React 에서 Custom Select 구현하기 (0) | 2021.12.10 |
[부캠위키] 반응형 UI 구현하기 (0) | 2021.12.10 |