mingg IT

[이펙티브 타입스크립트] 5장 any 다루기 본문

FrontEnd

[이펙티브 타입스크립트] 5장 any 다루기

mingg123 2023. 8. 10. 23:34

https://mingg123.tistory.com/226

 

[TS] 이펙티브 타입스크립트 리뷰

1장 타입스크립트 알아보기 https://mingg123.tistory.com/225 [이펙티브 타입스크립트] 1장 구조적 타이핑 mingg IT [이펙티브 타입스크립트] 1장 구조적 타이핑 본문 FrontEnd [이펙티브 타입스크립트] 1장 구

mingg123.tistory.com

 

자바스크립트는 런타임시에 변수의 타입이 정해지는 동적인 언어이다.

타입스크립트는 정적이면서도 동적인 특징을 가지고 있기 때문에 프로그램의 일부에만 적용시킬 수 있다. 

이로인해 자바스크립트에 타입스크립트를 처음 적용하거나, 일부를 적용할 때 any를 많이 사용하는데

any는 프로젝트 전체에 많은 영향을 끼칠 수 있기 때문에 장점을 살리면서 단점을 줄이는 방법에 대해 설명한다. 

 

아이템 38 any 타입은 가능한 한 좁은 범위에서만 사용하기 

 

지양하는 방식

function f1() {
  const x: any = expressReturingFoo();
  processBar(x);
}

변수 x의 any 타입이 processBar호출 이후에도 영향에 가게 된다.

이는 만약 f1함수에서 x를 리턴할 경우, x에 대한 타입 체크가 되지 않기 때문에 문제가 전파된다. 

 

 

지향하는 방식

function f1() {
  const x = expressReturingFoo();
  processBar(x as any);
}

해당 방식을 이용하면 any타입이 processBar 함수의 매개변수에만 영향을 미친다. 

 

 

또 다른 방식

function f2() {
  const x = expressReturingFoo();
  // @ts-ignore
  processBar(x);
}

@ts-ignore를 이용하면 processBar에서 타입 관련된 오류가 없어진다. 

오류는 사라졌지만 다른 곳에서 문제가 발생할 수 있기 때문에 근본적인 원인을 찾아서 해결하는 편이 좋다.

 

요약

  • any의 사용 범위는 최소한으로 좁혀야 한다.
  • 함수의 반환 타입이 any일 경우 타입 안전성이 불안해진다. any타입을 반환하지 않도록 하자.
  • 강제로 타입 오류를 제거하고 싶다면 @ts-ignore를 사용하자 

 

 

아이템 39 any를 구체적으로 변형해서 사용하기

any는 숫자, 문자열, 배열, 객체, 정규식, 함수, 클래스 DOM, null, undefined등 모두 포함된다. 

이처럼 any는 구체적인 타입을 정의하는데 방해된다. 

 

지양하는 방식

function getLengthBad(array: any) {
  return array.length;
}

 

 

지향하는 방식

function getLengthGood(array: any[]) {
  return array.length;
}

 

any가 아닌 any[]를 사용하였을 때 이점 

  • 함수 내의 array.length 타입이 체크됨
  • 함수의 반환 타입이 any대신 number로 추론됨
  • 함수가 호출될 때 매개변수가 배열인지 아닌지 체크 해줌 

 

함수의 매개변수가 객체지만 값을 알 수 없을 경우1

function hasTweleveLetterKey(o: {[key: string]: any}) {
  for(const key in o) {
    if(key.length === 12) {
      return true;
    }
  }
  return false;
}

{[key: string]:any} 를 이용한다. 

간혹 api 응답이 정해져있는 형식이 아닐 경우에 사용하면 될 듯 하다. 

 

 

함수의 매개변수가 객체지만 값을 알 수 없을 경우2

function hasTweleveLetterKey(o: object) {
  for(const key in o) {
    if(key.length === 12) {
      console.log(key, o[key]) // 에러 발생 {} 형식에 인덱스 시크니처가 없음으로 요소에 암시적으로 'any' 형식이 있습니다.
      return true;
    }
  }
  return false;
}

object를 이용할경우 key를 이용해서 속성에 접근할때 타입 에러가 발생한다.

object 타입은 객체의 키를 열거할 수는 있지만 속성에 접근할 수 없다는 점이 {[key: string]:any} 와 다르다. 

 

요약

  • any를 사용할 때는 충분히 검토해야 한다
  • any보다 정확하게 타입설계를 할 수 있도록 any[] 혹은 {[key: string]:any} 이런식으로 사용해야 한다. 

 

 

아이템 40 함수 안으로 타입 단언문 감추기

함수에 타입을 작성하다보면, 외부로 드러나는 함수의 타입 정의는 간단하지만, 내부 복잡한 로직은 타입 구현이 힘든 경우가 있다.

모든 함수를 안전한 타입으로 구현하는 것이 이상적이지만, 불필요한 예외 상황까지 타입 정보를 힘들게 구성할 필요는 없다.

 

예시

declare function shallowEqual(a: any, b: any): boolean;

function cacheLast<T extends Function>(fn: T): T {
  let lastArgs: any[]|null = null;
  let lastResult: any;
  return function(...args: any[]) { // 에러 발생 (...args:any[]=> any 형식은 T형식에 할당할 수 없다
    if(!lastArgs || !shallowEqual(lastArgs, args)) {
      lastResult = fn(...args);
      lastArgs = args;
    }
    return lastResult;
  }
}

return function의 반환 타입과 원본 함수의 T 타입이 다르기 때문에 에러가 발생한다.

 

해결법

