ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TypeScript로 불필요한 변수 선언 없애기: `satisfies` 활용법
    Javascript 2024. 8. 24. 10:34

     

    이번 글에서는 TypeScript에서 satisfies를 활용하여 코드 내 불필요한 변수 선언을 줄이는 방법을 소개합니다.

     

    satisfies는 코드를 더 간결하고 효율적으로 만드는 데 유용한 기능입니다.

     

    변수 선언 없이 타입을 지정하는 방법

    타입 주석을 통해 변수 선언 시 타입을 지정할 수 있지만, 이로 인해 불필요한 변수를 선언해야 하는 경우가 많습니다.

     

    satisfies를 사용하면 이러한 불필요한 변수를 줄일 수 있습니다. 구체적인 예시를 통해 알아보겠습니다.

     

    예시 1: 타입 포괄성 체크

    switch 문에서 모든 가능한 타입을 포괄적으로 확인하는 방법으로 never 타입을 활용할 수 있습니다.

     

    다음은 그 예시입니다.

    type Animal = "dog" | "cat" | "pig";
    
    const awesomeFunction = (animal: Animal) => {
      switch (animal) {
        case "cat":
          return "meow";
        case "dog":
          return "bowwow";
        case "pig":
          return "oink oink";
        default:
          const _: never = animal;
      }
    };

     

    이 코드는 모든 case를 처리했기 때문에 default 절에 도달했을 때 animalnever 타입이 됩니다.

     

    Animal에 새로운 값이 추가되면 컴파일러가 이를 감지하여 알려줍니다.

     

    하지만 위 코드는 불필요한 변수를 선언하고 있습니다. satisfies를 이용해 이를 제거할 수 있습니다.

     

    satisfies를 사용한 개선

    satisfies를 사용하여 불필요한 변수를 제거한 코드는 다음과 같습니다.

    type Animal = "dog" | "cat" | "pig";
    
    const awesomeFunction = (animal: Animal) => {
      switch (animal) {
        case "cat":
          return "meow";
        case "dog":
          return "bowwow";
        case "pig":
          return "oink oink";
        default:
          return animal satisfies never;
      }
    };

     

    이렇게 하면 불필요한 변수 선언 없이도 필요한 기능을 구현할 수 있습니다.

     

    default 절은 남은 animal이 없는지를 확인하는 역할을 하므로, 변수 선언이 필요하지 않습니다.

     

    default 절에서 return animal을 하면 awesomeFunction의 타입은 다음과 같습니다.

    typeof awesomeFunction = (animal: Animal) => "meow" | "bowwow" | "oink oink"

     

    만약 return animal을 하지 않는다면 타입에 undefined가 포함됩니다. 따라서 return을 명시하는 것이 좋습니다.

     

    예시 2: 함수 인자에 적용하기

    라이브러리에서 종종 "인자로 객체를 받지만, 키는 문자열이면 상관없다"는 함수를 보게 됩니다.

     

    이럴 때도 satisfies가 유용합니다. 예를 들어, zod를 사용한 폼을 구현해보겠습니다.

     

     

    satisfies 없이 zod 스키마 선언

     

    아래는 기본적인 폼 구현입니다.

    import { zodResolver } from "@hookform/resolvers/zod";
    import { useForm } from "react-hook-form";
    import { z } from "zod";
    
    const awesomeFormSchema = z.object({
      name: z.string(),
      address: z.string(),
    });
    
    type AwesomeFormSchema = z.infer<typeof awesomeFormSchema>;
    
    const AwesomeFormPage = () => {
      const { register } = useForm<AwesomeFormSchema>({
        resolver: zodResolver(awesomeFormSchema),
      });
    
      return (
        <form>
          <label>
            이름
            <input {...register("name")} />
          </label>
          <label>
            주소
            <input {...register("address")} />
          </label>
        </form>
      );
    };

     

    여기서 "이름"과 "주소" 필드에 필수 입력과 오류 메시지를 추가해보겠습니다.

    import { zodResolver } from "@hookform/resolvers/zod";
    import { useForm } from "react-hook-form";
    import { z } from "zod";
    
    const awesomeFormSchema = z.object({
      name: z.string().min(1, { message: "이름은 필수 항목입니다." }),
      address: z.string().min(1, { message: "주소는 필수 항목입니다." }),
    });
    
    type AwesomeFormSchema = z.infer<typeof awesomeFormSchema>;
    
    const AwesomeFormPage = () => {
      const {
        register,
        formState: { errors },
      } = useForm<AwesomeFormSchema>({
        resolver: zodResolver(awesomeFormSchema),
      });
    
      return (
        <form>
          <label>
            이름
            <input {...register("name")} />
            <p>{errors.name?.message}</p>
          </label>
          <label>
            주소
            <input {...register("address")} />
            <p>{errors.address?.message}</p>
          </label>
        </form>
      );
    };

     

    하지만 오류 메시지와 라벨에 사용된 문구가 각각 하드코딩되어 있는 점이 아쉽습니다.

     

    이를 통일해보겠습니다.

    const awesomeFormLabel = {
      name: "이름",
      address: "주소",
    };
    
    const awesomeFormSchema = z.object({
      name: z.string().min(1, { message: `${awesomeFormLabel.name}은 필수 항목입니다.` }),
      address: z.string().min(1, { message: `${awesomeFormLabel.address}은 필수 항목입니다.` }),
    });
    
    type AwesomeFormSchema = z.infer<typeof awesomeFormSchema>;
    
    const AwesomeFormPage = () => {
      const {
        register,
        formState: { errors },
      } = useForm<AwesomeFormSchema>({
        resolver: zodResolver(awesomeFormSchema),
      });
    
      return (
        <form>
          <label>
            {awesomeFormLabel.name}
            <input {...register("name")} />
            <p>{errors.name?.message}</p>
          </label>
          <label>
            {awesomeFormLabel.address}
            <input {...register("address")} />
            <p>{errors.address?.message}</p>
          </label>
        </form>
      );
    };

     

    이렇게 하면 awesomeFormLabelawesomeFormSchema 사이에 타입적인 연결이 없기 때문에 다른 키를 추가할 수 있습니다.

     

    이를 방지하기 위해 satisfies를 사용해봅시다.

     

    satisfies로 개선하기

    satisfies를 사용하면 불필요한 변수를 없애고 필요한 것을 구현할 수 있습니다.

    import { z, type ZodSchema } from "zod";
    
    type SchemaKey = "name" | "address";
    
    const awesomeFormLabel = {
      name: "이름",
      address: "주소",
    } as const satisfies Record<SchemaKey, string>;
    
    const awesomeFormSchema = z.object({
      name: z.string().min(1, { message: `${awesomeFormLabel.name}은 필수 항목입니다.` }),
      address: z.string().min(1, { message: `${awesomeFormLabel.address}은 필수 항목입니다.` }),
    } satisfies Record<SchemaKey, ZodSchema>);

     

    이렇게 하면 불필요한 변수를 없애고 awesomeFormLabelawesomeFormSchema 간에 타입적 연결을 설정할 수 있습니다.

    satisfies를 사용하면 변수를 선언하지 않고도 타입을 강제할 수 있습니다.

     

    이처럼 satisfies는 코드를 간결하게 하고 타입 안전성을 높이는 데 유용합니다.

     

    요약

    이번 글에서는 satisfies를 활용하여 불필요한 변수 선언을 줄이고 타입 안전성을 높이는 방법을 알아보았습니다.

     

    다양한 상황에서 satisfies가 유용하게 쓰일 수 있으니, 아직 사용해보지 않으셨다면 꼭 시도해보시기 바랍니다!

     

    그럼.

Designed by Tistory.