본문 바로가기

TypeScript

20210510 TypeSciprt 05, CompileOptions(strict 계열), Interface, Type alias

TypeScript05

CompileOptions : strict

  • 엄격하게 type check하는 옵션들을 전부다 킴
  • --noImplicitAny, --noImplicitThis, --strictNullChecks, --strictFunctionTypes, --strictPropertyInitialization, --strictBindCallApply, --alwaysStrict
"strict": {
"description": "Enable all strict type checking options.",
"type": "boolean",
"default": false,
"markdownDescription": "Enable all strict type checking options. See more: https://www.typescriptlang.org/tsconfig#strict"
},

 

--noImplicitAny

  • 명시적이지 않게 any 타입을 사용하여, 표현식과 선언에 사용하면, 에러 발생.
// 매개변수에서 error 발생 (any 명시 필요)
function noImplicitAnyTest(arg) {
  console.log(arg);
}
  • 타입스크립트가 타입 추론에 실패한 경우, any를 의도 했으면 any를 적어라라고 알려줌
  • type으로 아무것도 안쓰면 error 발생
  • 오류 해결시, any라고 지정되어 있지 않은 경우는 any가 아닌 것임(타입이 추론 되었으니까)

 

--suppressImplicitAnyIndexErrors

  • noImplicitAny 사용시, 인덱스 객체에 인덱스 signature가 없는 경우 오류 발생하는 것을 예외처리함 (즉, 허용한다.)
const obj = {
  bar: 10,
};

obj["foo"] = 10; // error : 인덱스가 없어서 -> 이것을 허용시켜 줌
obj.baz = 10;

 

--noImplicitThis

  • 명시적이지 않게 any 타입을 사용하여, this 표현식에 사용하면, 에러 발생
  • call, apply, bind와 같이 this를 대체하여 함수 콜을 하는 용도로 쓰임
  • Class 에서는 this 사용시 noImplicitThis의 에러가 안남
  • Class에서는 constructor 를 제외하고 멤버 함수의 첫번째 매개변수도 일반함수처럼 this 사용 가능
// error 예시
function noImplicitThisTest01(name: string, age: number) {
  this.name = name; // this error
  this. age = age; // this error
  return this; // this error
}

// 일반함수의 경우  (TS에서만 사용하는 문법) -> 매개변수 첫번째 자리에 python self처럼 this를 넣어서 타입 지정해야 error가 안 발생
function noImplicitThisTest02(this: any, name: string, age: number) {
  this.name = name;
  this. age = age;
  return this;
}

// Class 에서 this 사용시
class NoImplicitThisTest03 {
  private _name: string;
  private _age: number;

  constructor(name: string, age: number) { // 첫번째 매개변수 this (X)
    this._name = name;
    this._age = age;
  }

  public print(this: NoImplicitThisTest03) {
    console.log(this._name, this._age);
  }
}

new NoImplicitThisTest03('mark', 36).print()

 

--strictNullChecks (중요)

  • 해당 옵션을 키면 null 및 undifined 값이 모든 유형의 도메인에 속하지 않으며, 그 자신을 타입으로 가지거나, any일 경우에만 할당 가능함
  • 예외적으로 undefined만 void 할당 가능
  • null, undefined를 가지려면 union으로 사용해야 함 (해당 값을 조건부로 제외하고 사용하는 것이 바람직)
const a: number = null; // error
const b: string = undefined; // error
const c: any = null; // 가능
const d: any = undefined; // 가능
const e: void = undefined; // 가능

 

--strictFunctionTypes

  • 함수 타입에 대한 bivariant 매개변수 검사를 비활성화 함.
  • 서브타입의 가능성
  • 공변, 반병 -> 반환타입은 공변적, 인자 타입은 반공변적
  • Animal -> Greyhound , Dog -> Dog
  • 매개변수는 같거나 넓어야 되고, return type은 같거나 하위여야 문제가 없음
  • typescript 에서는 인자타입은 공변적이면서, 반공변적인게 문제라서 옵션을 켜서 해결

 

strictPropertyInitialization

  • 정의되지 않은 클래스의 속성이 생성자에서 초기화되었는지 확인합니다.
  • 이 옵션 사용하려면 --strictNullChecks를 사용해야 함
  • class 내에서 property가 선언만 되고 값이 할당 안되었을 경우 error를 뿜음
class Person01 {
  private _name: string; // error
  private _age: number; // error

