관리 메뉴

꿈꾸는 개발자

4장 객체 본문

Learning Typescript

4장 객체

rickysin 2023. 3. 1. 20:15

4.1 객체 타입


  • {…}과 같이 객체 리터럴을 생성하면 TS는 해당 속성을 기반으로 새로운 객체 타입 또는 타입 형태를 고려한다. ⇒ 객체 값과 동일한 속성명 및 원시 타입을 갖는다.
const hoho={
    born:1948,
    name: "Mary",
}
hoho['born'] //number 타입
hoho.name //string 타입

hoho.end //에러 발생 (does not exist)
  • 객체 타입은 TS가 JS코드를 이해하는 방법에 대한 핵심 개념이다. null과 undefined을 제외한 모든 값은 해당 값에 대한 실제 타입의 멤버 집합을 가짐(string, number 등 집합에 속함) ⇒ TS는 모든 타입의 값을 확인하기 위해 객체 타입을 이해해야 함

4.1.1 객체 타입 선언


  • 명시적으로 객체 타입 선언하기 ⇒ 추가로, 객체의 형태를 설명하는 방법이 필요함
  • 객체 타입은 필드 값 대신 타입을 사용해 설명
let hoho:{
    born:number,
    name: string,
}

hoho={
    born:1934,
    name: "mary",
}

hoho= "sldslf"; //string is not assignable to type {.....}
  • born/name은 이전과 동일한 타입이지만, 위에서는 타입 명시를 통해 나타내줬음

4.1.2 별칭 객체 타입


  • 객체 타입을 계속 작성하는 것보다 ⇒ 타입 별칭(call signature?)을 사용하는 것이 일반적
type ho={
    born:number;
    name:string;
};

let hoho:ho; //call signature으로 명시를 해줌

hoho={
    born:1945,
    name:"mary",
}

hoho="maymmay"; //string is not assignable to type {.....}
  • 주의: 대부분의 TS 프로젝트에선 객체 타입을 설명할 때 인터페이스 keyword를 사용하는 것을 더 선호함 (인터페이스와 별칭 타입을 구별하는 것이 중요하다) ⇒ 그럼에도 별칭 타입을 배우는 이유: TS가 객체 리터럴을 해석하는 방법을 이해하는 것이 중요하기 때문

4.2 구조적 타이핑


TS의 타입 시스템은 구조적으로 타입화(strucally typed)돼 있음 ⇒ 타입을 충족하는 모든 값을 해당 타입의 값으로 사용 가능!

type FirstName={
    first:string;
}

type LastName={
    last:string;
}
const hasTwo={
    first:"hoho",
    last:"gogo",
}
//hasTwo는 string 타입의 first를 포함함.
let withFirstName:FirstName=hasTwo;

//hasTwo는 string 타입의 last를 포함함.
let withLastName:LastName=hasTwo;
  • hasTwo는 타입 명시를 하진 않았지만, 두 개의 별칭 객체 타입을 다 가지고 있음 ⇒ 위처럼 사용이 가능함!
    • 의문점: 그럼 withFristName은 “hoho”만 출력되는 것??
  • 구조적 타이핑: 정적 시스템이 타입을 검사하는 경우
  • 덕 타이핑: 런타임에서 사용될 때까지 객체 타입을 검사하지 않는 것을 말함
    • JS: 덕 파이핑/ TS: 구조적 타이핑

4.2.1 사용 검사


  • 객체 타입에 필요한 멤버가 없다면 TS는 타입 오류를 발생시킴
type FirstAndLastName={
    first:string; 
    last:string;
}

const hasBoth:FirstAndLastName={
    first:"abcde",
    last:"asdfsd", 
}; 

//FirstAndLastName는 first와 last 둘다 포함해야함
const hasOnlyOne: FirstAndLastName={
    first:"asdfasdf",
}
  • 일치하지 않는 타입도 허용하지 않는다
type Time={
    first:string; 
}
//타입 불일치에 따른 에러 발생함
const errortype:Time={
    first:59, 
}

4.2.2 초과 속성 검사


type Time={
    born:number;
    name:string;
}

const correctOne:Time={
    born:59,
    name:"Mary",
}
//타입보다 많기 대문에 에러가 발생함
const errorEx:Time={
    activity:"walkring",
    born:59,
    name:"Mary",
}
  • 객체 타입으로 선언된 것보다 더 많을 경우 에러 발생
const errorEx={
    activity:"walkring",
    born:59,
    name:"Mary",
}

const extraPropertyButOk:Time=errorEx;
  • 위와 같이 객체 타입으로 선언된 위치에서 생성되는 객체 리터럴이 아니면 초과 검사는 우회된다. ⇒ 배열, 클래스 필드, 함수 매개변수 모두 초과 속성이 일어난다.

4.2.3 중첩된 객체 타입


 

type Poem={
    author:{
        firstName:string; 
        lastName:string;
    }
    name:string;
};

const poemMatch:Poem={
    author:{
        firstName:"siva",
        lastName:"plath",
    },
    name:"lady gaga",
};

//당연히 일치하지 않는 name이 들어있으니 에러 발생
const poemMismatch:Poem={
    author:{
        name:"siva path",
    }, 
    name:"tuple"
}

