Learning TypeScript 7장

hyena_lee·2023년 3월 21일
0

TypeScript

목록 보기
7/7
post-thumbnail

타입별칭 vs. 인터페이스

  • debut: number와 namae: string을 가진 객체를 타입 별칭으로 구현하는 간락한 구문
type bts = {
  debut: number;
  name: string;
};
  • 인터페이스로 구현한 동일한 구문
interface bts {
  debut: number;
  name: string;
}

=> 위의 두 구분은 거의 같다.

tip) 세미콜론(;)을 선호하는 타입스클비트 개발자는 대부분 인터페이스 뒤가 아닌 타입별창 뒤에 세미콜론을 넣는다. 이 기본 설정은 세미콜론을 사용해 변수를 선언하는 것과 세미콜론 없이 클래스 또는 함수를 선언하는 것의 차이를 반영한다.

  • 인터페이스에 대한 타입스크립트의 할당 가능성 검사와 오류 메시지는 객체 타입에서 실행되는 것과 거의 동일하다.
let valueHybe: Bts;

// ok
valueHybe = {
  	dubut: 2013;
  	name: "bangTan", 
};

valueHybe = "V";
// Error: Type 'string' is not assignable to 'Bts'.

valueHybe = {
  debut: true,
  // Error: Type 'boolea' is not assignabel to type 'number'.
  name: 'Jimin'
};
  • 인터페이스와 타입 별칭 사이에는 몇 가지 주요 차이점이 있다.
    - 인터페이스는 속성 증가를 위해 병할 할 수 있다.
    - 이 기능은 내장된 전역 인터페이스 또는 npm 패키지와 같은 외부 코드를 사용할 때 특히 유용하다.
    - 인터페이스는 클래스가 선언된 구조의 타입을 확인하는데 사용할 수 있지만 타입 별칭은 사용할 수 없다.
    - 인터페이스는 타입스크립트 타입 검사기가 더 빨리 작동합니다.
    - 인터페이스는 타입 별칭이 하는 것처럼 새로운 객체 리터럴의 동적인 복사 붙여넣기보다 내부적으로 더 쉽게 캐시 할 수 있는 명명된 타입을 선언한다.
    - 인터페이스는 이름 없는 객체 리터럴의 별칭이 아닌 이름 있는(명명된) 객체로 간주되므로 어려운 특이한 케이스에서 나타나는 오류 메시지를 좀 더 쉽게 읽을 수 있다.

속성 타입

선택적 속성

  • 객체 타입과 마찬가지로 모든 객체가 필수적으로 인터페이스 속성을 가질 필요는 없다.
  • 타입 애너테이션 : 앞에 ? 를 사용해 인터페이스의 속성이 선택적 속성임을 나타낼 수 있다.
interface Pachinko {
  author?: string;
  pages: number;
};

// ok
const ok: Pachinko = {
  author: "min Jin Lee",
  pages: 527,
};

const missing: Pachinko = {
  pages: 527 
};

-> Book 인터페이스는 필수 속성 pages의 선택적 속성 author 를 갖는다
-> Book 인터페이스를 사용하는 객체에 필수 속성만 제공된다면 선택적 속성은 제공되거나 생략할 수있다.

읽기 전용 속성

  • 타입스크립트는 속성 이름 앞에 readonly 키워드를 추가해 다른 값으로 설정될 수 없음을 나타낸다.
  • readonly 속성은 평소대로 읽을 수 있지만 새로운 값으로 재할당 하지 못한다
interface Page {
  	readonly text: string;
}

function read(page: Page) {
  // ok: text 속성을 수정하지 않고 읽는 것
  console.log(page.text);
  
  page.text += "!";
  // Error : Cannot assign to 'text' because it is a read-only property.
}
  • readonly 제한자는 타입 시스템에만 존재하며 인터페이스에서만 사용할 수 있다.
  • readonly 제한자는 객체의 인터페이스를 선언하는 위치에서만 사용되고 실제 객체에는 적용되지 않는다.
const pageIsh = {
  text: "Hi, There!",
};

// ok : pageIsh는 page 객체가 아니라 text가 있는, 유추뒨 겍체 타입이다.
pageIsh.text += "!";

