TYPESCRIPT

[Typescript]Typescript 정리

[Typescript]Typescript 정리

타입스크립트(Typescript)를 사용하는 이유

우리가 사용하는 브라우저는 타입스크립트를 이해하지 못한다. 타입스크립트를 브라우저에서 실행하기 위해서는 브라우저가 이해할 수 있는 자바스크립트로 코드를 변경하는 트랜스파일(transpile) 과정을 거친다. 그럼에도 불구하고 굳이 자바스크립트가 아닌 타입스크립트로 코드를 작성하는 이유는 무엇일까?

타입스크립트는 자바스크립트의 문제점을 보안하기 위해 만들어진 언어이다. 타입스크립트의 가장 큰 특징인 정적타입은 개발자들의 실수를 미연에 방지할 수 있다는 장점이 있다. 기본의 자바스크립트와는 달리 타입스크립트는 타입을 명시해야 한다. 명시하지 않으면 자체적으로 에러 표시를 해준다.

transpile VS compile

컴파일(compile)

자바와 같은 컴파일 언어에서 사람들이 이해할 수 있는 .java 파일을 기계(OS)가 이해할 수 있는 .class 파일로 변환하는 과정

트랜스파일(transpile)

타입스크립트를 브라우저에서 실행하기 위해 브라우저가 이해할 수 있는 자바스크립트로 변환하는 과정

타입(Type)의 종류

기본 타입

let firstname:string = 'dahye'
let age:number = 30;
let isAdult:boolean = true;
let array1:number[] = [1,2,3];
let array2:Array = [1,2,3];

let week1:string[] = ['mon', 'tue', 'wed'];
let week2:Array = ['mon', 'tue', 'wed'];

// week1.push(3) -> 다른 타입 추가하려고 하면 에러
타입 명시
let car1:string = 'bow';
재선언
car1 = 'benz'
// car1 = 1 -> 다른 타입으로 재선언하면 에러 

 

튜플(Tuple)

한 가지 타입의 배열이 아닌 여러 타입의 배열을 만들 수 있게 만들어준다.

let b:[string, number];

b = ['z', 1];
// b = [1, 'z']; -> 타입 일치하지 않으면 에러

b[0].toLowerCase();
// b[1].toLowerCase(); -> 숫자를 소문자로 바꿀 수 없으므로 에러

 

void

함수에서 아무거도 반환하지 않을 때 주로 사용

function sayHello():void{
    console.log('hello');
}

 

never

에러를 반환하거나 영원히 끝나지 않는 함수를 반환할 때 사용

function showErr():never{
    throw new Error();
}

function isfLoop():never{
    while(true){
        // do something...
    }
}

 

enum

비슷한 것끼리 묶어두는 것

아무 값을 넣지 않으면 0부터 차례대로 값을 넣어준다.
enum OsString {
    Window, // 0
    Ios, // 1
    Android // 2
}
첫번째 값만 넣어주면 그 다음 값부터 차례대로 전값 + 1을 넣어준다.
enum OsString {
    Window = 3,
    Ios = 10,
    Android // 11
}
문자 타입의 경우 양방향 매핑이 가능하다
enum OsString {
    Window = 3,
    Ios = 10,
    Android
}
console.log(OsString['Window']) // 3
console.log(OsString[3]) // Window
enum 타입의 변수를 선언할 수 있다.

특정 값만 입력 받고 싶을 때 혹은 그 값들에 공통점이 있을 때 사용하면 좋다.

let myOs:OsString;

 

null, undefined

let x:null = null;
let y:undefined = undefined;

 

Interface(인터페이스)

인터페이스 사용 용도

let user:object;

 user = {
     name : 'dahye',
     age : 30
 }

 console.log(user.name);  // name에서 에러남

프로퍼티를 정의해서 객체를 표현하고자 할 때는 인터페이스를 사용하는 것이 좋다.

인터페이스 정의 방법

