타입스크립트 스터디2회

안윤경·2022년 12월 30일

2장 타입스트립트의 타입 시스템


아이템6.편집기를 사용하여 타입시스템 탐색하기

타입스크립트는 컴파일러 실행도 목적이지만, 언어 서비스도 제공을 합니다.
언어서비스에는 코드의 자동완성, 검색, 리팩토링, 명세검사가 가능하고 또한 타입 추론을 가능하게 해줍니다.

const Num =(a:number,b:number)=>{
  return a+b;
}
// 반환타입이 없어도 알아서 number로 추론이 가능하다.

위의 예시처럼 반환타입을 정해주지 않아도 인자의 타입으로 추론을 하여 반환타입을 number로 추론할 수 있습니다.

언어서비스는 라이브러리와 라이브러리 타입 선언을 탐색할 때 도움이 됩니다.

const response = fetch('http://example.com')

fetch에서 정의로 이동 옵션을 누르면 타입스크립트에 포함되어 있는 DOM타입 선언인 lib.dom.ts로 이동하게 됩니다.
이 안에서 자세한 타입을 알 수 있습니다.

아이템7. 타입이 값들의 집합이라 생각하기

타입이란?

할당 가능한 값들의 집합

이러한 집합들은 타입의 '범위'라고 부르기도 합니다.

 42,34 =>number타입의 범위
'canada' ==> number타입의 범위가 아니다.
  • 가장 작은 집합은 아무 값도 포함되지 않는 공집합 타입스크립트에서는 never이다.

즉 아무 값도 할당 불가능합니다.

  • 반대로 가장 큰 집합은 unknown입니다. 어떠한 값이든 할당할 수 있는 집합입니다.

unknown과 any의 차이점?

  let str : unknown = 'this is msg';
    let num : number = 10;
    num = str;
    //num은 number 타입이라 str 대입 불가

    let str2 : any = 'this is msg';
    let num2 : number = 10;
    num2 = str2;
    //num2는 number 타입이지만 str2로 인해 봉인 해제 (대입가능)

위의 예시처럼 any는 타입 체크를 해제하여 타입이 달라도 에러를 발생시키지 않지만 unknown은 여러가지 타입의 값을 변수에 대입은 할 수 있으나 unknown 지정된 변수의 값과 다른 타입의 변수에 대입을 하려고 할 때 컴파일 에러가 발생합니다.
(unknown은 가장 큰 범위라 number,string모두 할당될 수 있어 값으로 쓸 때 에러가 발생하는 것?)

  • 한가지 값만 포함하는 타입 - 유닛타입 =리터럴 타입
  • 합집합을 나타내는 유니온 타입
  • 교집합을 나타내는 &(인터렉션)타입

할당가능한~? 의 뜻은 집합의 관점에서 '~의 원소(값과 타입의 관계)' 또는 '~의 부분 집합을 의미합니다.'

interface Identified{
id:string
}

예시처럼 어떤 객체가 string으로 할당 가능한 id속성을 가지고 있다면 그 객체는 Identified가 됩니다.
타입체커의 역할 중 하나로 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것입니다.

타입을 집합의 관점에서 바라보기

추가적으로 집합에 대한 시각으로 볼 때 유니온타입과 구조적타입이 다릅니다.

keyof (A&B) = (keyof A) | (keyof B) //구조적 타입
keyof (A|B) = (keyof A) & (keyof B) //유니온타입

자세히 살펴봅시다.
1.구조적 타입에서의 교집합이란?

interface A {
name: string
}

interface B {
age: number
gender: string
}

이라는 타입이 2개있을 때
각각

const AB = A & B
const CD = key of(C|D)

라고 할 때 과연 둘의 값이 어떻게 나올까요?

interface AB {
name: string
age: number
gender: string
}

interface CD 는 never가 나옵니다

왜 이런일이 일어날까요?

&연산자는 두 타입의 인터섹션을 계산합니다.
const AB = A & B에서 &는 교집합이라 공통된 것을 가져와야 할 것 같지만 구조적타이핑에 따르면 AB가 A 와 B의 타입에도 속해야 한다는 뜻입니다.
즉 A와B의 속성들을 다 가지면 문제가 양쪽의 타입을 갖게 되어 오류가 나오지 않습니다.
그래서 &는 공통된 것을 찾는 것이 아닌 구조적타이핑에 의해 두 타입의 모든 것을 포함한 타입이 나오게됩니다

