Type System

100pearlcent·2021년 8월 24일
0

TypeScript

목록 보기
2/6
post-thumbnail

Type System

  1. 컴파일러에게 사용하는 타입을 명시적으로 지정하는 시스템
  2. 컴파일러가 자동으로 타입을 추론하는 시스템

TypeScript의 Type System

  • 타입을 명시적으로 지정할 수 있다
  • 타입을 명시적으로 지정하지 않으면, 타입스크립트 컴파일러가 자동으로 타입을 추론

TypeScript의 추론에 의지하는 경우

// 타입스크립트 코드이지만,
// a의 타입을 명시적으로 지정하지 않은 경우이기 때문에, a는 any로 추론된다
// 함수의 리턴 타입은 number로 추론된다 (NaN도 number의 하나)

function f1(a) {
	return a * 26;
}

// 사용자는 a가 any이기 때문에, 사용법에 맞게 문자열을 사용해서 함수를 실행했다
console.log(f1(10)); // 260
console.log(f1('Jinju') + 1); // NaN\

// > f1 함수의 작성자는 올바른 사용법을 사용자에게 전달하지 못했음

👉 noImplicitAny 옵션

타입을 명시적으로 지정하지 않은 경우, 타입스크립트가 추론 중 'any'라고 판단하게 되면 컴파일 에러를 발생시켜 명시적으로 지정하도록 유도한다.

number 타입으로 추론된 리턴 타입

// 매개변수의 타입은 명시적으로 지정했다
// 명시적으로 지정하지 않은 함수의 리턴타입은 number로 추론된다

function f2(a: number) {
	if(a > 0) {
    	return a * 38;
    }
}

// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실행했다
// 해당 함수의 리턴 타입은 number이기 때문에, 타입에 따르면 이어진 연산을 바로 할 수 있다
// 하지만 실제로는 undefined + 5가 실행되어 NaN이 출력된다

console.log(f2(5)); // 190
console.log(f2(-5) + 5); // NaN

// > undefined가 number안에 포함되어 있기 때문이다

👉 strictNullChecks 옵션

모든 타입에 자동으로 포함되어 있는 null과 undefined를 제거해준다

number | undefined 타입으로 추론된 리턴 타입

// strictNullChecks 옵션을 켰을 때
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number | undefined로 추론된다

function f2(a: number) {
	if(a > 0) {
    	return a * 38;
    }
}

console.log(f2(-5) + 5); // error Ts2532: Object is possibly 'undefined';

명시적으로 리턴 타입을 지정해야하는가?

// 매개변수의 타입과 함수의 리턴 타입을 명시적으로 지정했다
// 실제 함수 구현부의 리턴 타입과 명시적으로 지정한 타입이 일치하지 않아 컴파일 에러 발생

// error TS2366
function f3(a: number): number {
	if(a > 0) {
    	return a * 38;
    } // else에 대한 부분은 명시되어 있지 않아 error
}


// if가 아닌 경우 return을 직접 하지 않고 코드가 종료된다
// error TS7030: Not all code paths return a value
function f4(a: number) {
	if (a > 0) {
    	return a * 38;
    }
}

👉 noImpicitReturns 옵션

함수 내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생 시킨다

매개변수에 object가 들어오는 경우

// JavaScript

function f5(a) {
	return `이름은 ${a.name} 이고,
			연령대는 ${Math.floor(a.age / 10) * 10} 대 입니다.`;
}

console.log(f5({ name: 'Jinju', age: 26})); // 이름은 Jinju이고, 연령대는 20대입니다.
console.log(f5('Jinju')); // 이름은 undefined이고, 연령대는 NaN대 입니다.

👉 object literal type

function f6(a: {name: string; age: number}): string {
	return `이름은 ${a.name} 이고,
			연령대는 ${Math.floor(a.age / 10) * 10} 대 입니다.`;
}

console.log(f5('Jinju')); // error TS2345: Argument of type 'string' is not assinable to parameter of type '{ name: string; age: number; }'.



// ❗ 저 타입이 너무 길다면 타입을 생성해주면 된다 ❗
// interface, typeAlias, class 셋 중 아무거나 써도 가능

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

type PersonTypeAlias = {
	name: string;
  	age: number;
};

function f6(a: PersonInterface): string {
	return `이름은 ${a.name} 이고,
			연령대는 ${Math.floor(a.age / 10) * 10} 대 입니다.`;
}

console.log(f5('Jinju')); // error TS2345: Argument of type 'string' is not assinable to parameter of type 'PersonInterface'.

Structural Type System vs Nominal Type System

Structural Type System - 구조가 같으면, 같은 타입이다

TypeScript가 여기에 해당 됨

interface IPerson {
	name: string;
  	age: number;
  	speak(): string;
};

type PersonType = {
	name: string;
  	age: number;
  	speak(): string;
}

let perseonInterface: IPereson = {} as any;
let personType: PersonType = {} as any;

personInterface = personType;
personType = personInterface;

Nominal Type System - 구조가 같아도 이름이 다르면, 다른 타입이다

C, Java가 해당 됨

type PersonID = string & { readonly brand: unique symbol };

function PersonID(id: string): PersonID {
	return id as PersonId;
};

function getPersonByID(id: PersonID) {};

getPersonById(PersonID('id-aaaaaa'));
getPersonById('id-aaaaaa'); // error TS2345: Argument of type 'string' is not assinable to parameter of type 'PersonID'. Type 'string' is not assinable to type '{ readonly brand: unique symbol; }'.

