Cute Bow Tie Hearts Blinking Pink Pointer

프론트엔드/Typescript

[TS] 타입스크립트 기본 + 실전편 종결

청포도 에이드 2024. 12. 28. 16:55
728x90

 

 

전에 근무하던 회사에서는 자바스크립트를 사용했다보니

타입스크립트는 안 쓴지 시간이 조금 지났다

그래서 이번 기회에

예전에 했던 타스 + 리액트 프로젝트를 되짚어보며 찬찬히 정리해보려고 함!

 

 

문자열

let name: string = "청포도에이드";

 

숫자

let today: number = 28;

 

배열

let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1 , 2, 3];
let arr3: Array<string> = ["청포도", "에이드"];
let arr4: [string, number] = ["writer", 27];

 

객체

let grape: object = { name: "청포도에이드", age: 27 };

 

 

object로 타입 지정 후 안에 요소는 마음 대로 추가할 수 있음
그러나 위의 경우 자유도가 너무 높아지기 때문에 ts를 쓰는 의미가 없기에
아래처럼 사용하는 것을 추천

 

let info: { name: string; age: number } = {
  name: "ade",
  age: 27
};

 

불리언

let check: boolean = true;

 

 

함수

a, b라는 숫자를 받아 합을 구하는 함수를 작성한다면?

function 함수(a: number, b: number): number {
  return a + b;
}

 

위처럼 적어준다. 이 때 소괄호 안에 매개변수의 타입도 지정해주어야하며, 소괄호와 중괄호 사이엔

return 값의 타입을 지정해주어야한다.

 

그렇다면 return이 없는 함수는 어떡함?

 

function 함수(a: number, b: number): void {
  console.log("return 없음");
}

 

void를 적어주면 된다 ^_^

 

그럼 매개변수를 선택적으로 받아야하는 상황에선 어떡함?

특정 매개변수가 필요없을 수도 있잖아요

 

function optionalFun(a: string, b?: string): string {
	if(b){
    	return a+b
    }else{
    	return a
  	}
    
}

 

해당 매개변수 뒤에 ? 를 적어주면 된다.

그럼 b는 있어도 그만 없어도 그만

 

 

미리 타입 지정하기

 

type vs interface

 

두 가지 모두 ts의 타입을 지정할 때 사용하는 타입 선언자(?)이다

 

뭘 쓰든 크게 차이는 없는 듯하고 메서드만 조금 다르다

 

진행하는 프로젝트 코딩 컨벤션에 맞춰 개발하면 됨

 

 

interface

 

User라는 타입을 만들어보자.

참고로 type 변수의 맨 앞 글자는 대문자로 적는 게 관례이다.

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

,(콤마)아니고 ;(세미콜론)임 주의!!

 

 

이제 얘를 활용한다면?

const writer: User = { name: "청포도", age: 27 }

 

타입을 지정하지 않고 썼다면

let writer: { name: string; age: number } = {
  name: "청포도",
  age: 27
};

 

이런 식으로 적었어야 하는데

interface로 타입을 따로 정의 해두어서 가독성이 좋아졌다

그리고 프로젝트 크기가 커질 수록 같은 타입을 쓸 일이 굉장히 많아지는데

이렇게 한 번 타입을 지정해두면 반복적인 코드를 상당히 많이 줄일 수 있다.

 

근데 함수에서도 쓸 수 있음?

ㅇㅇ

 

function getUser(user: User) {
  console.log(user);
}

getUser({ name: "청포도", age: 27 });

 

 

ES6 애로우 함수 (화살표 함수)에서도 쓸 수 있음?

ㅇㅇ

interface Plus {
  (x: number, y: number): number;
  // 순서대로 매개변수 타입, 매개변수2 타입, return값 타입
}

let plusFun: Plus = (a, b) => a + b;

plusFun(1,4)

 

배열에 쓰기

interface StringArr {
  [index: number]: string;
}

let arr: StringArr = ["하", "이", "하", "이"];

 

