[React] 선언적으로, Type 확장에 용이하게

0_jin·2025년 3월 31일
1

프론트엔드 개발을 하면서, 팀 내에서 나의 역할에 대해 자주 고민하곤 한다.
비즈니스 로직은 주로 서버 개발자가 다루는 경우가 많다 보니, 사용자 경험을 고려한 개발을 하더라도 과연 내가 팀에 필수적인 존재일까? 라는 의문이 들 때가 있다.

마치 단순히 외주 개발을 수행하는 것과 다를 바 없다는 생각이 들기도 한다. 하지만 프론트엔드 개발자는 서비스 사용자와 가장 가까운 위치에서 제품을 다룬다. 이 때문에 데이터 분석을 위한 로깅을 설정하고, 빠른 템포로 A/B 테스트를 도입하여 최적의 사용자 경험을 찾아가는 역할이 필수적이다.

이런 고민 속에서 유지보수하기 좋은 코드, 선언적이고 Type 확장에 용이한 코드에 대해 생각하게 된다.

프론트엔드 개발자는 더 나은 서비스를 위해 더 많은 시도를 하고, 더 많은 변화를 만들어내야 하는 직군이다. 즉, 변경이 많을 수밖에 없는 환경에서 유지보수성을 고려한 코드 작성이 곧 실력이며, 개발의 핵심 가치가 된다.

따라서, 앞으로 다룰 내용들은 단순히 "좋은 코드 스타일"을 넘어서, 변화를 유연하게 수용할 수 있는 코드의 중요성과 이를 실현하는 방법에 대해 이야기하려 한다.


프론트엔드 개발에서 조건 분기 로직을 작성할 때, 유지보수성과 확장성을 고려한 코드 구조가 중요하다.
특히, 새로운 타입이 추가되거나 변경해야하는 경우 이를 쉽게 인지하고 대응할 수 있는 구조를 갖추는 것이 필수적이다.

다음과 같은 데이터가 있다고 가정해보자.

enum animal {
  dog = 'dog',
  cat = 'cat',
}

type dogType = {
  name: string;
  age: number;
  bark: () => void;
};

type catType = {
  name: string;
  age: number;
  leg: number;
};
const useHook = () => {
  return {
    [animal.dog]: {
      name: 'jenny',
      age: 8,
      bark: () => '낑낑',
    },
    [animal.cat]: {
      name: 'nabi',
      age: 3,
      leg: 4,
    },
  };
};

이 데이터를 기반으로 useHook의 반환값을 상세하게 렌더링하는 Component가 존재한다고 가정하자.


삼항 연산자

const Component = ({ target }: { target: animal }) => {
  const data = useHook();
  return (
    <div>
      {target === animal.dog ? (
        <div>
          <span>{data[target].name}</span>
          <span>{data[target].age}</span>
          <button onClick={data[target].bark}>click</button>
        </div>
      ) : (
        <div>
          <span>{data[target].name}</span>
          <span>{data[target].age}</span>
          <span>{data[target].leg}</span>
        </div>
      )}
    </div>
  );
};

위와 같은 방식은 코드가 간결하다는 장점이 있지만, Animal 타입이 확장될 경우 문제가 발생할 수 있다.
새로운 타입이 추가되어도 TypeScript가 경고를 발생시키지 않기 때문에, 개발자가 이를 기억하고 수동으로 수정해야 하는 문제가 발생한다.

이처럼 "기억에 의존한다" 는 것은 유지보수성을 저해하는 요소가 될 수 있다.


SwitchCase

switch-case 구문을 컴포넌트에 접목시켜 caseBy의 객체 타입이 value로 추론이 가능한 점에 대해
훌륭하다고 생각한다.

하지만, 각 case에 대한 타입 추론이 안된다는게 너무 아쉽다.

결국, as 를 사용한 타입 단언이 필요하다는 것인데, 이것 또한 에러 유발 코드가 된다고 생각한다.


&& 연산자

const Component = ({ target }: { target: animal }) => {
  const data = useHook();
  return (
    <div>
      {target === animal.dog && (
        <div>
          <span>{data[target].name}</span>
          <span>{data[target].age}</span>
          <button onClick={data[target].bark}>click</button>
        </div>
      )}
      {target === animal.cat && (
        <div>
          <span>{data[target].name}</span>
          <span>{data[target].age}</span>
          <span>{data[target].leg}</span>
        </div>
      )}
    </div>
  );
};

target 값에 따라 렌더링하고자 하는 컴포넌트가 명확하게 분리 되어있다.
심지어 SwitchCase Component에서 불가했던 타입 추론이 가능하다.
하지만, animal의 타입 확장에 대해서 우리는 알아차릴 수 없을 것이며 아무런 데이터도 렌더링하지 않을 것이다.


Switch-Case

내가 생각한 최고의 방법이다.

function TypeExtend(value: never) {
  return null;
}

const Component = ({ target }: { target: animal }) => {
  const data = useHook();
  return (
    <div>
      {(() => {
        switch (target) {
          case animal.dog:
            return (
              <div>
                <span>{data[target].name}</span>
                <span>{data[target].age}</span>
                <button onClick={data[target].bark}>click</button>
              </div>
            );
          case animal.cat:
            return (
              <div>
                <span>{data[target].name}</span>
                <span>{data[target].age}</span>
                <span>{data[target].leg}</span>
              </div>
            );
          default:
            return TypeExtend(target);
        }
      })()}
    </div>
  );
};

프론트엔드 개발을 하면서, switch-case와 즉시실행함수는 많이 사용한 경험이 없었다.

그럼에도 내가 즉시실행함수를 사용한 이유는 시점 이동 줄이기 에 대해 많은 공감이 되기 때문이다.

모든 상수 변수는 컴포넌트 내부에 선언해야해! 가 아니라,상황에 맞게 선언하면 될 것 같다.

뿐만아니라, switch-case는 case 별로 type을 털어낼 수 있는데,
결국 switch-case 구문의 default에 다다르면 해당 value의 type은 never가 된다.

이 때, TypeExtend 라는 never 타입의 value를 인자로 받는 함수를 통해,
animal Type이 확장 될 경우, default에는 never type의 target 이 아닌 확장된 type의 target 변수가 존재할것이다.

따라서,

다음과 같이 dx적으로 type 확장을 쉽게 파악할 수 있어 변경에 유연하게 대처할 수 있는 코드가 된다.


프론트엔드 개발자는 확장과 변경에 용이한 코드를 작성해야 한다고 생각하며,
확장과 변경에 용이한 코드가 어떤 코드일까에 대해 고찰을 작성해보았습니다.

더 나은 방법이나 아이디어가 있다면, 댓글로 소통을 진행해주면 좋을 것 같습니다.

읽어주셔서 감사합니다.

profile
가독성 좋은 코드, 성능 개선, 좋은 dx 경험, 자동화 를 생각합니다.

0개의 댓글

관련 채용 정보