function cacheLast<T extends Function>(fn: T): T {
  let lastArgs: any[]|null = null;
  let lastResult: any;
  return function(...args: any[]) {
    if(!lastArgs || !shallowEqual(lastArgs, args)) {
      lastResult = fn(...args);
      lastArgs = args;
    }
    return lastResult;
  } as unknown as T
}

이럴 경우 우리는 이미 반환 타입을 예상하고 있기 때문에 as unknown as T 단언문을 추가해서 오류를 제거해도 된다. 

 

 

아이템41 any의 진화를 이해하기

변수의 타입은 선언할 때 결정되고 새로운 값이 추가되도록 확장할 수 없다. 허나 any는 예외가 존재한다.

 

예시

function range(start: number, limit: number) {
  const out = []; // any[]
  for(let i = start; i < limit; i++) {
    out.push(i); // any[]
  }
  return out; //반환 타입 number로 추론됨 
}

out의 배열타입은 any였다가, number값을 넣는 순간부터 number[]로 진화함. 

이는 타입 좁히기와는 다르다. 

 

 

예시

function makeSquars(start: number, limit: number) {
  const out = []; // 오류발생 out 변수는 일부 위치에서 암시적으로 any[] 형식 입니다. 
  range(start, limit).forEach(i => {
    out.push(i*i);
  });
  return out;  // out 변수에는 암시적으로 any[] 형식이 포함됩니다.
}

any상태인 변수에서 어떠한 할당도 하지 않고 사용하려고 하면 오류가 발생한다. 

any 타입의 진화는 암시적 any 타입에 어떤 값을 할당할 때만 발생한다. 

 

 

any 상태인 변수에서 아무값도 할당하지 않고 사용하려고하면 오류가 발생하는줄 처음 알았다... 

 

요약

  • any와 any[] 타입은 진화할 수 있다. 이때 타입이 진화되었다고 놀라지말고 코드를 인지하고 이해할 수 있어야 한다.
  • any 를 진화시켜서 예상치 못한 동작이 발생하는 것 보단 타입 선언을 이용하는 것이 좋다. 

 

아이템42 모르는 타입의 값에는 any대신 unknown을 사용하기

함수의 반환 타입으로 any를 이용할 경우 문제점

  • 어떠한 타입이든 any 타입에 할당 가능하다
  • any 타입은 어떠한 타입으로도 할당 가능하다

any대신 unknown을 사용하자. 

  • unknow은 어떠한 타입이든 unknow 타입에 할당 가능하다.
  • unknow은 오직 unknow과 any에만 할당 가능하기 때문에, 어떠한 타입으로도 할당가능은 만족하지 않는다. 
  • 어떠한 값이 있지만 타입을 모르는 경우에 unknow을 사용함

 

unknown 예제

interface Feature {
  id?: string | number;
  geometry: Gemotery;
  properties: unknown;
}

function processValue(val: unknown) {
  if(val instanceof Date) {
    val // 타입이 Date
  }
}
  • 매개변수를 unknown으로 이용한 경우, instanceof를 이용하여 원하는 타입으로 변환 가능 

 

unknown 대신 제너릭 예시

function safeParseYAML<T>(yaml: string): T{
  return parseYAML(yaml);
}

타입스크립트에서 좋지 않은 스타일이라는데  => ..왜?

제너릭보다는 unknown을 반환하고 사용자가 직접 단언문을 사용하거나 원하는 대로 타입을 좁히도록 강제하는 것이 좋다. 

 

요약

 

  • unknown 은 any 대신 사용할 수 있는 안전한 타입임
  • 타입을 알지 못하는 경우라면 unknown을 사용하자 
  • 사용자가 타입 단언문이나 타입 체크를 사용하도록 강제하려면 unknown을 사용하면됨 

 

아이템43 몽키 패치보다는 안전한 타입을 사용하기

자바스크립트는 런타임시에 객체나 클래스에 임의의 속성을 추가할 수 있다.

window.monkey = 'Tamarin';

이는 기본적으로 전역 변수되고 사이드 이펙트가 발생할 수 있다. 

 

허나 타입스크립트는 추가한 속성에 대해 알 수 없기 때문에 any를 사용하게 된다. 

window.monkey = 'TEST'; // 에러 발생  
(window as any).monkey = 'TEST'; // 정상

 

해결책 1

 

이전에 배웠던 Interface 보강 기법을 활용한다.

interface Window {
  monkey: string;
}

window.monkey = 'TEST'; // 정상

보강기법 사용시 주의점 

 

  • 전역적으로 적용됨으로 코드나 다른 라이브러리부터 분리할 수 없음 (Scope 주의)
  • 런타임시 속성이 추가된다면, 런타임 시점에 보강을 적용할 방법이 없음 

해결책 2

 

구체적인 타입 단언문을 사용

interface MonkeyWindow extends Document {
  monkey: string;
}

(document as MonkeyWindow).monkey = 'TEST';

이런식으로 확장해서 사용이 가능하다. 

 

요약

 

  • window, document 에 전역변수를 저장하지말고, 데이터를 분리해서 사용하자. 
  • 추가해야할 경우엔 보강기법이나 사용자 정의 인터페이스(해결책2)를 이용하즈 

 

 

아이템44 타입 커버리지를 추적하여 타입 안전성 유지하기

noImplictAny를 설정함에도 불구하고 any타입이 존재할 수 있다. 

 

 

any 개수 추적 방법

$ npx type-coverage

95.48% 가 any가 아님을 의미한다.

 

 

any가 사용된 파일 검색

$ npx type-coverage --detail

 

any 사용을 줄여 나가자.

 

Comments