[TS] 개발자처럼 사용하기 4 : Type-Safty한 환경 제공하기

Zero·2023년 11월 11일
0
post-thumbnail

살펴보게된 계기

타입스크립트를 사용할 때에는 타입 단언과 타입 가드문에 따라 다른 컴포넌트로 만들어서 보여주는 방식을 사용하였습니다. 그러나 이는 다른 타입이 들어왔을 때 타입 세이프티한 환경을 제공하지 못하고 에러를 내어 프로그램을 멈추는 치명적인 오류를 발생시킵니다. 타입 단언을 하지않고 또 타입 가드문을 사용하지 않고 만들 수 있는 방법은 없을까? 고민을 하여 작성하게 되었습니다.

타입 선언 (Type Declaration)

타입스크립트는 일반 변수, 매개 변수, 객체 속성에 변수: type과 같은 형태로 타입을 지정할 수 있습니다. 이를 이용하여 객체 프로퍼티들을 모두 강제하기 때문에 휴먼 에러(사용자가 입력을 하지 않거나, 잘못된 키값을 입력한 경우)를 방지할 수 있습니다.

사용 방법

type TodoType = {
        title: () => void;
        content: () => void;
    };

// Type Declaration
const todoDec : TodoType = {
  title() {
    console.log('오늘 할 내용입니다!');
  },
  content() {
    console.log('오늘 할 세부내용입니다.');
  },
};

타입 단언 (Type Assertion)

타입 단언은 타입스크립트의 컴파일러보다 프로그램을 개발하는 개발자가 타입에 대한 확신이 있을 때 사용하는 방법입니다.

기본적으로 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}
	</>
	);
  }

다음은 피드백을 받은 내용입니다.

  1. 타입 단언을 사용하지 않는 것을 습관화 해보세요. 이슈가 있는 것은 아니지만 타입이 단언 되어있기에 다른 타입이 들어왔을 때 타입세이프티한 환경을 제공하지 못하고 에러를 내버립니다.
  1. 타입가드문에 따라 다른 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>

🙄 타입 단언을 사용해서 만드는 것이 더 좋은 것 같은데 왜 문제가 되는 것일까요?

타입 단언을 사용하는 것이 문제가 되는 것은 아닙니다. 하지만 다음과 같은 문제점을 야기할 수 있습니다.

  1. 타입 안정성의 감소: 타입 단언을 사용하면 TypeScript가 제공하는 타입 안정성이 감소할 수 있습니다. 컴파일러가 타입을 강제하는 대신, 개발자가 직접 타입을 단언하게 되면 코드에서 타입 에러가 발생할 수 있는데도 이를 감지하지 못할 수 있습니다.

  2. 유지보수의 어려움: 타입 단언을 남용하면 코드의 유지보수가 어려워질 수 있습니다. 특히, 타입 단언을 한 부분을 수정할 때 해당 부분과 관련된 다른 부분들을 찾아 수정해야 할 수 있습니다.

  3. 신뢰성의 문제: 개발자가 직접 타입을 단언하면 이에 대한 책임이 개발자에게 있습니다. 만약 실제로 타입이 예상과 다르게 동작한다면, 런타임 에러가 발생할 수 있습니다.

  4. 타입 체킹의 장점 상실: TypeScript는 강력한 타입 체킹을 제공하여 런타임 에러를 사전에 방지하는데 중점을 두고 있습니다. 타입 단언을 남용하면 이러한 장점을 상실할 수 있습니다.

😥 그럼 타입 단언은 사용하면 안되는 것일까요?

그렇지 않습니다. 타입 단언이 필요한 시점도 있습니다 다음과 같은 상황에서는 사용하면 좋습니다.

  1. 외부 라이브러리와의 상호 작용을 할 때: 외부 라이브러리에서 타입 정보를 얻기 어려운 경우, 타입 단언을 사용하여 컴파일러에게 해당 타입임을 알려줄 수 있습니다.

  2. 복잡한 데이터 변환: 어떤 이유로 인해 컴파일러가 특정 타입을 정확하게 추론하지 못하는 경우, 개발자가 타입 단언을 사용하여 정확한 타입으로 변환할 수 있습니다.

  3. 프로토타이핑 단계: 초기에는 프로토타이핑 단계에서 빠르게 코드를 작성하고 테스트하기 위해 타입 단언을 사용할 수 있습니다.

프로토타이핑(Prototyping)은 제품이나 소프트웨어의 초기 버전을 빠르게 개발하고 시험하는 과정을 말합니다.

😤결론

타입 단언만을 사용하여 각기 다른 컴포넌트들을 리턴을 하였었는데 무슨 문제를 만들 수 있는지를 생각하지 않고 타입스크립트의 자동완성 기능만을 보고 풀이했던 문제였습니다. 타입 단언의 경우 사용해야 할 때를 다시 한 번 생각을 해보고 사용을 하는 것이 좋을 것 같습니다.

타입단언을 할 때에는 다음과 같은 상황들을 고려하자.
1. 필요성 검토: 왜 타입 단언이 필요한지를 고민해보고, 컴파일러가 제공하는 타입 추론 기능을 최대한 활용할 수 있는지를 검토해야 합니다.

  1. 타입 변경 가능성: 코드의 유지보수성을 고려하여, 나중에 타입이 변경될 수 있는 상황을 고려합니다. 타입 단언을 사용하면 타입이 변경될 때 수동으로 업데이트해야 하는 번거로움이 생길 수 있습니다.

  2. 외부 라이브러리와의 상호 작용: 외부 라이브러리와의 상호 작용에서는 타입 정보를 명시적으로 제공해야 할 때가 있습니다. 이때는 적절한 타입 단언을 사용하는 것이 타당할 수 있습니다.

profile
0에서 시작해, 나만의 길을 만들어가는 개발자.

0개의 댓글