"Boldness has genius, power, and magic in it." - Johann Wolfgang von Goethe

TypeScript

[TS] TypeScript 타입체계 | noImplicityAny | strictNullChecks

Toproot 2021. 9. 1. 09:57
728x90
728x90

 

📘 타입스크립트 타입시스템에 대한 이해

타입 시스템

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

 

타입스크립트의 타입 시스템

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

 

자신의 코드에서 해당 함수를 사용하는 사용자 vs 해당 함수를 구현하는 구현자

  • 타입이란 해당 변수가 할 수 있는 일을 결정합니다.
  • 함수 사용법에 대한 오해를 야기하는 자바스크립트
// JavaScript

// (f2 실행의 결과가 NaN 을 의도한 것이 아니라면)
// 이 함수의 작성자는 매개변수 a 가 number타입이라는 가정으로 함수를 작성했습니다.

function f2(a) {
    return a * 38;
}

// 사용자는 사용법을 숙지하지 않은 채, 문자열을 사용하여 함수를 실행했습니다.
console.log(f2(10); // 380
console.log(f2('Mark')); // NaN

 

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

function f3(a) {
    return a * 38;
}

// 사용자는 a가 any이기 때문에, 문자열을 사용하여 함수를 실행했습니다.
console.log(f3(10); // 380
console.log(f3('Mark') + 5); // NaN

 

noImplicitAny 옵션을 켜면

  • 타입을 명시적으로 지정하지 않은 경우, 타입스크립트가 추론 중 any라고 판단하게 되면,
  • 컴파일 에러를 발생시켜 명시적으로 지정하도록 (any라고 작성) 유도한다.
  • nolmplicitAny에 의한 방어
// error Ts7006: Parameter 'a' implicitly has an 'any' type.

function f3(a) {
    return a * 38;
}

// 사용자의 코드를 실행할 수 없습니다. 컴파일이 정상적으로 마무리 될 수 있도록 수정해야합니다.

console.log(f3(10));
console.log(f3('Mark') + 5);

 

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

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

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

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

 

strictNullChecks 옵션을 켜면

  • 모든 타입에 자동으로 포함되어 있는 null과 undefined를 제거해줍니다.
  • number | undefined 타입으로 추론된 리턴 타입
// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number | undefined 로 추론됩니다.

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

// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실행했습니다.
// 해당 함수의 리턴 타입은 number | undefined 이기 때문에,
// 타입에 따르면 이어진 연산을 바로 할 수 없습니다.
// 컴파일 에러를 고쳐야하기 하기 때문에 사용자와 작성자가 의논을 해야합니다.

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

 

명시적으로 리턴 타입을 지정해야 할까?

  • 리턴부의 타입과 함수 내의 타입을 비교해 일치하는 지 TS가 봐주기 때문에 코드를 검토하며 작성.
// 매개변수의 타입과 함수의 리턴 타입을 명시적으로지정했습니다.
// 실제 함수 구현부의 리턴 타입과 명시적으로 지정한 타입이 일치하지 않아 컴파일 에러가 발생합니다.

// error TS2366: Function lacks ending return statement and return type does not inc

function f5(a: number): number {
    if (a > 0) {
        return a * 38;
    } // if 가 아닌 부분은 작성이 덜 된 것 같다고 에러 발생
}

 

noImplicitReturns 옵션을 켜면

  • 모든 코드에서 리턴을 직접해야한다.
// if 가 아닌 경우 return을 직접 하지 않고 코드가 종료된다.

// error TS7030: Not all code paths return a value.
function f5(a: number) {
    if (a > 0) {
        return a * 38;
    }
}

 

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

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

console.log(f6({ name: 'Mark', age: 38 })); // 이름은 Mark이고, 연령대는 30대 입니다.
console.log(f6('Mark')); // 이름은 undefined이고, 연령대는 NaN대 입니다.

 

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

console.log(f7({ name: 'Mark', age: 38 })); // 이름은 Mark이고, 연령대는 30대 입니다.
console.log(f6('Mark')); // error TS2345: Argument of type 'string' is not
// assignable to parameter of type '{ name: string; age: number; }'.

 

나만의 타입을 만드는 방법 (TS 주요 기능)

  • 타입을 계속해서 작성해주는 불편함을 해소.
// 예시

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

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

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

console.log(f8({ name: 'Mark', age: 38 })); // 이름은 Mark이고, 연령대는 30대 입니다.
consoel.log(f8('Mark')); // error TS2345: Argument of type 'string' is not
// assignable to parameter of type 'PersonInterface'.

 

📘 Structure Type과 Nominal Type의 비교

  • TS 타입체계를 이해하기!

 

structural type system - 구조가 같으면, 같은 타입이다. (TS)

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

let personInterface: IPerson = {} as any;
let personType: PersonType = {} as any;

persontInterface = 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
// assignable to parameter of type 'PersonID'. Type 'string' is not assignable to type
// '{ readonly brand: unique symbol; }'.

 

duck typing (duck testing에서 유래) - TS는 duck typing이 아닙니다.

  • 만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리르 낸다면 나는 새를 오리라고 부를 것이다.
class Duck:
	def sound(self):
		print u"꽥꽥"

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

def get_sound(animal):
	animal.sound()

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

 

 

 

 

728x90
728x90