TypeScript - HandBook(Object Types)

2021. 6. 8. 22:52프론트엔드/Typescript

728x90

최근에 많이 놀았다. 솔직히 놀았다.
안 좋은 일들도 많아서 힘도 안 나고, 자존감도 낮아졌다.

그래도 이제 어느정도 회복하기도 했고, 운동도 시작했다.
이제 다시 가즈앗


Object 객체를 다루는 기본 방법

전의 TypeScript for JS Programmers에서 봤듯, 그리고 여기저기 나왔듯 다음과 같은 사용법이 있다.

// 가장 기본적인 사용법
const person: { name: string, age: number } = { name: "Inho", age: 27 };

// 인터페이스를 활용하는 방법
interface Person {
  name: string;
  age: number;
}

const person2:Person = { name: "Ahrom", age: 30 };

// 타입을 이용하는 방법
type Person = {
  name: string;
  age: number;
};

const person3:Person = { name: "Jaewon", age: 25 };

프로퍼티 활용하기 (Property Modifiers)

선택적 프로퍼티 (Optional Properties)

포함될수도, 안 될수도 있는 프로퍼티는 이름 뒤에 '?'를 붙인다.

// 기본적인 사용법. age가 선택적이기 때문에 age없이도 초기값을 지정했다.
let person: { name: string; age?: number } = { name: "Inho" };
// age를 포함하는경우도 가능
person = { name: "Inho", age: 27 };

// Interface를 사용하는 방법.
interface Person {
  name: string;
  age?: number;
}

const person2:Person = { name: "Ahrum" };

핸드북에서는 이러한 선택적 프로퍼티를 Narrowing 할 수 있는 방법을 알려준다.

interface PaintOptions {
  shape: string;
  xPos?: number;
  yPos?: number;
}

function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
  // 여기서 xPos의 기본값을 설정해주지 않는다면, xPos의 타입은 number | undefined이다.
  // 왜냐하면 있을수도, 없을수도 있기 때문. (paint.xPos === undefined인 경우)
  // 그렇기 때문에 xPos=0이라고 초기값을 설정해 주면, xPos의 타입은 number가 된다.
  // 인자로 받은 변수가 undefined여도 0이 대입되기 때문.
}

보다시피 해체 대입을 통해 Object인자의 프로퍼티를 변수로 대입시켰다.
해체 대입(destructuring pattern)은 아래 링크에서 잠깐 나온다. 조회수 뻥튀기 ㄳ

https://goldfishdiary.tistory.com/45

핸드북에서는 기가막히는 예제가 하나 있다.

function draw({ shape: Shape, xPos: number = 100 }) {
  // error: cannot find name 'shape'
  // 위의 에러가 나는 이유는, 해체 대입의 특성이라고 이해하면 된다.
  // 간단하게 요약하면 '해체 대입 안에서 자료형 선언을 하지 말라'
  console.log(shape);
}

