TypeScript

GitLab과의 역사

TypeScript는 GitLab에서 수년간 고려되었으며, 토론되었으며, 홍보되었으며, 기각되었습니다. 일반적인 결론은 비용이 이점을 상회하므로 TypeScript를 주 프로젝트에 통합할 수 없다는 것입니다.

  • 주 요소가 강력하게 유형화되지 않은 미리 작성된 많은 코드가 있습니다.
  • 주요 기여자들 중 일부는 TypeScript를 알지 못합니다.

주 프로젝트 이외에도, TypeScript는 소수의 위성 프로젝트에서 유익하게 활용되었습니다.

TypeScript를 사용하는 프로젝트

다음 GitLab 프로젝트들이 TypeScript를 사용합니다:

권장 사항

ESLint 및 TypeScript 구성 설정

새 TypeScript 프로젝트를 설정할 때, ESLint 및 TypeScript에 엄격한 유형 안전 규칙을 구성하세요. 이렇게 하면 프로젝트가 가능한 한 유형 안전한 상태를 유지할 수 있습니다.

GitLab Workflow Extension 프로젝트는 TypeScript 프로젝트의 보일러플레이트 및 구성을 위한 좋은 모델입니다. 거기서 tsconfig.json.eslintrc.json을 복사하는 것을 고려해 보세요.

tsconfig.json에 대해:

  • "strict": true을 사용하세요. 이렇게 하면 프로젝트의 가장 강력한 유형 검사 기능이 강제되고 유형 안전성 재정의가 금지됩니다.
  • "skipLibCheck": true을 사용하세요. 이렇게 하면 모든 .d.ts 파일이 아닌 node_modules의 모든 .d.ts 파일을 참조하는 컴파일 시간이 개선됩니다.

.eslintrc.json (또는 .eslintrc.js)에 대해:

any 피하기

가능한 한 any를 피하세요. 프로젝트의 린터에 이미 구성되어 있을 수 있지만, 여기서 강조하는 것이 값어치가 있습니다.

개발자들은 흔히 도메인 경계를 넘나드는 데이터 구조(예: HTTP 응답 처리 또는 유형이 지정되지 않은 라이브러리와 상호 작용)를 다룰 때 any를 사용합니다. 처음에는 편리해 보입니다. 그러나 잘 정의된 유형을 선택하거나(unknown을 사용하고 예측을 통해 유형을 좁히는) 하는 것이 상당한 이점이 있습니다.

// 나쁨 :(
function handleMessage(data: any) {
  console.log("데이터가 무엇인지 모르겠어요. 이것은 폭발할 수 있어요!", data.special.stuff);
}

// 좋음 :)
function handleMessage(data: unknown) {
  console.log("가끔은 알지 못해도 됩니다.", JSON.stringify(data));
}

// 또한 좋음 :)
function isFooMessage(data: unknown): data is { foo: string } {
  return typeof data === 'object' && data && 'foo' in data;
}

function handleMessage(data: unknown) {
  if (isFooMessage(data)) {
    console.log("우리는 지금 foo를 알고 있습니다. 안전합니다!", data.foo);
  }
}

<> 또는 as로 캐스팅 피하기

가능한 한 <> 또는 as를 사용한 캐스팅을 피하세요.

명시적으로 유형을 캐스팅하면 유형 안전성이 우회됩니다. 유형 예측을 사용하는 것을 고려하세요.

// 나쁨 :(
function handler(data: unknown) {
  console.log((data as StuffContainer).stuff);
}

// 좋음 :)
function hasStuff(data: unknown): data is StuffContainer {
  if (data && typeof data === 'object') {
    return 'stuff' in data;
  }

  return false;
}

function handler(data: unknown) {
  if (hasStuff(data)) {
    // 캐스팅이 필요 없습니다 :)
    console.log(data.stuff);
  }
  throw new Error('데이터에 stuff가 있을 것으로 예상합니다. 재앙적인 결과가 따를 수 있습니다...');
}

이것이 허용될 수 있는 드문 경우가 있습니다(이 테스트 유틸리티를 고려하세요.). 그러나 99%의 경우 더 나은 방법이 있습니다.

새 구조에 대해 interface 대신 type 사용

새 구조를 정의할 때 새 interface 대신 type 별칭을 선언하는 것을 선호하세요.

인터페이스와 타입 별칭은 많은 부분이 겹칩니다. 하지만 클래스는 typeimplements 할 수 없으므로(interface만 할 수 있음) type을 사용하면 구조의 사용성이 제한됩니다.

// 나쁨 :(
type Fooer = {
  foo: () => string;
}

// 좋음 :)
interface Fooer {
  foo: () => string;
}

TypeScript 가이드에 따르면:

휴리스틱을 사용하려면, type 대신에 interface를 사용하세요.

기존 유형에 별칭을 정의하기 위해 type 사용

기존 유형, 클래스 또는 인터페이스에 대한 별칭을 정의하기 위해 type을 사용하세요. TypeScript 유틸리티 유형을 사용하여 변환을 제공하세요.

interface Config = {
  foo: string;

  isBad: boolean;
}

// 나쁨 :(
type PartialConfig = {
  foo?: string;

  isBad?: boolean;
}

// 좋음 :)
type PartialConfig = Partial<Config>;
// 안 좋은 방법 :(
interface Foo { type: string }
interface FooBar extends Foo { bar: string }
interface FooZed extends Foo { zed: string }

const doThing = (foo: Foo) => {
  if (foo.type === 'bar') {
    // 캐스팅 안 좋음 :(
    console.log((foo as FooBar).bar);
  }
}

// 좋은 방법 :)
interface FooBar { type: 'bar', bar: string }
interface FooZed { type: 'zed', zed: string }
type Foo = FooBar | FooZed;

const doThing = (foo: Foo) => {
  if (foo.type === 'bar') {
    // 캐스팅이 필요 없음 :) - TS가 우리가 지금 FooBar임을 인지
    console.log(foo.bar);
  }
}

미래 계획

  • TypeScript 프로젝트 전반에 재사용되는 공유 ESLint 구성.

관련 주제