TypeScript 자료구조

2022. 2. 22. 22:05공부/TypeScript

TypeScript Types vs JavaScript Types

Static Types vs Dynamic Types
set during development vs resolved at runtime



예시

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 add(n1: number, n2: number) {
  return n1 + n2;
}

const result = add(39, 28);



For programs to be useful, we need to be able to work with som of the simplest units of data

: numbers, strings, structures, boolean values, and the like.


프로그램이 유용하려면, 가장 간단한 데이터 단위로 작업할 수 있어야 한다.
: numbers, strings, structures, boolean 값 등등.

In TypeScript, we support the same types as you would expect in JavaScript, with an extra enumeration type thrown in to help things along.


TypeScript에서, JavaScript에서 기대하는 것과 동일한 타입을 지원하며, 추가적인 열거 타입이 제공된다.



데이터타입

사용자가 만든 타입은 결국 이 기본자료형의 파생형이다.

  • JavaScript 기본 자료형 포함(superset)
    • Boolean
    • Number
    • String
    • Null
    • Undefined
    • Symbol (ESMAScript 6에 추가)
    • Array : object 형
  • 프로그래밍을 돕는 추가 타입
    • Any, Void, Never, Unknown
    • Enum
    • Tuple : object 형

 

Primitive Type

오브젝트와 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형.
프리미티브 형의 내장 함수를 사용 가능한 것은 자바스크립트 처리방식 덕분.
(ES2015 기준) 6가지

  • boolean
  • number
  • string
  • symbol (ES2015)
  • null
  • undefined

literal 값으로 Primitive 타입의 서브 타입을 나타낼 수 있다.

true;           // Primitive 타입 Boolean의 서브 타입
'hello';        // Primitive 타입 String의 서브 타입
3.14;           // Primitive 타입 Number의 서브 타입
null;           // ...
undefined;

또는 레퍼런스 객체로 만들 수 있다. (권장하지 않는 방식)

// 파스칼 케이스로 함수 사용
new Boolean(false);     // typeof new Boolean(false)    : 'object'
new String('world');    // typeof new String('world')   : 'object'
new Number(42);         // typeof new Number(42)        : 'object'

 

Type Casing

  • TypeScript의 핵심 primitive types는 모두 소문자이다.
  • Number, String, Boolean, Symbol 또는 Object 와 같은 파스칼 케이스 유형이 위에서 권장한 소문자 버전과 동일하다고 착각할 수 있음
  • 그러나 이러한 유형은 언어 primitives를 나타내지 않으며, 타입으로 사용해서는 안됨.
    function reverse(s: String): String {
    return s.split("").reverse().join("");
    }
    

reverse("hello world"); // Syntax Error 발생.

- 그 대신 number, string, boolean, object and symbol과 같은 소문자 타입을 사용해야 함.
```ts
function reverse(s: string): string {
  return s.split("").reverse().join("");
}

reverse("hello world")



boolean

let isDone: boolean = false;

isDone = true;

console.log(typeof isDone) // 결과 : 'boolean'

let isOk: Boolean = true;

console.log(typeof isOk)    // 결과 : 'boolean'

let isNotOk: boolean = new Boolean(true); // ERROR!
  • 마지막 라인은 boolean 타입의 isNotOk에 Boolean()이라는 객체를 넣으려고 하여 에러가 발생한다.



number

  • JavaScript와 같이, TypeScript의 모든 숫자는 부동 소수점 값이다.
  • TypeScript는 16진수 및 10진수 리터럴 외에도, ECMAScript 2015에 도입된 2진수 및 8진수를 지원.
  • NaN, 1_000_000과 같은 표기법 또한 표기 가능하다.
    let decimal: number = 6;
    

let hex: number = 0xf00d;

let bin: number = 0b1010;

let oct: number = 0o744;

let notANumber: number = NaN;

let underscoreNum: number = 1_000_000;

<br/><br/>

## String
- 다른 언어에서와 마찬가지로 텍스트 형식을 참조하기 위해 'string' 형식을 사용.  
- 문자열 데이터를 둘러싸기 위해 따옴표(''), 큰따옴표("") 사용.  
```ts
let myName: string = "Mark";

myName = "Anna";

