typescript
타입과 javascript
타입의 차이typescript | javascript |
---|---|
static | dynamics |
개발 도중에 에러를 확인할 수 있음 | 런타임 할 때가 돼서야 에러를 발견할 수 있음 |
//javascript
function add(n1, n2) {
if (typeof n1 !== "number" || typeof n2 !== "number") {
throw new Error("Incorrect input!"); // 런타임시 에러를 감지
}
return n1 + n2;
}
const result = add(39, 28);
// typescript
function add1(n1: number, n2: number) {
// 개발 환경에서 에러를 체크해주기 때문에 런타임시 에러 감지를 위한 코드를 작성할 필요가 없다.
return n1 + n2;
}
typescript는 자바스크립트에서 제공하지 않는 타입을 추가로 제공한다.
ECMA 표준에 따른 기본 자료형 | 타입스크립트 추가 자료형 |
---|---|
Boolean, Number, String, Null, Undefined, Symbol, Array: object 형 | Any, Void, Never, Unknown, Enum, Tuple : object형 |
오브젝트와 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형.
Boolean / Number / String / Null / Undefined / Symbol이 해당한다.
리터럴표기로 타입을 나타낼 수 있다.
let b = "hello";
new 생성자를 활용해 prmitype type을 래퍼 객체로 만들어 참조형 자료들처럼 사용할 수 있다. 이렇게 만들어진 래퍼 객체는 primitive type 이 아니다.
const str = "문자열"; // 문자열 리터럴 생성
const strObj = new String(str); // 문자열 객체 생성
str.length; // 리터럴 값은 내부적으로 래퍼 객체를 생성한 후에 length 프로퍼티를 참조함.
str == strObj; // 동등 연산자는 리터럴 값과 해당 래퍼 객체를 동일하게 봄.
str === strObj; // 일치 연산자는 리터럴 값과 해당 래퍼 객체를 구별함.
typeof str; // string 타입
typeof strObj; // object 타입
타입 스크립트에서 타입을 지정할 때에는 위처럼 대문자로 시작되는 래퍼 객체 생성은 지양한다. number,string, boolean, symbol
은 primitive type 이고 Number, String, Boolean, Symbol
은 참조 자료형이다. 둘은 다른 자료형이며 래퍼객체는 타입으로 사용해서는 안된다.
ex) 잘못된 예
function reverse(s: String): String {
// 래퍼객체로 파라미터와 리턴타입 지정
return s.split("").reverse().join("");
}
ex) 옳은 예
function reverse(s: string): string {
return s.split("").reverse().join("");
}
typescript
타입let isDone: boolean = false;
isDone = true;
console.log(typeof isDone); // `boolean`
let isOk: boolean = true;
let isNotOk: boolean = new Boolean(true); // 'error', boolean 타입과 래퍼 객체 Boolean 은 다르기 때문에
let decimal: number = 6;
// 2진수 8진수 16진수도 사용할 수 있다.
let hex: number = 0xf00d; // 16진수
let binary: number = 0b1010; // 2진수
let octal: number = 0o744; // 8진수
// NaN은 number 타입에 해당한다.
let notANumber: number = NaN;
// 천단위로 _를 사용해 끊어 표기해도 가능하다.
let underScore: number = 1_000_000;
let myName: string = "Mark";
myName = "Anna";
let fullName: string = "Mark Lee";
let age: number = 39;
let sentence: string = `Hello, My name is ${fullName}, and I'm ${age + 1}`;
// 타입스크립트에서 Symbol타입이 인식이 안될 땐 tsconfig.json파일에서 "lib" : [], 주석을 해제하고 ["ES2015", "DOM"]을 추가한다.
console.log(Symbol("foo")); // Symbol은 String이나 Number와 같은 래퍼 객체가 아니다.
console.log(Symbol("foo") === Symbol("foo")); //false
Symbol은 프라이미티브 타입의 값을 담아서 사용한다.
Symbol은 해당 값을 고유하고 수정 불가능한 값으로 만들어준다.
따라서 주로 접근을 제어하는데 많이 쓰인다.
const sym = Symbol();
const obj = {
[sym]: "value",
};
// obj.sym 또는 obj["sym"] 으로는 접근할 수 없다
console.log(obj[sym]); // "value"
undefined와 null은 각각 undefined 및 null 이라는 타입을 가진다. void와 마찬가지로 그자체로는 유용하지 않다. 둘다 소문자만 존재한다.
let u: undefined = undefined;
let n: null = null;
따로 설정을 하지 않는 이상 undefined와 null은 모든 다른 타입의 서브타입으로 존재한다. number 타입 변수에 null 이나 undefined를 할당할 수 있다는 의미이다. 하지만 데이터의 정확도와 안정성을 높이기 위해선 이러한 상황을 방지하는 것이 좋다. 이를 위해 tsconfig.js파일에서 "strict": true,
또는 --strictNullChecks
주석을 해제하면 null은 자기 자신에게만 undefined는 자기 자신과 void에만 할당가능하다. 하지만 이렇게 strict한 상황에서 코딩하는 것은 매우 힘들다. 이 경우, union type을 활용해 null과 undefined할당을 가능하게 할 수 있다.
let myName: number = undefined; //error
let u: undefined = null; // error
let v: void = undefined; // void는 타입이고 값으로는 존재할 수 없다. void에는 undefined 할당이 가능하다.
let union: string | null = null; // union type으로 설정하여 null과 string을 모두 사용할 수 있게 한다.
union = "Mark";
타입스크립트에서 object는 "primitive type이 아닌 것"을 나타내고 싶을 때 사용하는 타입이다. "primitive type이 아닌 것"은
원시 자료형을 제외한 모든 자료형
을 뜻한다.
const person1 = { name: "Mark", age: 39 };
// person1은 타입스크립트 관전에서 object 타입이 아니다.
console.log(typeof person1); // "{name: string, age: number}" type.
const person2 = Object.create({ name: "Mark", age: 39 }); // 괄호 안에는 객체 형태 또는 null을 넣을 수 있다.
const person3 = Object.create(39); // error
const person4 = Object.create([]); // 배열도 가능하다.
const person5 = Object.create(function () {}); // 함수도 가능하다.
// 함수는 주로 아래와 같이 사용한다.
declare function create(o: object | null): void;
create({ prop: 0 }); //ok
create(null); //ok
create(42); // error
create("string"); // error
create(false); // error
create(undefined); // error
// 배열 타입을 표현하는 두가지 방법
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3]; // 리액트와 사용할 때 jsx언어와 충돌할 수 있어서 첫번쨰 표현식을 권장한다.
let list: (number | string)[] = [1, 2, 3, "4"]; // union type으로도 지정 가능하다.
let x: [string, number];
x = ["hello", 39];
x = [10, "Mark"]; // error
const person: [string, number] = ["Mark", 39];
const [first, second, third] = person;
// first는 string, second는 number
// 지정한 tuple의 길이를 초과했기 때문에 third는 Error
any타입의 변수에는 어떤 타입의 값도 올 수 있다. 타입 시스템의 안정성을 위해서 any 사용은 권장되지 않는다.
function returnAny(message: any): any {
// 이 함수의 리턴값은 어떤 값도 될 수 있다.
console.log(message);
}
// any 타입은 전파된다.
function leakingAny(obj: any) {
const a = obj.num;
const b = a + 1;
return b;
}
// function 안 변수들의 타입은 모두 any이다.
const c = leakingAny({ num: 0 });
function leakingAny(obj: any) {
const a: number = obj.num; // 이렇게 누수를 막으면 이 이후에 변수들의 타입은 number가 된다.
const b = a + 1;
return b;
}
모르는 변수의 타입을 묘사해야할 때 (ex. 동적 콘텐츠 값을 받아와야 할 때), 이 경우에는 컴파일러와 미래의 코드를 읽는 사람에게 이 변수가 어떤 타입도 될 수 있음을 알려주는 unknown type을 활용한다. 이 때는 런타임과정에서 타입가드인 if문을 통해 unknown 타입을 한정시켜 사용한다.
declare const maybe: unknown;
const aNumber: number = maybe; // error
만약 maybe가 any였다면 여기서 error가 발생하지 않는다. 그러면 개발자가 예상치 못한 곳에서 실수할 가능성이 늘어난다. unknown은 컴파일러가 타입을 추론할 수 있게끔 타입의 유형을 좁히거나 타입을 확정해주지 않으면 다른 곳에 할당 할 수 없고, 사용할 수 없다.
// 타입 가드
if (maybe === true) {
const aBoolean: boolean = maybe; // maybe가 true일 때만 작동하기 때문에 여기서 maybe는 boolean 타입으로 에러가 발생하지 않는다.
const aString: string = maybe; // error, maybe는 boolean 타입이기 때문에 string 타입과 맞지 않아 에러가 발생한다.
}
if (typeof maybe === "string") {
const aString: string = maybe; // maybe의 타입이 string일때만 작동하기 때문에 에러가 발생하지 않는다.
}
any대신 unknown을 활용하면 runtime error를 줄일 수 있다.
never 타입은 모든 타입의 subtype이며 모든 타입에 할당 할 수 있다. 하지만 never에는 그 어떤 것도 할당할 수 없다. any조차도 never에 할당할 수 없다. 주로 잘못된 타입을 넣는 실수를 방지하고자 할 때 사용한다.
function error(message: string): never {
throw new Error(message);
//return 값이 없기 때문에 never를 지정할 수 있다.
}
function fail() {
// 리턴하고 있는 에러 함수에 리턴 타입이 never이기 때문에 이 함수의 return 타입도 never이다
return error("failed");
}
function infiniteLoop(): never {
while (true) {}
// 출구 없는 while 문으로 return 값이 없어 return type은 never 이다.
}
let a: string = "hello";
if (typeof a !== "string") {
a; // 여기서 a의 타입은 never이다.
}
declare const b: string | number;
if (typeof b !== "string") {
b; // 여기서 b의 타입은 number이다.
}
function returnVoid(message: string) {
// return이 없는 함수의 경우 기본 타입은 void
console.log(message);
}
const r = returnVoid("리턴이 없다."); // 여기서 r의 타입도 void가 된다.
const r: undefined = returnVoid("리턴이 없다."); // Error!
function returnVoid(message: string): void {
// void가 지정된 함수는 해당 함수의 리턴값을 사용하지 않겠다는 명시적 표현이다.
console.log(message);
return undefined; // 유일하게 undefined만 리턴할 수 있다.
}