TS스터디 이펙티브 item7~8

온호성·2023년 3월 24일
1

🤣item7 타입이 값들의 집합이라고 생각하기

타입은 '할당 가능한 값들의 집합'이라고 생각하자(예시를 들어 모든 숫자의 집합은 'number'이다.)

타입의 종류

never

가장 작은 타입이며 아무 값도 포함하지 않는 공집합( ∅ )이다.

유닛(unit)타입, 리터럴(literal)타입

never 다음으로 작은 집합으로 한 가지 값만 포함하는 타입이다.

type A = 'A';
type B = 'B';
type Twelve = 12;

유니온(union)타입

두개 혹은 세 개로 묶을 수 있는 타입이다.

type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12;

(🚫여기서 잠깐🚫 ts 오류에서 '할당 가능한' 이라는 문구를 볼 수 있는데, 이 문구는 집합의 관점에서, '~의 원소(값과 타입의 관계)' 또는 '~의 부분 집합(두 타입의 관계)'을 의미한다.)
위 오류에 대한 예시를 들면

const a: AB = 'A'; // 정상, 'A'는 집합 {'A', 'B'}의 원소이다.
const c: AB = 'C'; //~'"C"' 형식은 'AB' 형식에 할당할 수 없다.

"C"는 유닛 타입이다 범위는 단일 값 "C"로 구성되며 AB("A"와"B"로 이루어진)의 부분 집합이 아니므로 오류이다. 집합의 관점에서, 타입 체커의 주요역할은 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것이라고 볼 수 있다.

타입체커

집합의 관점에서 타입 체커는 하나의 집합이 다른 집합의 부분 집합인지 검사하는 역할을 가졌다.

const ab: AB = Math.random() < 0.5 ? 'A' : 'B'; // 정상
const back: AB = 1; // '1' 형식은 'AB' 형식에 할당할 수 없습니다.
  • 할당: T1이 T2에 할당 가능하다. = T1이 T2의 부분집합이다.
  • 상속: T1이 T2를 상속한다. = T1이 T2의 부분집합이다.
type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12

const ab: AB = Math.random() < 0.5 ? 'A' : 'B'; // 정상, {"A", "B"}는 {"A", "B"} 의 부분집합

const ab12: AB12 = ab; // 정상, {"A", "B"} 는 {"A", "B", 12} 의 부분집합

declare let twelve: AB12;
const back: AB = twelve;
// Type 'AB12' is not assignable to type 'AB'.
//   Type '12' is not assignable to type 'AB'.
// AB12 타입의 값은 AB 타입의 변수에 할당 불가능하다. 부분집합이 아니다.
  • 구조적 타이핑 규칙을 따른다면, 어떤 값이 다른 속성도 가질 수 있음을 의미한다. 심지어 함수의 매개변수에서도 다른 속성을 가질 수 있다.
  • 잉여 속성 체크: 특정 상황에서 추가 속성을 허용하지 않는 타입 체크이다.

인터섹션(intersection, 교집합)

아래 코드에서 Person와 Lifespan가 공통적으로 가지는 속성이 없기 때문에 PersonSpan은 never 타입일 것이다.

interface Person {
    name: string;
}

interface Lifespan {
    birth: Date;
    death?: Date;
}

type PersonSpan = Person & Lifespan;
// Person와 Lifespan가 공통적으로 가지는 속성이 없다.
// PersonSpan은 never 일까?

그러나 타입 연산자는 인터페이스 속성이 아닌, 값의 집합(타입의 범위) 에 적용된다.
따라서 구조적 타이핑에 의해 추가적인 속성을 가지는 값도 여전히 그 타입에 속하게 된다. 그래서 Person과 Lifespan을 둘 다 가지는 값은 인터섹션 타입에 속하게 된다.

const ps: PersonSpan = {
    name: 'Alan Turing',
    birth: new Date('1912/06/23'),
    death: new Date('1954/06/07'),
} // 정상

값 ps 는 Person 타입에 속하는가? => name 속성을 가지고 있기 때문에 다른 속성들을 가지고 있어도 구조적 타이핑에 의해 Person타입에 할당 가능하다.

값 ps 는 Lifespan 타입에 속하는가? => birth 속성을 가지고 있기 때문에 다른 속성들을 가지고 있어도 구조적 타이핑에 의해 Lifespan타입에 할당 가능하다.

값 ps 는 PersonSpan 타입에 속하는가? => Person과 Lifespan을 둘 다 가진다.