// 아무 string이 아닌 PersonID로 치환 된 형식만 들어 갈 수 있음을 보여줌
// 자주 사용되는 방식은 아님

Duck Typing

런타임 시 발생하는 타이핑

만약 어떤 새가 오리처럼 걷고, 헤엄친다면 나는 그 새를 오리라고 부를 것이다

Python이 해당

class Duck;
	def sound(self):
    	print u "꽥꽥"

class Dog:
	def sound(self):
    	pring u "멍멍"

def get_sound(animal):
	animal.sound()

def main():
	bird = Duck()
	dog = Dog()
	get_sound(bird)
	get_sound(dog)

타입 호환성 (Type Compatibility)

// sub1 타입은 sup1 타입의 서브타입이다
let sub1: 1 = 1;
let sup1: number = sub1;

sub1 = sup1; // error! Type 'number' is not assignable to type '1'.


// sub2 타입은 sup2 타입의 서브타입이다
let sub2: number[] = [1];
let sup2: object = sub2;

sub2 = sup2; // error! Type 'number[]' is not assignable to type '[number. number]'. Target requires 2 elements but source may have fewer.

// sub3 타입은 sup3 타입의 서브타입이다
let sub3: [number, number] = [1, 2];
let sup3: number[] = sub3;

sub3 = sup3; // error! Type 'number[]' is not assignable to type '[number, number]'. Target requires 2 elemnets but source may have fewer.

// sub4 타입은 sup4 타입의 서브타입이다
let sub4: number = 1;
let sup4L any = sub4;

sub4 = sup4; // any이므로 가능

// sub5 타입은 sup5 타입의 서브타입이다
let sub5: never = 0 as never;
let sup5: number = sub5;

 sub5 = sup5; // error! Type 'number' is not assignable to type 'never';

class Animal {}
class Dog extends Animal { // 상속
	eat() {}
}

// sub6 타입은 sup6 타입의 서브타입이다
let sub6: Dog = new Dog();
let sup6: Animal = sub6;

// 반대로는 안된다
sub6 = sup6; // error! Property 'eat' is missing in type 'SubAnimal' but required in type 'SubDog'.

1. 같거나 서브 타입인 경우, 할당이 가능하다 > 공변

// primitive type
let sub7: string = '';
let sup7: string | number = sub7;

// objecet - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다
let sub8: { a: string; b:number } = { a: '', b : 1 };
let sup8: { a : string | number; b: number } = sub8;

// array - object와 마찬가지
let sub9: Array<{ a: string; b: number }> = [{ a: '', b : 1}];
let sup9: Array<{ a: string | number; b: number}> = sub8;

2. 함수의 매개변수 타입만 같거나 슈퍼타입인 경우, 할당이 가능하다 > 반병

class Person {}

class Developer extends Person {
	coding() {}
}

class StartUpDeveloper extends Developer {
	burning() {}
}

// Developer를 인자로 받고 Developer를 리턴하는 함수
function tellMe(f: (d: Developer) => Developer) {}

// Developer => Developer 에다가 Developer => Developer를 할당하는 경우
tellMe(function dToD(d: Developer): Developer {
       return new Developer();
	  });

// Developer => Developer 에다가 Person(상위, 슈퍼타입) => Developer 를 할당하는 경우
tellMe(function pToD(d: Person): Developer {
       return new Developer();
       });


// Developer => Developer 에다가 StartupDeveloper(하위) => Developer를 할당하는 경우

tellMe(function sToD(d: StartupDeveloper): Developer {	return new Developer();
       });

// StartupDeveloper가 하위이므로 Developer는StartUpDeveloper를 모른다 > 논리적으로 오류
// 하지만 타입스크립트는 이 부분에 대해 사용자에게 선택을 준다
// strict 옵션을 켜지 않으면 에러가 뜨지 않지만 논리적으로는 에러가 발생하는 부분이다
// strictFunctionTypes 옵션을 켜면
// 함수를 할당할 시에 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우, 에러를 통해 경고한다

반병은 strictFunctionTypes를 꺼놨을 때 TypeScript의 융통성을 보여주는 사례

타입 별칭 (Type Alias)

  • interface와 유사해보이지만 다르다
  • Primitive, Union Type, Tuple, Function 을 다른이름으로 부르기 위해 쓴다
  • 기타 직접 작성해야 하는 타입을 다른 이름으로 지정할 수 있다
  • 만들어진 타입의 refer로 사용하는 것이지 타입을 만드는 것은 아니다

Aliasing Primitive

type MyStringType = string;

const str = 'world';

let myStr: MyStringType = 'hello';
myStr = str;

// 사실 의미가 없는 코드이긴하다

Aliasing Union Type

let person: string | number = 0;
person = 'Jinju';

type StringOrNum = string | number;

let another: StringOrNum = 0;
another = 'Someone';
  • 유니온 타입은 A도 가능하고 B도 가능한 타입
  • 유니온 특성상 매우 기니까 짧게 쓰도록 도와준다

Aliasing Tuple

let person: [string, number] = ['Jinju', 26];

type Person = [string, number];

let another: Person = ['Someone', 30];

Aliasing Function

type EatType = (food: string) => void;

Alias와 인터페이스를 구분하는 법

타입이 타입으로써의 목적이 분명하면 > 인터페이스
그저 별명으로 존재한다면 > type alias

기술적으로 인터페이스와 type alias가 차이가 없는 것은 아님!

0개의 댓글