이것이 타입관점에서의 교집합입니다.

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

const CD는 위와 반대로 never가 나옵니다.
이유는 어떤 타입이 A에 속하거나 B에 속해야한다는 뜻인데 위에 예시에서는 두개의 타입이 될 수 있는 속성이 아무것도 없습니다.
그래서 never가 나오게 됩니다.

만약 타입가드를 쓴다면 하나의 타입만 이용하기에 상관이 없지만 만약 그대로 유니온타입만을 쓴다면 함수는 어느타입이 들어올 지 알 수없어 공통된 것만을 가져와 오류가 안나오도록 하는 것입니다.

결론적으로 이렇게 되는 이유는 자바스크립트의 구조적 타이핑을 타입스크립트가 모델링했기 때문입니다.

또한 이러한 이유로 유니온 타입에서는 extneds를 사용할 수 없습니다.


interface Vector1D { x: number; }
interface Vector2D { x: number; y: number; }
interface Vector3D { x: number; y: number; z: number; }


interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
interface Vector3D extends Vector2D { z: number; }

와 같습니다

원을 그려보면 가장 위에 vector1이 있고 그 밑에 vector2 vector3가 있는 것입니다 왜냐하면 vector1안에는 여러가지 속성들이 있을 수 있고 vector1만 만족시키면 추가적인 속성을 가져도 vector1에 해당하기 때문입니다.

서브타입이란?

어떤 집합이 다른 집합의 부분집합이라는 의미입니다.

아이템8 타입공간과 값 공간의 심벌 구분하기

타입스크립트의 심벌은 타입 공간이나 값 공간 중의 한곳에 존재합니다.
속하는 공간에 따라 값또는 타입으로 쓸 수 있습니다.

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

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

위의 예시를 보면 같은이름이지만 타입으로써의 Cylinder와 함수로써의 Cylinder가 존재 할 수 있습니다.

구별하는 법

타입
앞에 type,interface가 붙는다앞에 const,let이 붙는다
타입선언(:),타입 단언(as}다음에 나오는 심벌은 타입= 다음에 나오는 모든 겂은 값
type T1 :'string' //타입
const V1 = 123 //  값

interface Person{
  first: string;
  last:string;
}

const p:Person ={first:'Jane',last:'Jacobs'};
//    -         --------------------------- 값
//      ------                              타입
  • playground에서 확인을 할 수 있다

단 class , enum은 값과 타입 두가지 모두 가능한 예약어입니다.

class Cylinder {
  radius = 1;
  height = 1;
}

function calculateVolume2(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape.radius; // 에러 발생 X => 값 공간으로 사용
  }
}

연산자 중에서도 typeof는 타입과 값에서 이용시 다른 기능을 합니다. 자바스크립에서는 런타임에서 실행되어 값을 구별하지만 타입에 관점에서는 typeof는 값을 읽어서 타입스크립트 타입을 반환합니다.

InstanceType<>은 생성자 함수의 리턴타입을 반환한다.

const v = typeof Cylinder; // 값이 'function'

type T8 = typeof Cylinder; // 타입이 typeof Cylinder  =>  === Cylinder 생성자 함수

// InstanceType: 생성자 함수의 리턴 타입을 얻는다.
// InstanceType 제네릭을 이용하여 생성자 타입과 인스턴스 타입을 전환할 수 있다.

type C = InstanceType<typeof Cylinder>; // 타입이 Cylinder 완벽히 이해x

[ ]를 이용하여 속성의 타입에 접근할 수 있다

아이템9 타입단언보다 타입선언 이용하기

interface Person{
  name:string
}

const Alice:Person ={name:Alice}; //타입 선언
const Bob ={name:Bob} as Person //타입 단언

타입단언은 강제로 그 타입을 주는 것이라서 만약 실제 타입과 다르더라도 오류를 표시하지 않습니다.

interface Person{
    name: string
}