  constructor() {} // 할당 안되어 있어서
  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  } // 이렇게 하면 해결

  public print() {
    console.log(this._name, this._age);
  }
}

// constructor를 사용하지 않는 경우
// (async를 사용하고 싶어서 일반 함수로 initialize 하는 경우)
class Person02 {
  private _name!: string;
  private _age!: number;

  public async initialize(name: string, age: number) {
    this._name = name;
    this._age = age;
  } // 일반 함수로 나중에라도 할당 시키는 경우 (property 선언시 `!`를 붙여서 나중에 해줄거니까 에러 무시해로 사용)

  public print() {
    console.log(this._name, this._age);
  }
}

 

--strictBindCallApply

  • function의 내장함수 bind, call, apply를 사용할때 더 엄격하게 함
  • bind는 해당 함수에서 사용할 this와 인자를 설정해주는 역할을 함
  • call과 apply는 this와 인자를 설정한 후, 실행 까지 함
    • call은 함수의 인자를 여러 인자의 나열로 넣어서 사용
    • apply는 모든 인자를 배열 하나로 넣어서 사용함

 

--alwaysStrict

  • 각 소스 파일에 대해 JS의 strict mode로 코드를 분석, 엄격하게 사용을 해제 (에밋 시킴)
  • syntex 에러가 ts error로 나오게 됨
  • 컴파일 된 js 파일에 use strict가 추가됨

결론 : strict 는 필수 옵션 임

 

Interfaces

  • interface는 js에 없고 TS에만 존재하는 keyword임
  • 컴파일 하면 js파일에서는 없어짐 결국 관계가 맞는지 compile할때 필요함

Interface 사용

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

