TypeScript
GitLab과의 역사
TypeScript는 GitLab에서 수년간 고려되었고, 토론되었고, 홍보되었으며, 거부되었습니다. 일반적인 결론은 비용이 혜택을 상회하여 TypeScript를 기본 프로젝트에 통합할 수 없다는 것입니다.
- 기본 프로젝트에는 강하게 유형 지정되지 않은 사전 코드가 많습니다.
- 기본 프로젝트의 주요 기여자들은 TypeScript를 모두 알지 못합니다.
기본 프로젝트를 제외하고 TypeScript는 소수의 위성 프로젝트에서 수익성 있게 활용되었습니다.
TypeScript를 사용하는 프로젝트
다음 GitLab 프로젝트가 TypeScript를 사용합니다:
gitlab-web-ide
gitlab-vscode-extension
gitlab-language-server-for-code-suggestions
gitlab-org/cluster-integration/javascript-client
권장 사항
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
)에 대해:
- TypeScript에 특화된 구문 분석 및 린팅이
**/*.ts
파일에 대한overrides
에 배치되도록 해야 합니다. 이렇게 하면 정규.js
파일의 린팅이 TypeScript 특정 규칙에 영향받지 않게 됩니다. -
plugin:@typescript-eslint/recommended
에서 확장하세요. 이는 몇 가지 매우 합리적인 기본값을 갖고 있습니다. 예를 들어:
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%의 경우 더 나은 방법이 있습니다.
새로운 구조체를 정의할 때는 type
대신 interface
를 선호하세요
새로운 구조체를 정의할 때 새로운 type
alias를 선언하는 대신 새로운 interface
를 선언하는 것을 선호하세요.
인터페이스와 타입 alias는 많은 부분에서 겹치지만, implements
키워드와 함께 사용할 수 있는 것은 인터페이스뿐입니다. 클래스는 type
을 implements
할 수 없기 때문에(interface
만 가능), type
을 사용하면 구조체의 유용성이 제한됩니다.
// 나쁨 :(
type Fooer = {
foo: () => string;
}
// 좋음 :)
interface Fooer {
foo: () => string;
}
휴리스틱을 원한다면,
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 구성.