const People = ['A','B','C'].map((name:Person)=>({name}))    //name이 타입이 person이고 반환값이 any또는 void라 오류 발생                 

const People = ['A','B','C'].map((name):Person=>({name}))       //name의 반환값이 person이라 오류발생 x

그렇다면 타입단언을 써야 할 때는?

내가 그 타입을 확실하다고 생각할때
예시로 dom을 사용 할 경우나 null을 사용할 경우 타입스크립트가 타입을 잘 모르기에 단언을 해줄 수 있습니다.

아이템10. 객체 레퍼 타입 피하기

자바스크립트에는 기본형 7가지의 타입이 존재합니다(number,string,null,undefined,boolean,symbol,bigint
이들은 불변적이며 메소드를 갖지 않습니다 하지만

'primitive'.charAt(3)
"m"

이라는 결과가 나옵니다? 왜그런 것일까요?

이유는 string기본형에는 메서드가 없지만 자바스크립트에서는 기본형과 객체 타입을 서로 자유롭게 변환이 가능하기 때문에,charAt를 사용시 기본형 => 객체형으로 변환 =>메소드 호출=> 마지막에 래핑한 객체를 버림순으로 진행이 되어 메소드를 사용하는 것 처럼 보이는 것입니다.

단 객체형과 기본형이 서로 변환이 쉽다하지만 객체형은 오직 자기자신하고만 동일합니다.

또한 객체래퍼의 타입변환은 어떤속성을 기본형에 할당하면 그 속성이 사라집니다.

기본형마다 객체래퍼가 존재하는데 대문자와 소문자 구별을 조심해야 합니다.

타입스크립트는 기본형과 객체 래퍼타입을 별도로 모델링하므로 객체래퍼는 필요할 때 제외하고 안쓰는게 좋습니다.

아이템 11. 잉여속성체크의 한계 인지하기


interface Room {
    numDorrs: number,
    ceilingHeightFt: number;
}

const r: Room = {
    numDorrs : 1,
    ceilingHeightFt: 10,
    elephant: 'present' // 오류
};

const obj = {
    numDorrs : 1,
    ceilingHeightFt: 10,
    elephant: 'present'
};

const r: Room = obj // 정상, 임시 변수를 도입하면 잉여 속성 체크가 동작하지 않습니다.

위 예제에서 확인 되듯, 타입이 명시된 변수에 리터럴을 할당할 때는 구조적 타이핑이 무력화되는 것을 확인할 수 있습니다. 이와 같은 동작을 '잉여 속성 체크'라고 합니다.

단 임의로 변수를 이용해서 할당 시에는 잉여 속성 체크가 작동하지 않습니다

이게 가능한 이유는 잉여속성체크가 할당가능 검사와는 별도의 과정이기 때문입니다.

잉여 속성 체크는 엄격한 객체 리터럴 체크라고도 하며 객체리터럴을 사용하지 않은 할당이나 타입 단언문을 사용할 때에도 적용되지 않습니다.

아이템12. 함수 표현식에 타입 적용하기

함수 표현식을 사용하면 함수의 매개변수와 반환값를 한번에 함수 타입으로 선언할 수 있기 때문입니다.
반면에, 함수 선언문을 사용하면 함수의 매개변수와 반환값의 타입을 따로 선언해야합니다.
즉 재사용이 가능하기 때문에 표현식으로 작성시 수고로움을 덜 수 있습니다.

// 선언 방식으로 함수를 구현할 때, 반복적으로 함수의 형태를 표현하고잇다.
function add(a: number, b: number): number { return a + b;}
function sub(a: number, b: number): number { return a - b;}
function mul(a: number, b: number): number { return a * b;}
function div(a: number, b: number): number { return a / b;}

// 기 정의된 타입을 이용하여 표현식 방식으로 함수를 구현할 때 반복적인 함수의 형태를 코딩하지 않는다.
type Calc = (a:number, b: number) => number;
const add2: Calc = (a, b) => ( a + b );
const sub2: Calc = (a, b) => ( a - b );
const mul2: Calc = (a, b) => ( a * b );
const div2: Calc = (a, b) => ( a / b );
profile
프론트엔드 개발자 안윤경입니다

0개의 댓글