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를 사용하세요. 이렇게 하면 node_modules의 모든 .d.ts 파일이 아닌 참조 .d.ts 파일만을 확인함으로써 컴파일 시간이 개선됩니다.

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

any 피하기

가능한 한 any를 피하세요. 이것은 프로젝트의 린터에 이미 구성되어 있어야 하지만, 여기서 언급할 가치가 있습니다.

개발자들은 일반적으로 any를 사용하여 도메인 간 경계를 넘나드는 데이터 구조(예: HTTP 응답 처리 또는 유형이 지정되지 않은 라이브러리와 상호 작용)를 다룰 때 사용합니다. 처음에는 편리해 보입니다. 그러나 잘 정의된 유형(또는 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할 수 없으므로(인터페이스만 implements할 수 있음) type을 사용하면 구조의 유용성이 제한됩니다.

// 좋지 않음 :(
type Fooer = {
  foo: () => string;
}

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

TypeScript 가이드에 따르면: > 휴리스틱을 원한다면, interface를 사용하세요. 그렇지 않을 때에만 type을 사용하세요.

기존 유형에 별칭을 정의하기 위해 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 구성.

관련 주제