TIL 47. 타입스크립트의 자료형 타입

CHAEIN·2021년 8월 20일
1

TypeScript

목록 보기
1/2
post-thumbnail

typescript 타입과 javascript 타입의 차이


typescriptjavascript
staticdynamics
개발 도중에 에러를 확인할 수 있음런타임 할 때가 돼서야 에러를 발견할 수 있음
//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형

PRIMITIVE TYPE

오브젝트와 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형.

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 타입

1) boolean

let isDone: boolean = false;
isDone = true;
console.log(typeof isDone); // `boolean`

let isOk: boolean = true;
let isNotOk: boolean = new Boolean(true); // 'error', boolean 타입과 래퍼 객체 Boolean 은 다르기 때문에

2) number

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;

3) string

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}`;

4) symbol

// 타입스크립트에서 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"

5) undefined 와 null

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";

6) object

타입스크립트에서 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

7) Array

// 배열 타입을 표현하는 두가지 방법
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3]; // 리액트와 사용할 때 jsx언어와 충돌할 수 있어서 첫번쨰 표현식을 권장한다.

let list: (number | string)[] = [1, 2, 3, "4"]; // union type으로도 지정 가능하다.

8) tuple

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

9) any

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;
}

10) unknown

모르는 변수의 타입을 묘사해야할 때 (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를 줄일 수 있다.

11) never

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이다.
}

12) void

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만 리턴할 수 있다.
}

0개의 댓글