// ok : pageIsh의 더 구체적인 버전인 Page를 읽는다
read(pageIsh);
  • Page 예제에서 text 속성의 부모 객체는 함수 내부에서 text로 명시적으로 사용되지 않았기 때문에 함수 밖에서 속성을 수정 할 수 있다ㅣ.
  • 쓰기 가능한 속성을 readonly 속성에 할당 할 수 있으므로 pageIsh는 Page로 사용할 수 있다.
  • 가변속성은 readonly 속성이 필요한 모든 위치에서 읽을 수 있다.
  • 명시적 타입 애너테이션인 : Page로 변수 pageIsh를 선언하면, 타입스크립트에 text 속성이 readonly 라고 한다. (단, 유추된 타입은 readonly 가 아니다.)
  • readonly 인터페이스 멤버는 코드 영역에서 객체를 의도치 않게 수정하는 것을 편리한 방법이다.
  • readonly 는 타입 시스템 구성 요소일 뿐 컴파일된 자바스크립트 출력 코드에는 존재하지 않는다.
  • readonly 는 단지 타입스크립트 타입 검사기를 사용해 개발 중 그 속성이 수정되지 못하도록 보호나는 역할!!!

함수와 메서드

  • 타입스크립트에서 인터페이스 멤버 함수타입으로 선언 할 수 있다
    - 메서드 구문: 인터페이스 멤버를 member(): void와 같이 객체의 멤버로 호출되는 함수로 선어
    - 속성 구문: 인터페이스 멤버를 member: () => void와 같이 독립함수와 동일하게 선언
interface HasBothFunctionTypes {
  property: ()  => string;
  method(): string;
}

const hasBoth: HasBothFunctionTypes = {
  property: () => "",
  method() {
    return "";
  }
};

hasBoth.property();  // ok
hasBoth.method();  // ok

=> 두 가지 방법 모두 선택적 속성 키워드인 ?를 사용해 필수로 제공하지 않아도 되는 멤버로 나타낸다.

interface OptionalReadonlyFunctions {
  optionalProperty?: () => string;
  oprtionalMethod?() : string;
}
  • 메서드와 속성 선언은 대부분 서로 교환하여 서용할 수 있다.
  • 메서드와 속성의 차이점
    - 메서드는 readonly로 선언할 수 없지만 속성은 가능하다.
    - 인터페이스 병합은 메서드와 속성을 다르게 처리한다.
  • 메서드와 속성 함수의 차이점에 대해 더 엄격한 옵션을 추가할지 모르기 때문에 현시점에서 추천하는 스타일 가이드는 다음과 같다.
    - 기본 함수가 this를 참조할 수 있다는 것을 알고 있다면 메서드 함수를 사용하기
    - 가장 일반적으로 클래스의 인스턴스에서 사용된다.
    - 반대의 경우는 속성 함수를 사용한다.
      

호출 시그니처

  • 인터페이스와 객체 타입은 호출 시그니처로 선언 할 수 있다.
  • 호출 시그니처는 값을 함수처럼 호출하는 방식에 대한 타입 시스템에 설명이다.
  • 호출 시그니처가 선언한 방식으로 호출되는 값만 인터페이스에 할당 할 수 있다.
  • 할당 가능한 매개변수와 반환 타입을 가진 함수이다.
  • 호출 시그니처는 함수 타입과 비슷하지만, 콜론(:) 대신 화살표(=>) 로 표시한다.
type Func = (input: string) => number;
interface CallSignature {
  (input : string): number;
}

// 타입 : (input : string) => number
const typedFunc: Func = (input) => input.length;  //ok

// 타입 : (input: string) => number
const typeCallSignature: CallSignature = (input) => input.length;  // ok
  • Func와 CallSignature 타입은 모두 동일한 함수 매개변수와 반환 타입을 설명한다.
  • 호출 시그니처는 사용자 정의 속성을 추가로 갖는 함수를 설명하는데 사용할 수있다.
  • 타입스크립트는 함수 선언에 추가된 속성을 해당 함수 선언의 타입에 추가하는 것으로 인식한다.
interface FuncWithCount{
  count: number;
  (): void;
}

let hasCallCount: FuncWithCount;