Template String

  • 행에 걸쳐있거나 표현식을 넣을 수 잇는 문자열
  • 이 문자열은 backtick(= backquite) 기호에 둘러쌓여 있다.
  • 포함된 표현식은 `${변수명}`으로 사용할 수 있다.
    let fullName: string = `Bob Bobbington`;
    let age: number = 38;
    

let sentence: string = `Hello, my name is ${ fullName }.

I'll be ${age +1} years old next month.`;

//template string 을 사용하지 않는 경우
let sentence2: string = "Hello, my name is "+ fullName + ".\n\n" + "I'll be "+ (age +1) + " years old next month.";


<br/><br/>

## Symbol
- ECMAScript 2015 의 Symbol 이다.
- new Symbol로 사용할 수 없다.
- Symbol을 함수로 사용하여 symbol타입을 만들어 낼 수 있다.

```ts
console.log(Symbol('foo') == Symbol('foo')) // 결과: false
  • primitive 타입의 값을 담아서 사용한다.
  • 충돌되지 않는 고유의 값을 가지게 한다.
  • 보통 접근 제어용으로 사용
  • 함수로 사용할 땐 파스칼 케이스, 타입으로 사용할 때는 [sym]를 사용한다.
    console.log(Symbol('foo') === Symbol('foo'));
    

const sym = Symbol();

const obj = {
[sym]: "value",
};

obj["sym"] // 접근 불가

obj[sym]


<br/><br/>

## Undefined & Null
- 실제로 각각 Undefined 및 Null이라는 타입을 가진다.
- void와 마찬가지로 그 자체로는 그다지 유용하지 않음.
- 소문자 형태로만 존재.
```ts
let u: undefined = undefined;
let n: null = null;
  • 다른 모든 타입의 sub 타입으로 존재한다.
    • number 에 null 또는 undefined를 할당할 수 있다는 의미.
    • 컴파일 옵션에서 '--strictNullChecks'를 사용하면 null과 undefined는 자기 자신들을 타입으로 가지는 변수에만 할당 가능.
      tsconfig.json의 "strict": true가 되어있으면 --strictNullChecks를 사용하는 것.
      이 경우, null과 undefined를 할당할 수 있게 하려면, union type을 이용해야 함.
      let name: string = null;
      let age: number = undefined;
      

// strictNullChecks => true
// Type 'null' is not assignable to type 'string'.
let name: string = null; //(X)

// null => null || void, undefined => undefined || void
// Type 'null' is not assignable to type 'undefined'.
let u: undefined = null; //(X)

let v: void = undefined; //(O)
// void는 undefined와 의미상으로 비슷하므로 undefined를 할당할 수 있다. null은 안됨.

let union: string | null | undefined = 'str';

void는 undefined와 의미상으로 비슷하므로, undefined를 할당할 수 있다. (null은 안됨)

### null
- null이라는 값으로 할당된 것을 null이라고 한다.
- 사용할 준비가 덜 된 상태, 의도적으로 비워져있는 상태.
- null이라는 타입은 null이라는 값만 가질 수 있다.
- 런타임에서 typeof 연산자를 이용하면 object라고 뜬다.
```ts
let n: null = null;

console.log(n);         // null
console.log(typeof n);  // object

undefined

  • 값을 할당하지 않은 변수는 undefined라는 값을 가진다.
  • 무언가가 아예 준비가 되지 않은 상태. 의도적이지 않게 비워진 상태.
  • object 의 property가 없을 때에도 undefined이다.
  • 런타임에서 typeof 연사자를 이용하면 undefined라고 뜬다.
    let u: undefined = undefined;
    

console.log(u); // undefined
console.log(typeof u); // undefined


<br/><br/>

# Reference Type

## Object
```ts
// create by object literal
const person1 = {name: 'Mark', age: 39};

// create by Object.create
const person2 = Object.create({name: 'Mark', age: 39});
// Object.create() 의 매개변수로는 object 형태를 넣게 되어있다.

console.log(typeof(person1))    // object
console.log(typeof(person2))    // object
  • primitive 타입이 아닌 것을 나타내고 싶을 때 사용하는 타입.
  • non-primitive type
    • not number, string, boolean, bigint, symbol, null or undefined
      let obj: object = {};
      obj = {name: 'Mark'};
      obj = [{name: 'Mark'}];
      obj = 39;               // Error
      obj = 'Mark';           // Error
      obj = true;             // Error
      obj = 100n;             // Error
      obj = Symbol();         // Error
      obj = null;             // Error
      obj = undefined;        // Error
      declare function create(o: object | null): void;
      // primitive 타입은 받지 않을 때 사용.
      

