TypeScript
GitLab v19.4요약
TypeScript는 GitLab에서 수년 동안 검토되고, 논의되고, 추진되고, 또 거부되어 왔습니다. 메인 프로젝트 외에, TypeScript는 몇몇 위성 프로젝트에서 유용하게 활용되고 있습니다. 다음 GitLab 프로젝트가 TypeScript를 사용합니다.
GitLab 에서의 TypeScript 도입 경과#
TypeScript는 GitLab에서 수년 동안 검토되고, 논의되고, 추진되고, 또 거부되어 왔습니다. 일반적인 결론은 비용이 이점보다 크기 때문에 메인 프로젝트에 TypeScript를 통합할 수 없다는 것입니다.
- 메인 프로젝트에는 타입이 엄격하게 지정되지 않은 기존 코드가 많습니다.
- 메인 프로젝트의 주요 기여자가 모두 TypeScript에 익숙하지는 않습니다.
메인 프로젝트 외에, TypeScript는 몇몇 위성 프로젝트에서 유용하게 활용되고 있습니다.
TypeScript를 사용하는 프로젝트#
다음 GitLab 프로젝트가 TypeScript를 사용합니다.
gitlab-web-idegitlab-vscode-extensiongitlab-language-server-for-code-suggestionsgitlab-org/cluster-integration/javascript-client
권장 사항#
ESLint 및 TypeScript 구성 설정#
새 TypeScript 프로젝트를 설정할 때는 ESLint와 TypeScript에 엄격한 타입 안전성 규칙을 구성합니다. 이렇게 하면 프로젝트가 가능한 한 타입 안전한 상태로 유지됩니다.
GitLab for VS Code 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과 프레디케이트를 통한 타입 좁히기를 사용하면 이점이 큽니다.
// 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;
}
TypeScript 가이드의 설명은 다음과 같습니다.
판단 기준이 필요하다면,
type의 기능이 필요해지기 전까지는interface를 사용합니다.
기존 타입의 별칭 정의에 type 사용#
기존 타입, 클래스, 인터페이스의 별칭을 정의할 때는 type을 사용합니다. 변환이 필요하면 TypeScript 유틸리티 타입을 사용합니다.
interface Config = {
foo: string;
isBad: boolean;
}
// Bad :(
type PartialConfig = {
foo?: string;
isBad?: boolean;
}
// Good :)
type PartialConfig = Partial<Config>;
유니온 타입으로 추론 개선#
// 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 구성.