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
을 사용하세요. 이렇게 하면 모든.d.ts
파일이 아닌node_modules
의 모든.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%의 경우 더 나은 방법이 있습니다.
새 구조에 대해 interface
대신 type
사용
새 구조를 정의할 때 새 interface
대신 type
별칭을 선언하는 것을 선호하세요.
인터페이스와 타입 별칭은 많은 부분이 겹칩니다. 하지만 클래스는 type
을 implements
할 수 없으므로(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 구성.