type Author={
    firstName:string; 
    lastName:string;
}
type Poem={
    author:Author
    name:string;
};
  • 위와 같이 객체 내 중첩되는 객체까지도 타입 객체로 축약할 수 있다.

4.2.4 선택적 속성


  • 타입 속성 애너테이션: 앞에 ?를 추가하면 선택적 속성임을 나타낼 수 있다.
type Book={
    author?:string;//있어도 되고 없어도 됨
    pages:number;

}

const ok:Book={
    author:"Hoin",
    pages:80,
}
//객체 타입과 다르게 pages를 명시하지 않아서 에러가 발생함!
const missing:Book={
    author:"hoin", 
}
  • ?를 사용해 선택적으로 선언된 속성은 필수가 아님!
  • xxx | undefined의 형태의 경우 undefined으로 선언을 하더라도 무조건 존재해야 함을 의미 ?과 혼동하지 말 것!

4.3 객체 타입 유니언


  • 하나 이상의 서로 다른 객체 타입이 될 수 있는 타입을 설명할 수 있어야 함

4.3.1 유추된 객체 타입 유니언


  • 초깃값으로 선정될 수 있는 여러 객체 타입이 주어지면 TS는 해당 타입을 객체 타입 유니언으로 간주한다.
const poem=Math.random()>0.5  ?{name:"hello world", page:7}:{name:"theres", rhymes:true};

poem.name; //string 
poem.page; // number | undefiend
poem.rhymes; //boolean | undefined

4.3.2 명시된 객체 타입 유니언:


  • 객체 타입의 조합을 명시하면 객체 타입을 더 명확히 정의 가능, 특히 유니언 타입에 존재하는 속성에만 접근이 가능
type PoemWithPages={
    name:string;
    pages:number;
}

type PoemWithRhymes={
    name:string;
    rhymes:boolean;
}

type Poem= PoemWithPages | PoemWithRhymes;

const peom:Poem =Math.random()>0.5 ?{name:"hoin", pages:7}:{name:"otrhes",rhymes:true};

peom.name //둘 다 존재하기 때문에 에러 발생X 
peom.pages //없을 수도 있기 때문에 에러가 발생한다.
  • 잠재적으로 존재하지 않을 수도 있느 객체 멤버에 대한 접근 제한은 코드 안정성을 높인다. (객체 유니언에서 내로잉이 필요하다)

4.3.3 객체 타입 내로잉


if("pages" in peom){ //poem pages가 타입 가드의 역할을 함!
    peom.pages;
}else{
    peom.rhymes;
}
  • TS에선 if(peom.pages)을 허용하지 않음, 존재 여부가 확실하지 않는 속성 접근 자체를 제한기 때문!

4.3.4 판별된 유니언


  • 객체의 속성이 객체의 형태를 나타내도록 하는 것 ⇒ 객체 속성이 가리키는 속성이 판별값 (TS는 코드에서 판별 속성을 활용해 타입 내로잉을 함)
type PoemWithPages={
    name:string;
    pages:number;
    type: 'pages';
}

type PoemWithRhymes={
    name:string;
    rhymes:boolean;
    type:'rhymes';
}

type Poem= PoemWithPages | PoemWithRhymes;

const peom:Poem =Math.random()>0.5 ?{name:"hoin", pages:7, type:"pages"}:{name:"otrhes",rhymes:true,type:"rhymes"};

if(peom.type==="pages"){
    console.log("its got pages: ");
}else{
    console.log("it")
}

peom.type; //타입: 'pages' | 'rhymes'; 둘 중 하나!

4.4 교차 타입


**&**교차 타입을 사용해 여러 타입을 동시에 나타낼 수 있음 ⇒ 기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성함

type Artwork={
    genre:string; 
    name:string;
}

type Writing={
    pages:number;
    name:string;
}
type WrittenArt=Artwork & Writing; //결합된 새로운 타입을 생성!

{
genre:string;
name:string;
pages:number;
}
type ShortPoem={author:string}&(|{kigo:string; type:"haha"}|{meter:number; type:"vava"};);

//author은 항상 포함됨
const morning:ShortPoem={
    author:"hoho",
    kigo:"asdf",
    type:"haha",
}

4.4.1 교차 타입의 위험성


교차 타입을 사용할 때 가능한 코드를 간결하게 유지하라!

긴 할당 가능성 오류

복잡한 교차 타입 ⇒ 할당 가능성 오류 메세지를 읽기 어렵게 됨

  • 따라서 분할을 사용하면 훨씬 간결함
type ShortPoemBse={author:string};
type Haha= ShortPoemBse &{kigo:string; type:"haha"}
type Vava=ShortPoemBse&{meter:number; type:"vava"};
type ShortPoem=Haha | Vava;

//author은 항상 포함됨
const morning:ShortPoem={
    author:"hoho",
    kigo:"asdf",
    type:"haha",
}
  • 앞선 코드를 위와 같이 축약할 수 있음

never: 원시 타입의 결합은 시도하지 말 것!

 


Reference:

조시 골드버그, 러닝 타입스크립트, 고승원 옮김, 2023