TS 스터디 회고록 - 4

밍글·2023년 8월 6일
0

TS 스터디 회고록

목록 보기
4/5
post-thumbnail

⌨️서론

이번에도 계속해서 TypeScript와 관련되어 필자가 헷갈리는 부분을 적어보고자 한다. 이번에는 타입조작이랑 관련된 방법만 작성할 것이다. 🙇‍♂️
지난번 회고에서도 적었듯이 최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.


타입 조작

다양한 타입 연산자를 조합하여 복잡한 연산과 값을 간결하고 유지보수 가능한 방식으로 표현할 수 있다. 대표적인 방식으로는 Generic이 있으며 typeof도 이에 해당한다.
이 두 가지 방법 외에도 기존의 타입이나 값에 기반하여 새로운 타입을 표현하는 방법에 대해 다룰 것이다.

Indexed Access Types

객체,배열,튜플 타입에서 특정 프로퍼티 혹은 요소의 타입을 추출하는 타입을 의미한다.
예를 들어 다음과 같은 interface가 있다고 가정을 해보자

interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
  };
}

Post라는 interface에 있는 author에 프로퍼티가 점점 많이 필요하게 된다면 어떻게 할까? 다음과 같이 작성할 수도 있지만 이는 비효율적이기도 하고 author 프로퍼티가 늘어날 때마다 수정을 해야되기 때문에 이 방법은 피해야 한다.
function printAuthorInfo(author: { id: number; name: string, age: number }) ⬅️ 이런 방식은 피해야 된다.
Indexed Access Types를 활용하면 다음과 같이 나타낼 수 있다.

function printAuthorInfo(author: Post["author"])
다만 이 Types를 활용할 때에는 아래와 같은 규칙을 지켜야 한다.
1️⃣ 값이 아니라 타입만 명시할 수 있으며 존재하지 않는 건 쓸 수 없다!
2️⃣ author라는 객체에 특정 property만 가져오고 싶다면 ?(예를들면 author.name) ➡️ Post[“author”][“name”] 이런식으로 표현하면 된다!
3️⃣ 만약 Post가 list형태로 되어있다면, Post[number] 이런식으로 해당 요소만 찾으면 된다.(아래 코드 참고)

//3번 규칙 관련 코드
const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];
type Person = typeof MyArray[number];
/*  type Person = {
    name: string;
    age: number;
}*/
type Age = typeof MyArray[number]["age"];
//   type Age = number
// 혹은 아래와 같이 표현할 수 있다.
type Age2 = Person["age"];
//   type Age2 = number

Keyof Type Operator

특정 객체 타입으로부터 프로퍼티 키들을 모두 스트링 리터럴 유니온 타입으로 추출하는 연산자

  • 언제 쓸까? interface 안에 property가 많은데 이 중에 key값을 찾아서 쓰고 싶을 때 일일이 key : “name” | “age” | … 이런형식은 비효율적이므로 이럴 때 사용한다.
  • 형식 및 유의할 점
    형식 ➡️ key : keyof Person 이런식으로 해주기!
    유의할 점➡️keyof연산자는 무조건 뒤에 타입만 사용할 수 있다!
//Keyof Type Operator 활용 예시
interface Person {
  name: string;
  age: number;
  location: string; // 3개의 프로퍼티
}

function getPropertyKey(person: Person, key: keyof Person) {
  return person[key];
}

const person: Person = {
  name: "밍글",
  age: 27,
  location : "Seoul"
};

console.log(getPropertyKey(person,"location")); 
// 이런식으로 key는 문자열 형태로("") 호출해주기
// Seoul

Mapped Types

기존의 객체 타입으로부터 새로운 객체 타입을 만드는 타입
ex. 수정하는 기능(변경되는 값만 바꾸고 싶은 경우. 전체를 보내면 필요없는 부분도 수정이 되어야 하기 때문)
사용 방식 ➡️ [key in “id” | “name” | “age” (혹은 keyof User)] ?: User[key]

//코드 예시
interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = {
  [key in "id" | "name" | "age"]?: User[key];
};

