TypeScript - 작성자와 사용자의 관점으로 코드 바라보기
작성자와 사용자의 관점으로 코드 바라보기
타입 스크립트의 타입 시스템
- 타입을 명시적으로 지정할 수 있다.
- 타입을 명시적으로 지정하지 않으면, 타입스크립트 컴파일러가 자동으로 타입을 추론.
타입이란
해당 변수가 할 수 있는 일을 결정.
// 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