공부/TypeScript

TypeScript - 작성자와 사용자의 관점으로 코드 바라보기

도리암 2022. 2. 27. 20:29

작성자와 사용자의 관점으로 코드 바라보기

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

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

타입이란

해당 변수가 할 수 있는 일을 결정.

// f1이란 함수의 body에서는 a를 사용할 것.
// a가 할 수 있는 일은 a의 타입이 결정함.
function f1(a: 타입) {
  return a;
}

함수 사용법에 대한 오해를 야기하는 자바스크립트.

// f2 실행의 결과가 NaN을 의도한 것이 아닌 경우 오류 예제

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

// 사용자가 사용법을 숙지하지 않은 채, 문자열을 사용하여 함수를 실행하게 되면
console.log(f2(10));          // 결과: 380
console.log(f2('Mark'));      // 결과: NaN

위 함수의 작성자는 매개변수 a가 number 타입이라는 것을 가정하여 함수를 작성했다.
하지만 사용자가 사용법을 숙지하지 못한 채 문자열을 매개변수로 넘기면 의도하지 않은 결과가 도출된다.

타입스크립트의 추론에 의지하는 경우

// 타입스크립트 코드지만, 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

typescript에서는 문자열에 곱셈을 지원하지 않는다.

noImplicitAny 옵션

  • 타입을 명시적으로 지정하지 않은 경우,
    타입스크립트가 추론 중 'any'라고 판단할 경우 컴파일 에러를 발생시키는 옵션
  • 타입을 명시하도록 유도한다.noImplicitAny 에 의한 방어
  • // 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 타입으로 추론된 리턴 타입
```js
// 매개변수의  타입은 명시적으로 지정했다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number로 추론됨.

function f4(a: number) {
  if (a>0){
    return a * 38;
  }
  // a<0이면 a의 값은 비어있으므로 undefined가 된다.
}

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

console.log(f4(5));           // 결과: 190
console.log(f4(-5) + 5);      // 결과: NaN

기본적으로 number에 undefined가 포함되어 있으므로 두번째 실행문이 오류가 발생하지 않고 실행된다.

noImplicitReturns 옵션

  • 함수 내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생시킨다.
// if가 아닌 경우 return을 직접 하지 않고 코드가 종료된다.

// error TS7030: Not all code paths return a value.
function f5(a: number){
  if (a>0) {
    return a * 38;
  }
  // a<0일 때의 return이 없다.
}

StrictNullCheck 옵션

  • 모든 타입에 자동으로 포함되어 있는 'null'과 'undefined'를 제거한다.
  • null과 undefined는 각각의 타입으로 된 변수에만 할당이 가능해진다.number | undefined 타입으로 추론된 리턴 타입매개변수의 타입을 명시적으로 지정했으나
    함수의 리턴을 정확하게 명시하지 않은 경우 함수의 결과가 undefined가 될 수 있다.
    noImplicitReturns와 유사한 옵션.
    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'.


### 명시적으로 리턴 타입을 지정해야 하는 이유
실제 함수 구현부의 리턴 타입과 명시적으로 지정한 타입이 일치하지 않아 컴파일 에러가 발생할 수 있다.
```js
// 매개변수의 타입과 함수의 리턴 타입을 명시적으로 지정

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

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

// if가 아닌부분에서 undefined가 발생하므로 에러가 발생한다.

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

객체의 key들을 매개변수의 하위 속성으로 사용할 수 있다.

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 f6(a: {name: string; age: number}): string {
  return `이름은 ${a.name}이고, 연령대는 ${
    Math.floor(a.age / 10) * 10
  }대 입니다.`;
}

console.log(f6({ 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;}'.

나만의 타입 만들기

인터페이스 또는 타입 알리아스를 통해 자신이 원하는 형태의 타입을 만들 수 있다.
인터페이스는 타입을 확장 가능하지만 알리아스는 불가능하다.
타입 알리아스는 원시값, 유니온 타입, 튜플 등도 타입으로 지정 가능하다.
평소에는 인터페이스를 사용하는 것이 권장됨.
(좋은 소프트웨어는 언제나 확장이 용이해야 하는 원칙)

```js
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대 입니다.
console.log(f8('Mark'));
// error TS2345: Argument of type 'string' is not assignable to parameter of type '{ name: string; age: number;}'.

function f9(b: PersonTypeAlias): string {
return name: ${b.name}, Age: ${b.age}
}
console.log(f9({name: 'Riam', age: 18})) // name: Riam, Age: 18