TS | 리터럴, 유니온/교차타입, 클래스, 제네릭, 유틸리티 타입

지현·2023년 9월 23일
1

Javascript / TypeScript

목록 보기
16/16
post-thumbnail

리터럴, 유니온/교차 타입

리터럴타입

리터럴 타입은 정해진 값을 가진 타입이다.

const userName1 = 'Bob'; // 얘는 재할당 불가능하므로 무조건 'Bob'임
let userName2 : string | number = 'Tom';
userName2 = 3 // ㄴ> 이렇게 string이랑 number 둘 다로 설정 안해놓으면 초기 타입이 string이었기 때문에 string만 넣을 수 있음

type Job = 'police' | 'developer' | 'teacher'

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

const user : User = {
  name : 'Bob',
  // job : 'student',  < Job 타입에 지정된 값이 아니므로 에러뜸
  job : 'developer',
}

interface HighSchoolStudent {
  name : string,
  grade : 1 | 2 | 3
}

유니온 타입

| (or 조건 타입) , 동일한 속성의 타입을 다르게 해서 구분할 수 있는 것을 식별 가능한 유니온 타입이라고 한다.

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);
  //gift.start(); error : Property 'start' does not exist on type 'Mobile'  start() 는 Car에만 있어서
  if(gift.name==='car'){
    gift.start();
  }else{
    gift.call();
  }
}

교차 타입 (Intersection Types)

& (and 조건 타입) , 모든 속성을 다 기입하기 전까지는 에러 안사라진다. 교차타입은 여러개의 타입을 하나로 합쳐준다. 필요한 모든 기능을 가진 하나의 타입을 만들어주는 것이다.

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

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

const toyCar : Toy & Car = {
  name : '타요',
  start(){}.
  color : 'red',
  price : 1000,
}

클래스 (Class)

타입스크립트에서는 자바스크립트 es6의 클래스와 달리 멤버 변수를 constructor 전에 먼저 선언해줘야한다.

class Car {
 color: string
 constructor(color:string){
   this.color = color;
 }
  start(){
    console.log('start')
  }
}

const bmw = new Car('red')

먼저 선언하지 않는 방법은 readonly 사용하면됨

접근제한자(Access modifier)

es6 의 클래스는 접근제한자 제공하지 않음, 하지만 타입스크립트는 접근 제한자 제공한다. 타입스크립트의 접근제한자는 public, private,protected가 있다.

public

아무것도 안쓰면 public 이다

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

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

const z4 = new Bmw('black')

private

속성 이름 앞에 private를 쓰거나 #을 입력하면 private 속성으로 해당 클래스에서밖에 사용하지 못한다.

class Car {
 private name : string = 'car';
 //#name : string = 'car';
 color : string;
 constructor(readonly color:string){
   this.color = color;
 }
  start(){
    console.log('start')
  }
}

//위 코드처럼 Bmw 에서는 못쓴다

protected

protected도 public과 마찬가지로 자식클래스에서 접근이 가능하다. public과 다른 점은 자식클래스 내부에서는 참조할수있으나 클래스 인스턴스로는 참조할 수 없다.

class Car {
  protected name : string = 'car';
  color : string;
 constructor(readonly color:string){
   this.color = color;
 }
  start(){
    console.log('start')
  }
}

class Bmw extends Car {
  constructor(color:string){
    super(color);
  }
  showName(){
    console.log(super.name);
  }
}
const z4 = new Bmw('black')
console.log(z4.name) // error : property 'name' is protected and only accessible within class 'Car' and its subclassess
  • public : 자식클래스, 클래스 인스턴스에서 모두 접근 가능
  • protected : 자식클래스에서 접근 가능
  • private : 해당 클래스 내부에서만 접근 가능

static 프로퍼티

static 이용하면 정적 멤버 변수 만들 수 있다. 접근하려면 클래스이름.속성 으로 접근해야한다.

class Car {
  protected name : string = 'car';
  color : string;
  static wheels = 4;
 constructor(readonly color:string){
   this.color = color;
 }
  start(){
    console.log('start')
    console.log(Car.wheels); //이렇게 클래스명을 써줘야한다!
  }
}

추상클래스

추상클래스는 new 를 이용하여 객체 만들 수 없고 상속을 이용해서만 사용가능함. class 앞에 abstract 기입

abstract class Car {
  protected name : string = 'car';
  color : string;
  static wheels = 4;
 constructor(readonly color:string){
   this.color = color;
 }
  start(){
    console.log('start')
  }
  abstract doSomething(): void // 추상메서드는 반드시 상속 받은 곳에서 구체적인 구현을 해줘야함, 여기서는 이름 정도만 정한다고 생각하면 된다.
}

const car = new Car('red') // error

class Bmw extends Car{
  . . . 
  doSomething(){
    alert(3)
  }//이렇게 상속받은 곳에서 어떤 기능을 할건지 구현을 해줘야한다.
  
} // 이렇게만 가능

제네릭 (Generics)

제네릭을 이용하면 클래스나 함수, 인터페이스를 다양한 타입으로 재사용 할 수 있다. 선언할 때는 그냥 파라미터만 적어주고 사용할 때 파라미터의 구체적인 타입을 정해준다.