객체에 쓰기

interface Info {
  [key: string]: string;
}

const info: Info {
  name: "청포도",
  hobby: "running"
}

 

객체 안에 속성이 몇 개 들어가냐에 관계 없이

대괄호 하나로 퉁칠 수 있어서 편함!

 

이미 지정해 높은 interface에 요소 몇 개만 추가한 또다른 interface를 쓰고 싶으면 어떻게 함? 하나 더 만듦?

ㄴㄴ 그럼 반복되는 코드가 많아져서 개발자들이 싫어함.

 

extends를 쓰면 됨ㅇㅇ

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

interface New extends Old {
  job: string;
}
// 대충 Old의 정신을 받아... New로 발전시키겠다는 뜻

const info: New = {
  name: "청포도",
  age: 27,
  job: "developer"
};

 

이렇게 쓰면 날먹 가능

 

 

interface는 이제 끝

 

type으로 가보자

 

 

Type

 

필자는 개인적으로 type이 더 좋음. 이유는 개날먹 메서드가 존재하기 때문이다. 그건 후반에 설명하겠음

 

타입 선언하기

직관적이어서 좋은 듯

type something = string | number;
//그냥 js let, const, var랑 똑같이 사용 가능

const str1: something = "hello";
const str2: something = 123132;

 

 

 

타입을 확장하는 여러가지 방법

1. interface + interface = type

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

interface Old2 {
  name: string;
  job: string;
}

type New = Old1 & Old2;

let info: New = {
  name: "청포도",
  age: 27,
  job: "dev"
};

 

2. type + type = type

type Info1 = { name: string; age: number };
type Info2 = { age: number; hobby: string };

type Info3 = Info1 & Info2

 

3. type + 직접 지정 = type

type Info1 = { name: string; age: number };
type Info2 = { age: number; hobby: string };

type Info3 = Info1 & {
	hobby:string
   }

 

이렇게 하면 interface에서 extends로 확장하던 걸

& 딸깍 하나로 날먹할 수 있다.

 

 

그리고 대망의 type의 개날먹 매우 효율적인 메서드를 알아보자!

 

 

type Info = {
    name: string;
    age: number;
    hobby: string;
    address:string;
  };

 

이런 type이 있다 쳐보자...

근데 아... 나는 address없는 타입 하나 만들고 싶은데...

반복 코드 적긴 싫은데... 할 때!!!

 

type InfoWithoutAddress = Omit<Info, "address">

 

이렇게 쓰면 address만 뺀 Info 타입이 짜잔하고 생긴다

 

위의 경우는 안 쓸 거 같지만 생각보다 많이 쓰인다.

리액트를 쓰다보면 props로 함수부터 변수... 뭐 이것저것 많이 내려보내주는데

이 때 굳이 자식 컴포넌트에서 쓰지 않는 정보는 보내줄 필요가 없다

근데 그러면 자식 컴포넌트에서 쓰지 않는 정보가 빠진 새로운 type을 또 새로 정의해줘야한다.

개발을 하다보면

타입을만들었다가뺐다가없앴다가다시만들었다가버전1만들었다가2만들었다가......

하는 일이 많이 생기기 때문에 여러가지로 유용한 메서드이다

 

 

사실 아래처럼 사용해도 된다. ES6 옵셔널 체이닝 쓸 때처럼...

물음표 하나 붙이면 된다. address는 존재할 수도 있고 없을 수도 있다는 뜻이다.

type Info = {
    name: string;
    age: number;
    hobby: string;
    address?:string;
  };

하지만 Omit을 쓰는 게 typescript 의 취지에 더 맞아보이고 위험도 역시 낮다.

 

 

 

아니 근데 타입 형식 하나만 빼는 거 말고 하나의 형식만 가져오는 건 없음?

없었으면 글 쓰지도 않았다

 

type Info = {
    name: string;
    age: number;
    hobby: string;
    address:string;
  };

 

여기서 name 속성만 가져와서 새로운 타입을 지정해보겠음

 

