TypeScript 시작하기 1
2022.05.11
1. 타입스크립트란 & 설치
Tyepscript란
타입스크립트는 자바스크립트 슈퍼셋입니다. 새로 만들어진 언어가 아닌 자바스크립트 언어를 사용해 새로운 기능과 장점을 추가한 언어로, 자바스크립트 환경인 브라우저나 node.js 또한 타입스크립트를 실행 할 수 없습니다. 즉, 기본적으로 자바스크립트를 실행하는 환경에서는 타입스크립트가 지원되지 않습니다.
타입스크립트는 프로그래밍언어이지만 도구에 가깝습니다. 코드를 실행하여 타입스크립트 코드를 자바스크립트로 컴파일 하여 실행합니다. 자바스크립트 런타임에서 에러가 발생하기 전에 코드의 에러를 개발자들이 미리 알 수 있도록 해서 개발 중에 발생하는 에러를 초기에 발견하고 수정 할 수 있도록 합니다.
다음은 코드 예시 입니다.
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum("1", "2"));
만약 위와 같이 숫자를 더해서 반환하는 함수에 문자열을 더해도 결과값(12
)이 나옵니다.
이 값은 의도하지 않는 값으로 문자열을 넣었을때는 오류를 반환 해야합니다.
하지만, 자바스크립트에서는 기술적인 에러가 아니기 때문에 에러가 발생하지는 않겠지만, 논리적으로 잘못된 부분이고 숫자값을 더하는 함수는 숫자값만을 input으로 받고 output해줘야합니다.
이 부분은 조건문(if)으로 에어를 검사할 수 있지만, 개발자는 이런 모든 작업들을 포착하고 처리 하기는 힙니다. 그렇기 때문에 유효하지 않는 코드들을 타입스크립트로 방지할 수 있습니다.
- 장담점 요약
- 에러를 예방할 수 있다.
- 자바스크립트 코드로 컴파일되기 때문에 구형 브라우저에도 작동한다.
타입스크립트 설정
타입스크립트도 결국 자바스크립트로 컴파일하여 실행하기 때문에 node.js를 설치한 환경해서 실행 해야 합니다.
-
설치
npm install -g typescript
typescript를 전역적으로 설치해서사용할 수 있습니다.
-
관찰 모드
tsc app.ts -w
타입스크립트를 그때 그때 npm start로 컴파일하지 않고 실시간 변경하여 실행 결과를 볼 수 있다.
-
타입스크립트 프로젝트
tsc --init
해당 폴더에서 이 명령어를 실행하면 타입스크립트 프로젝트로 지정할 수 있다. 해당 명령어를 실행하면
tsconfig.json
파일이 생성된다. 이 후tsc
명령어로 모든.ts
파일을 컴파일 할 수 있다. 하지만 이렇게 하면 모든 파일에 컴파일을 하기 때문에 관찰 모드를 붙여서tsc -w
명령어를 사용하면 변경점만 컴파일을 실행 시킬 수 잇다. -
tsconfig.json
설정하기{ "compilerOptions": { ... }, "exclude": [ ... ] "include": [ ... ] "files": [ ... ] }
compilerOptions
-target
JS에서 어떤 버전으로 컴파일 할지 ex)es5, es6, es2019, es2020 등 -lib
dom으로 작업을 수행하는 항목 등(기본 객체, 기능)json "lib" : [ "dom", "es6", "dom.iterable", "scripthost" ]
-allowJS
&checkJS
allowJS
는 타입스크립트가 자바스크립트 파일을 컴파일 할 수 있도록 해준다. -outFile
&outDir
&rootDir
outDir를 설정하면 생성된 파일의 저장되는 위치를 타입스크립트 컴파일에게 알릴 수 있다. 이를 dist 폴더로 지정할 수 있다. 이렇게 지정하면 컴파일시 dist폴더에 생성하게 된다.json "outDir" : "./dist" "rootDir" : "./src"
rootDir로 컴파일 시작 폴더를 src로 지정해서, src에 TS파일들을, dist에 컴파일된 JS파일들로 프로젝트 구조를 TS와 컴파일된 JS를 같게 가져 갈 수 있다. - removeCommentsjson "removeComments": true
굳이 컴파일한 파일에 주석을 가지고 갈 필요가 없을때, 파일 크기를 줄이기 위해 사용한다.
-
exclude
특정 파일을 타입스크립트로 컴파일을 하는 것을 제외 시킬 수 있다.json "exclude" : ["abc.ts", "*.dev.ts", "**/*.dev.ts", "node_modules"]
→abc.ts
파일을 무시하고 컴파일하라는 것 →*
는 전체를 뜻한다. 즉,.dev.ts
로 끝나는 파일은 무시하고 컴파일한다. →**/
와 같이 특정 폴더를 무시할 수 있다. →node_modules
를 지정함으로 패키지 파일들은 무시하라고 설정할 수 있다. (exclude를 아예 지정하지 않는다면 node_modules는 자동으로 제외된다., 하나라도 추가하면 node_modules는 추가해야한다.) -include
exclude와 반대 되는 개념, 컴파일할 파일 포함"include" : ["app.ts"]
exclude에서 제외한 폴더 안에 있는 특정 파일만 컴파일할때 지정할 수 있다. -
files
json "files" : ["app.ts"]
include와 비슷하지만 컴파일하고자 하는 개별 파일만을 지정 할 수 있다.
2. 타입
Number, String, Boolean
- number 정수, 소수점을 가진, ,실수
- string 작은 따옴표(’), 큰따옴표(”), 백틱(`)로 정의
- boolean 참 거짓 (true or false) if조건문에서 숫자 0을 사용하면 이는 거짓으로 처리되서 거짓값이 된다.
// JavaScript
function sum(num1, num2) {
return num1 + num2;
}
const number1 = 5;
const number2 = 2.8;
const number3 = "5";
console.log(sum(number1, number2));
// 7.8
// number
console.log(sum(number2, number3));
// 52.8
// string
숫자와 숫자를 더할때는 문제가 없지만 JS에서 숫자와 문자열을 더할 경우 타입을 문자열로 변환하여 “52.8”이 출력이 된다.
// TypeScript
function sum(num1, num2) {
return num1 + num2;
}
const number1 = 5;
const number2 = 2.8;
const number3 = "5";
console.log(sum(number1, number2));
// = 7.8
// number
console.log(sum(number2, number3));
// error
반면에 타입스크립트에서는 잘못된 타입을 입력할 경우 다음과 같이 에러를 출력한다.
사전에 이러한 오류를 방지하기 위해 TS에서는 타입 배정(type assignment)를 추가할 수 있다.
// TypeScript
function sum(num1: number, num2: number) {
return num1 + num2;
}
const number1 = 5;
const number2 = 2.8;
const number3 = "5";
console.log(sum(number1, number2));
// = 7.8
// number
console.log(sum(number2, number3));
// error
그렇게 되면 기존에 number3에서 IDE에서 에러를 보여준다.
브라우저에서는 내장 타입스크립트 지원이 없기 때문에 런타임에서 자바스크립트가 다른 식으로 작동하도록 변경하지 않는다. 타입스크립트 코드를 자바스크립트 코드로 컴파일 하기 전까지, 즉 개발 도중에만 유용하지만 이는 추가적인 단계와 추가적인 온정성 검사를 추가하기 때문에 아주 유용하다.
타입 추론
기본적으로 TypeScript는 타입 추론이라는 것을 한다.
// TypeScript
const number1 = 5;
const boolean1 = false;
const string1 = "5";
let number2 = 7;
number2 = "str"; // err
이코드에서 number1, boolean1, string1의 경우 타입을 명시하지 않았지만 에러가 나지 않는다.
그 이유는 TS에서 자동으로 타입을 추론해서 해당 타입이 무엇인지 알기 때문이다.
상수의 경우에는 값이 안변하닌 2번과 같이 상수가 아닌 변수를 선언하고 값을 할당하려고 할 경우 해당 값에서 오류를 보여주게 된다.
Object
const person: object = {
name : 'Lee'
age : 30
};
객체의 경우 obejct
로 선언하는 것이 아닌 다음과 같이 키 타입을 추가해야한다.
const person: {name: string; age: number;} = {
name : 'Lee'
age : 30
};
- 복잡한 Object 타입의 예시
const product: { id: string; price: number; tags: string[]; details: { title: string; description: string; }; } = { id: "product1", price: 1200, tags: ["TypeScript", "Javascript"], details: { title: "TypeScript", description: "how to TypeScript", }, };
Array
배열 또한 해당 내용들의 타입들을 정의해 주어야한다.
const person: string[] = {
hobby: ["sprots", "game"],
};
위와 같이 내용물의 string
타입일 경우 string[]
으로 정의해준다.
Tuple
튜플은 배열과 비슷하지만 길이와 타입이 고정된 배열이다.
const role: [number, string] = [2, "person"];
const role: number[] = [2, "person"]; // error
const role: string[] = [2, "person"]; // error
다음과 같이 있을때 role
에는 첫번째 값은 number
타입, 두번째 값은 string
타입의 형식이 오게 할때 사용한다.
이와 같이 코드를 작성하게 되면 해당 배열에 해당 타입 순서에 맞지 않게 코드를 넣게 되면 오류가 난다.
const role: [number, string] = [2, "person"];
role[1] = 10; // error : string이 있는 부분에 number를 넣을 수 없다.
role = []; // 빈배열도 error가 나오며, 무조건 해당 타입 구조와 일치해야한다.
하지만 여기에는 약간의 예외가 있다.
role.push(”Hello”)
를 하게 되면 number
가 와야하는 차례이지만 push
의 경우 강제성의 해당하지 않는다.
Enum
enum
은 열거형 타입으로 JS에는 없는 TS에만 있는 개념인데 열거형 목록을 제공한다.
자바스크립트에서 열거형 목록을 사용할때에는 다음과 같이 사용한다.
예시로 person
에 관리자랑 일반 사용자를 각각 0, 1로 구분하고 조건문으로 해당 값을 구분한다 했을때 다음과 같아 진다.
const admin = 0;
const user = 1;
const user2 = 2;
const user3 = 3;
const user4 = 4;
person = {
name : "lee",
role : 0;
}
person = {
name : "kim",
role : 1;
}
if(person.role === 0) reutrn console.log("관리자");
else return console.log("일반 유저");
위의 코드를 typescript에서는 enum
을 사용해서 해결할 수 있다.
또한 이때 user에 등급의 여러가지로 나누어 진다고 하면 일일이 해당 값을 정의해 줘야 하는데 이때 enum
을 사용하면 효과적이다.
enum Role {admin, user, user2, user3, user4}
person = {
name : "lee",
role : Role.admin
}
person = {
name : "kim",
role : ROLE.user
}
if(person.role === Role.admin) reutrn console.log("관리자");
else return console.log("일반 유저");
위에서 설정한 것처러럼 ADMIN은 0, USER는 1순으로 값이 할당 된다.
그리고 0부터 시작하는게 아니라아니라 다음과 같이 임의이 값을 할당 할 수 있다.
enum Role {
admin = 5,
user,
user2,
user3,
user4,
}
이때에는 뒤에 값은 5,6,7 … 순으로 순서대로 증가한다.
물론 enum Role {admin = 5, user = 20, user2 = 100 }
와 같이 각각 모든 값을 할당 하는 것도 가능하다.
그리고 숫자만 열거형으로 할당하는게 아닌 다음과 같이 문자열도 가능하고 혼합도 가능하다.
enum Role {
admin = "ADMIN",
user = 20,
user2 = 100,
}
Any
any타입은 모든 종류의 값을 저장할 수 있다.
const num: any = "1"; // true
const str: any = 1; // true
const arr: any = ["1", 1, 2]; // true
하지만 타입스크립트 추론이 제 기능을 하도록 하거나 타입을 정확하게 설정해야 되며 필요하지 않는 다면 any를 사용하지 않도록 해야한다.
Union 타입
function combie(input1: number | string, input2: number | string) {
return input1 + input2;
}
console.log(combie(3, 2)); // 5 : true
console.log(combie("Hello", "World")); // HelloWorld : true
console.log(combie("Hello", 1)); // Hello1 : true
console.log(combie("Hello", true)); // Error
|
기호를 통해 여러가지 타입을 받을 경우를 할 수 있다.
Literal 타입
숫자나 문자열도 아닌 정확한 값을 타입이다.
예를 들면 위의 Union
값에서는 input값으로 string
+ number
의 조합으로 값을 받아도 해당 값이 에러가 나지 않고 JavaScript로 받아 지게 되었다. 그래서 아래와 같이 해당 값을 같은 타입인 경우만 더하는 코드가 있다.
function combie(input1: number | string, input2: number | string){
if(typeof input1 === 'number' && typeof input2 ==== 'number'){
return +input1 + +input2; // +를 붙이면 숫자가된다. parseInt()롤 바꾸는 것과 비슷
}else {
return input1.toString() + input2.toString();
}
}
console.log(combie(3, 2)); // 5 : true
console.log(combie("Hello", "World")); // HelloWorld : true
console.log(combie("Hello", 1)); // ERROR
위의 코드를 통해서 문자 + 숫자 조합을 방지할 수 있다.
여기에 더해서 리턴 값에서 사용자가 어떠한 방식으로 변환되서 반환하게 하려는 경우, 예를 들어 string으로 반환하려한다면 다음과 같이 resultType으로 값을 따로 받아서 지정할 수 있다.
function combie(input1: number | string, input2: number | string, resultType: string){
if(typeof input1 === 'number' && typeof input2 ==== 'number' || resultType === "as-number"){
return +input1 + +input2;
}else {
return input1.toString() + input2.toString();
}
}
console.log(combie(3, 2, "as-number")); // 5 : true
console.log(combie("Hello", "World", "as-text")); // HelloWorld : true
// 아래의 값은 as-number이기 때문에 문자열 "1"를 숫자로 바꾸어서 더한 결과 값이 나온다.
console.log(combie("1", 1, "as-number")); // 2 :
이렇게 함으로써 “1” + 1
을 계산하게 만들 수 있다.
하지만 이 방법의 단점은 해당 값인 ‘as-number’,
‘as-text’
를 기억해야 한다.
그래서 다음과 같이 리터럴 타입을 사용해서 기억을 하지 않아도 어떤 값이 들어가는지를 알 수 있게 할 수 있다.
function combie(input1: number | string, input2: number | string, resultType: 'as-number' | 'as-text'){
if(typeof input1 === 'number' && typeof input2 ==== 'number' || resultType === "as-number"){
return +input1 + +input2;
}else {
return input1.toString() + input2.toString();
}
}
console.log(combie(3, 2, "as-number")); // 5 : true
console.log(combie("Hello", "World", "as-text")); // HelloWorld : true
// 아래의 값은 as-number이기 때문에 문자열 "1"를 숫자로 바꾸어서 더한 결과 값이 나온다.
console.log(combie("1", 1, "as-number")); // 2 :
리터럴 타입은 resultType: 'as-number' | 'as-text'
와 같이 아무 string
이 아닌 특정 string
값만 받게 할 수 있다.
이렇게 할때 조건문 안에 if(typeof input1 === 'number' && typeof input2 ==== 'number' || resultType === "as-num")
과 같이 틀리게 쓸 경우 에러를 발생 시켜 준다.
Aliases 타입
aliases 타입은 반복되는 타입을 저장 해 둘 수 있다.
type DataTypes = number | string
type resultTypes = "as-number" | 'as-text'
type User = { name: string; age: number };
function Data(input1: DataTypes, input2: DataTypes, resultType: resultTypes){
...
}
const u1: User = { name: 'Lee', age: 20 };
Return 타입
반환된 값의 타입 number값을 두개 받아서 더한 값을 return 할 경우 typescript는 반환 값도 number라는 추론을 한다.
// 반환값을 따로 지정하지 않고 타입스크립트에서 추론한 결과
// true
function sum(num1: number, num2, number) {
return num1 + numb2;
}
// 반환값 타입 따로 지정할 경우 결과
// true
function sum(num1: number, num2, number): number {
return num1 + numb2;
}
// false
function sum(num1: number, num2, number): string {
return num1 + numb2;
}
위와 같이 올바른 타입의 반환 값을 지정할 경우 정상적으로 작동하지만 number
가 반환 될 수 밖에 없는데도 string
을 반환 값으로 지정할 경우 Error가 난다.
void 타입
이 Return
타입에는 void
타입이 있다.
function sum(num1: number, num2, number): number {
return num1 + numb2;
}
function printResult(num: number): void {
console.log("Result: " + num);
}
printResult(sum(1, 2)); // Result: 3
이때 printResult()
는 아무것도 반환하지 않기때문에 void
타입을 가진다.
이 아무것도 반환하지 않는 함수를 console.log(printResult())
를 찍게 되면 undefined
를 반환한다.
기존에 undefined
랑 다른 것이 타입스크립트에는 값을 지정하지 않는 경우에 undefined
가 나오기 때문이다. 즉, 함수 반환값이 없다고 undefined
를 넣게 되면 반환문이 있을 것이라고 여기게 된다.
만약 다음과 같이 return;
을 쓰게 되면 실행을 할 수 있게 된다.
// true
function printResult(num: number): void {
console.log("Result: " + num);
}
// false
function printResult(num: number): undefined {
console.log("Result: " + num);
}
// true
function printResult(num: number): undefined {
console.log("Result: " + num);
return;
}
정리하자면 void
는 반환값이 없을때 사용하고 undefined
는 반환되는 값이 없을때 사용한다.
Function 타입
function sum(num1: number, num2, number): number {
return num1 + numb2;
}
let func1: Function;
func1 = sum;
func1 = 5; // error
만약 function 타입을 지정하지 않을 경우 IDE에서 func1 = 5
를 했을때 실행전에 에러를 발견할 수 없게 된다.
화살표 함수로 반환 타입이 지정된 Function 타입을 지정 할 수 있다.
function sum(num1: number, num2, number): number {
return num1 + numb2;
}
let func2: () => number;
func2 = sum;
또한 파라미터 값(매개변수)를 추가해줄 수 있는데 원래 함수의 파라미터명과 일치하지 않아도 된다.
이 와같이 파라미터값또한 지정하게 되면 아래 처럼 사전에 에러를 잡을 수 있다.
function sum(num1: number, num2, number): number {
return num1 + numb2;
}
function sum2(num1: string, num2, string): string {
return num1 + numb2;
}
let func3: (a: number, b: number) => number;
func3 = sum; // true
func3 = sum2; // false
func3
는 파라미터가 number
값을 가져야하기 때문에 파라미터가 string
인 sum2
로는 값을 지정할 수 없다.
callBack 타입
function sum(num1: number, num2, number, cb: (num: number) => void) {
const result = num1 + numb2;
cb(result);
}
sum(10, 20, (result) => {
console.log(result);
});
// false : 인자가 하나인 값을 반환한다.
sum(10, 20, (result, result2) => {
console.log(result);
});
여기서 cb: (num: number) => void
으로 void를 Retrun 타입으로 지정하면서 기본적으로 여기서 반환하게 되는 모든 결과를 무시하게 된다. (return으로 값 자체는 반환 할 수 있긴하다.)
무언가를 반환할 수 있긴 하지만 반환되는 값으로는 아무 작업도 수행하지 않는다고 callback함수 타입에 정의되어 아무 작업도 하지 않게 된다.
unknown 타입
unknown
타입은 알 수 없는 타입이다.
타입스크립트에서 기본 제공되는 any
와는 타입으로 무엇을 입력할지 않수 없는 타입이다. (any
보다 좀더 제한적으로 사용한다.)
에러 발생 없이 어떤 값이든 저장할 수 있고 허용한다.
// any를 사용할때
let value: any;
let name: string;
value = 5;
value = "Lee";
name = value;
// unknown을 사용할때
let value: unknwon;
let name: string;
value = 5;
value = "Lee";
name = value; // error
위와 같이 unknwon
을 사용하는 경우 value
에 현재 저장되어 있는 타입을 확인해야지 문자열을 원하는 변수에 할당할 수 있게 된다.
let value: unknwon;
let name: string;
value = 5;
value = "Lee";
if (typeof value === "string") {
name = value; // true
}
if문에서 value
가 string
이라고 설정했기 때문에 할당이 될 수 있게 된다.
즉, 추가적인 타입 검사를 필요로 한다.
Never 타입
아무것도 반환하지 않는 void
와 달리 never
타입은 함수가 반환할 수 있는 타입이다.
function ErrorFunc(msg: string, code: number){
thorw {message: msg, errorCode: code};
}
ErrorFunc("Error !!!", 500);
위와 같이 실행시키게 되면 error를 발생시키게 되고 아무것도 반환하는 것이 아닌게 된다.
정확히는 이 함수는 never
를 반환하며 반환값을 생성하지 않는다.
function ErrorFunc(msg: string, code: number){
thorw {message: msg, errorCode: code};
}
const result = ErrorFunc("Error !!!", 500);
console.log(result);
try~catch
문을 사용하여 계속 진행할 수 있지만 이 함수가 스크립트와 항상 출동하기 때문에 아무것도 반환하게 되지 않는다.
따라서 이 함수는 void
뿐만 아니라 never
도 가능하게 된다.
3.OOP - class
객체 지향 프로그래밍 Object-oriented Progamming (OOP)
객체 지향 방법으로 객체를 구성하면서 앱에서 로직을 로직의 일부를 관리하는 객체로 분할 할 수 있다.
데이터를 저장하고 메서드를 실행하기 위해 메서드를 저장하는 데 사용하는 데이터 구조로 객체의 청사진이다.
클래스를 사용하여 객체의 형태, 포함해야하는 데이터, 클래스를 기반으로 객체를 쉽게 만들 수 있으려면 어떤 메서드가 필요한지 정의할 수 있기 때문에 이를 클래스 내의 ‘인스턴스’라고한다.
따라서 객체는 클래스 내의 인스터스인 것이다.
이러한 클래스를 기반으로 하면 동일한 구조, 동일한 클래스를 기반으로 하는 동일한 메서드로 여러 객체를 빠르게 복제할 수 있다.
이처럼 클래스는 객체의 형태, 포함해야할 속성과 메서드를 정의하는데에 도움이 된다.
Class
클래스 생성
class Department {
name: string;
}
클래스명은 관례상 첫글자를 대문자로 한다.
변수의 경우 let
이나 const
없이 입력한다.
예시로 name
이라는 변수를 선언했는데 이것은 객체는 아니다.
객체는 키-값 쌍으로 되어 있어햐하는데 키에 해당하는 값만 선언한다.
이 것을 클래스의 필드라고 한다.
그리고 name
에 Default값으로 기본값을 초기화(name: string = “Lee”
)할 수 있지만 굳이 사용하지 않아도 된다.
생성자
class Department {
name: string;
// 생성자
constructor(n: string) {
this.name = n;
}
}
기본적으로 클래스에는 constructor
라는 생성자 메서드가 예약어로 있다.
이를 사용하여 객체에 대한 초기화 작업을 수행할 수 있다.
this
키워드로 내부의 변수인 name
에대가 n
이라는 값을 받아서 값을 초기화 할 수 있다.
Department
객체를 생성할때 n
값을 받아서 constructor
를 통해 name
값을 초기화 한다.
-
클래스로 객체 생성
class Department { name: string; constructor(n: string) { this.name = n; } } const Accounting = new Department("Accounting"); console.log(Accounting); // Department {name : "Accounting"}
new
키워드를 통해Department
객체를 생성할 수 있다.이때
new
키워드가 있는 클래스를 실행할때constructor
(생성자)가 호출되고n
이라는 값을 전달 해주어야한다.
클래스 method
생성 및 this
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe() {
console.log("deparment: " + this.name);
}
}
const Accounting = new Department("Accounting");
accounting.describe(); // Department {name : "Accounting"}
클래스에 있는 함수를 method
라고 하며 메서드의 경우에는 function
예약어 없이 만들면 된다.
이때 클래스 내부에 있는 변수값을 사용할때는 this
키워드를 사용한다.
실행할때는 객체를 실행하는 것처럼 호출하면 된다.
여기서 재밋는 점은 해당 메서드를 복사할 경우 이다.
const accountingCopy = { describe: accounting.describe };
accountingCopy.describe();
위 accouting
에서 describe
함수로 호출한 값을 복사한 변수 accountingCopy
가 있을때 해당 함수에서 describe();
를 실행하면 undefined
가 나온다.
그 이유는 accountingCopy
에서 describe
를 호출하기 때문에 accountingCopy
를 참조하고 이 accountingCopy
는 name
속성이 없는 객체라서 this.name
에 접근하게 되면 에러가 발생한다.
그래서 이문제를 해결하기 위해 describe
메서드에 매개변수를 추가할 수 있다.
이때 복사하는 변수에 name값도 같이 추가해주어야한다.
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
}
const accountingCopy = { name: "Kim", describe: accounting.describe };
accountingCopy.describe();
Private & public 제어자
class Department {
name: string;
employees: string[] = [];
constructor(n: string) {
this.name = n;
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
// emplyess를 추가하는 메서드
addEmployee(employee: string) {
this.employees.push(emplyee);
}
// emplyess를 출력하는 메서드
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
const Accounting = new Department("Accounting");
accounting.addEmplyee("Lee");
accounting.employees[1] = "Kim";
accounting.printEmplyeeInformation(); // ["Lee", "Kim"]
위와 같이 emplyees
라는 변수가 있고 해당 변수에 값을 추가하는 메서드인 addEmplyee
와 해당 변수값을 출력하는 printEmplyeeInformation
메서드가 있다.
addEmplyee
를 통해서 원하는 값을 emplyees
에 추가할 수 있는데, 이때 accounting.employees[1] = 'Kim';
으로 원치 않는 방식으로 값을 추가할 수도 있다.
이때 private
키워드를 통해서 해당 방식을 막고 의도하는 방식으로만 값을 추가하게 할 수 있다.
의도하지 않는 방식을 사용하게 되면, 코드 컨벤션이나 여러 이슈들이 생겼을때 문제가 생길수 있기 때문에 의도 되로 명확하게 동작하도록 해야한다. 만약 addEmplyee가 단순히 배열에 값을 추가하는 것 이상의(예시로 유효성 검사를 추가한다던가 하는.. ) 작업을 수행하할 수도 있기 때문에 통일된 방법을 쓰게 해야한다.
class Department {
name: string;
private employees: string[] = [];
constructor(n: string) {
this.name = n;
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
// emplyess를 추가하는 메서드
addEmployee(employee: string) {
this.employees.push(emplyee);
}
// emplyess를 출력하는 메서드
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
const Accounting = new Department("Accounting");
accounting.addEmplyee("Lee");
accounting.employees[1] = "Kim"; // Error
위와 같이 emplyees
를 private
으로 선언해두면 해당 값을 class
외부에서 접근할 수 없게 된다.
이러한 private
키워드를 제어자라고 하면 기본값으로 있어서 추가할 필요 없는 public
키워드도 있다. (public은 외부에서 접근 가능하다는 것이다.)
약식 필드
필드에 값을 일일이 추가하고 생성자를 통해 초기화를 할 때 반복적인 작업이 많아진다.
이때 필드명과 생성자의 명을 동일시하게 하여 초기화를 해주고 접근자를 적어주면 위에서 했던 방식과 같이 해당 값들을 초기화 할 수 있다.
class Department {
constructor(public name: string, private employee: string) {}
describe(this: Department) {
console.log("deparment: " + this.name);
}
// emplyess를 추가하는 메서드
addEmployee(employee: string) {
this.employees.push(emplyee);
}
// emplyess를 출력하는 메서드
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
물론 내부적으로 호출할 때에는 this
키워드를 통해 this.name
으로 호출한다.
이렇게 축약된 코드로 코드의 양을 줄일 수 있다.
readonly 제어자
private
이나 public
이어서도 안되고, 초기화 이후 변경되면 안되는 특정 필드에 경우는 readonly
로 추가해주면 된다.
해당 객체를 구분하는 유일한 고유 id
값이 있다고 할때 예시를 들면 아래와 같다.
class Department {
private employee: string;
// priavate readonly id: string;
constructor(private readonly id: string, public name: string) {
// this.id = id;
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
// emplyess를 추가하는 메서드
addEmployee(employee: string) {
this.employees.push(emplyee);
}
// emplyess를 출력하는 메서드
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
이렇게 선언된 id
값은 초기화 중에 한번만 사용할 수 있다.
상속
위에서 만든 Department
라는 부서가 있고 특정 부서에 고유 속성과 메서드를 가지는 개별 부서를 생성할때 extends
키워드로 상속을 사용한다.
상속은 하나의 클래스에서만 상속할 수 있고 여러 클래스를 동시에 상속할 수 없다.
class ITDepartment extends Department {}
const IT = new ItDepartment("IT 001", "Accounting");
위와 같이 Department
를 상속하면 Department
의 생성자를 그대로 사용할 수 있다.
그리고 해당 값에 고유 생성자를 추가할 수 있다.
class ITDepartment extends Department {
admins: string[];
constructor(id: string, admins: string[]) {
super(id, "IT");
this.admis = admins;
}
}
const it = new ItDepartment("IT 001", ["Lee"]);
it.addEmployee("Lee");
it.addEmployee("Kim");
it.describe();
it.printEmplyeeInformation();
이때 부모의 생성자의 필드를 그대로 사용하려면 super 키워드를 사용한다.
그러면 super
는 부모 생성자의 인수인 id
와 ㄷ을 취하므로 id
값을 super
로 전송하고 name
값을 하드 코딩할 수 있다.
admins
는 부모 클래스의 Deaprtment
에 없는 것이기 때문에 ITDepartment
에 추가한다.
상속하여 메서드 추가하기
기존 메서드를 그대로 사용하면서 상속받은 클래스에서만 사용하는 메서드 추가
class AccountingDepartment extends Department {
constructor(id: string, private reports: string[]) {
super(id, "IT");
}
addReport(text: string) {
this.reports.push(text);
}
printReports() {
console.log(this.reports);
}
}
const accounting = new AccountingDepartment("IT 002", []);
accounting.addReport("Add Reports ...");
accounting.printReports(); // [Add Reports ...]
상속 재정의
기존에 있던 부모 클래스의 메서드를 재정의
// 부모 클래스
class Department {
protected employee: string; // private -> protected
// priavate readonly id: string;
constructor(private readonly id: string, public name: string) {
// this.id = id;
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
// emplyess를 추가하는 메서드
addEmployee(employee: string) {
this.employees.push(emplyee);
}
// emplyess를 출력하는 메서드
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
// 하위 클래스
class AccountingDepartment extends Department {
constructor(id: string, private reports: string[]) {
super(id, "IT");
}
addEmployee(name: string) {
// name이 "Lee"가 아닐 경우 추가
if (name === "Lee") {
return;
}
this.emplyees.push(name);
}
}
const accounting = new AccountingDepartment("IT 002", []);
accounting.addEmployee("Kim");
private employee
는 private
이기 때문에 Department
에서만 사용가능하다.
접근할 수 있도록 하면서 외부에서 변경 불가능한 속성으로 만들려고 한다면 protected
로 바꾸면 된다.
protected
는 private
와 비슷하지만 상속 받은 클래스에서도 사용가능하게 한다.
getter & setter
getter
class AccountingDepartment extends Department {
private lastReport: string;
constructor(id: string, private reports: string[]) {
super(id, "IT");
this.lastReport = reports[0];
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
위에서 lastReport
필드는 private
로 지정되어 있어서 이 메서드 내에서 접근은 가능하지만 점(.)을 표기하여 접근하는 것은 불가능하다.
이때 값을 가지고 올때 함수나 메서드를 실행하는 속성인 getter
를 통해 접근 가능하도록 할 수 있다.
class AccountingDepartment extends Department {
private lastReport: string;
// 해당 값으로 this.lastReport를 반환하게 할 수 있다.
get mostRecentReport1() {
return this.lastReport;
}
// this.lastReport값이 존재하는지 확인하고 반환한다.
get mostRecentReport2() {
if (this.lastReport) {
return this.lastReport;
}
throw new Error("값이 존재하지 않습니다.");
}
constructor(id: string, private reports: string[]) {
super(id, "IT");
this.lastReport = reports[0];
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = new AccountingDepartment("IT 002", []);
console.log(accounting.mostRecentReport); // O
console.log(accounting.mostRecentReport()); // X
메서드를 실행하는 것처럼 accounting.mostRecentReport()
이 아닌 accounting.mostRecentReport
으로 호출한다.
setter
class AccountingDepartment extends Department {
private lastReport: string;
// this.lastReport값이 존재하는지 확인하고 반환한다.
get mostRecentReport2() {
if (this.lastReport) {
return this.lastReport;
}
throw new Error("값이 존재하지 않습니다.");
}
set mostRecentReport(value: string) {
this.addReport(value);
}
set mostRecentReport(value: string) {
if (!value) {
throw new Error("값을 확인하세요.");
}
this.addReport(value);
}
constructor(id: string, private reports: string[]) {
super(id, "IT");
this.lastReport = reports[0];
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = new AccountingDepartment("IT 002", []);
accounting.mostRecentReport = ""; // Error
accounting.mostRecentReport = "setter text";
console.log(accounting.mostRecentReport);
정적 메서드 & 속성
정적 메서드와 속성으로 클래스의 인스턴스에 접근할 수 없는 속성과 메서드를 클래스에 추가할 수 있다.
새 클래스 이름을 먼저 호출하지 않고 클래스에 직접 추가하는 것이다.
JS에 에 내장된 예시를 보면 Math
생성자 함수나 클래스를 Math.PI
, Math.Pow()
와 같이 접근할 수 있다. Math의 인스턴스에 접근할 수 없는 메서드와 속성이므로 new Math를 호출하여 사용하지 않는다.
호출할 경우 작동하지 않고 직접 클래스 자체에서 속성과 메서드에 접근해야한다.
class Department {
private employee: string;
static fiscalYear = 2022;
constructor(private readonly id: string, public name: string) {
// console.log(this.fiscalYear); -> 작동안함
console.log(Deoartment.fiscalYear);
}
static createEmployee(name: string) {
return { name: name };
}
describe(this: Department) {
console.log("deparment: " + this.name);
}
addEmployee(employee: string) {
this.employees.push(emplyee);
}
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
const employee1 = Deparment.createEmployee("Lee");
console.log(employee1); // {name : "Lee" }
console.log(Department.fiscalYear); // 2022
추상 클래스
Department
클래스를 ITDepartment
가 상속할때 위에서 했던 것처럼 재정의를 할 수 있지만, 해당 메서들르 재정의하는 옵셕을 입력하고 싶지 않을때나, 특정 클래스를 확장시킬때 특정 메서드를 구현하거나 재정의하도록 하게 하는 경우가 있다.
이때 abstract
를 사용하여 추상화 할수 있다.
해당하는 메서드 앞에 붙일 수 있고, 하나라도 해당 키워드를 쓰면 class
앞에도 사용해야 한다.
abstract class Department {
private employee: string;
static fiscalYear = 2022;
constructor(private readonly id: string, public name: string) {
// console.log(this.fiscalYear); -> 작동안함
console.log(Deoartment.fiscalYear);
}
static createEmployee(name: string) {
return { name: name };
}
abstract describe(this: Department): void;
addEmployee(employee: string) {
this.employees.push(emplyee);
}
printEmplyeeInformation() {
console.log(this.emplyees);
}
}
class ITDepartment extends Department {} // Error
class ITDepartment extends Department {
describe(this: Department) {
console.log("Hello World");
}
}
추상화 키워드를 사용할 경우 해당 부분을 구현하지 않으면 Error가 발생한다.
싱글톤 & 개인 생성자
- private 생성자
OOP에 있는 싱글톤 패턴은 특정 클래스의 인스턴스를 정확하게 하나만 갖도록 한다.
IT 부서는 하나 이상일 수 있지만 회계 부서는 하나로 구현해야 할때
new AccountingDepartmnet
를 여러번 수동으로 호출하지 않기 위해서 AccountingDepartmnet
생성자 앞에 preivate
키워드를 붙인다.
class AccountingDepartment extends Department {
private lastReport: string;
get mostRecentReport() {
if (this.lastReport) {
return this.lastReport;
}
throw new Error("값이 존재하지 않습니다.");
}
private constructor(id: string, private reports: string[]) {
super(id, "IT");
this.lastReport = reports[0];
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = new AccountingDepartment("d2", []); // Error
이렇게 하면 new 키워드를 하여 호출할때 error가 나온다.
그래서 클래스 자체에서 정적 메서드를 호출하게 하여 인스턴스화할 필요가 없게한다.
class AccountingDepartment extends Department {
private lastReport: string;
private static instance: AccountingDepartment;
get mostRecentReport() {
if (this.lastReport) {
return this.lastReport;
}
throw new Error("값이 존재하지 않습니다.");
}
private constructor(id: string, private reports: string[]) {
super(id, "IT");
this.lastReport = reports[0];
}
static getInstance() {
if (AccountingDepartment.instance) {
return this.instance;
}
this.instance = new AccountingDepartmnet("d2", []);
return this.instance;
}
addReport(text: string) {
this.reports.push(text);
this.lastReport = text;
}
}
const accounting = AccountingDepartment.getInstance();
const accounting2 = AccountingDepartment.getInstance();