💥인터섹션 타입의 값(속성에 대한 인터섹션)은 각 타입 내의 속성을 모두 포함하는 것이 일반적인 규칙이다.

유니온(Union, 합집합)

인터섹션 타입의 값(속성에 대한 인터섹션)은 각 타입 내의 속성을 모두 포함하는 것이 규칙이다. 하지만 두 인터페이스의 유니온(속성에 대한 유니온)에서는 그렇지 않다.

interface Person {
    name: string;
}

interface Lifespan {
    birth: Date;
    death?: Date;
}

type K = keyof (Person | Lifespan); // never

Person과 Lifespan의 합집합에 속하는 값은 어떠한 키도 없기 때문에, 유니온에 대한 keyof는 never이다.

Person | Lifespan 에 { name: string; } 을 할당 가능한가? => Person 에 포함되기 때문이다.

Person | Lifespan 에 { birth: Date; } 을 할당 가능한가? => Lifespan 에 포함되기 때문이다.

{ name: string; }, { birth: Date; } 의 합집합은 name 을 가질 수도, 안가질 수도 있으며 birth 또한 그러하다. 따라서 Person | Lifespan에 속하는 값은 어떤 키를 가질지 알 수 없다.

keyof

type A = {
    a: string
}
type B = {
    b: string;
}

// keyof (A&B) 와 동일하다.
type AnB = (keyof A) | (keyof B); // "a" | "b" -> 각 속성을 모두 포함

// keyof (A|B) 와 동일하다.
type AuB = (keyof A) & (keyof B); // never
  • 유니온에 대한 keyof 과, 인터섹션에 대한 keyof
    keyof (A|B) === (keyof A) & (keyof B)
    keyof (A&B) === (keyof A) | (keyof B) -> 각 속성을 모두 포함

유니온(합집합) |

타입의 합집합은 더 넓은 값의 범위를 가진다는 의미이다.

type C = A | B ;

C는 A도 포함하고 B도 포함한다. 즉 A도 C를 만족하며 B도 C를 만족한다.

만약 A한테만 name 프로퍼티가 있다면 C 타입을 매개변수로 받아 name 프로퍼티에 접근하려 할 때 B 에는 name이 없다는 오류가 발생할 것이다.

즉 C타입이 A타입보다 범위가 넓어졌기 때문에 발생한 오류다.

인터섹션(교집합) &

타입의 교집합은 값의 범위가 더욱 좁아진다는 의미이다.

interface A {
  name: string;
}
interface B {
  age: number;
}

type C = A & B;

const c: C = {
  name: 'hee',
  age: 25
}

타입 C는 A도 만족해야하고 B도 만족해야한다. 즉 C 의 값의 범위는 훨씬 좁아졌다.

만약 age가 없거나 name이 없다면 타입C 를 만족시키지 못한다.

ts 타입이 되지 못하는 값의 집합

  • 정수에 대한 타입
  • x와 y 속성 외에 다른 속성이 없는 객체
    Exclude 를 사용하여 일부 타입을 제외하는 경우엔, 그 결과가 적절한 ts 타입일 때만 유효하다.
type T = Exclude<string|Date, string|number>; // 적절한 경우 - 타입이 Date가 됨
type NonZeroNums = Exclude<number, 0>; // 적절하지 못한 경우 - 타입은 여전히 number

keyof T

keyof는 객체의 키타입을 반환한다. 즉 객체의 키값의 집합이다.

interface P{
  x: number;
  y: number;
}

type PKey = keyof P; // "x" | "y"

타입스크립트 용어와 집합 용어

😗tiem8 타입 공간과 값 공간의 심벌 구분하기

ts의 symbol은 타입 공간이나 값 공간 중 한 곳에 존재한다. ts가 사용하는 공간과 값 공간(변수, 함수 등)은 서로 분리되어 있어서 같은 이름으로 사용할 수 있다. 즉 같은 이름의 타입과 식별자가 있으면 둘이 속한 공간이 다르게 된다.
(Symbol은 ES 6에서 추가 된 자료형이다. 자바스크립트에서 Boolean, string, number, null, undefined와 같이 객체와 메소드가 아닌 데이터이다.)

// 서로 다른 공간을 사용하기 때문에 오류가 나지 않음
type Person = {
  name: string;
  age: number;
};

const Person = {
  name: "alice",
  age: 26,
};
interface A { // 타입
  b: number;
}
const A = 1; // 변수

