코딩앙마 TypeScript 강좌 를 듣고 요약한 내용입니다
TS는 JS에서 알아차리지 못하는 오류를 사전에 알아채고 경고해준다
JS (동적언어)
런타임에 타입이 결정되고 그때 오류를 발견한다
TS (정적언어)
컴파일 타임에 타입이 결정되고 그때 오류를 발견한다
(코드를 실행하기 전에 오류를 미리 알아볼 수 있다)
ex) 문자열로 이루어진 배열 → string[]
or Array<string>
ex) 객체형태로 이루어진 배열 → { }[ ]
or Array<Object>
배열과 같은 형태이지만, 인덱스 별로 타입이 다른 경우
let test: [string, number, boolean];
test = ['hi', 3, true];
void
: 함수에서 아무것도 반환하는 값이 없을 때 사용
never
: 함수에서 항상 에러를 반환하거나, 영원히 끝나지 않는 함수의 타입으로 사용
특정한 값만 입력할 수 있도록 강제하고 싶고, 그 값들이 무언가 공통점이 있을 때 주로 사용한다
비슷한 값끼리 묶어줬다고 생각하면 된다
enum Os {
Window,
Ios,
Android,
};
Window = 0
Ios = 1
Android = 2
값을 지정해주지 않으면 자동으로 1씩 증가하며 값이 대입된다
숫자 입력시 양방향 매핑, 문자열 입력시 단방향 매핑이 이루어진다
프로퍼티를 정해서 객체로 표현하고자 할 때 사용한다
구현하지 않고 타입만 나타낸다
type Score = "A" | "B" | "C" | "D";
interface User{
name: string;
age: number;
gender?: string;
readonly birthYear: number;
[grade: number]: Score; // value에 해당하는 값을 Score 타입으로 제한
};
let user : User = {
name : 'xx',
age : 20,
birthYear : 2000,
1 : "A",
2 : "D",
};
?
→ 선택적 매개변수, 말그대로 선택적으로 사용할 수 있다
readonly
→ 읽기전용 속성, 생성 이외에는 수정 불가능
[] : type
→ 문자열 인덱스 서명
[grade : number] : Score
→ number형태의 값이 key, Score형태의 값이 value
interface Add{
(num1: number, num2: number): number;
};
const add: Add = function(x, y){
return x + y;
};
add(10, 30) //40
interface IsAdult{
(age: number): boolean;
};
const a: IsAdult = (age) => {
return age > 19;
};
a(23); //true
interface Car{
color: string;
wheels: number;
start(): void;
};
interface Benz extends Car{
door: number;
stop: void;
};
// Benz 타입은 Car의 속성을 상속받기 때문에 Car와 Benz의 모든 속성을 필요로 한다
const benz: Benz = {
color : 'blue',
wheels : 4,
start(){
console.log("출발");
},
door : 4,
stop(){
console.log("정지");
},
};
// 클래스인 Bmw는 Car의 속성을 상속받는다
class Bmw implements Car{
color;
wheels = 4;
constructor(c: string){
this.color = c;
};
start(){
console.log("출발");
};
};
const newCar = new Bmw('green');
console.log(newCar); // Bmw {"wheels" : 4, "color" : "green"}
b.start(); // "출발"
extends
→ interface에서 interface를 상속받음
implements
→ Class에서 interface를 상속받음
functon add(num1: number, num2: number): void {
console.log(num1 + num2); //콘솔이므로 반환하는 값 X
};
functon isAdult(age: number): boolean {
return age > 19; //반환하는 값은 boolean형
};
// 매개변수 작성순서는 '필수매개변수 → 선택적매개변수'
functon hello(age: number, name?: string): string {
return `Hello, ${name || "world"}. You are ${age}`;
};
const result1 = hello(30); //Hello, world. You are 30
const result2 = hello(20, "JAN"); //Hello, JAN. You are 20
// 수정
// name을 선택적 매개변수로 설정하지 않고, defalut 값을 지정할 수도 있다
functon hello(age: number, name = "world"): string {
return `Hello, ${name}. You are ${age}`;
};
this 사용하기
interface User{
name: string;
};
const Sam: User = {name : 'Sam'};
function showName(this: User){
console.log(this.name);
};
const a = showName.bind(Sam); // bind를 이용하여 Sam 객체로 this를 대체
a();
함수 오버로드
동일한 함수이지만, 매개변수의 갯수나 타입에 따라 다른 방식으로 동작하게 한다
interface User{
name: string;
age: number;
};
// 오버로드
function join(name: string, age: number): User;
function join(name: string, age: string): string;
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");
TS에서의 const
와 let
을 예제로 살펴보자
const user1 = "Jan1";
let user2 = "Jan2";
user2 = true; // Error!!
const
로 선언한 변수는 정해진 해당 값만을 가질 수 있다 (=문자열 리터럴 타입)
반면에, let
으로 선언한 변수는 타입 추론이 일어나 string 타입을 가지게 된다
이때 user2
에 값을 재할당 할 경우에는 string 타입만 가능하다
동일한 속성의 타입을 다르게하여 구분하는 역할
or
의 개념으로 코드에서는 |
로 사용한다
// 유니온타입
interface Car{
name: "car";
color: string;
start(): void;
};
interface Phone{
name: "phone";
color: string;
call(): void;
};
function getGift(gift: Car | Mobile){ // gift의 타입을 둘 중 하나로 정의
console.log(gift.color);
gift.start(); //Error! (Car인지 먼저 확인해줘야 함)
if(gift.name === "car"){
gift.start();
} else {
gift.call();
};
};
여러개의 타입을 하나로 합쳐주는 역할
and
의 개념으로 코드에서는 &
로 사용한다
// 교차타입
interface Car{
name: string;
start(): void;
};
interface Toy{
name: string;
color: string;
price: number;
};
const toyCar: Toy & Car = { // toyCar의 타입을 Toy + Car로 정의
name: "벤츠",
start(){},
color: "black",
price: 5000
};
public
①해당클래스 + ②자식클래스 + ③클래스인스턴스 모두 접근 가능
프로퍼티 앞에 아무것도 작성하지 않는다면 자동으로 public
protected
①해당클래스 + ②자식클래스 에서 접근 가능
private
①해당클래스 내부에서만 접근 가능
'private 변수이름' 대신에 '#변수이름' 으로 사용 가능
멤버변수를 작성하는 대신 접근제한자를 작성할 수도 있다
아래 예시를 접근제한자로 변경해보자
////////// 변경 전 //////////
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){ // 멤버변수를 삭제하고 접근제한자(public) 작성
this.color = color;
};
start(){
console.log("start!");
};
};
const bmw = new Car("red");
readonly
가 선언된 프로퍼티는 선언 시 or 생성자 내부에서만 값을 할당할 수 있다
그 외에는 오직 읽기만 가능하다
class Car{
readonly color: string;
constructor(color: string){ // 멤버변수를 삭제하고 접근제한자(public) 작성
this.color = color;
};
};
const bmw = new Car("red");
bmw.color = "blue"; // Error! (color 프로퍼티는 읽기전용이므로 수정이 불가능)
static
으로 선언된 프로퍼티는 this가 아닌 클래스명을 작성해줘야한다
class Car{
readonly color: string;
static wheels = 4;
constructor(color: string){
this.color = color;
Car.wheels = Car.wheels; // this.wheels가 아니라 Car.wheels
};
};
console.log(Car.wheels);
클래스명 앞에 abstract
키워드를 추가
추상클래스는 객체를 만들 수 없다
오직 상속을 통해서만 사용이 가능하다
추상클래스 내부에 추상메소드를 작성하는 경우에는 형태와 타입만 지정해주고
추상클래스를 상속받은 클래스에서 구체적인 구현을 해줘야한다
abstract class Car{
color: string;
constructor(color: string){
this.color = color;
};
abstract doSomething():void; // 추상메소드 : 구체적인 구현X, 형태와 타입만 지정
};
const car = new Car("red"); // Error! (추상클래스는 객체 생성 불가)
class Bmw extends Car{
constructor(color: string){
super(color);
};
doSomething(){
console.log("무언가 일 하는 중");
};
};
const bmw = new Bmw("red");
bmw.doSomething(); // "무언가 일 하는 중"
function getSize(arr: number[] | string[] | boolean[] | object[]): number{
return arr.length;
};
const arr1 = [1, 2, 3];
getSize(arr1); // 3
const arr2 = ["a", "b", "c"];
getSize(arr2); // 3
const arr3 = [true, false, true];
getSize(arr3); // 3
const arr4 = [{name:"phone"}, {color:"purple"}, {price:3000}];
getSize(arr4); // 3
위 예시에서 getSize
함수의 매개변수에 여러가지 타입이 기재되어 있다
이런식으로 전부 기재하지 않아도 되는 방법이 있는데 바로 제네릭이다
제네릭은 꺽쇠 <> 내부에 타입파라미터를 적어주면 되는데, 일반적으로는 <T>
를 사용한다
<T>
라는 제네릭은 어떠한 타입을 전달받아서 함수 내부에서 사용할 수 있도록 해준다
<T>
가 string이면 T는 string타입, number이면 T는 number타입이 되는 것이다
// 제네릭으로 수정한 코드
function getSize<T>(arr:T[]): number{
return arr.length;
};
const arr1 = [1, 2, 3];
getSize<number>(arr1); // <T> = <number>, T : number
const arr2 = ["a", "b", "c"];
getSize<string>(arr2); // <T> = <string>, T : string
const arr3 = [true, false, true];
getSize<boolean>(arr3); // <T> = <boolean>, T : boolean
const arr4 = [{name:"phone"}, {color:"purple"}, {price:3000}];
getSize<object>(arr4); // <T> = <object>, T : object
interface에서의 제네릭
하나의 interface이지만 제네릭을 적용함으로써 원하는대로 여러 객체를 생성할 수 있다 (재사용성↑)
interface Mobile<T>{
name: string;
price: number;
option: T;
};
const m1: Mobile<object> = {
name: "s21",
price: 1000,
option: {
color: "red",
coupon: false,
},
};
const m2: Mobile<string> = {
name: "s20",
price: 900,
option: "good",
};
해당 interface의 키값을 유니온 형태로 받을 수 있다
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
};
type Userkey = keyof User; // "id" | "name" | "age" | "gender"
프로퍼티를 모두 옵션으로 변경시켜준다 → 원하는 프로퍼티만 사용가능
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
};
// 모든 프로퍼티가 옵션으로 변경된다면?
// interface User{
// id?: number;
// name?: string;
// age?: number;
// gender?: "m" | "f";
// };
let partialAdmin: Partial<User> = {
id: 1,
gender: "f",
};
모든 프로퍼티를 필수로 변경시켜준다
interface User{
id: number;
name?: string;
age?: number;
};
// 모든 프로퍼티가 필수로 변경된다면?
// interface User{
// id: number;
// name: string;
// age: number;
// };
let requiredAdmin: Required<User> = {
id: 1,
name: "Bob",
age: 20,
};
모든 프로퍼티를 읽기전용으로 변경시켜준다
interface User{
id: number;
name?: string;
age?: number;
};
// 모든 프로퍼티가 필수로 변경된다면?
// interface User{
// id: number;
// name: string;
// age: number;
// };
let readonlyAdmin: Readonly<User> = {
id: 1,
};
readonlyAdmin.id = 3; // Error! (readonly 속성으로 수정 불가능)
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",
};
이 코드를 Record를 사용하여 수정하면
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",
};
T = type, K = key
T타입에서 K프로퍼티만 골라서 사용한다
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
};
const pickAdmin: Pick<User, "id" | "name"> = { // id, name 프로퍼티만 사용
id: 0,
name: "Bob",
};
T = type, K = key
T타입에서 K프로퍼티만 제외하고 사용한다 (프로퍼티를 제외)
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
};
const omitAdmin: Omit<User, "id" | "name"> = { // id, name 프로퍼티만 제외
age: 20,
gender: "m",
};
T1타입에서 T2타입을 제외하고 사용한다 (타입을 제외)
type T1 = string | number;
type T2 = Exclude<T1, number>; // string | number 에서 number를 제외 => T2는 string타입
Type에서 null과 undefined를 제외시킨다
type T1 = string | null | undefined | void;
type T2 = NonNullabel<T1>; // T1에서 null과 undefined 제외 => T2는 string | void 타입