create ({ prop: 0});
create(null);
create (42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

// Object.create
Object.create(0); // Error


<br/><br/>

## Array
- 원래 자바스크립트에서 array는 객체이다.
- 사용방법
  - Array<타입>
  - 타입[]
```ts
let list1: number[] = [1, 2, 3];

let list2: Array<number> = [1, 2, 3];

// 둘 다 같은방식.
// 아래의 방식은 JSS나 TSS에서 충돌이 일어날 가능성이 존재하여 사용 지양

// TS에서는 각 요소의 자료형이 앞에 선언한 자료형과 같아야한다.
// 복수의 자료형을 사용하기 위해선 유니온 표기 사용.

let list3: (number|string)[] = [1, 2, 3, 'string'];



Tuple

  • 배열에서 순서와 자료형이 강제되는 자료구조.
  • 크기에서 벗어나는 영역은 undefined로 처리되어 접근 불가.
    let x: [string, number];
    

x = ["hello", 39];

// x = [10, 'Mark'];

// x[3] = "world";

// x[2];

const person: [string, number] = ["Mark", 39];

const [first, second] = person; // 분해할당

let y: [string, number, number];

y = ["hello", 1, 2]


<br/><br/>

## Any
- 정확하게 알고 사용하는것이 매우 중요.
- 어떤 타입이어도 상관 없는 타입.
- 동적 콘텐츠(사용자 또는 API로부터 들어오는 모든 값)를 수용하는 데 사용.
- 최대한 사용하지 않는 것이 핵심.  
  (컴파일 타임에 타입 체크가 정상적으로 이뤄지지 않기 때문.)
- 컴파일 옵션 중에 any를 써야 하는데 쓰지 않으면 오류를 발생시키는 옵션도 존재함.
  - nolmplicitAny
```ts
function returnAny(message): any { 
  console.log(message);
}
// message에 아무거나 와도 상관이 없는 경우 Any를 지정해준다.
function returnAny(message:any): any {
  console.log(message);
}

const any1 = returnAny('리턴은 아무거나');

any1.toString(); // type적인 에러가 발생하지 않는다.
// any1이 어떤 일을 할 지 모르는 상태임 -> 어떤 일이든 할 수 있다.
// 따라서 사용이 지양됨.

// any가 사용될 수 밖에 없는 경우: 어떤 타입이 와도 받을 수 있는 경우.
  • any는 계속해서 개체를 통해 전파됨.
  • 결국, 타입 안정성이 떨어지는 결과를 초래한다.
  • 따라서 필요하지 않은 경우에는 사용하지 말아야 한다.
    let looselyTyped: any = {};
    

const d = looselyTyped.a.b.c.d;
// any에 의해 .a, .b, .c, .d 어디에서도 에러가 검출되지 않는다.
// any가 개체를 통해 전파됨.

```ts
function leakingAny(obj: any){
  const a = obj.num;
  const b = a+1;
  return b;
}

const c = leakingAny({num: 0});
const e = c.indexOf('0');
// a, b, c, e 모두 'any'가 된다.

위 코드에서는 매개변수에 any가 지정되어 있다.
이렇게 되면 내부의 변수 a, b 모두 any가 되고
그것을 할당받는 c 또한 any가 된다.
따라서 c.indexOf()에도 에러가 검출되지 않는 상황이 발생한다.


이를 방지하기 위해서는 매개변수 자체를 any로 지정하지 않거나
내부 변수의 자료형을 지정해주면 된다.

function leakingAny(obj: any){
  const a: number = obj.num;
  const b = a+1;
  return b;
}

const c = leakingAny({num: 0});
const e: string = c.indexOf('0'); // 에러
// a, b, c 가 number가 되며 number에는 indexOf함수가 없어 에러가 발생한다.



Unknown

  • any가 가지고 있던 타입 불안정성을 개선하고자 나온 자료구조.
  • 동적 콘텐츠(사용자 또는 API로부터 들어오는 모든 값)를 수용할 때 보통 사용.
  • Typescript 3.0 버전부터 지원.
  • any와 짝으로 any 보다 Type-safe한 타입.
    • any와 같이 아무거나 할당할 수 있다.
    • 컴파일러가 타입을 추론할 수 있게끔 타입의 유형을 좁히거나
      타입을 확정해주지 않으면 다른곳에 할당할 수 없고 사용할 수도 없다.
  • unknown 타입을 사용하면 runtime error를 줄일 수 있다.
    • 사용 전에 데이터의 일부 유형의 검사를 수행해야 함을 알리는 API에 사용할 수 있다.
      declare const maybe: unknown;
      
      

if (maybe === true) { // maybe라는 매개변수가 true라는 값으로 지정된다. (리터럴 타입가드)

const aBoolean: boolean = maybe;
// boolean 데이터에는 들어갈 수 있다.

const aString: string = maybe;
// 에러. string 자료형에는 들어갈 수 없다.
}

if (typeof maybe === 'string') { // 여기서는 maybe라는 매개변수가 string으로 고정된다.(typeof 타입가드)
const aString: string = maybe;

const aBoolean: boolean = maybe;
// 에러.
}

// 타입 가드를 통해 타입을 한정시켜야 사용할 수 있는 자료형이 unknown.
// any 였으면 그냥 사용됐음.


<br/><br/>

## *Type Guard*
조건문에서 객체의 타입을 좁혀나갈 수 있는 기능
### typeof
조건문에 `typeof`와 `instanceof`를 사용하면, TypeScript는 해당 조건문 블록 내에서는 해당 변수의 타입이 다르다는 것(=좁혀진 범위의 타입)을 이해함.
```ts
// typeof type guard 예제
function doSomethid(x: number | string){
  if (typeof x === 'string') {
    console.log(x.subtr(1)); // 에러. subtr은 string에 존재하지 않는 메소드.
    console.log(x.substr(1)); // 가능
  }
  x.substr(1); // 에러. x가 string이라는 보장이 없음.
}
// instance of type guard 예제
class Foo {
  foo = 123;
  common = '123';
}

class Bar {
  bar = 123;
  common = '123';
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ㅇㅋ
    console.log(arg.bar); // Error!
  }
  else {      // 타입가드로 하나 걸러냈으니 나머지 하나를 인식한다.
    console.log(arg.foo); // Error!
    console.log(arg.bar); // ㅇㅋ
  }

  console.log(arg.common); // ㅇㅋ
  console.log(arg.foo); // Error!
  console.log(arg.bar); // Error!
}