위 예제는 오류가 발생하지 않는데 타입A와 값 A는 서로 아무런 관련이 없다.

interface Cylinder {
  radius: number;
  height: number;
}

const Cylinder = (radius: number, height: number) => ({ radius, height });

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    // instanceof는 타입이 아닌 함수를 참조한다.
    shape.radius; // '{}' 형식에 'radius' 속성이 없습니다.
  }
}

이번 예제도 마찬가지로 shape instanceof Cylinder 에서 Cylinder 는 함수로 참조된다.

type vs const

type T1 = 'string literal';
const v1 = 'string literal';

위에서 type 으로 선언한 T1 은 타입, const로 선언한 v1 는 값이다. 이를 js 코드로 컴파일 하면

const v1 = 'string literal';

즉 타입은 컴파일 하는 과정에서 제거된다.

  • 타입 : type, interface
  • 값 : const, let, var
  • 둘 다 : class, 생성자함수, enum

class

  • 타입으로 쓰일 때 : 형태(속성과메서드)
  • 값으로 쓰일 때 : 생성자
    class 는 타입과 값 두 가지 모두로 사용된다. 따라서 런타임 때 타입을 확인하고 싶다면 클래스를 사용해서 타입을 선언하면 된다.

typeof 연산자

  • 타입에서 쓰일 때 : ts의 타입
  • 값에서 쓰일 때 : js 런타임 typeof 연산자
interface Person {
 name: string; 
}
const p: Person = { name: 'hee' };

type T = typeof p; // 타입은 Person
const v1 = typeof p; // 값은 'object'
  • 클래스에서 typeof 를 사용한 경우 =>
    타입으로 쓰일 때 : 인스턴스 타입이 아닌, 생성자 함수
    값으로 쓰일 때 : 'function'
class Cylinder {
    radius=1;
    height=1;
}

const v = typeof Cylinder; // 값이 function
type T = typeof Cylinder; // 타입이 class Cylinder, 즉 생성자 함수

const c = new fn(); // 타입이 Cylinder

만약 클래스의 인스턴스를 타입으로 사용하고 싶다면 다음과 같이 InstanceType를 작성하면 된다.

type C = InstanceType<typeof Cylinder>; // 타입이 Cylinder

속성 접근자([])

타입에서 []로 접근하는 방법을 의미한다. 문법적 오류가 아니면 어떠한 값도 넣을 수 있다.
속성 접근자 []은 값 또는 타입으로 사용할 때 동일하게 동작한다.하지만 접근자는 타입의 속성을 얻을 때 사용할 수 없다.
따라서 타입의 속성을 얻을 때에는 반드시 [] 접근자를 사용해야 한다.

//EX.1
type Person = {
  name: string;
  age: number;
  vision: {
    right: number;
    left: number;
  };
};

const vision1: Person["vision"] = {
  right: 1.2,
  left: 1.5,
};

// 아래와 같이 속성 접근자에는 "어떤한 값도 가능"합니다.
const vision2: Person[keyof Person] = {
  right: 1.2,
  left: 1.5,
};
//EX.2
type Person = {
    first: string;
    age: number;
}
const pigme = {
    first: 'hee',
    age: 20,
}

const first: Person['first'] = pigme['first']; // 정상, string
const first2: Person.first = pigme['first']; // 오류, 
// Cannot access 'Person.first' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'first' in 'Person' with 'Person["first"]'?

this

  • 값 : 자바스크립트의 this 키워드
  • 타입 : 다형성(polymoriphic) this, 서브 클래스 메서드 체인을 구현할 때 유용하다.

&, |

  • 값: AND, OR 비트 연산자
  • 타입 : 인터섹션, 유니온

const

  • 값: 새 변수 선언
  • 타입: as const 는 리터럴 또는 리터럴 표현식의 추론된 타입을 바꿈

extends

  • 값: 클래스의 상속
  • 타입: 서브클래스 또는 서브타입 또는 제네릭 타입의 한정자를 정의함
    -서브클래스 : class A extends B
    -서브타입: interface A extends B
    -제네릭 타입의 한정자 : Generic
    (제네릭: 클래스 또는 함수에서 사용할 타입(Type)을, 그 클래스나 함수를 사용할 때 결정하는 프로그래밍 기법)

in

  • 값: for in
  • 타입: 맵핑된 타입

0개의 댓글

관련 채용 정보