interface User {
    name : string;
    age : number;
    gender? : string; // 옵션(값을 넣어도 되고 안 넣어도 된다.)
    readonly birthYear : number; // 읽기 전용
    [grade:number] : String; // 문자열 index 서명
}

let user : User = {
    name : 'dahye',
    age : 30,
    birthYear : 2000,
    1 : 'A',
    2 : 'B'
}

user.age = 10; // 값의 재할당 가능
user.gender = "male"; // 옵션은 나중에 추가할 수 있다.
// user.birthYear = 1990; -> readonly가 적용되어 있어 값을 변경 할 수 없다.

console.log(user.age); // 접근가능

 

문자열 리터럴 타입
type Score = 'A' | 'B' | 'C' | 'F'; // 문자열 리터럴 타입
interface User {
    name : string;
    age : number;
    gender? : string;
    readonly birthYear : number;
    [grade:number] : Score; // 해당 값 이외의 값이 들어오면 에러
}

let user : User = {
    name : 'dahye',
    age : 30,
    birthYear : 2000,
    1 : 'A',
    2 : 'B'
}

 

인터페이스로 함수 정의

interface 인터페이스명 { (인자:인자타입): 반환타입;}
interface Add {
    (num1:number, num2:number): number;
}

const add : Add = function(x, y){
    return x + y;
}

add(10, 20);
interface IsAdult {
    (age:number):boolean;
}

const a:IsAdult = (age) => {
    return age>19;
}

a(33); // true

 

인터페이스로 클래스 정의

interface를 implement
interface Car{
    color:string;
    wheels:number;
    start():void;
}

class Bmw implements Car {
    color = "red";
    wheels = 4;
    start(){
        console.log('go...'); 
    }
}
interface 확장하기(extends)
interface Car{
    color:string;      
    wheels:number;
    start():void;
}

interface Benz extends Car {
    door: number;
    stop(): void;  
}

const benz : Benz = {
    door : 5,
    stop(){
        console.log('stop');
    },
    color:'black',
    wheels:4,
    start(){
        console.log('goooooooo');
    }
}

확장받은 interface의 프로퍼티는 무조건 정의해야한다.

두가지 interface 확장하기
interface Car{
    color:string;      
    wheels:number;
    start():void;
}

interface Toy{
    name:string;
}

interface ToyCar extends Car, Toy{
    price :number;
}

interface Benz extends Car {
    door: number;
    stop(): void;  
}

 

 

함수(function)

함수 타입 정의

function add(num1 :number, num2 : number):number{
    return num1 + num2
}

function isAdult(age:number):boolean{
    return age > 19;
}
선택적 매개 변수
function hello(name?:string){
    return `Hello, ${name || "world"}`;
}

const result1 = hello();
const result2 = hello('dahye');
// const result3 = hello(123); -> 타입 불일치시 에러

함수의 매개 변수도 옵션으로 지정할 수 있다.

매개변수 디폴트 값
function hello2(name = "world"){
return `Hello, ${name}`;
}

매개 변수에 디폴트 값을 사용할 수도 있다.

매개변수 순서 변경
function hello(name:string, age?:number):string{
    if(age!==undefined){
        return `Hello, ${name}. You are ${age}.`;
    }else{
        return `Hello, ${name}`;
    }
}

console.log(hello("Sam"));
console.log(hello("Sam", 30));
function hello(age:number | undefined, name:string):string{
    if(age!==undefined){
        return `Hello, ${name}. You are ${age}.`;
    }else{
        return `Hello, ${name}`;
    }
}

console.log(hello(30, "Sam"));
console.log(hello(undefined, "Sam"));

undefined로 명시적으로 표시

나머지 매개 변수
function add(...nums:number[]){
    return nums.reduce((result,num) => result + num, 0);
}

add(1,2,3); // 6
add(1,2,3,4,5,6,7,8,9,10); // 55 

나머지 매개 변수의 타입은 배열로 넣어준다.

this의 타입 선언
interface User {
    name : string;
}
  
