TypeScript
GitLab v19.1TypeScript는 GitLab에서 수년간 논의되고, 검토되고, 추진되고, 거부되어 왔습니다. 메인 프로젝트에는 강력한 타입이 적용되지 않은 기존 코드가 매우 많습니다. 메인 프로젝트의 주요 기여자 모두가 TypeScript에 익숙한 것은 아닙니다.
GitLab과 TypeScript의 역사#
TypeScript는 GitLab에서 수년간 논의되고, 검토되고, 추진되고, 거부되어 왔습니다. 일반적인 결론은, 비용이 이점을 초과하기 때문에 TypeScript를 메인 프로젝트에 통합하기 어렵다는 것입니다.
-
메인 프로젝트에는 강력한 타입이 적용되지 않은 기존 코드가 매우 많습니다.
-
메인 프로젝트의 주요 기여자 모두가 TypeScript에 익숙한 것은 아닙니다.
메인 프로젝트 외에도 TypeScript는 몇 가지 위성 프로젝트에서 유용하게 사용되었습니다.
TypeScript를 사용하는 프로젝트#
다음 GitLab 프로젝트는 TypeScript를 사용합니다:
권장 사항#
ESLint 및 TypeScript 구성 설정#
새 TypeScript 프로젝트를 설정할 때는 ESLint와 TypeScript에 대해 엄격한 타입 안전성 규칙을 구성하세요. 이를 통해 프로젝트가 최대한 타입 안전하게 유지됩니다.
GitLab for VS Code 확장 프로젝트는 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를 확장하세요:
"@typescript-eslint/no-explicit-any": "error"
any 사용 피하기#
any는 최대한 피하세요. 프로젝트의 린터에 이미 구성되어 있어야 하지만, 여기서 별도로 강조할 가치가 있습니다.
개발자들은 HTTP 응답을 처리하거나 타입이 없는 라이브러리와 상호작용하는 것처럼 도메인 경계를 넘나드는 데이터 구조를 다룰 때 흔히 any에 의존합니다. 처음에는 편리해 보일 수 있습니다. 그러나 잘 정의된 타입을 선택하거나(unknown을 사용하고 프레디케이트를 통해 타입 좁히기를 적용) 하면 상당한 이점이 있습니다.
// Bad :(
function handleMessage(data: any) {
console.log("We don't know what data is. This could blow up!", data.special.stuff);
}
// Good :)
function handleMessage(data: unknown) {
console.log("Sometimes it's okay that it remains unknown.", JSON.stringify(data));
}
// Also good :)
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("We know it's a foo now. This is safe!", data.foo);
}
}
<> 또는 as를 이용한 캐스팅 피하기#
<> 또는 as를 이용한 캐스팅은 최대한 피하세요.
타입 캐스팅은 타입 안전성을 명시적으로 우회합니다. 타입 프레디케이트 사용을 고려하세요.
// Bad :(
function handler(data: unknown) {
console.log((data as StuffContainer).stuff);
}
// Good :)
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)) {
// No casting needed :)
console.log(data.stuff);
}
throw new Error('Expected data to have stuff. Catastrophic consequences might follow...');
}
드물게 허용 가능한 경우가 있습니다(이 테스트 유틸리티 참고). 그러나 99%의 경우 더 나은 방법이 있습니다.
새로운 구조에는 type보다 interface 선호#
새로운 구조를 정의할 때는 새 type 별칭을 선언하는 것보다 새 interface를 선언하는 것을 선호하세요.
인터페이스와 타입 별칭은 공통점이 많지만, implements 키워드와 함께 사용할 수 있는 것은 인터페이스뿐입니다. 클래스는 type을 implement할 수 없으며(interface만 가능) 따라서 type을 사용하면 구조의 사용성이 제한됩니다.
// Bad :(
type Fooer = {
foo: () => string;
}
// Good :)
interface Fooer {
foo: () => string;
}
경험 법칙으로, type의 기능을 사용해야 할 필요가 생기기 전까지는 interface를 사용하세요.
기존 타입의 별칭 정의에는 type 사용#
기존 타입, 클래스 또는 인터페이스의 별칭을 정의할 때는 type을 사용하세요. TypeScript 유틸리티 타입을 사용하여 변환을 제공하세요.
interface Config = {
foo: string;
isBad: boolean;
}
// Bad :(
type PartialConfig = {
foo?: string;
isBad?: boolean;
}
// Good :)
type PartialConfig = Partial;
유니온 타입을 사용하여 추론 개선#
// Bad :(
interface Foo { type: string }
interface FooBar extends Foo { bar: string }
interface FooZed extends Foo { zed: string }
const doThing = (foo: Foo) => {
if (foo.type === 'bar') {
// Casting bad :(
console.log((foo as FooBar).bar);
}
}
// Good :)
interface FooBar { type: 'bar', bar: string }
interface FooZed { type: 'zed', zed: string }
type Foo = FooBar | FooZed;
const doThing = (foo: Foo) => {
if (foo.type === 'bar') {
// No casting needed :) - TS knows we are FooBar now
console.log(foo.bar);
}
}
향후 계획#
- TypeScript 프로젝트 전반에서 재사용할 수 있는 공유 ESLint 구성.