function keepsTrackOfCalls() {
  keepTrackOfCalls.count += 1;
  console.log("Hi There! ${keepsTrackOfCalls.count} times!');
}
              
keepsTrackOfCalls.count = 0;
hasCallCount = keepsTrackOfCalls;  // ok
              
function doesNotHaveCount() {
    console.log("No idea!");
  }
  
  hasCallCount = doesNotHaveCount;
  // Error : Property 'count' is missing in type
  // '() => void' but required in type 'FuncWithCount'

=> keepsTrackCalls 함수 선언에서는 number 타입인 counter 속성이 주어져 funcWithCount 인터페이스 할당 할 수 있다.
=> FuncWithCount 타입의 hasCallCount 인수에 할당 할 수 있다.

인덱스 시그니처

  • 일부 자바스크립트 프로젝트는 임의의 string 키에 값을 저장하기 위한 객체를 생성한다.
  • 타입스크립트는 인덱스 시그니처 구문을 제공해 인터페이스의 객체가 임의의 키를 받고, 해당 키 아래의 특정 타입을 반환할 수 있음을 나타낸다.
  • 자바스크립트 객체 속성 조회는 암묵적으로 키를 문자열로 반환하기 때문에 인터페이스의 객체는 문자열 키와 함께 가장 일반적으로 사용한다.
  • 인덱스 시그니처는 일반 속성 정의와 유사하지만 키 다음에 타입이 있고 {[i: string]: ...}과 같이 배열의 대괄호를 갖는다.
interface BtsNums {
  [i: string]: number;
}

const nums: BtsNums = {};

nums.v = 0;  // ok
nums.jimin = 1;  // ok

nums.rm = false;
// Error : Type 'boolean' is not assignable to type 'number'.
  • BtsNums 인터페이스는 number 값을 갖는 모든 string 키를 허용하는 것으로 선언된다.

  • number면 string 키가 아닌 그 어떤 키도 바인딩 할 수 없다.

    		- 인덱스 시그니처는 객체에 값을 할당 할 때 편리하지만 타입 안전성을 완벽학게 보장하진느 않는다.
      - 인댁스 시그니처는 객체가 어떤 속성에 접근하든 간에 값을 반환해야 한다.
      
interface DatesByBts {
  [i: string]: Date;
}

const publishDate: DateByBts = {
  Frankenstein: new Date("1 January 1818"),
};

publishDates.Frankenstein;   // 타입: Date 
console.log(publishDates.Frankstein.toString());  // ok

publishDates.Beloved;   // 타입은 Date 이지만 런타임 값은 undefined
console.log(publishDates.Beloved.toString());
// 타입 시스템에서는 오류가 나지 않지만 실제 런타임에서는 오류가 발생함
// Runtime error: Cannot read property 'toString'
// of undefined (reading publishDates.Beloved)

=> publishDats 값은 Date 타입으로 Frankenstein을 안전하게 반환하지만 타입스크립는 Beloved 가 정의되지 않았음에도 불구하고 정의되었다고 생각한다.

  • 키/ 값 쌍을 저장하려고 하는데 키를 미리 알 수없다면 Map을 사용하는 편이 더 안전하다.
  • .get 메서드는 항상 키가 존재하지 않음을 나타내기 위해서 | undefined 타입을 반환한다.

속성과 인덱스 시그니처 혼합

  • 인터페이스는 명시적으로 명명된 속성과 포괄적인 용도의 string 인덱스 시그니처를 한번에 포함할 수 있다.
  • 각각dml 명명된 속성의 타입은 포괄적인 용동의 인덱스 시그니처로 할당할 수 있다.
  • 명명된 속성이 더 구체적인 타입을 제공하고, 다른 모든 속성은 인덱스 시그니처의 타입으로 대체하는 것으로 혼합해야서 사용할 수 있다.
interface FlyHight {
 	Oroonoko: number;
  	[i: stirng]: number;
}

// ok
const fly: FlyHight = {
  Outlander: 1991,
  Oroonoko: 1688,
};

const missingOroonoko: FlyHight = {
  // Error: Property 'Oroonoko' is missing in type
  // '{ Outlander: number; }' but required in type 'FlyHight'.
  Outlander: 1991,
};
  • 속성과 인덱스 시그니처를 혼합해서 사용하는 일반적인 타입 시스템 기법 중 하나는 인덱스 시그니처의 원시 속성보다 명명된 속성에 대해 더 구체적인 속성 타입 리터럴을 사용하는 것이다.
  • 명명된 속성의 타입이 인덱스 시그니처에 할당될 수 있는 경우 (각각의 리터럴 및 원시 속성에 해당) 타입스크립트는 더 구체적인 속성 타입 리터럴을 사용하는 것을 허용한다.
interface HybeNewJeans {
  members: 5;
  [i, string]: number;
}

const corretMembers: HybeNewJeans = {
  members: 5;
  night: 1;
  shopping: 7
};

const wrongMembers: HybeNewJeans = {
  members: 1,
  Error: Type '1' is not assignable to type '5'.
};
  • members속성은 5이고, 다른 모든 속성은 더 일반적인 number를 갖도록 선언한다.
  • HybeNewJeans를 사용하는 모든 객체의 members 속성은 반드시 5 이어야 한다.

숫자 인덱스 시그니처

  • 타입스크립트 인덱스 시그니처는 키로 string 대신 number 타입을 사용할 수 있다.
  • 명명된 속성은 그 타입을 포괄적인 용도의 string 인덱스 시그니처의 타입으로 할당 할 수 있다.
// ok
interface MoreNarrowNumbers {
  [i: number]: string;
  [i: string]: string | undefined
}


// ok
const mixesNumbersAndStrings: MoreNarrowNumbers = {
  0: '';
  key1: '',
  key2: undefined,
}

interface MoreNarrowStrings {
  [i: number]: string | undefined;
  // Error: 'number' index type string | undefined'
  // is not assignable to 'string' index type 'string'
  [i: string]: string;
}

-MoreNarrowNumbers 인터페이스는 string을 string | undefined 에 할당할 수 있지만 MoreNarrowStrings 인터페이스는 string | undefined 을 string 할당 할 수 없다.

중첩 인터페이스

  • 객체 타입이 다른 객체 타입의 속성으로 중첩될수 있는 것 처럼 인터페이스 타입도 자체 인터페이스 타입 혹은 객체 타입을 속성으로 가질 수 있다.
interface Novel {
  author: {
    name: string;
  };
  setting: Setting;
}

interface Setting {
  place: string;
  year: number;
}

let myNovel: Novel;


// ok
myNovel = {
  author: {
    name: 'Min Jin Lee',
  },
  setting: {
    place: 'NY',
    year: 2018,
  }
};

myNovel = {
  author: {
    name: 'Emily Bronte',
  },
  setting: {
    // Error: Property 'year' is missing in type
    // { place: string } but required in type 'Setting'
    place: 'West',
  },
};

인터페이스 확장

  • 타입스크립트는 인터페이스가 다른 인터페이스의 모든 멤벌르 복사해서 선언할 수 있는 확장된인터페이스를 허용한다.
  • 확장할 인터페이스의 이름 뒤에 extends 키워드를 추가해서 다른 인터페이스를 확장한 인터페이스라는 걸 표시한다.
  • 파생 인터페이스를 준수하는 모든 객체가 기본 인터페이스의 모든 멤버도 가져와야 한다.
interface Writing {
  title: string;
}

interface Novella extends Writing {
  pages: number;
}


// ok
let myNovella: Novella = {
  pages: 222,
  title: 'Ethan',
};

let missingPages: Novella = {
  // Error: Property 'pages' is missing in type '{ title: string }' but 
  // required in type 'Novella'
  title: "Wake up".
};

let extraProperty: Novella = {
  // Error: Type '{ pages: number; strategy: string; style: string; }'
  // is not assignable to type 'Novella'.
  // Object literal may only specify known properties,
  // and 'strategy' does not exist in type 'Novella'
  pages: 300,
  strategy: 'baseline',
  style: 'Naturalis'
};
  • 인터페이스 확장은 프로젝트의 한 엔티티 타입이 다른 엔티티의 모든 멤버를 포함하는 상위 집합을 나타내는 실용적인 방법이다.
  • 인터페이스 확장 덕분에 여러 인터페이스에 관계를 나타내기 위해 동일한 코드를 반복 읿력하는 작업을 피할 수 있다.

재정의된 속성

  • 파생 인터페이스는 다른 타입으로 속성을 다시 선언해 기본 인터페이스의 속성을 재정의하거나 대체할 수 있다.
  • 타입스크립트의 타입 검사기는 재정의된 속성이 기본 속성에 할당 되어 있도록 강요된다.
  • 속성을 재선언하는 대부분의 파생 인터페이스는 해당 속성을 유니언 타입의 더 구체적인 하위 집합으로 만들거나 속성을 기본 인터페이스의 타입에서 확장된 타입으로 만들기 위해 사용한다.
interface WithNullableName {
  name: string | null;
}

interface WithNonNullableName extends WithNullableName {
  name: string;
}

interface WithNumricName extends WithNullableName {
  // Error : Interface 'WithNumericName' incorrectly extends interface
  // 'WithNullableName'.
  // Type of property 'name' are incompatible.
  // Type 'string | number' is not assignable to type 'string | ull'.
  // Type 'number' is not assignable to type 'string'
  name: number | string;
}
  • WithNullableName 타입은 WithNonNullableName 에서 Null을 허용하지 않도록 적절하게 다시 설정한다.
  • WithNumericName의 name에는 number | string 이 허용되지 않는다.
  • number | string은 string | null 에 할당 할 수 없다.

다중 인터페이스 확장

  • 타입스크립트의 인터페이스는 여러개의 다른 인터페이스를 확장해서 선언할 수 있다.
  • 파생 인터페이스 이름에 있는 extends키워드 뒤에 쉼표로 인터페이스 이름으로 구분해 사용한다.
  • 파생 인터페이스는 모든 기본 인터페이스의 모든 멤버를 받는다.
interface GivesNumber {
  giveNumber(): number;
}

interface GivesString {
  giveString(): string;
}

interface GivesBothEither extens GivesNumber, GivesString {
  giveEither(); number | string;
}

function useGivesBoth(instance: GivesBothEither) {
  instance.giveEither();  // 타입: number | string
  instance.giveNumber();  // 타입: number
  instance.giveString();  // 타입: string
}
  • 여러 인터페이스를 확장하는 방식으로 인터페이스를 사용하면 코드 중복을 줄이고 다른 코드 영역에서 객체의 형태를 더 쉽게 재사용할 수 있다.

인터페이스 병합

  • 인터페이스의 중요한 특징 중 하나는 서로 병합하는 능력이다.
  • 두 개의 인터페이스가 동일한 이름으로 동일한 스코프에 선언된 경우, 선언된 모든 필드를 포함하는 더 큰 인터페이스가 코드에 추가된다.
interface Merged {
  fromFirst: string;
}

interface Merged {
  fromSecond: number;
}

// interface Merged {
// 		fromFirst : string
// 		fromSecond : number;
// }
  • 타입스크립트 개발에서는 인터페이스 병합을 자주 사용하지 않는다.
  • 여러곳에서 선언되면 코드를 이해하기가 어려워지고 가능하면 인터페이스 병합을 사용하지 않는 것이 좋다.
  • 인터페이스 병합은 외부 패키지 또는 window 같은 내장된 전역 인터페이스를 보강하는데 유용하다.
interfase Window {
  myEnviVariable: string;
}

window.myEnvibariable;  //  타입 : string

이름이 충돌되는 멤버

  • 병합된 인터페이스는 타입이 다른 동일한 속성을 어려번 선언 할 수 있다.
  • 속성이 이미 인터페이스에 선언되어 있다면 나중에 병합된 인터페이스에서도 동일한 타입을 사용해야한다.
interface MergedProperties {
  same: (input:boolean) => string;
  different: (input: string) => string;
}


interface MergedPropertyies {
  same: (input: boolean) => string  // ok
  different: (input: number) => string;
  // Error: Subsequent property declarations must have the same type.
  // Property 'differnt' must be of type '(input: string)' => string.
  // but here has type '(input: string) => string'.
  • MergedProperties 인터페이스 선언에서는 same 속성이 모두 동일하므로 문제 없지만 different 속성은 타입이 서로 다르기 때문에 오류가 발생한다.
  • 병합된 인터페이스는 동일한 이름과 다른 시그니처를 가진 메서드는 정의할 수 있다.
  • MergedMethods 인터페이스는 두가지 오버로드가 있는 diffrent 메서드를 생성한다.
interface MergedMethods {
  diffrent(input: string): string;
}
interface MergedMethods {
  different (input: number): string; //ok
}
  • MergedMethods 인터페이스는 두가지 오버로드가 있는 differnt 메서드를 생성한다.
profile
실수를 두려워 말고 계속 도전 하는 개발자의 여정!

0개의 댓글