[TS] Record - 저평가 우량주 타입

taez·2024년 1월 9일

짧은 글

목록 보기
1/4
post-thumbnail

📋 Summary

  • Typescript - Record 활용법

📝 Details

Typescript를 사용하는 가장 큰 이유는 강력한 타입 시스템을 바탕으로 안정성과 가독성을 높여 효율적으로 작업하기 위함일 것입니다.

이를 위해 Typescript 자체적으로 제공하는 Utility Type들이 있는데 이번 글에서 소개할 것은 그 중에서 유용성에 비해 주목을 덜 받고있다고 생각하는 Record 타입입니다.

정의

Record는 한마디로 객체의 키와 밸류의 타입을 정의하는 유틸리티 타입입니다.
Record의 기본 형식은 Record<Keys, Type> 으로 Keys가 키로 사용될 타입이며, Type은 그 키에 연결될 밸류의 타입입니다.
Keys에 다양한 타입이 들어갈 수 있지만 실질적으로 string 혹은 number의 리터럴 타입을 쓰게 되는 경우가 가장 많고 또한 효율적입니다.
Type에는 어떠한 타입도 사용할 수 있습니다.

사용 예시

Typescript Docs의 Record 사용 예시는 다음과 같은데 사실 처음 보면 굳이 Record 타입을 사용해야 할 이유를 공감 못할 사람이 더 많을 것입니다.

interface CatInfo {
	age: number;
	breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
	miffy: { age: 10, breed: "Persian" },
	boris: { age: 5, breed: "Maine Coon" },
	mordred: { age: 16, breed: "British Shorthair" },
};

const borisCatInfo = cats.boris; // CatInfo

솔직히 지금 다시 봐도 저걸 실제로 어떤 상황에 쓰라는건지 감이 안오는데, 그래도 일단 분석해보면

cats의 type을 Record<CatName, CatInfo>라 정의했는데 이는 이 Object의 key는 CatName으로 하고 value는 CatInfo type이라는 걸 명시한 것입니다.

Record<CatName, CatInfo>를 interface로 변환하면 다음과 같습니다.

interface CatNameInfo {
	miffy: CatInfo,
	boris: CatInfo,
	mordred: CatInfo,
}

const cats_interface: CatNameInfo = {
	miffy: { age: 10, breed: "Persian" },
	boris: { age: 5, breed: "Maine Coon" },
	mordred: { age: 16, breed: "British Shorthair" },
}

그럼 타입을 interface와 Record로 정의했을 때, 어떤 차이가 있는지 살펴봅시다.
고양이 한 마리가 더 생겨서 현재 3가지인 CatName 타입에 하나가 추가되었다고 하죠.

type CatName = "miffy" | "boris" | "mordred" | "neo"; // neo 추가

interface CatNameInfo {
	miffy: CatInfo,
	boris: CatInfo,
	mordred: CatInfo,
}

// ✅ OK
const cats_interface: CatNameInfo = {
	miffy: { age: 10, breed: "Persian" },
	boris: { age: 5, breed: "Maine Coon" },
	mordred: { age: 16, breed: "British Shorthair" },
}


// ❌ Property 'neo' is missing in type
const cats_record: Record<CatName, CatInfo> = {
	miffy: { age: 10, breed: "Persian" },
	boris: { age: 5, breed: "Maine Coon" },
	mordred: { age: 16, breed: "British Shorthair" },
};

CatName에 neo가 추가되었지만 CatNameInfo interface에는 추가되지 않았으므로 interface로 타입을 지정한 cats_interface는 여전히 3 고양이의 정보를 가지고 있습니다.
하지만 Record 타입의 cats_record는 새로 추가된 neo에 해당하는 밸류가 없다고 즉시 타입 오류를 내뱉는 것을 볼 수 있습니다.

Record가 타입 안정성과 관련되었다는것이 이런 의미입니다.
티를 내지 않는 interface와 달리 컴파일 단계에서 key가 누락된 것을 확인할 수 있기 때문에 어떤 객체에 어떤 key가 필요한 지 즉시 알 수 있고 반영할 수 있게 합니다.

조금 더 실용적인? 예시

고양이도 좋지만 조금 더 실용적인 예시를 살펴봅시다.
만만한 Todo list에서 Todo의 진행 상태에 따라 count 하는 코드입니다.

Typescript Playground <= 직접 실행할 수 있습니다.

type TodoStatus = 'TODO' | 'PROGRESS' | 'DONE';

interface Todo {
	title: string,
	status: TodoStatus,
	description: string,
}

// Record type 정의
type TodoStatusCount = Record<TodoStatus, number>;

// initialize 함수
const emptyTodoStatusCount = (): TodoStatusCount => ({ TODO: 0, PROGRESS: 0, DONE: 0 });

// todo를 status에 따라 count
const getTodoStatusCount = (todos: Todo[]) => todos.reduce<TodoStatusCount>(
	(statusCount, todo) => {
		statusCount[todo.status] += 1;
		return statusCount;
	},
	emptyTodoStatusCount()
);

//
const todos: Todo[] = [
    { title: 'Todo 1', status: 'DONE',     description: '1 - Done' },
    { title: 'Todo 2', status: 'PROGRESS', description: '2 - Progress' },
    { title: 'Todo 3', status: 'TODO',     description: '3 - Todo' },
    { title: 'Todo 4', status: 'PROGRESS', description: '4 - Progress' },
];

console.log(getTodoStatusCount(todos)); // { "TODO": 1, "PROGRESS": 2, "DONE": 1 }

언제 Record 타입을 사용하는가?

  1. 정의된 키 집합: 키가 미리 정의된 집합에 속하는 경우, Record 타입을 사용하여 타입 안정성을 보장하는 것이 좋습니다.
  2. 타입 추론과 유지보수: Record 타입은 코드의 가독성과 유지보수성을 향상시키며, 타입 추론을 통해 개발자가 보다 명확한 코드를 작성하도록 돕습니다.
  3. 유틸리티 타입과의 조합: Record 타입은 다른 Typescript 유틸리티 타입(Partial, Readonly 등)과 조합하여 사용하기에 용이합니다.

🔗 Links/References

profile
흔하지 않은 개발자

0개의 댓글