const Sam: User = {name:'Sam'}

function showName(this:User){
    console.log(this.name)
}

const a = showName.bind(Sam);
a();
interface User {
    name : string;
}
  
const Sam: User = {name:'Sam'}

function showName(this:User, age:number, gender:'m'|'f'){
    console.log(this.name, age, gender)
}

const a = showName.bind(Sam);
a(30, 'f');

첫번째 매개변수 자리에 this의 타입을 선언할 수 있다.

함수의 오버로드(overload)

interface User {
    name : string;
    age : number;
}


function join(name:string, age:number):User; // number 타입 오버로드
function join(name:string, age:string):User; // 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:User = join("Jane","30"); 

전달 받은 매개변수의 개수나 타입에 따라 다른 동작을 하게 만들 수 있다.

리터털(Literal) 타입

값이 변화하지 않는 타입

const userName1 = "Bob"; // 변하지 않는 값 -> 문자형 리터럴 타입
let userName2:string|number = "Tom"; // 변하는 값

userName2 = 3; 

type Job = "police" | "developer" | "teacher";

interface User {
    name : string;
    job : Job;
}

const user: User = {
    name : "Bob",
    job : "developer"
}

interface HighShoolStudent{
    name:string;
    grade:1|2|3; // 해당 값만 사용가능(변하지 않는 값) -> 숫자형 리터럴 타입
}

유니온(Union) 타입

다양한 값을 선택할 수 있게 하는 타입

interface Car {
    name:"car";
    color:string;
    start():void;
}

interface Mobile{
    name:"mobile";
    color:string;
    call():void;
}

function getGift(gift:Car|Mobile){
    console.log(gift.color);
    // 식별가능한 유니온 타입
    if(gift.name === "car"){
        gift.start();
    }else{
        gift.call();
    }
}

교차(Intersection) 타입

모든 값을 가져오게 하는 타입

interface Car {
    name:string
    start():void;
}

interface Toy{
    name:string
    color:string;
    price:number;
}

const toyCar: Toy & Car = {
    name : "타요",
    start(){},
    color:"blue",
    price:1000
}

클래스(Class)

class Car{
    color:string;
    constructor(color:string){
        this.color = color;      
    }
    start(){
        console.log("start");
    }
}
const bmw = new Car("red");

맴버 변수를 미리 선언해아한다.

class Car{
    // color:string;
    constructor(public color:string){
        this.color = color;      
    }
    start(){
        console.log("start");
    }
}
const bmw = new Car("red");
class Car{
    // color:string;
    constructor(readonly color:string){
        this.color = color;      
    }
    start(){
        console.log("start");
    }
}
const bmw = new Car("red");

맴버 변수를 미리 선언하지 않는 방법으로 매개변수에 public 혹은 readonly를 붙인다.

접근제한자

접근제한자는 변수명 앞에 아무거도 적지 않으면 디폴트가 public이다.

public

자식 클래스, 클래스 인스턴스에서 모두 접근 가능하다.

class Car2 {
    name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name);
    }
}
class Car2 {
    public name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name);
    }
}
private

private 혹은 #을 적으면 해당 클래스에서만 접근할 수 있고 자식 클래스에서는 접근할 수 없다.

class Car2 {
    private name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name); // err: private 변수를 자식 클래스에서 사용할 수 없다.
    }
}
class Car2 {
    #name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name); // err: private 변수를 자식 클래스에서 사용할 수 없다.
    }
}
protected

자식 컴포넌트에서 접근할 수 있으나 클래스 인스턴스로는 접근할 수 없다.

class Car2 {
    protected name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name); // 여기서는 에러가 생기지 않는다.
    }
}

const z4 = new Bmw("black");
console.log(z4.name); // 하지만 여기서는 에러가 생긴다.
readonly

readonly를 사용하면 public과 같이 접근 가능하나 데이테 변경을 막을 수 있다.