// 얘는 정상적으로 가능하다.
function draw({ shape, xPos = 100 }: { shape: Shape, xPos: number ) {
  console.log(shape);
}

설명에서도 자바스크립트에서 다른 걸 의미하기 때문이라고만 설명한다. ㅠㅠ


읽기전용 프로퍼티 (readonly Properties)

변수명 앞에 readonly를 붙이면, 읽기만 가능해진다.

interface SomeType {
  readonly prop: string;
}

function doSomething(obj: SomeType) {
  // 읽는건 언제나 쌉가능
  console.log(`prop has the value '${obj.prop}'.`);

  // 에러: read-only이기 때문에 대입할 수 없습니다!
  obj.prop = "hello";
}

readonly가 붙은 프로퍼티는 대입이 불가능해지지만,
Object라면 프로퍼티를 변경할 순 있다.

const를 생각하면 된다. 변수 자체에 대입은 안 되지만, 내부는 바뀔 수 있는.

interface Home {
  readonly resident: { name: string; age: number };
}

function visitForBirthday(home: Home) {
  // readonly인 Object의 프로퍼티를 바꾸는건 가능
  home.resident.age++;
  
  // 에러: read-only에 대입할 수 없습니다.
  home.resident = { name: home.resident.name, age: home.resident.age };
}

인덱스로 프로퍼티 만들기 (Index Signatures)

[]를 이용하면, 프로퍼티 이름에 관계없이 만들기가 가능하다.
이 점을 이용하여, key가 number인 Object를 만들 수 있다.

// 이런식으로 []를 사용하면 프로퍼티 이름이 아닌, Type에 따라 설정이 가능하다.
interface StudentsInClass {
  // 키가 string이고 value가 number인 오브젝트가 된다.
  [className: string]: number;
}

const students:StudentsInClass = { A: 3, B: 6, C: 5 };

// 이 방식을 이용하여 key가 number인 Object를 만들어보자.
interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = getStringArray();
const secondItem = myArray[1];

여기서 궁금증이 생길 수 있다. 그렇다면 key가 string인 애들이랑 key가 number인 애들이랑 공존할 수 있을까?

답은 "야스"다.

interface NumberDictionary {
  [index: string]: number;

  length: number; // ok
  // 얘는 안 된다. 이미 string키는 위에서 다 가져갔기 때문에.
  name: string;
}

인터페이스를 확장해보기 (Extending Types)

class와 마찬가지로 extends확장자를 이용하여 부모 인터페이스를 상속할 수 있다.

interface BasicAddress {
  name?: string;
  street: string;
}

interface AddressWithUnit extends BasicAddress {
  unit: string;
}

게다가 이 interface 상속은 멀티 상속이 된다! 와!

interface Colorful {
  color: string;
}

interface Circle {
  radius: number;
}

interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

여기서 꾸러기 금붕어가 멈출리 없다. 두 interface에 같은 property를 넣어서 싸움을 붙여보겠다.

interface Colorful {
  color: string;
}

interface Circle {
  color: number;
}

// error: 요약하자면 color 프로퍼티의 타입이 서로 달라서 안된다고 한다
interface ColorfulCircle extends Colorful, Circle {}

단, 같은 이름의 프로퍼티의 type이 같을 땐 가능했다.


타입들을 교차하기 (Intersection Types)

extends와 같은 용도로, '&'기호를 이용하여 여러 타입을 상속받을 수 있다.
단, 상속받은 자식은 interface가 아니라, type이다.

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

// 다음과 같이 &을 이용하여 상속한다.
type ColorfulCircle = Colorful & Circle;

// 따로 타입을 선언하지 않고 코드에서 바로 사용해도 가능
const some: Colorful & Circle = { color: "Red", radius: 40 };

그럼 Interface extends와 intersection은 무엇이 다른가?
충돌을 처리하는 방법이 다르다고 한다. 그래서 쓰고싶은대로 쓰라고 한다.


제너릭을 이용한 오브젝트 타입 (Generic Object Types)

함수와 마찬가지로 interface와 Type Alias도 Generic을 이용할 수 있다.

interface Box<Type> {
  contents: Type;
}

type Box<Type> = {
  contents: Type;
};

let box: Box<string>;

읽기 전용 배열 (ReadonlyArray)

여기서 중요한것은 Readonly는 타입이지, 생성자는 없다.

// 생성 및 초기화
const roArray: ReadonlyArray<string> = ["red", "green", "blue"];

// 이렇게 하면 안 됩니다.
// new ReadonlyArray("red", "green", "blue");

// ReadonlyArray는 Array에서 추가, 삭제와 같은 기능을 제거한 타입이다.
function doStuff(values: ReadonlyArray<string>) {
  // 이런식으로 읽기는 가능하다
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);

  // error: readonly string[]에는 push라는 프로퍼티가 없습니다! (안 만들어놨으니 쓰지마!)
  values.push("hello!");
}

튜플 타입 (Tuple Types)

파이썬 등 여러 언어에 있는 이 튜플은, 배열의 형태로 타입을 선언할 수 있다.

튜플의 특징은, 몇번째 아이템이 어떤 타입인지 정할 수 있다.

type StringNumberPair = [string, number];

// 다음과 같이 해체 대입 쌉가능
function doSomething([c, d]: StringNumberPair) {
  console.log(c, d);
}

// Object와 마찬가지로, 이렇게 하면 에러.
// error: 그냥 에러. 이런거 안 됨.
function doSomething([c: string, d: number]) {
  console.log(c, d);
}

그리고 튜플에서는 선택적 프로퍼티를 사용할 수 있다

type Either2dOr3d = [number, number, number?];

그리고 다음과 같이 중간에 N개가 들어가는 경우도 처리할 수 있다.

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

출처

https://www.typescriptlang.org/docs/handbook/2/objects.html

 

Documentation - Object Types

How TypeScript describes the shapes of JavaScript objects.

www.typescriptlang.org