(...)

readonly 📖
readonly 속성을 통해 요소들을 읽기전용으로만 만들 수 있다.(object관련)
그냥 요소 앞에 readonly를 추가하면 된다. ex) readonly name : Name
array의 경우 push가 안되는거지 filter나 map은 가능하다.

Template Literal Types

스트링 리터럴 타입을 기반으로 정해진 패턴의 문자열만 포함하는 타입(실제로 사용하는 경우는 특수하다.)
예시코드

type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";

type ColoredAnimal = `${Color}-${Animal}`;

const coloredAnimal : ColoredAnimal = '' 
//이렇게 하면 해당 부분에 `red-dog` | 'red-cat' | 'red-chicken' | 'black-dog' ... 등 담을 수 있다.

Conditional Types

//조건부 타입 예시
 type A = number extends string ? String : number;
// 이거의 답은 거짓이므로 type A는 number type이 되는 것!
//제네릭과 조건부 타입 예시
type StringNumberSwitch<T> = T extends number ? string : number;
let varA : StringNumberSwitch<number> //string
//반대로 StringNumberSwitch<string>을 넣으면 number타입으로 된다.

⭐️함수에서 활용해보기(with generic)⭐️

//예시코드
function removeSpaces(text : string | undefined | null){
	if(typeof text ===string){return text.replaceAll(“ “,””);}
	else {return undefined}
}

이렇게 작성해도 되지만 이럴경우에
const result = removeSpaces(“hello my name is Mingle”) 는 string | undefined으로 타입을 추론하기 때문에 뒤에 as string을 넣어줘야만 toUpperCase()같은 기능을 쓸 수 있다.
이를 generic과 조건부 타입을 활용한다면 as string을 안 써도 된다. 단, 이 방법의 경우 overload도 활용해야 한다

function removeSpaces<T>(text : T) : T extends string ? string : undefined;
function removeSpaces(text : any){	if(typeof text === "string"){return text.replaceAll(' ','')}
	else {return undefined}}
const result = removeSpaces('hello my name is Mingle');
const newResult = result.toUpperCase();
const otherResult = removeSpaces(undefined);
console.log(newResult); //대문자로 치환되서 나오게 된다.
console.log(typeof otherResult); //undefined

Distributive Conditional Types

//예시
type StringNumberSwitch<T> = T extends number ? string : number;
const testA = StringNumberSwitch<number | string>;
// 유니온 타입으로 할당하게 되면 하나하나 다 확인을 하기 때문에 유니온 타입으로 가지게 된다.
type Exclude<T,U> = T extends U ? never : T;
type A = Exclude<number | string | boolean, string>;
/*이 경우 경우의 수를 number,string | string,string | boolean, string으로 각각 조건부로 보게 되고,
결과는 number | never | boolean이 된다.
이 때 never는 사라지게 되므로 최종적으로 number | boolean type이 된다.*/

만약 유니온 타입으로 만들기 싫다면 ?
type Exclude<T,U> = [T] extends [U] ? never : T;
<- 이런식으로 각 조건문에 []를 하면 된다.

Inferring Within Conditional Types

조건부 내에서 특정 타입만 딱 추론해 내올 수 있는 것을 뜻한다.
이로써 true 분기 내에서 Type의 요소 타입을 어떻게 찾아내는지를 지정한다. 주로 함수 타입에서 반환 타입을 추출할 때 사용한다.

type ReturnType<T> = T extends () => infer R ? R : never;
type A = ReturnType<()=>string> // string
type B = ReturnType<()=>number> // R이 number로 자동으로 추론
type C = ReturnType<number> // 이럴때는 추론이 불가능하게 된다. any타입으로 판단하게 되고 에러를 일으킨다.

📚참고문헌
https://nomadcoders.co/typescript-for-beginners
https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard
https://www.typescriptlang.org/docs/handbook/2/types-from-types.html ⬅️ 해당 부분 중 Type Manipulation을 참고

profile
예비 초보 개발자의 프로젝트와 공부 기록일지

0개의 댓글