class Car2 {
    readonly name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string){
        super(color);
    }
    showName(){
        console.log(super.name);
    }
}

const z4 = new Bmw("black");
console.log(z4.name); 
z4.name = 'zzzz4'; // readonly를 사용하면 데이터의 변경을 막을 수 있다.
class Car2 {
    readonly name:string = "car";
    color:string;
    constructor(color:string, name:string){
        this.color = color;
        this.name = name;
    }
    start(){
        console.log("start");
    }
}

class Bmw extends Car2{
    constructor(color:string, name:string){
        super(color, name);
    }
    showName(){
        console.log(super.name);
    }
}

const z4 = new Bmw("black", "dahye");
console.log(z4.name); 

readonly인 상태에서 변수 값을 변경하기 위해서는 constructor 내부에서 선언해 주어야 한다.

static

static으로 선언된 정적 멤버변수나 메서드는 this로 가져 올 수 없다.

class Car2 {
    readonly name:string = "car";
    color:string;
    static wheels = 4;
    constructor(color:string, name:string){
        this.color = color;
        this.name = name;
    }
    start(){
        console.log("start");
        console.log(this.wheels); // err
    }
}

class Bmw extends Car2{
    constructor(color:string, name:string){
        super(color, name);
    }
    showName(){
        console.log(super.name);
    }
}

const z4 = new Bmw("black", "dahye");
console.log(z4.wheels); // err
class Car2 {
    readonly name:string = "car";
    color:string;
    static wheels = 4;
    constructor(color:string, name:string){
        this.color = color;
        this.name = name;
    }
    start(){
        console.log("start");
        console.log(Car2.wheels); // 클래스 명으로 접근할 수 있다.
    }
}

class Bmw extends Car2{
    constructor(color:string, name:string){
        super(color, name);
    }
    showName(){
        console.log(super.name);
    }
}

const z4 = new Bmw("black", "dahye");
console.log(Car2.wheels); // 클래스 명으로 접근할 수 있다.

 

추상 클래스

클래스 앞에 abstract 선언하여 추상 클래스를 정의할 수 있다.

abstract class Car3 {
    readonly name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
}

const caar = new Car3("red"); // err : 추상 클래스는 new하여 객체를 만들 수 없다. 

// 추상클래스는 상속해서 사용할 수 있다.
class Benz extends Car3{
    constructor(color:string){
        super(color);
    }
}

const v4 = new Benz("black");
abstract class Car3 {
    readonly name:string = "car";
    color:string;
    constructor(color:string){
        this.color = color;
    }
    start(){
        console.log("start");
    }
    abstract doSomething():void; // 추상 클래스 내부의 추상 메서드는 반드시 상속 받은 곳에서 구체적인 구현을 해야한다.
}

class Benz extends Car3{
    constructor(color:string){
        super(color);
    }
    doSomething(){
        alert('구체적인 기능 구현');
    }
}

const v4 = new Benz("black");

 

제너릭 타입(Genric)

한가지 타입으로 정의 내릴 수 없을 때 어떤 타입의 데이터도 유연하게 받을 수 있는 것이 바로 제네릭 타입니다.

제네릭 타입을 사용하지 않은 경우
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);

const arr3 = [false, true, true];
getSize(arr3);

const arr4 = [{},{},{name:"Dahye"}];
getSize(arr4);
제네릭 타입을 사용한 경우
function getSize<T>(arr:T[]):number {
    return arr.length;
}

const arr1 = [1,2,3];
getSize<number>(arr1); // 특정 타이븡로 제한 하고 싶을 때 <>안에 타입을 선언


const arr2 = ["a","b","c"];
getSize(arr2);

const arr3 = [false, true, true];
getSize(arr3);

const arr4 = [{},{},{name:"Tim"}];
getSize(arr4);
// 하나의 객체만을 선언하여 각기 다른 타입으로 선언하여 사용할 수 있다.
interface Mobile<T> {
    name: string;
    price: number;
    option: T;
}

