You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Rule TP-3-1(권장): 가능하면 `type` 대신 `interface`를 사용하세요.
- Rule TP-3-2(권장): 구조적 타이핑은 가능하면 `class` 대신 `interface` 혹은 `type`을 사용하세요.
- Rule EX-1-10(필수): `==` 대신 `===`을 사용하세요.
- Rule TP-5-1(권장): `any`보다는 `any[]`, `Record`를 사용하세요.
- Rule CL-1-5(권장): `#` private 접근 제어자를 사용하지 마세요.
- Rule CL-1-6(권장): 멤버 변수 선언 대신 생성자 매개 변수 선언을 적극 활용하세요.
- Rule SD-1-1(필수): Deprecated 되거나 구식 기능을 사용하지 마세요.
Copy file name to clipboardExpand all lines: README.md
+18-2
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
## 개요
4
4
5
-
본 문서는 TypeScript와 TypeScript의 뿌리인 JavaScript에 대한 코딩 가이드라인에 대해 정리한 문서입니다. TypeScript의 기능만으로는 절대 하나의 실행 가능한 프로그램을 만들 수 없기에, TypeScript 뿐만 아니라 JavaScript의 코딩 가이드라인로 같이 담았습니다.
5
+
본 문서는 TypeScript와 TypeScript의 뿌리인 JavaScript에 대한 코딩 가이드라인에 대해 정리한 문서입니다. TypeScript의 기능만으로는 절대 하나의 실행 가능한 프로그램을 만들 수 없기에, TypeScript 뿐만 아니라 JavaScript의 코딩 가이드라인으로 함께 담았습니다.
6
6
7
7
하나의 동작 가능한 프로그램을 위해 필요한 가장 작은 단위인 식별자, 타입부터 시작하여 표현식, 문, 함수 순으로 가이드라인을 작성했습니다.
8
8
@@ -43,10 +43,19 @@ PR을 올려주시거나 `Discussions` 메뉴을 통해서 수정, 추가 규칙
43
43
타입스크립트(x), TypeScript(O)
44
44
```
45
45
46
-
- 용어는 [MDN 용어 가이드](https://github.com/mdn/translated-content/blob/main/docs/ko/guides/glossary-guide.md)에 따릅니다.
46
+
- 용어는 기본적으로 [MDN 용어 가이드](https://github.com/mdn/translated-content/blob/main/docs/ko/guides/glossary-guide.md)를 따릅니다.
47
+
- 위 용어 가이드 중 아래 용어는 재정의(Override)합니다
48
+
49
+
```
50
+
type: 형 -> 타입
51
+
error: 오류 -> 에러
52
+
syntax: 구문 -> 문법
53
+
```
47
54
48
55
## 전체 규칙 목록
49
56
57
+
총 80개의 규칙이 있습니다.
58
+
50
59
1. 식별자(Identifier)
51
60
52
61
- ID-1-1(권장): 식별자는 영어 알파벳, 숫자, 언더바 만을 사용해야 합니다.
@@ -63,9 +72,12 @@ PR을 올려주시거나 `Discussions` 메뉴을 통해서 수정, 추가 규칙
63
72
- TP-2-1(필수): `undefined`과 `null`을 구분해서 사용하세요.
64
73
- TP-2-2(필수): 타입 별칭에 `null`이나 `undefined`을 포함하지 마세요.
65
74
- TP-2-3(필수): `| undefined` 대신 `?`을 사용하세요
75
+
- TP-3-1(권장): 가능하면 `type` 대신 `interface`를 사용하세요.
76
+
- TP-3-2(권장): 구조적 타이핑은 가능하면 `class` 대신 `interface` 혹은 `type`을 사용하세요.
66
77
- TP-4-1(필수): `any`를 가능하면 사용하지 마세요.
67
78
- TP-4-2(필수): `any`를 사용해야 하는 코드가 있다면 린트 경고를 끄고 사유를 적으세요.
68
79
- TP-4-3(필수): `any`보다는 `unknown`을 사용하세요.
80
+
- TP-5-1(권장): `any`보다는 `any[]`, `Record`를 사용하세요.
69
81
- TP-6-1(권장): `string`, `boolean`, `number`, `new` 표현식으로 초기화된 변수나 매개변수에 대한 타입 명시는 생략합니다.
70
82
- TP-6-2(필수): 반환 타입을 명시하세요
71
83
@@ -81,6 +93,8 @@ PR을 올려주시거나 `Discussions` 메뉴을 통해서 수정, 추가 규칙
81
93
- CL-1-2(권장): `public` 접근 제어자에 일관성이 있어야 합니다.
82
94
- CL-1-3(필수): `toString()`를 가능하면 재정의하지 마세요.
83
95
- CL-1-4(권장): 빈 생성자는 생략하세요.
96
+
- CL-1-5(권장): `#` private 접근 제어자를 사용하지 마세요.
97
+
- CL-1-6(권장): 멤버 변수 선언 대신 생성자 매개 변수 선언을 적극 활용하세요.
84
98
85
99
5. 표현식(Expression)
86
100
@@ -93,6 +107,7 @@ PR을 올려주시거나 `Discussions` 메뉴을 통해서 수정, 추가 규칙
93
107
- EX-1-7(필수): 전개 연산자 사용 시 같은 타입에 사용하세요.
94
108
- EX-1-8(필수): 다차원 배열을 전개 연산자의 피연산자로 사용하지 마세요.
95
109
- EX-1-9(참고): 객체 인스턴스 생성 이후에 속성을 추가하거나 제거하지 마세요.
110
+
- EX-1-10(필수): `==` 대신 `===`을 사용하세요.
96
111
97
112
6. 문(Statement)
98
113
@@ -123,6 +138,7 @@ PR을 올려주시거나 `Discussions` 메뉴을 통해서 수정, 추가 규칙
123
138
124
139
9. 표준 내장 객체 및 함수(Standard Built-in Objects/Functions)
125
140
141
+
- SD-1-1(필수): Deprecated 되거나 구식 기능을 사용하지 마세요.
126
142
- SD-2-1(필수): `Array.prototype.map()`은 호출한 `Array`의 요소의 값 만 변경시켜야 합니다.
127
143
- SD-2-2(필수): `Array.prototype.map()`의 반환 값을 반드시 사용하세요
const targetIp ='192.168.0.1'; // "IP"는 모두가 다 아는 약어
122
-
const errorCount =1; // 명확한 의미
123
-
const warehouseId =1; // 명확함
121
+
const targetIp ='192.168.0.1'; //✅ "IP"는 모두가 다 아는 약어
122
+
const errorCount =1; //✅ 명확한 의미
123
+
const warehouseId =1; //✅ 명확함
124
124
```
125
125
126
126
예외: 10~15 줄 내의 작은 범위(Scope)에서 사용하거나 외부로 노출되지 않는 메서드, 변수의 인자명으로 쓸 경우 `n`, `i` 와 같이 짧은 변수명은 허용 가능하지만 해당 범위 내의 코드가 늘어날 경우 다시 의미가 모호해질 가능성이 있다는 점 유의하셔야 합니다.
@@ -176,8 +176,8 @@ function printLength(value: string) {
176
176
console.log(value.length);
177
177
}
178
178
179
-
printLength('hello'); // 정상 동작
180
-
printLength(newString('hello')); // 컴파일 에러
179
+
printLength('hello'); //✅ 정상 동작
180
+
printLength(newString('hello')); //❌ 컴파일 에러
181
181
```
182
182
183
183
위 코드에서는 두 번째 `printLength()` 호출 시 컴파일 에러가 발생합니다. 이를 고치려면 아래와 같이 불필요하고 번거롭게 타입 정의를 해야합니다.
@@ -296,7 +296,64 @@ function buyHoody(address?: string): void {
296
296
297
297
### 2.3 구조적 타입(Structural Type)
298
298
299
-
TODO
299
+
#### Rule TP-3-1(권장): 가능하면 `type` 대신 `interface`를 사용하세요.
300
+
301
+
현재 TypeScript에서 `type`과 `interface`는 일면 비슷합니다. 하지만 버전 4.2 이전은 그렇지 않았습니다.
302
+
303
+
TypeScript 4.2 버전 이전에는 타입 별칭 선언(type alias declaration)을 사용하면 `.d.ts`출력이 훨씬 더 커지는 문제가 있었습니다. 타입 별칭 선언은 경우에 따라 타입 별칭의 내용이 중복해서 인라인으로 처리하는 반면 인터페이스는 항상 이름으로 참조되기 때문이었습니다.
304
+
305
+
현재는 `union`및 interchange 유형에 대한 유형 별칭을 보존하도록 수정했기 때문에 해당 문제는 사라졌고 일부 개발자들은 `interface`보다 `type`을 선호하기 시작했습니다.
306
+
307
+
하지만 `type`보다 `interface`를 먼저 고려해야하는 건 교차(intersection) 타입을 정의 및 사용할 때 입니다.
308
+
309
+
```ts
310
+
interfaceA {
311
+
x:number;
312
+
}
313
+
314
+
interfaceB {
315
+
x:string;
316
+
}
317
+
318
+
interfaceCextendsA, B {} // 에러 발생! (x의 타입 충돌)
319
+
```
320
+
321
+
`interface`는 속성 충돌을 감지하는 단일 평면 객체 유형을 생성합니다. 반면 type은 속성을 재귀적으로 병합할 뿐, 어떤 경우에는 `never`를 생성합니다.
322
+
323
+
```ts
324
+
typeA= { x:number };
325
+
typeB= { x:string };
326
+
327
+
typeC=A&B; // x는 never
328
+
```
329
+
330
+
또한 `interface`의 타입 관계는 캐시되지만, 인터섹션 타입(`type & type`)은 전체를 다시 검사해야 합니다.
331
+
332
+
```ts
333
+
interfaceA {
334
+
foo:string;
335
+
}
336
+
337
+
interfaceBextendsA {}
338
+
339
+
const obj:B= { foo: 'hello' }; // 빠른 타입 검사 가능
340
+
341
+
typeA= { foo:string };
342
+
typeB= { foo:string };
343
+
typeC=A&B;
344
+
345
+
const obj:C= { foo: 'hello' }; // 전체 타입을 다시 검사함
346
+
```
347
+
348
+
이 때문에 성능 문제가 있을 수 있습니다.
349
+
350
+
이런 이유로 객체 타입 간의 교차 유형을 만들때는 `interface`를 사용하는게 좋습니다. 그리고 객체 타입 외 원시 타입, 튜플 타입, 객체 타입이 아닌 타입 간의 교차 타입은 타입 별칭을 사용하는게 좋습니다.
351
+
352
+
#### Rule TP-3-2(권장): 구조적 타이핑은 가능하면 `class` 대신 `interface` 혹은 `type`을 사용하세요.
353
+
354
+
`class`는 JavaScript, TypeScript 모두 지원하는 문법입니다. 따라서 TypeScript 를 컴파일해도 `class`는 그대로 결과물로 나온 `.js`파일에 들어갑니다. 하지만 `interface`나 `type`은 타입 정보만 담고 있기 때문에 컴파일하면 결과물로 나온 `.js`에 들어가지 않습니다.
355
+
356
+
따라서 instanceof, 데코레이터나 리플렉션 등 런타임에서 해당 구조적 타입을 다룰 일이 있다면 사용해야하지만 단순히 구조적 타이핑 용도로만 사용한다면 `interface` 혹은 `type`을 사용하시기 바랍니다.
300
357
301
358
### 2.4 any
302
359
@@ -351,7 +408,14 @@ if (typeof helloUnknown === 'string') {
`any`타입은 집합으로 치자면 전체 집합입니다. 모든 타입을 커버할 수 있죠. 그래서 `any`를 사용하면 어떠한 타입 시스템도 동작하지 않습니다. 하지만 만약 어떤 값이 들어올지 모르는 배열이라면? 문자열 키를 가졌지만 어떤 값이 올지 모르는 객체라면 그래도 타입을 좁힐 수 있습니다.
414
+
415
+
어떤 값이 들어올지 모르는 배열: `any[]`
416
+
문자열 키를 가졌지만 어떤 값일지 모르는 객체: `Record<string, any>`
417
+
418
+
이렇게 사용하면 IDE의 도움을 받을 수 있고 최소한의 타입 시스템이 동작하게 됨으로서 버그를 예방할 수 있습니다.
355
419
356
420
### 2.6 타입 추론(Type Inference)
357
421
@@ -622,6 +686,85 @@ class Foo3 {
622
686
}
623
687
```
624
688
689
+
#### Rule CL-1-5(권장): `#` private 접근 제어자를 사용하지 마세요.
690
+
691
+
ES2020(ES11)부터 도입된 JavaScript의 Private 필드(#)은 외부에서 절대 접근할 수 없고 클래스 내부에서만 접근할 수 있는 접근 제어자입니다. 얼핏 `private`과 비슷하지만 내부 동작 방식은 많이 다릅니다.
위와 같은 `User` 클래스가 있습니다. 큰 문제는 없어 보입니다. 그냥 `private` 처럼 동작하는거 아닌가 싶습니다. 하지만 다시 말씀드리지만 TypeScript는 JavaScript로 컴파일하기 때문에 그 동작을 보려면 실제 JavaScript로 컴파일된 결과를 봐야합니다.
705
+
706
+
```js
707
+
use strict";
708
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
709
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
710
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
711
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
첫 번째로 `#` Private 필드는 클래스에 포함되지 않습니다. 즉 일반적인 객체처럼 객체 속성이어야 히든 클래스로 관리하는데 컴파일 한 결과는 `User` 클래스 외부에 `WeakMap`으로 정의되어 있습니다. 그렇기에 JavaScript 엔진이 `#` private 필드에 접근할 때는 일반적인 속성에 접근(예: this.propertyName) 할 때와는 달리 별도의 내부 매커니즘으로 호출해야기 때문에 성능이 저하됩니다.
730
+
731
+
두 번째로 `#` private 필드는 위 코드에서 보듯이 객체 외부에 정의되기 때문에 객체의 프로토타입 체인에 존재하지 않게 됩니다. 이 때문에 리플렉션이나 디버깅이 어렵습니다. 객체의 속성이 아니기에 `Object.keys()`, `JSON.stringify()`에도 해당 필드는 나타나지 않습니다.
732
+
733
+
세 번째로 일반적인 멤버와는 달리 별도의 `WeakMap`으로 관리하기 때문에 다량의 인스턴스를 만들 경우 일반적인 클래스보다 메모리 사용량이 증가합니다.
734
+
735
+
절대 외부에 해당 속성을 노출시키지 않아야 할 특별한 이유가 없는한 `#`Private 접근 제어자를 사용하지 마세요.
736
+
737
+
#### Rule CL-1-6(권장): 멤버 변수 선언 대신 생성자 매개 변수 선언을 적극 활용하세요.
738
+
739
+
TypeScript에서는 클래스 멤버 변수 선언 대신 생성자 매개 변수로 멤버 변수 선언이 가능합니다.
740
+
741
+
```ts
742
+
classUser {
743
+
name:string;
744
+
address:string;
745
+
746
+
constructor(name:string, address:string) {
747
+
this.name=name;
748
+
this.address=address;
749
+
}
750
+
}
751
+
```
752
+
753
+
위 코드는 일반적인 클래스 사용법입니다. 멤버 변수를 선언하고 생성자에서 값을 초기화합니다.
위 코드는 생성자 매개변수를 통해 멤버 변수를 선언하는 방식입니다. 이 방식을 사용하면 멤버 변수 선언과 초기화를 한 번에 함으로서 코드가 깔끔해지기도 하고 `public`, `private`, `readonly` 등의 접근 제한자와 함께 사용할 수 있습니다.
762
+
763
+
다만 아래의 경우에는 굳이 사용하실 필요는 없습니다.
764
+
765
+
- 생성자 로직이 복잡한 경우
766
+
- 기본 값이 필요한 경우
767
+
625
768
## 5. 표현식(Expression, EX)
626
769
627
770
### 5.1 일반
@@ -847,7 +990,7 @@ const user2 = new User('박씨', '광주광역시'); // Hidden Class #1 (재사
847
990
848
991
위에서 `user1`, `user2`는 같은 히든 클래스를 사용합니다. 흡사 같은 틀을 사용하는 붕어빵이라고 보면 되겠습니다. 그렇기에 `user1.address` 접근은 기존에 만들어놓은 히든 클래스의 오프셋을 이용해서 바로 접근 가능합니다.
849
992
850
-
그런데, 여기서 동적으로 속성을 추가/삭제함으로서 위의 `User`의 히든 클래스의 틀이 깨진다면 어떻게 될까요? 어 저는 꼬리 없는 붕어빵이요. 저는 지느러미 없는 붕어빵이요. 주문이 들어올 때마다 틀을 다시 만들어야 합니다. 즉 새로운 히든 클래스를 만들게 되면 JIT 컴파일러가 최적화했던 코드를 다시 분석하며 이 과정에서 성능 저하가 발생합니다.
993
+
그런데, 여기서 동적으로 속성을 추가/삭제함으로서 위의 `User`의 히든 클래스의 틀이 깨진다면 어떻게 될까요? 어 저는 꼬리 없는 붕어빵이요. 저는 지느러미 없는 붕어빵이요. 주문이 들어올 때마다 틀을 다시 만들어야 합니다. 즉 새로운 히든 클래스를 만들게 되면 JIT 컴파일러가 최적화했던 코드를 다시 분석하며 이 과정에서 성능 저하가 발생합니다.
851
994
852
995
```ts
853
996
const user3 =newUser('이씨', '부산광역시');
@@ -857,14 +1000,33 @@ const user3 = new User('이씨', '부산광역시');
857
1000
TypeScript 코드에서는 동적으로 속성을 추가/삭제하는 일은 잘 없지만, 가장 문제되는 경우는 `Object`를 해시맵으로 사용하는 경우입니다. 속성을 추가할때마다 새로운 히든 클래스를 만들기에 성능 저하가 발생합니다.
이는 뒤에 나오는 `SD-2-10(권장): 해시맵이 필요할 때 Object대신 Record 혹은 Map을 사용하세요.` 규칙을 참고하세요.
867
1010
1011
+
#### Rule EX-1-10(필수): `==` 대신 `===`을 사용하세요.
1012
+
1013
+
JavaScript에서는 다른 프로그래밍 언어에도 있는 연산자인 `==` 연산자가 존재합니다. 거기에 덧붙여 `===` 연산자도 있습니다. `==`는 `비교 연산자(Equality)`라 부르며 `===`는 `엄격한 비교 연산자(Strict equality)`라고 합니다. 문젠 `=` 하나 차이인데 두 연산자의 동작이 매우 다릅니다.
1014
+
1015
+
`==`는 타입을 자동 변환해서 비교하지만, `===`는 타입 변환 없이 값과 타입을 모두 비교합니다. 이 자동 변환 과정때문에 프로그래머가 예상치 못한 결과를 받아볼 수 있습니다.
1016
+
1017
+
```ts
1018
+
console.log(false==''); // true -> 빈 문자열은 false로 변환
1019
+
console.log(false===''); // false -> 다른 타입
1020
+
1021
+
console.log(0=='0'); // true -> 타입 변환 발생
1022
+
console.log(0==='0'); // false -> 다른 타입
1023
+
1024
+
console.log(null==undefined); // true -> 둘 다 "없음"을 의미한다고 간주
1025
+
console.log(null===undefined); // false -> 다른 타입
1026
+
```
1027
+
1028
+
따라서 언제나 `===`을 사용하세요. `==`와 `===`의 비교표는 이 [링크](https://dorey.github.io/JavaScript-Equality-Table/)를 참고하세요.
0 commit comments