📘 학습 후기
타입스크립트를 학습하다보면 타입스크립트가 단순히 타입을 지정하는 언어가 아니라, 개발자가 실수할 가능성을 줄이고 안전한 코드를 작성하도록 도와주는 강력한 도구라는 걸 확실히 깨닫게 된다.
알고 쓰면 타입스크립트의 장점을 확실히 느끼며 개발할 수 있을 것 같다.
우아한 타입 스크립트 with 리액트 학습 내용을 정리했다.
프로젝트에서 상숫값을 관리할 때 객체를 사용한다.
컴포넌트나 함수에서 이런 객체를 사용할 때 열린 타입으로 설정할 수 있다.
키 타입을 해당 객체에 존재하는 키값으로 설정하는 것이 아니라 string으로 설정하면 colors에 어떤 값이 추가될지 모르기 때문에 getColorHex 함수의 반환 값은 any가 된다.
const colors = {
red: "#F45452",
green: "#0C952A",
};
const getColorHex = (key: string) => colors[key];
객체 타입을 저확하고 안정하게 설정하는 방법을 알아보자.
Atom 단위의 작은 컴포넌트는 다양한 환경에서 유연하게 사용될 수 있도록 구현되어야 하는데 이러한 설정값은 props로 넘겨주도록 설계한다.
Atom 컴포넌트에서는 theme 객체의 색상, 폰트 사이즈의 키값을 props로 받은 뒤 theme 객체에서 값을 받아오도록 설계하여 문제를 해결한다.
어떻게하면 정확하고 안전한 값을 받아오도록 설계할 수 있을지 알아보자.
// 1. 타입
interface Props {
color?: string;
backgroundColor?: string;
}
// 2. 스타일
const Button = styled.button<Props>`
color: ${({ color })=>theme.colors[color]};
backgroundColor: ${({ backgroundColor })=>theme.colors[backgroundColor]};
`;
// 3. 버튼 컴포넌트
<Button backgroundColor="red" />; // "blue"를 넣어도 에러가 발생하지 않는다.
해당 문제는 theme 객체로 타입을 구체화해서 해결할 수 있다.
keyof 연산자는 객체 타입을 받아 해당 객체의 키값을 string 또는 number의 리터럴 유니온 타입을 반환한다.
interface ColorType {
red: string;
green: string;
}
type ColorKeyType = Keyof ColorType; // 'red'| 'green'
type ColorsType = typeof colors;
/*
{
red: string;
green: string;
}
*/
// 1. keyof typeof 타입 정의
interface Props {
color: keyof typeof theme.colors;
backgroundColor: keyof typeof theme.colors;
}
// 2. 스타일
const Button = styled.button<Props>`
color: ${({ color })=>theme.colors[color]};
backgroundColor: ${({ backgroundColor })=>theme.colors[backgroundColor]};
`;
// 3. 사용
const App = () => {
return (
<>
<Button color="red" backgroundColor="green">button1</Button>
<Button color="red" backgroundColor="blue">Error</Button>
<>
);
};
Record<K, V>는 타입스크립트의 유틸리티 타입 중 하나로, 특정 키와 값을 가지는 객체 타입을 정의할 때 사용한다.
객체 선언 시 키가 어떤 값인지 명확하지 않다면 string이나 number 같은 원시 타입으로 명시하곤 한다.
타입스크립트는 키가 유효하지 않더라도 타입상으로는 문제가 없기 때문에 오류를 표시하지 않기 때문에 예상치 못한 런타임 에러를 야기할 수 있다.
type Category = string;
interface Food {
name: string;
}
// Category를 키로 사용하는 객체
const foodByCategory: Record<Category, Food[]> = {
한식: [{name: "떡볶이"}, {name: "삼겹살"}];
일식: [{name: "초밥"}],
}
foodByCategory["양식"]; // Food[]로 추론
foodByCategory["양식"].map((food) => console.log(food.name)); // 오류 발생하지 않는다.
Record를 명시적으로 사용하는 방법을 알아보자.
옵셔널 체이닝은 객체의 속성을 찾을 때 중간에 null 또는 undefined가 있어도 오류 없이 안전하게 접근하는 방법이다.
?. 문법으로 표현되며 옵셔널 체이닝을 사용할 때 중간에 null 또는 undefined인 속성이 있는지 검사한다.
속성이 존재하면 해당 값을 반환하고, 존재하지 않으면 undefined를 반환한다.
foodByCategory["양식"]?.map((food) => console.log(food.name));
키가 유한한 집합이라면 유닛 타입을 사용할 수 있다.
유닛 타입은 다른 타입으로 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입을 말한다.
type Category = "한식"|"일식"
interface Food {
name: string;
}
// Category를 키로 사용하는 객체
const foodByCategory: Record<Category, Food[]> = {
한식: [{name: "떡볶이"}, {name: "삼겹살"}];
일식: [{name: "초밥"}],
}
// Property '양식' does not exist on type 'Record<Category, Food[]>'
foodByCategory["양식"];
그러나 키가 무한해야 하는 상황에서는 적합하지 않다.
키가 무한한 상황에서는 Partial을 사용하여 해당 값이 undefined일 수 있는 상태임을 표현할 수 있다.
// 1. PartialRecord 타입 선언
type PartialRecord<K extends string, T> = Partial<Record<K, T>>;
type Category = string;
interface Food {
name: string;
}
// Category를 키로 사용하는 객체
const foodByCategory: PartialRecord<Category, Food[]> = {
한식: [{name: "떡볶이"}, {name: "삼겹살"}];
일식: [{name: "초밥"}],
}
foodByCategory["양식"]; // Food[] 또는 undefined 타입으로 추론
foodByCategory["양식"].map((food) => console.log(food.name)); // Object is possibly 'undefined'
foodByCategory["양식"]?.map((food) => console.log(food.name)); // OK