const m1: Mobile<Object> = {
    name: "s21",
    price: 10000,
    option: {
        color: "red",
        coupon: false
    }
}

// Option 객체의 모습이 정해져 있다면
const m1: Mobile<{color:string; coupon:boolean}> = {
    name: "s21",
    price: 10000,
    option: {
        color: "red",
        coupon: false
    }
}

const m2: Mobile<string> = {
    name: "s20",
    price: 9000,
    option: "good"
}
제네릭 확장
interface User {
    name:string;
    age: number;
}

interface Car {
    name:string;
    color:string;
}

interface Book {
    pirce:number;
}

const user: User = {name:"Dahye", age:10};
const car: Car = {name:"bmw", color:"red"};
const book: Book = {pirce:3000};

function showName<T extends {name:string}>(data:T):string{
    return data.name;
}

showName(user);
showName(car);
showName(book); // name을 extends로 확장하였기 때문에 무조건 name이 있어야한다.

유틸리티(Utility) 타입

keyof
interface User {
    id: number;
    name:string;
    age:number;
    gender:"m"|"f";
}

type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender'

const test1:UserKey = "age"; 
const test2:UserKey = "birthday";  // User 인터페이스에 없는 값을 넣으면 에러가 난다.

선언된 특정한 값만 선택할 수 있게 만들어준다.

Partial
// User 인터페이스에 선언된 값을 모두 적어주지 않아도된다.
let admin: Partial<User> = {
    id: 1, 
    name: "Choi",
    job: "teacher" // User 인터페이스에 선언되지 않은 값을 새로 적어줄 수 없다.
}

선언된 특정한 값을 모두 사용하지 않아도 된다.

Required
let admin: Required<User> = {
    id: 1,  
    name: "Choi",
    age: 30,
    gender: 'f'

필수로 값을 입력 받게 만든다.

Readonly
interface User {
    id: number;
    name: string;
    age?: number;
}

let admin: Readonly<User> = {
    id: 1,
    name : "Choi"
}

admin.id = 4; 

처음에 할당만 가능하고 변경이 불가능하다.

Record

Record<Key,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 = {
    1: 'A',
    2: 'C  ',
    3: 'B',
    4: 'D'
}

이렇게 사용하는 것 보다

type Grade = '1' | '2' | '3' | '4'
type Score = 'A' | 'B' | 'C' | 'D'

const score: Record<Grade,Score> = {
    1: 'A',
    2: 'C',
    3: 'B',
    4: 'D'
}

이렇게 Record를 사용한 코드가 훨씬 확장성이 좋다.

interface User {
    id: number;
    name: string;
    age: number;
}

function isValid(user:User){
    const result: Record<keyof User, boolean> = {
        id: user.id > 0,
        name: user.name !== '', 
        age: user.age > 0
    }
    return result;
}
Pick
interface User {
    id: number;
    name: string;
    age: number;
    gender: 'm' | 'f'
}

const admin: Pick<User, 'id' | 'name' > = {
    id: 0,
    name: 'choi'

user에서 제외할 것을 선언하여 필요한 것만 사용할 수 있다.

Omit
interface User {
    id: number;
    name: string;
    age: number;
    gender: 'm' | 'f'
}

const admin: Omit<User, 'age' | 'gender' > = {
    id: 0,
    name: 'choi'
}

user에서 제외할 것을 선언하여 필요한 것만 사용할 수 있다.

Exclude

Exclude<T1, T2> 형식으로 선언해서 사용하는데 T1에서 T2와 겹치는 타입을 제외한다.

type T1 = string | number;
type T2 = Exclude<T1, number>;

T2는 string만 남게 된다.

NonNullable

NonNullable<T> 형식으로 선언해서 사용한다. Null, undefined를 제외한 타입을 생성한다.

type T1 = string | null | undefined | void;
type T2 = NonNullable<T1>;

Null, undefined를 제외한 string, void만 남는다.

최신글