type OnlyName = Pick<Info,"name">

 

이게 끝.... ;;;

 

사실상 이것때문에 글 썼다고 해도 과언이 아님

 

 

그럼 목적을 이루었으니

이제 남은 것들을 차차 정리해보겠습니다.

 

Class로 타입 선언

class Info {
  // constructor 위에 선언
  private name: string;
  public age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

 

constructor 위에 변수 선언해주고

constructor 안에서 this << 적어주는 것 주의

 

 

아 근데 다 알겠는데.. 들어올 변수의 타입을 확정할 수 없으면 어떡함? API data가 매번 다른데?

그것 또한 방법이 있다

 

 

제네릭을 활용하자

 

<T>와 같이 타입을 선언한다. 알파벳은 대부분 T를 사용한다.

type Response<T> = {
    data:T[],
    totalPage:number
    page:number,
  };

 

예시 코드를 이렇게 작성한 이유가 있다

현업에선 대부분 백엔드 개발자로부터 api를 받았을 때 위와 같은 형식인데

data type이 다 다르기 때문에 매번 형식을 지정해주기엔 무리가 있다.

하지만 제네릭을 활용해주면 Response타입 하나 지정해두고 계속 우려먹으면서 쓸 수 있다.

 

함수에서 쓰려면?

function 뭐가들어올지모르는함수<T>(text: T): T {
  console.log(text);
  return text;
}

뭐가들어올지모르는함수<string>('Hello World!');

 

그러니까 이 방법은 !! 딱 사용할 때에 맞춰서 type을 즉시 선언해주고 사용할 수 있는 방법이다!

 

interface로도 가능??

ㅇㅇ 가능

interface Info<T> {
  name: T;
  job: string;
}

const grape: Info<string> = { name: '청포도', job : developer };

이런 식으로 사용하면 된다~~

 

함수에서 쓰는 방법

function printText<T>(txt: T[]): T[] {
    console.log(txt.length);
    return txt;
}

printText<string>(['welcome', 'to', 'the', 'show']);

 

제네릭 타입 제약조건 

 

 

interface TextType {
    length: number;
}

function countTextLen<T extends TextType>(text: T): T {
    console.log(text.length);
    return text;
}

countTextLen("welcome to the show!"); // 20
countTextLen({ length: 123 }); // 123

 

 

T extends TextType은 제네릭 타입의 제한 조건을 의미함

T는 반드시 TextType을 상속하거나 구현하는 타입이어야 함.

즉, T 타입은 반드시 length 속성을 가져야 하며, 그 값은 숫자여야 함.

 

 

가능한 다른 예제

 

countTextLen([1, 2, 3, 4]);
// 출력: 4
// 반환: [1, 2, 3, 4]


const obj = { length: 10, value: "example" };
countTextLen(obj);
// 출력: 10
// 반환: { length: 10, value: "example" }

 

length 속성을 가지고 있는 string, array, object 모두 가능하다.

 

제약 조건에 맞지 않는 경우

countTextLen(123);
// 오류: Type 'number' does not satisfy the constraint 'TextType'.


const invalidObj = { length: "not a number" }; // 오류
countTextLen(invalidObj);
// 오류: Type 'string' is not assignable to type 'number'.

 

 

keyof

interface Category {
    music: number;
    movie: number;
    recital: number;
}
å
function countType<T extends keyof Category>(category: T): T {
    return category;
}

// 'music', 'movie', 'recital'만 인자로 사용 가능
countType('price');

Category 에 있는 key 값만 인자로 사용 가능하도록 제한하는 방식이다.

 

 

 

다음 편은 리액트 + 타입스크립트로 돌아오겠음

728x90

'프론트엔드 > Typescript' 카테고리의 다른 글

[TS + REACT/REACT-NATIVE] 사용법  (1) 2024.12.30
[Typescript] 타입스크립트 interface  (0) 2022.06.09
[Typescript] 기본 설정, 세팅  (0) 2022.06.09