doStuff(new Foo());
doStuff(new Bar());



Never

  • 일반적으로 return에 사용된다.
  • never타입은 모든 타입의 subtype이며, 모든 타입에 할당할 수 있다.
  • never에는 어떤 값도 할당할 수 없다. (any 포함)
  • 잘못된 타입을 넣는 실수를 방지할 때 사용하기도 함.
    function error(message: string): never {
    throw new Error(message);
    }
    // 어떠한 값도 리턴되지 않는다는 의미로 사용.
    

function fail() {
return error("failed");
}

function infiniteLoop(): never {
while(true) {

}
}

이처럼 무한 루프 등 끝나지 않거나 return되지 않는 함수를 사용할 때 never를 사용할 수 있다.
```ts
let a : string = "hello";

if (typeof a !== 'string') {
  a;        // a는 string이 될 수 없으므로 never가 됨.
}

declare const b: string | number;

if (typeof b !== "string") {
  b;        // b는 string이 될 수 없으므로 number가 됨. (typeof 타입가드)
}

type Indexable<T> = T extends string ? T & {[index: string]: any} : never
// 조건부 타입.

type ObjectIndexable = Indexable<{}>;
// 매개변수로 준 값이 string이 아니면 never를 할당하는 코드.

const b: Indexable<{}> = ''; // 에러 발생함.



Void

  • 값은 없고 타입만 존재하는 타입.
  • 어떤 타입도 가지지 않는 빈 상태.
  • 소문자로 사용.
  • return을 undefined로 하게 될 경우 return 타입으로 사용.
  • JavaScript에서는 undefined가 있으므로 void가 필요 없었으나 다른 언어에서 사용하므로 호환성을 위해 존재한다.
  • function returnVoid(message: string) { console.log(message); return; // void가 return됨. return 생략 가능. }

const r = returnVoid("리턴이 없다."); // r의 타입은 void가 된다.

// const r: undefined = returnVoid("undefined도 할당할 수 없다.")

function returnVoid1(message: string): void{ // void를 명시해주는 것.
console.log(message);

return undefined; // undefined만 할당 가능
}
```