이렇게 일일이 타입을 다 적어주면 번거롭기도 하고 정해진 타입이 아닌 경우 재사용성이 떨어진다.

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:'Tims'}]
getSize(arr4) // 3

이 때 제네릭을 이용하면 편하게 재사용성을 높일 수 있다.

function getSize<T>(arr:T[]):number{
  return arr.length
}

//<T> (꼭 T가 아니어도 됨, 그냥 타입이라는 뜻인듯) 를 써주고 파라미터의 타입을 배열이라는 것만 설정한다. 그 후 아래처럼 사용할 때 원하는 타입 쓰면 된다.

const arr1 = [1,2,3]
getSize<number>(arr1) // 3

const arr2 = ['a','b','c']
getSize<string>(arr2) // 3
//사실 꼭 <string> 이렇게 안써줘도 받는 값의 타입을 알아서 인식해서 타입 지정해준다.

const arr3 = [true,false,true]
getSize<boolean>(arr3) // 3

const arr4 = [{},{},{name:'Tims'}]
getSize<object>(arr4) // 3

인터페이스에서 제네릭 사용

제네릭을 이용하면 하나의 인터페이스를 선언하고 각기 다른 모습의 객체들을 만들어줄 수 있다.

interface Moblie<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 : 1300,
  option : 'good'
}

//만약 option 의 형태가 정해져있다면 이렇게 미리 쓰는 것도 된다
const m1 : Mobile<{color:string;coupon:boolean}> = {
  name : 's21',
  price : 1000,
  option : {
    color : 'red',
    coupon: false,
  },
}

제네릭 다른 예제

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 : 'bmw',color:'red'}
const book : Book = {price:3000}

//어떤 T타입이 올건데 그 타입은 name이 string인 객체를 확장한 것이다
function showName<T extends {name : string}>(data:T):string{
  return data.name
}

showName(user)
showName(car) // error : name이 string이 아님
showName(book) // error : book 에는 name 없음

유틸리티 타입 (Utility Types)

keyof

keyof 키워드를 이용하면 key값들을 유니온 형태로 받을 수 있다.

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

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

const uk : UserKey = 'id' 

Partial

Partial<T>프로퍼티를 모두 optional로 바꿔준다.

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

//이렇게 인터페이스를 Partial로 묶어주면 User의 프로퍼티가 모두 optional이 된다
let admin : Partial<User> = {
  id:1,
  name:'Bob'
}

interface User{
  id? : number;
  name? : string;
  age? : number;
  gender? : 'm'|'f'
}
이렇게 되는 것과 마찬가지인것!

Required

Required<T>모든 프로퍼티를 필수로 바꿔준다

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

//이렇게 인터페이스를 Required로 묶어주면 User에서 optional 프로퍼티로 정해놨던 age까지 필수로 입력해야함
let admin : Required<User> = {
  id:1,
  name:'Bob',
  age : 30,
}

Readonly

Readonly<T> 프로퍼티를 읽기전용으로 바꾼다

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

//이렇게 인터페이스를 Readonly로 묶어주면 할당만 가능하고 값 수정은 불가능해진다
let admin : Readonly<User> = {
  id:1,
  name:'Bob',
  age : 30,
}

Record

Record<K,T>
K는 key , T는 type

const score : Record<'1'|'2'|'3'|'4','A'|'B'|'C'|'F'> = {
  1:'A',
  2:'C',
  3:'F',
  4:'B',
}

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

const score : Record<Grade,Score> = {
  1:'A',
  2:'C',
  3:'F',
  4:'B',
}
이렇게도 사용 가능
interface User{
  id : number;
  name : string;
  age : number;
}

// 유저 정보가 유효한지를 boolean으로 반환하는 함수
function isValid(user:User){
  const result : Record<keyof User,boolean> = {
    id : user.id >0,
    name : user.name !== "",
    age: user.age >0,
  }
  return result;
}

Pick

Pick<T,K> T타입에서 K프로퍼티만 골라서 사용한다

interface User{
  id : number;
  name : string;
  age : number;
}
  
// User에서 id와 name만 사용
const admin : Pick<User,'id'|'name'> = {
  id : 0;
  name : 'Bob';
}

Omit

Omit<T,K> T타입에서 K프로퍼티만 제외하고 사용한다

interface User{
  id : number;
  name : string;
  age : number;
  gender : 'M' | 'F'
}

// User에서 age와 gender만 빼고 사용  
const admin : Omit<User,'age'|'gender'> = {
  id : 0;
  name : 'Bob';
}

Exclude

Exclude<T1,T2> T1에서T2를 제외하고 사용하는 방식, Omit과 다른점은 Omit은 프로퍼티로 제외하는 것이고, Exclude는 타입으로 제외하는 방식

type T1 = string | number | boolean;
type T2 = Exclude<T1,number|string> // 이러면 boolean만 사용

NonNullable

NonNullable<Type> null을 제외한 타입 생성 null뿐만 아니라 undefined도 함께 제외시킨다.

type T1 = string | null | undefined | void;
type T2 = NonNullable<T1> // string과 void만 남는다.

0개의 댓글