function hello1(person: Person1): void {
  console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p1: Person1 = {
  name: "mark",
  age: 39,
};

hello1(p1);

 

Optional property 01 (?)

  • 매개변수에 대한 사용을 필수가 아닌 필요에 따라 쓰고 안쓰고 할 수 있음
  • ?를 사용해서 age 있어도 되고 없어도 되고
  • ?가 없으면 무조건 필요한 required임
interface Person2 {
  name: string;
  age?: number;
}

function hello2(person: Person2): void {
  console.log(`안녕하세요! ${person.name} 입니다.`);
}

hello2({ name: "Mark", age: 39 });
hello2({ name: "Anna" });

 

Optional propety 02 (Indexable type)

  • ?은 이미 지정해 놓은 특정 매개변수를 쓰고 안쓰고지만,
  • Indexable type을 이용하면 해당 type에 맞게 여러개를 추가해서 쓸수 있음
interface Person3 {
  name: string;
  age?: number;
  [index: string]: any;
}

function hello3(person: Person3): void {
  console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p31: Person3 = {
  name: "Mark",
  age: 39,
};

const p32: Person3 = {
  name: "Anna",
  sisters: ["Sung", "Chan"], // 할당 받는 값이 any 였으므로 배열도 상관 없음
};

const p33: Person3 = {
  name: "Bokman",
  father: p31, // 추가 (할당 값 type은 any이므로 상관 X)
  mother: p32, // 추가
};

hello3(p32);

 

Interface를 통한 함수 type 지정과 할당

Interface : function Type 지정하기

  • 함수 모양으로 property를 작성함
interface Person4 {
  name: string;
  age: number;
  hello(): void;
}

 

Interface : function 구현부 할당

  • 방법01
    • function 키워드를 사용한 방법
const p41: Person4 = {
  name: "Tom",
  age: 39,
  hello: function (): void {
    console.log(`안녕하세요! ${this.name} 입니다.`);
  },
};
  • 방법02
    • 함수호출 모양을 사용한 방법
    • 만약, 다른 객체를 대상으로 실행하고자 할때는 this 그것도 사용 가능하다는 것을 알려주기 위해서 this: Person4을 함수 첫번째 매개변수에 작성해도 된다.
const p42: Person4 = {
  name: "Tom",
  age: 39,
  hello(): void {
    console.log(`안녕하세요! ${this.name} 입니다.`);
  },
};

const p42: Person4 = {
  name: "Tom",
  age: 39,
  hello(this: Person4): void {
    console.log(`안녕하세요! ${this.name} 입니다.`);
  },
};

p41.hello();
p42.hello();
  • 주의(Arrow function - this 사용 불가)
    • arrow function안에서는 this 사용이 불가함
    • 해당 객체를 가르키지 않고 global this를 가르킴 -> 사용 불가
// 잘못된 사용
const p43: Person4 = {
  name: "Tom",
  age: 39,
  hello: (): void => {
    console.log(`안녕하세요! ${this.name} 입니다.`); // this error
  },
};

 

Interface를 바탕으로 class 만들기 (implements 키워드) & class를 type처럼 사용하기

Interface를 바탕으로 class 만들기 (implements 키워드)

interface IPerson1 {
    name: string;
    age?: number;
    hello(): void;
}

class Person implements IPerson1 {
    name: string;
    age?: number | undefined;

    constructor(name: string) {
        this.name = name;
    }

    hello(): void {
        console.log(`안녕하세요 ${this.name} 입니다.`);
    }
}

class를 type처럼 사용하기

// class로 type을 부를 수는 있지만 interface로 부르는게 더 정확하다.
const person12 = new Person("Mark"); // 변수의 type을 명시해줘야 확인하기 좋음
person12.hello();

const person: IPerson1 = new Person("Mark");
person.hello();

 

Interface : extends inheritance (상속)

interface IPerson2 {
  name: string;
  age?: number;
}

interface IKorean extends IPerson2 {
  city: string;
}

const k: IKorean = {
  name: "kim min young",
  city: "서울",
};

// overide 가능

 

interface로 function 표현하기 (이해가 안가는 부분)

  • interface를 통해서 interface를 통째로 함수 type형으로 만듦
  • ()안에 매개변수 type 지정하고, () 밖에 return type을 지정함
  • 구현체는 type 선언 된 곳과 비교하여 error를 체크함
  • 함수 호출 부분은 함수의 구현체 보다 type 선언된 부분을 비교하기 때문에 error가 없음
interface HelloPerson {
  (name: string, age?: number): void; // age의 경우 number | undefined 임
}

// 구현체는 type 선언 된 곳과 비교하여 error를 체크함
const helloPerson: HelloPerson = function (name: string) {
  console.log(`안녕하세요! ${name} 입니다.`);
};

// const helloPerson: HelloPerson = function (name: string, age: number) {
//     console.log(`안녕하세요! ${name} 입니다.`);
// };  // error 할당 불가 하다고 하는데 이해가 안감 ㅠ
helloPerson("Tom", 23);

 

interface : readonly

  • readonly 키워드로 property가 한번 지정되면 변경 불가하게 가능함
interface Person8 {
    name: string;
    age?: number;
    readonly gender: string;
}

// 한번 넣고 바뀌지 않는 값이라면, readonly를 붙여주는 버릇이 필요함

const p81: Person8 = {
    name: 'Mark',
    gender: 'male',
};

// p81.gender = "female";
// 코드에 의도에 담아서 의사표시를 하기 위해서 TS가 큰 도움을 줌

 

Type aliase vs Interface

function (함수 type 표현 방법)

  • Type alias
type EatType = (food: string) => void;
  • Interface
interface IEat {
  (food: string): void;
}

 

array

  • Type alias
type PersonList = string[];
  • Interface
interface IPersonList {
  [index: number]: string;
}

 

intersection

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtistsData {
  artists: { name: string }[];
}
  • Type alias
    • & 기호를 활용해서 표현 가능
type ArtistsResponseType = ArtistsData & ErrorHandling;
  • Interface
    • 두 개다 상속 받아서(다중 상속) 사용하므로 모두 사용 가능해짐
interface IArtistsResponse extends ArtistsData, ErrorHandling {}

let art: ArtistsResponseType;
let iar: IArtistsResponse;

 

union types

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}
  • Type alias
    • union type 가능
type PetType = Bird | Fish;
  • Interface
    • union type 불가능
    • union type 상속 받기, class 만들기 모두 불가
interface IPet extends PetType {} // error interface는 오직 object type또는 intersection 만 상속 가능

class Pet implements PetType {} // error class는 object type 또는 intersection만 class로 만들 수 있음

 

declaration merging - interface

  • type alias는 불가
  • interface로 type 표현시 같은 이름이면 두개가 합쳐짐
  • 기존의 interface에 뭔가 추가하는 경우
interface MergingInterface {
  a: string;
}
interface MergingInterface {
  b: string;
}
let mi: MergingInterface;
mi.a;
mi.b;

 

  • declaration merging - type alias
    • 불가하고, Duplicate error 발생
type MergingType = {
  a: string,
};
type MergingType = {
  b: string,
};
// Duplicate error 발생
  • type alias : type에 이름을 붙여주는 것
  • interface : type을 만드는 것이고