타입스크립트를 사용할 때에는 타입 단언과 타입 가드문에 따라 다른 컴포넌트로 만들어서 보여주는 방식을 사용하였습니다. 그러나 이는 다른 타입이 들어왔을 때 타입 세이프티한 환경을 제공하지 못하고 에러를 내어 프로그램을 멈추는 치명적인 오류를 발생시킵니다. 타입 단언을 하지않고 또 타입 가드문을 사용하지 않고 만들 수 있는 방법은 없을까? 고민을 하여 작성하게 되었습니다.
타입스크립트는 일반 변수, 매개 변수, 객체 속성에 변수: type
과 같은 형태로 타입을 지정할 수 있습니다. 이를 이용하여 객체 프로퍼티들을 모두 강제하기 때문에 휴먼 에러(사용자가 입력을 하지 않거나, 잘못된 키값을 입력한 경우)를 방지할 수 있습니다.
type TodoType = {
title: () => void;
content: () => void;
};
// Type Declaration
const todoDec : TodoType = {
title() {
console.log('오늘 할 내용입니다!');
},
content() {
console.log('오늘 할 세부내용입니다.');
},
};
타입 단언은 타입스크립트의 컴파일러보다 프로그램을 개발하는 개발자가 타입에 대한 확신이 있을 때 사용하는 방법입니다.
기본적으로 as
, is
와 같이 사용할 수 있습니다.
만들어진 객체에 타입을 단언함으로서 해당 todoAss를 TodoType으로 신뢰하고 있기 때문에 객체의 구성요소가 없어도 해당 구성으로 이루어져있다고 컴파일러가 믿게됩니다.
// Type Assertion
const todoAss = {} as TodoType;
console.log(todoAss.content); // no Error
다음 name은 Capts를 가리키고 있습니다. 또한 string이라는 타입을 가지고 있습니다.
let name: string = 'Capt';
해당 문법을 다음과 같이 변경하면 name은 Capt라는 값을 가지고 있고 string 타입이다 라는 것을 알 수 있습니다.
let name: = 'Capt' as string;
만약 name의 값을 타입 단언을 한 이후로 변경을 할려고하면 에러가 발생합니다.
let name = 'capts' as string;
// Type 'number' is not assignable to type 'string'.
타입 가드를 활용하여 자동 완성 지원되지 않는 속성을 자동 완성 되도록 만들어 보는 문제입니다.
export enum TodoEnum {
DAILY = 'DAILY',
WEEKLY = 'WEEKLY',
MONTHLY = 'MONTHLY',
}
export type TodoDataBase =
| {
type: TodoEnum.DAILY;
content: string;
title: string;
}
| {
type: TodoEnum.WEEKLY;
total: Date;
}
| {
type: TodoEnum.MONTHLY;
goal: string;
};
export type TodoType<T extends TodoEnum = TodoEnum> = Extract<TodoDataBase, { type: T }>;
각 함수들은 다음과 같이 전달 받은 코드 todo의 type과 TodoEnum의 문자열 값을 비교한 뒤에 만약 값이 같으면 todo는 해당 타입이다라는 것을 is
를 통하여 알려줍니다.
const OneTodo: React.FC<{ todo: TodoDataBase }> = ({ todo }) => {
let TodoComponent: React.ReactNode = <>Is Happy Todo</>;
if (todo as IDaily) {
TodoComponent = (
<>
{todo.goal} {todo.type}
</>
);
}
if (todo as IMonth) {
TodoComponent = (
<>
{todo.total} {todo.type}
</>
);
}
if (todo as IsWeekly) {
TodoComponent = (
<>
{todo.content} {todo.title} {todo.type}
</>
);
}
return <div>{TodoComponent}</div>;
};
이처럼 todo의 타입을 단언으로 보장한 뒤 조건문으로 설정하는 방법도 있지만, 보시다시피 가독성과 효율이 매우 떨어집니다.
그럼 이번에는 is
를 사용한 함수로 타입을 단언을 한다면 어떨까요?
const isDaily = (todo: TodoDataBase): todo is ITodoDaily => {
return todo.type === TodoEnum.DAILY;
};
const isWeekly = (todo: TodoDataBase): todo is ITodoWeekly => {
return todo.type === TodoEnum.WEEKLY;
};
const isMonthly = (todo: TodoDataBase): todo is ITodoMonthly => {
return todo.type === TodoEnum.MONTHLY;
};
if (isMonthly(todo)) {
TodoComponent = (
<>
{todo.goal} {todo.type}
</>
);
}
if (isWeekly(todo)) {
TodoComponent = (
<>
{todo.total} {todo.type}
</>
);
}
if (isDaily(todo)) {
TodoComponent = (
<>
{todo.content} {todo.title} {todo.type}
</>
);
}
다음은 피드백을 받은 내용입니다.
- 타입 단언을 사용하지 않는 것을 습관화 해보세요. 이슈가 있는 것은 아니지만 타입이 단언 되어있기에 다른 타입이 들어왔을 때 타입세이프티한 환경을 제공하지 못하고 에러를 내버립니다.
- 타입가드문에 따라 다른 html을 return하는 것은 좋은 패턴이 아닙니다. object, useCaballck 혹은 component를 사용하여 하나의 html만 return 하도록 해보세요.
타입 단언 as
, is
를 사용하지 않고 object의 객체 값을 이용한 타입 추론을 활용한 컴포넌트 생성입니다.
if (todo.type === TodoEnum.DAILY) {
// 이곳에서의 todo는 DAILY 타입을 갖고있습니다.
return (
<>
<h3>오늘의 목표입니다. {todo.title}</h3>
<span>오늘의 할일입니다. {todo.content}</span>
</>
);
}
if (todo.type === TodoEnum.WEEKLY) {
// 이곳에서의 todo는 WEEKLY 타입을 갖고있습니다.
return (
<>
<h3>주간 목표의 결과입니다. {todo.total.toUTCString()}</h3>
span>새로운 주도 화이팅입니다.</span>
</>
);
}
if (todo.type === TodoEnum.MONTHLY)
// 이곳에서의 todo는 MONTHLY 타입을 갖고있습니다.
return <h3>이번 달의 목표의 결과입니다. {todo.goal}</h3>;
return <h3>todo의 타입이 아닙니다.</h3>
🙄 타입 단언을 사용해서 만드는 것이 더 좋은 것 같은데 왜 문제가 되는 것일까요?
타입 단언을 사용하는 것이 문제가 되는 것은 아닙니다. 하지만 다음과 같은 문제점을 야기할 수 있습니다.
타입 안정성의 감소: 타입 단언을 사용하면 TypeScript가 제공하는 타입 안정성이 감소할 수 있습니다. 컴파일러가 타입을 강제하는 대신, 개발자가 직접 타입을 단언하게 되면 코드에서 타입 에러가 발생할 수 있는데도 이를 감지하지 못할 수 있습니다.
유지보수의 어려움: 타입 단언을 남용하면 코드의 유지보수가 어려워질 수 있습니다. 특히, 타입 단언을 한 부분을 수정할 때 해당 부분과 관련된 다른 부분들을 찾아 수정해야 할 수 있습니다.
신뢰성의 문제: 개발자가 직접 타입을 단언하면 이에 대한 책임이 개발자에게 있습니다. 만약 실제로 타입이 예상과 다르게 동작한다면, 런타임 에러가 발생할 수 있습니다.
타입 체킹의 장점 상실: TypeScript는 강력한 타입 체킹을 제공하여 런타임 에러를 사전에 방지하는데 중점을 두고 있습니다. 타입 단언을 남용하면 이러한 장점을 상실할 수 있습니다.
😥 그럼 타입 단언은 사용하면 안되는 것일까요?
그렇지 않습니다. 타입 단언이 필요한 시점도 있습니다 다음과 같은 상황에서는 사용하면 좋습니다.
외부 라이브러리와의 상호 작용을 할 때: 외부 라이브러리에서 타입 정보를 얻기 어려운 경우, 타입 단언을 사용하여 컴파일러에게 해당 타입임을 알려줄 수 있습니다.
복잡한 데이터 변환: 어떤 이유로 인해 컴파일러가 특정 타입을 정확하게 추론하지 못하는 경우, 개발자가 타입 단언을 사용하여 정확한 타입으로 변환할 수 있습니다.
프로토타이핑 단계: 초기에는 프로토타이핑 단계에서 빠르게 코드를 작성하고 테스트하기 위해 타입 단언을 사용할 수 있습니다.
프로토타이핑(Prototyping)은 제품이나 소프트웨어의 초기 버전을 빠르게 개발하고 시험하는 과정을 말합니다.
타입 단언만을 사용하여 각기 다른 컴포넌트들을 리턴을 하였었는데 무슨 문제를 만들 수 있는지를 생각하지 않고 타입스크립트의 자동완성 기능만을 보고 풀이했던 문제였습니다. 타입 단언의 경우 사용해야 할 때를 다시 한 번 생각을 해보고 사용을 하는 것이 좋을 것 같습니다.
타입단언을 할 때에는 다음과 같은 상황들을 고려하자.
1. 필요성 검토: 왜 타입 단언이 필요한지를 고민해보고, 컴파일러가 제공하는 타입 추론 기능을 최대한 활용할 수 있는지를 검토해야 합니다.
타입 변경 가능성: 코드의 유지보수성을 고려하여, 나중에 타입이 변경될 수 있는 상황을 고려합니다. 타입 단언을 사용하면 타입이 변경될 때 수동으로 업데이트해야 하는 번거로움이 생길 수 있습니다.
외부 라이브러리와의 상호 작용: 외부 라이브러리와의 상호 작용에서는 타입 정보를 명시적으로 제공해야 할 때가 있습니다. 이때는 적절한 타입 단언을 사용하는 것이 타당할 수 있습니다.