Type Annotation

let a = "Mark";

a = 39;
// JavaScript 상에는 문제가 없는 코드 이지만, 
// TypeScript 에서는 Type이 위에서 String으로 명시되어있기에, 해당 코드는 에러를 발생한다.

let b:number

a = 39;
//b의 변수 선언과 같이, 초기 값이 없으면 무조건 type을 지정해줘야한다.

Primitive Type


Type Casing

TypeScript 핵심 primitive types은 모두 소문자 이다.

  • Number, String, Boolean 또는 Object 유형이 위에서 권장한 소문자 버전과 동일하다고 생각하고 싶을 수 있다.
  • 그러나 이러한 유형은 primitives를 나타내지 않으며, 타입으로 더 이상 사용해서는 안된다.

function reverse(s:String): String{
	return s.split("").reverse().join("");
}
reverse("hello world");
function reverse(s:string):string{
	return s.split("").reverse().join("");
}
reverse("hello world");

Boolean


let isDone:boolean = false;

isDone = true

console.log(typeof isDone);
// boolean

실행하는 방법

# ts를 js로 변환 시켜주는 명령어
npx tsc
node boolean.js

Number / number


let decimal: number = 6

let hex: number = 0x00d

let binary: number = 0b1010

let octal: number = 0o744

let NotANumber: number = NaN

let underscoreNum: number = 1_000_000;

string


let name: string = "mark";

name = 'anna';
Template String

  • 행에 걸쳐 있거나, 표현식을 넣을 수 있는 문자열
  • 이 문자열은 backtick(=backquote) 기호에 둘러쌓여 있습니다.
  • 포함된 표현식은 `${expr}` 와 같은 형태로 사용합니다.

let fullName: string = `Bob Bobbington`;
let age: number = 38;

let sentence: string = `Hello, my name is ${ fullName }
I'll be ${ age + 1 } years old next month `

//template string 을 사용하지 않을 경우
let sentence: string = "Hello, my name is " + fullName + ".\n\n" + "I'll be " + (age + 1) + " years old next month"

Symbol


Symbol

  • primitive type의 값을 담아서 사용한다.
  • 고유하고 수정 불가능한 값으로 만들어 준다.
  • ECMAScript 2015의 Symbol 입니다.
  • new Symbol로 사용 할수 없다.
  • Symbol을 함수로 사용해서 symbol 타입을 만들 수 있다.

Symbol을 적용하기 위해서 해야할 사전 작업

...
"lib" : [
	"ES2015",
	"DOM"
]
...

Undefined & Null


let u: undefined = undefined
let n: null = null
let name: string = null;
let age: number = undefined;

let name: string = null; //(X)

let u: undefined = undefined // (O)

let v: void = undefined // (O)
let union :string | null | undefined = 'str';
null in JavaScript

  • null 이라는 값으로 할당된 것을 null 이라고 한다.
  • 무언가가 있는데, 사용할 준비가 덜 된 상태
  • null 이라는 타입은 null이라는 값만 가질 수 있다.
  • 런타임에서 typeof 연산자를 이용해서 알아내면, object 입니다.

undefined in JavaScript

  • 값을 할당하지 않은 변수는 undefined 라는 값을 가진다.
  • 무언가가 아예 준비가 안된 상태
  • object의 property가 없을 때도 undefined 이다.
  • 런타임에서 typeof 연산자를 이용해서 알아내면, undefined 이다.

Object


//create by object literal
const person1 = { name: 'Mark', age: 39 };

// person1 is not "object" type.
// person1 is "{ name: string, age: number}" type

//create by Object.create
const person2 = Object.create({name:"Mark", age:39});

Array


let list:number[] = [1,2,3];

let list: Array<number> = [1,2,3];

Tuple


let x: [string, number];

// 순서, 타입, 길이 모두 맞아야함
x = ["hello", 39]

Any


function returnAny(message): any{
	console.log(message);
}
const any1 = returnAny("리턴은 아무거나");
any1.toString();

unknown


unknwon

  • TypeScript 3.0 버전부터 지원
  • any와 짝으로 any보다 Type-safe한 타입
    • any와 같이 아무거나 할당 할 수 있다.
    • 컴파일러가 타입을 추론할 수 있게끔 타입의 유형을 좁히거나
    • 타입을 확정해주지 않으면, 다른 곳에 할당할 수 없고, 사용할 수 없다.
  • unknown 타입을 사용하면 runtime error를 줄 일 수 있을 것 같다.
    • 사용 전에 데이터의 일부 유형의 검사를 수행해야 함을 알리는 API에 사용할 수 있을 것 같다.

응용 프로그램을 작성할 때는 모르는 변수의 타입을 묘사할 수 있다.
이러한 값은 동적 컨텐츠(예 : 사용자로부터, 또는 우리 API의 모든 값을 의도적으로 수락하기를 원할 수 있다.
이 경우, 컴파일러와 미래의 코드를 읽는 사람에게 이 변수가 무엇이든 될 수 있음을 알려주는 타입을 제공하기를 원하므로, unkwon 타입을 제공한다.

declare const maybe: unknown;

const aNumber: number = maybe;
if(maybe === true){
	const aBoolean: boolean = maybe;
	// 위의 코드에서 이미 maybe가 boolean으로 추정이 됐기 때문애, 아래의 코드에서 에러가 발생한다.
	// 위의 if문 한정해서 boolean이 된다.
	// const aString: string = maybe;
}

if(typeof maybe === 'string'){
	const aString:string = maybe;
}

declare

  • 변수, 상수, 함수 또는 클래스가 어딘가에 이미 선언되어 있음을 알린다.
  • 즉, JS코드로 컴파일 되지 않고, TypeScript 컴파일러에게 타입 정보를 알리기만 한다.

never

예시

function error(message: string): never{
	throw new Error();
}

function fail(){
	return error("failed");
}

function infiniteLoop(): never{
	while(true){}
}
never의 특징

  • never 타입은 모든 타입의 subtype이며, 모든 타입에 할당 할 수 있습니다.
  • 하지만, never에는 그 어떤 것도 할당할 수 없다.
  • any 조차도 never에게 할당 할 수 없다.
  • 잘못된 타입을 넣는 실수를 막고자 할 때 사용한다.

let a: string = "hello"

if(typeof !== 'string'){
	a; // a는 여기서 never가 나오게 된다. -> string이 아닐때 a는 never로 리턴 된다
}

declare const a: string | number;

if(typeof a !== "string"){
	a; // 이때는 string을 제외한 타입을 생각하므로, number가 리턴 값이 된다.
}

// 제네릭 타입 -> 조건부 타입
type Indexable<T> = T extends string ? T & { [index: string] : any } : never;

type ObjectIndexable = Indexable<{}>;

Type System

TypeScript에 추론에 의지하는 경우

// 타입 스크립트 코드 지만,
// a의 타입을 명시적으로 지정하지 않는 경우이기에 a는 any로 추론 됩니다.
// 함수의 리턴 타입은 number로 추론됩니다. (NaN 도 Number의 하나입니다.)

function f3(a){
	return a*8;	
}

// 사용자가 a가 any이기 때문에, 사용법에 맞게 문자열을 사용하여 함수를 실행했습니다.
console.log(f3); // 380
console.log(f3('Mark') + 5); // NaN
noImplicitAny

  • 타입을 명시적으로 지정하지 않는 경우, TS가 추론중에 any 라고 판단하게 되면,
  • 컴파일 에러를 발생시켜 명시적으로 지정하도록 유도한다.

// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않는 함수의 리턴 타입은 number로 추론됩니다.
function f4(a:number){
	if(a > 0){
		return a * 38;
	}
}
// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실행했습니다.
// 해당 함수의 리턴 타입은 number이기 때문에, 타입에 따르면 이어진 연산을 바로 할 수 있습니다.
// 하지만, 실제 undefined + 5가 실행되어 NaN이 출력됩니다.

console.log(f4(5)); // 190
console.log(f4(-5) + 5); // NaN
strictNullChecks

  • 모든 타입에 자동으로 포함되어 있는 nullundefined를 제거해준다.

// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 number | undefined 로 추론됩니다.
function f4(a: number){
	if( a > 0 ){
		return a * 38;
	}
}

// 사용자는 사용법에 맞게 숫자형을 사용하여 함수를 실행했습니다.
// 해당 함수의 리턴타입은 number | undefined 이기 때문에, 
// 타입에 따르면 이어진 연산을 바로 할 수 없습니다.
// 컴파일 에러를 고쳐야하기 때문에, 사용자와 작성자가 의논을 해야합니다.

console.log(f4(5));
console.log(f4(-5) + 5) // error TS2523: Object is possibley 'undefined'. 

명시적으로 리턴 타입 지정하기

// 매개변수의 타입과 함수의 리턴 타입을 명시적으로 지정했습니다.
// 실제 함수 구현부의 리턴 타입과 명시적으로 지정한 타입이 일치하지 않아 컴파일 에러가 발생합니다.
// error TS2366: Function lacks ending return statement and return type does not icre...
function f5(a: number):number {
	if(a>0){
		return a * 38;
	}
}
noImplicitReturns

  • 함수 내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생시킨다.

// if가 아닌 경우 return을 직접하지 않고 코드가 종료된다.
// error TS7030: Not all code path return a value
function f5(a: number){
	if(a>0){
		return a*38;
	}
}

매개변수가 Object일 때

// JavaScript
function f6(a) {
	return `이름은 ${a.name}이고, 연령대는 ${
	Math.floor(a.age / 10)* 10
	}대 입니다.`;
}

console.log(f6({name : 'Mark', age: 38})); // 이름은 Mark이고, 연령대는 30대 입니다.
console.log(f6('Mark')); // 이름은 undefined이고, 연령대는 NaN 입니다. -> 해당 경우는 잘못된 경우 입니다.
function f7(a: { name: string; age: number}): string{
	return `이름은 ${a.name}이고, 연령대는 ${
	Math.floor(a.age / 10)* 10
	}대 입니다.`; 
}

console.log(f6({name : 'Mark', age: 38})); // 이름은 Mark이고, 연령대는 30대 입니다.
console.log(f6('Mark')); // error TS2345 ~ 

Structural Type System

interface IPerson{
	name: string;
	age: number;
	speak(): string;
}

type PersonType = {
	name: string;
	age: number;
	speak(): string;
};

// 위 두개의 객체는 똑같은 구조이다.

let personInterface: IPerson = {} as any;
let personType: PersonType = {} as any;

personInterface = personType;
personType = personInterface;

Nominal Type System

type personID = string & { readonly brand: unique symbol };

function PersonID(id: string): PersonID{
	return id as PersonID;
}
function getPersonById(id: PersonID) {}
getPersonById(PersonID('id-aaaaaa'));
getPersonById('id-aaaaa'); // error TS2345

duck typing

class Duck:
	def sound(self):
		print u"꽥꽥"
class Dog:
	def sound(self):
		print u"멍멍"
def get_sound(animal):
	animal.sound()
def main:
	bird = Duck()
	dog = Dog()
	get_sound(bird)
	get_sound(dog)

Type Compatibility

SubType

// sub1 타입은 sup1 타입의 서브이다.
let sub1: 1 = 1;
let sup1: number = sub1;
sub1 = sup1; // error! Type 'number' is not assingable to type '1'.

// sub2 타입은 sup2 타입의 서브 타입이다.
let sub2: number[] = [1];
let sup2: object = sub2;
sub2 = sup2; // error! Type '{}' is missing the following properties from type 'number[]': length, pop, push, concat, and 16 more

//sub3 타입은 sup3 타입의 서브 타입이다.
let sub3:  [number, number] = [1,2];
let sup3: number[] = sub3;
sub3 = sup3; // error! Type 'number[]' is not assignable to type '[number, number]'. Target requires 2 element(s) but source may have fewer.

//sub4 타입은 sup4 타입의 서브 타입이다.
let sub4: number = 1;
let sup4: any = sub4;
sub4 = sup4;

// sub5 타입은 sup5 타입의 서브 타입이다.
let sub5: never = 0 as never;
let sup5: number = sub5;
sub5 = sup5; // error! Type 'number' is not assignable to type 'never'

class Animal {}
class Dog extends Animal{
	eat() {}
}// 상속

// sub6 타입은 sup6 타입의 서브 타입이다.
let sub6: Dog = new Dog();
let sup6: Animal = sub6;
sub6 = sup6 // error! Property 'eat' is missing in type 'SubAnimal' but require in type 'SubDog'

공변

같거나 서브 타입인 경우, 할당이 가능하다.
// primitive type
let sub7: string = '';
let sup7: string | number = sub7;

// object - 각각의ㅏ 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
let sub8: { a: string; b: number } = { a: '', b: 1 };
let sup8: { a: string | number; b: number} = sub8;

// array - object 와 마찬가지
let sub9: Array<{ a:string; b: number }> = [{a: '', b:1 }];
let sup9: Array<{ a:string | number; b: number }> = sub8;

반병

함수의 매개변수 타입만 같거나 슈퍼타입인 경우, 할당이 가능하다.
class Person()
class Developer extends Person{
	coding(){}
}

class StartupDeveloper extends Developer{
	burning() {}
}

function tellme(f: (d: Developer) => Developer ) {}

// Developer => Developer 에다가 Developer => Developer 를 할당하는 경우
tellme(function pToD(d: Person): Developer){
	   return new Developer();
}

// Developer => Developer 에다가 StartipDeveolper 를 할당하는 경우
tellme(function sToD(d: StartupDeveloper): Developer {
	return new Developer();
}); // 해당 부분은 에러가 나야하지만, 융통성으로 안난다. -> return 값이 매개변수보다 super 타입이기 때문이다.
strictFunctionTypes

  • 해당 옵션을 키게 된다면, 함수를 할당할 시에 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우, 에러를 통해 경고한다.

Type Alias

Type Alias?

  • Interface랑 비슷 해 보인다.
  • Primitive, Union Type, Tuple, Function
  • 기타 직접 작성해야하는 타입을 다른 이름으로 지정할 수 있다.
  • 만들어진 타입의 refer로 사용하는 것이지 타입을 만드는 것은 아니다.

Aliasing Primitive

// ---
type MyStringType = string;
const str = 'world';
let myStr: MyStringType = 'hello';

myStr = str;
// 별 의미가 없다.

Aliasing Union Type

let person: string | number = 0;
person = 'Mark';

type StringOrNumber = string | number;

let another: StringOrNumber = 0;
another = 'Anna';
/*
1. 유니온 타입은 A도 가능하고 B도 가능한 타입
2. 길게 쓰는걸 짧게
*/

Aliasing Tuple

let person: [string, number] = ['Mark', 35];
type PersonTuple = [string, number];
let another: PersonTuple = ['Anna', 24];

/*
1. 튜플 타입에 별칭을 줘서 여러군데서 사용할 수 있다.
*/

Aliasing Function

type EatType = (food: string) => void;

TypeScript Complier


Compilation Context

The compilation context is basically just fancy term for grouping of the file that Typescript will parse and analyze to determine what is valid and what isn't
Along with the interface about which files, the compilation context contains information about which compiler options are in use
A great way to define this logical grouping(we also like to use the term project) is using a tsconfig.json.file
Screenshot 2023-05-30 at 8.57.55 PM.png

tsconfig schema

tsconfig파일의 스키마 구조 사이트

최상위 프로퍼티

  • compileIOnSave
  • extends
  • compileOption
  • files
  • include
  • exclude
  • references

tsconfig 구조

tsconfig 구조

compileOnSave

{
	...,
	"compileOnSaveDefinition" : {
		"properties" : {
			"compileOnSave" : {
				"description" : "Enable Compile-on-Save for this project.",
				"type": "boolean"
			}
		}
	},
	...
}

extends

{
	...,
	"extendsDefinition" : {
		"properties" : {
			"extends" : {
				"description" : "Path to base configuration file to inherit from. Require TypeScript version 2.1 or later",
				"type": "string"
			}
		}
	},
	...
}

예시

{
	"extends" : "./base.json",
	"compilerOptions": {
		...
		//"strict": true
		...
	}
}

tsconfig.json

{
	"compilerOptions": {
		"strict": true
	}
}

base.json

files, include, exclude

{
	...,
	"filesDefinition" : {
		"properties" : {
			"files" : {
				"description" : "If no 'file' or 'include' property is persent in a tsconfig.json, the compiler defaults to including all files in the containg directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. ",
				"type": "array",
				"uniqueItems" : true,
				"items": {
					"type": "string"
				}
			}
		}
	},
	"excludeDefinition" : {
		"properties" : {
			"exclude" : {
				"description" : "Specifies a list of files to be excludeed from compilation. The 'exclude' property only affects the files include via the 'include' property and property and not the 'files' property. Glob patterns require TypeScript version 2.0 or later",
				"type": "array",
				"uniqueItems" : true,
				"items": {
					"type": "string"
				}
			}
		}
	},
	"includeDefinition" : {
		"properties" : {
			"include" : {
				"description" : "Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later",
				"type": "array",
				"uniqueItems" : true,
				"items": {
					"type": "string"
				}
			}
		}
	},
	...
}
files, include, exclude

  • 셋다 설정이 없으면, 전부다 컴파일 한다.

files

include, exclude

include
exclude

compileOptions


typeRoots, types

type

{
	...,
	"typeRoots" : {
		"description" : "Specify multiple folders that act like `./node_modules/@types` .",
		"type": "array",
		"uniqueItems" : true,
		"items": {
			"type": "string"
		},
		"markdownDescription" : "Specify multiple folders that act like `./node_modules/@types` .\n\nSee more: https:www.typescriptlang.org/tsconfig#typeRoots"
	},
	"types":{
		"description" : "Specify type package names to be included without being referenced in a source file",
		"type" : "array",
		"uniqueItems" : "array",
		"items":{
			"type": "string"
		},
		"markdownDescription" : "Specify type package names to be included without being referenced in a source file .\n\nSee more: https:www.typescriptlang.org/tsconfig#types"
	},
	...
}
@types

  • TypeScript 2.0 부터 사용 가능해진 내장 type definition 시스템
  • 아무런 설정을 안한다면, node_modules/@types 라는 모든 경로를 찾아서 사용
  • typeRoots를 사용한다면, 배열 안에 들어있는 경로들 아래서만 가져온다.\
  • types 를 사용하면, 배열 안의 모듈 혹은 ./node)modules/@types 안의 모듈 이름에서 찾아온다.
    • [] 빈 배열을 넣는 다는 것은 이 시스템을 이용하지 않겠다는 것이다.
  • typeRootstypes를 같이 사용하지 않는다.

target과 lib

target

target?

  • 빌드의 결과물을 어떤 버전으로 할 것인지
  • 지정하지 않으면 default는 es3 입니다.

{
	"target": {
		"description": "Set the JavaScript language version for emittted JavaScript and include compatible library declarations.",
		"type": "string",
		"default": "ES3",
		"anyOf": {
			"enum": [
				"ES3",
				"ES5",
				"ES6",
				"ES2015",
				"ES2016",
				"ES2017",
				"ES2018",
				"ES2019",
				"ES2020",
				"ESNext",
			]
		},
		{
			"pattern": "^([Ee] [Ss] ([356] | (20(1[56789] | 20 )) | [Nn] [Ee] [Xx] [Tt] ))$"
		}
	},
	"markdownDescription": "Set the JavaScript language version for emitteed JavaScript and include compatible library declaration"
}

lib

lib?

  • 기본 typedefinition 라이브러리를 어떤 것을 사용할 것인지
    • targetes3이고, default로 lib.d.ts를 사용한다.
    • targetes5이면, default로 dom, es5, scripthost를 사용한다.
    • targetes6이면, default로 dom, es6, dom.interable, scripthost를 사용한다.
  • lib를 지정하면 그 lib 배열로만 라이브러를 사용한다.
    • 빈 [] => no definition found 어쩌궁,,,

ts-lib

outDir, outFile, rootDir

outDir? outFile?

  • 쉽게 말하면, Ts -> Js로 빌드 시에, 해당 작업을 통해서 나오는 파일의 경로를 말한다.

rootDir?

  • 우리가 지정한 실제 프로젝트 파일의 위치를 말한다.

{
  "outFile": {
    "description": "Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output.",
    "type": "string",
    "markdownDescription": "Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output.\n\nSee more: https://www.typescriptlang.org/tsconfig#outFile"
  },
  "outDir": {
    "description": "Specify an output folder for all emitted files.",
    "type": "string",
    "markdownDescription": "Specify an output folder for all emitted files.\n\nSee more: https://www.typescriptlang.org/tsconfig#outDir"
  },
  "rootDir": {
    "description": "Specify the root folder within your source files.",
    "type": "string",
    "markdownDescription": "Specify the root folder within your source files.\n\nSee more: https://www.typescriptlang.org/tsconfig#rootDir"
  }
}

strict

{
  "strict": {
    "description": "Enable all strict type checking options.",
    "type": "boolean",
    "default": false,
    "markdownDescription": "Enable all strict type checking options.\n\nSee more: https://www.typescriptlang.org/tsconfig#strict"
  }
}
Enable all strict type checking options

  • --noImplicitAny
  • --noImplicitThis
  • --stricNullChecks
  • --strictFunctionTypes
  • --strictPropertyInitialization
  • --strictBindCallApply
  • --alwaysStrict

--noImplicitAny

Raise error on expressions and declarations with an implied any type

--noImplicitThis

Raise error on this expressions with an implied any type

--strcitNullChecks

In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any (the one exception being that undefined is also assignable to void)

--strictFunctionTypes

Disable bivariant parameter checking for function types

(Animal -> Greyhound <: (Dog -> Dog))

--strictPropertyInitialization

Ensure non-undefined class properties are initialized in the constructor

--strictBindCallApply

Enable stricter checking of of the bind, call, and apply methods on functions

--alwaysStrict

Parse in strict mode and emit "use strict" for each source file

Interfaces

What are Interfaces?

JS에서 Interface?

  • JS로 컴파일 될때는 Interface가 적용되지 않는다.
  • 하지만, 컴파일 과정에서 타입 체크 및 검사를 통해서 해당 interface에 대입하여, 검사를 진행합니다.

function hello(person: { name: string; age: number; }): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p: { name: string; age: number; } = {
    name: 'Mark',
    age: 35
};

hello(p); // 안녕하세요! Mark 입니다.

///////////////////////////////////////////////////////////////

interface Person {
    name: string;
    age: number;
}

function hello(person: Person): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p: Person = {
    name: 'Mark',
    age: 35
};

hello(p); // 안녕하세요! Mark 입니다.

interface.ts

optional property

interface - optional property(1)

interface Person {
    name: string;
    age?: number;
}

function hello(person: Person): void {
    console.log(`안녕하세요! ${person.name} 입니다.`);
}

const p1: Person = {
    name: 'Mark',
    age: 35
};

const p2: Person = {
    name: 'Anna'
};

hello(p1); // 안녕하세요! Mark 입니다.
hello(p2); // 안녕하세요! Anna 입니다.

interface1.ts

optional property

interface - optional property(2)

indexable type

  • 어떤 타입이 오더라도 혹은, 어떤 key, value가 오더라도 괜찮은 타입이다.

interface Person3{
	name: string;
	age?: number;
	[index: string]: any;
}
function hello3(person: Person3):void {
	console.log(`안녕하세요! ${person.name} 입니다.`)
}

const p31: Person3{
	name: "Mark",
	age: 39,
}

const p32: Person3{
	name: "Anna",
	systers: ["Sung",Chan"]
}

const p33: Person3{
	name: "Bokdaengi",
	father: p31,
	mother: p32,
}

hello(p31)


function in interface

interface - function in interface

interface Person4{
	name: string;
	age: number;
	hello(): void;
}

const p41: Person4 = {
	name = 'Mark',
	age: 39,
	hello: function(): void{
		console.log(`안녕하세요! ${this.name} 입니다.`);
	}
}

const p42: Person4 = {
	name = 'Mark',
	age: 39,
	hello(): void{
		console.log(`안녕하세요! ${this.name} 입니다.`)
	}
};

const p43: Person4 = {
	name = 'Mark',
	age: 39,
	hello(): void =>{
		console.log(`안녕하세요! ${this.name} 입니다.`)
	}
} // Arrow function에서의 this는 무조건 전역 변수를 가르키기 때문에, 해당 this는 arrow fn에서는 사용 불가능하다.

class implements

class implements interface

interface IPerson1 {
    name: string;
    age?: number;
    hello(): void;
}

class Person implements IPerson1 {
    name: string;
	age?: number | undefined;

    constructor(name: string) {
        this.name = name;
    }// name은 반드시 받아야하는 값이기 때문에, 해당 값을 constructor(매개 변수 받듯이)

    hello(): void {
        console.log(`안녕하세요! ${this.name} 입니다.`);
    }
}

const person = new Person('Mark');
person.hello(); // 안녕하세요! Mark 입니다.

function interface

interface extends interface

interface IPerson2 {
	name: string;
	age?: number;
}

interface IKorean extends IPerson2{
	city: string;
}

const k:IKorea={
	name: "이웅재",
	city: "서울",
};


function interface

interface HelloPerson {
    // (name: string, age: number): void;
    (name: string, age?: number): void;
}

const helloPerson: HelloPerson = function (name: string) {
    console.log(`안녕하세요! ${name} 입니다.`);
};

const helloPerson: HelloPerson = function (name: string, age: number) {
    console.log(`안녕하세요! ${name} 입니다.`);
}; // 에러 발생
// interface 상에서 적혀있는 age와 현재 함수에 있는 매개변수 age와 맞지 않는다.


helloPerson('Mark'); // 안녕하세요! Mark 입니다.

/*

함수의 타입 체크는 할당할때가 아니라 사용할때 한다는 점을 명심

*/

Readonly Interface Properties

readonly

  • 상수에 대한 부분을 readonly로 적용하게 된다면, 값을 변경할 수 없다.

interface Person8{
	name: string;
	age? : number;
	readonly gender: string;
}

const p81: Person8 = {
	name: "Mark",
	gender: "male",
};

p81.gender = "female"; // 에러 발생 -> readonly를 적용하게 된다면, 중간에 값을 바꿀 수 없다.

type alias vs interface

function

// type alias
type EatType = (food: string) => void;

// interface
interface IEat {
  (food: string): void;
}

array

// type alias
type PersonList = string[];

// interface
interface IPersonList {
  [index: number]: string;
}

interface

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtistsData {
  artists: { name: string }[];
}

// type alias
type ArtistsResponseType = ArtistsData & ErrorHandling;

// interface
interface IArtistsResponse extends ArtistsData, ErrorHandling {}

let art: ArtistsResponseType;
let iar: IArtistsResponse;

union types

type alias에서만 가능
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

type PetType = Bird | Fish;

interface IPet extends PetType {} // error TS2312: An interface can only extend an object type or intersection of object types with statically known members.

class Pet implements PetType {} // error TS2422: A class can only implement an object type or intersection of object types with statically known members.

Declaration Merging - interface

interface만 가능
interface MergingInterface {
  a: string;
}

interface MergingInterface {
  b: string;
}

let mi: MergingInterface;
mi.

Screenshot 2023-06-01 at 11.06.50 PM.png

Declaration Merging - type alias

type alias에서는 불가능
type MergingType = {
  a: string;
};

type MergingType = {
  b: string;
};

Screenshot 2023-06-01 at 11.08.03 PM.png

Classes

What are Classes??

  • object를 만드는 blueprint(청사진, 설계도)
  • 클래스 이전에 object를 만드는 기본적인 방법 function
  • JavaScript 에도 class 를 es6 부터 사용 가능
  • OOP을 위한 초석
  • TypeScript 에서는 클래스도 사용자가 만드는 타입의 하나

Quick Start - Class

class Person {}

const p1 = new Person();

console.log(p1);
class Person {
  name: string;

  constructor(name: string) {
    this.name = string;
  }
}

const p1 = new Person('Mark');

console.log(p1);

class 이름은 보통 대문자이용한다.
new를 이용하여 class 를 통해 object를 만들 수 있다.
constructor를 이용하여 object를 생성하며 값을 전달 할 수 있다.
this를 이용해서 만들어진 object를 가리킬 수 있다.

constructor & initialize

// strictPropertyInitialization : false
class Person {
  name: string;
  age: number;
} // 위 값을 에러이다. -> initialliz가 되지 않았다.

const p1: Person = new Person();
console.log(p1); // Person1 {}
person.age = 39;
console.log(person.name); // undefined
class Person {
	name: string = 'Mark';
	age: number;

	constructor(age? : number){
		if(age === undefined){
			this.age = 20;
		}else{
			this.age = age;
		}
	}
	async init(){}
}

const p1: Person = new Person(39);
const p2: Person = new Person(); // 위와 같이 지원하려면 ?를 매개변수에 붙여야한다.


Construct

  • 생성자 함수가 없으면, 디폴트 생성자가 불린다.
  • 프로그래머가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.
  • strict 모드에서 프로퍼티를 선언하는 곳 또는 생성자에서 값을 할당해야 한다.
  • 프로퍼티를 선언하는 곳 또는 생성자에서 값을 할당하지 않는 경우에는 !를 붙여서 위험을 표현한다.
  • 클래스의 프로퍼티가 정의되어 있지만, 값을 대입하지 않으면 undefined 이다.
  • 생성자에는 async 를 설정할 수 없다.

Access Modifiers

public class Person {
	public name: string = 'Mark';
	public age: number;

	public constructor(age? : number){
		if(age === undefined){
			this.age = 20;
		}else{
			this.age = age;
		}
	}
	public async init(){ }
}

const p1: Person = new Person(39);

접근제어자(public, private, protected)

  • 접근 제어자에는 public, private, protected가 있다.
  • 설정하지 않으면 public
  • 클래스 내부의 모든 곳(생성자, 프로퍼티, 메서드) 설정 가능하다.
  • private 으로 설정하면 클래스 외부에서 접근할 수 없다.
  • JavaScript에서 private 지원하지 않아 오랜동안 프로퍼티나 메서드 이름 앞에 _ 를 붙여서 표현했다.

initialization in constructor parameters

public class Person {
	name: string = 'Mark';
	age: number;

	constructor(name: string, age : number){
		this. name = name;
		this.age = age;
	}
}

----------

class Person{
	public constructor(public name: string, public age: number){}
	const p1:Person = new Person("Mark", 39);
	console.log(p1)
}

Getters & Setters

class Person{
	public constructor(private _name: string, private age: _number){}

	get name(){
		console.log("get") // getter를 사용하게 된다면, getter를 호출하기 전에 해당 작업을 미리 할 수 있다.
		return this._name;
	}

	set name(n : string){
		this._name = n;
	}
}

const p1: Person = new Person("Mark", 39);
console.log(p1.name); // get 을 하는 함수 getter
p1.name = "Woongjae"// set을 하는 하수 setter

readonly properties

class Person{
	public readonly name: string = "Mark"

	public constructor(public _name: string, public age: number){}
}

Index Signatures in class

// {mark : 'male', jade : 'male'}
// {chloe : 'femail', alex : 'male', anna: 'female'}

class Students{
	mark: string = 'male' // 해당 방식은 새로운 사람이 들어오게 된다면, 사용이 불가능 하다는 단점을 가지고 있다.
	[index: string] : "male" | "female";
}

const a = new Students();
a.mark = "male";
a.jade = "male";

console.log(a);

const b = new Students();
b.chloe = "female";
b.alex = "male";
b.anna = "female";

console.log(b);
index signature

  • 프로퍼티 값이 동적으로 변경되어야할 때 유용한 문법이다.
  • 해당 부분은 남발하기에는 조금 리스크가 있을 것 같다

Static Properties & Methods

class Person {
	public static CITY = "Seoul";
	public static hello(){
		console.log("안녕하세요", this.CITY)
	}

	public change(){
		CITY = "LA"
	}
}
 
const p1 = new Person();
p1.hello(); // 원래의 방식대로라면 왼쪽의 방식대로, 생성자를 생성후에 사용해야했다.

Person.hello();//하지만, static 선언하는 하는 순간부터는 해당 className.~ 으로 커버가 가능하다.
Person.CITY; // 해당 클래스를 공통적으로 사용해야하는 순간이 오면 그때 static을 사용해서, class 사용이 가능합니다.

const p2 = new Person();
p2.hello();// 안녕하세요 Seoul
p2.change();
p1.change(); // 안녕하세요 LA
// 이처럼 하나의 클래스에서 하나의 변수를 공유할 때 static을 사용한다.

Singletons

Singletons?

  • 어플리케이션이 생성되는 중간에 클래스에서 단 하나의 Object만 생성하는 패턴을 말한다.

class ClassName{
	private static instance : ClassName | null = null;
	public static getInstance() :ClassName(){
		// ClassName으로부터 만든 object가 있으면 그걸 리턴
		// 없으면, 만들어서 리턴
		if(ClassName.instace === n ){
			ClassName.instace = new ClassName();
		}

		return ClassName.instance;
	}
	private constructor() {}; // 외부에서 생성자를 호출 할 수 없도록 막음
}

Inheritance

class Parent{
	constructor(protected _name: string, private _age: number){}
	public print(): void{
		console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 입니다.`)
		
	}
}

// public 까지는 접근이 가능하다.
// 생성자를 만들어서 값을 할당하는 것 까지 가능하다.
const p = new Parent("Mark", 39);
p.print();


class Child extends Parent{
	public _name = "Mark Jr";
	// 접근 제어까지도 Override가 된다.

	public gender = "male";
	constructor(age: number){
		super('Mark Jr', age) // 부모의 생성자를 호출 하는 모습
		// 자식 생성자에서는 무조건 super가 먼저 생성되어야 한다.
	}
}

const c = new Child(5);
c.print();
protected

  • 상속 관계에 있을 때만 접근 가능

Abstract Classes

abstract Class AbstractPerson{
	protected _name: string = 'Mark';
	
	abstract setName(name: string): void;
}

class Person extends AbstractPerson{
	setName(name: string): void{
		this._name = name;
	}
}

const p = new Person();
setName();
Abstract?

  • Abstract 타입의 Class 생성자를 생성할 수 없다.
  • Abstract 타입의 Class는 Abstract type의 함수가 있어야하며, 상속 받은 Class는 해당 함수를 제일 먼저 설정 해야한다.

Generics

Generics, Any와 다른 점

function helloString(message: string): string {
  return message;
}

function helloNumber(message: number): number {
  return message;
}

// 더 많은 반복된 함수들 ...

function hello(message: any): any {
  return message;
}

// generic 함수 
function helloGeneric<T>(message: T): T {
  return message;
}

console.log(hello('Mark').length);
console.log(hello(38).length); // undefined

console.log(helloGeneric('Mark').length);
// console.log(helloGeneric<number>('Mark').length); (X)

console.log(helloGeneric(38).toString());
// console.log(helloGeneric(36).length); (X)

Generics Basic

function helloBasic<T>(message: T): T {
  return message;
}

console.log(helloBasic<string>('Mark'));
const age = helloBasic(38); //  추론이 된다. -> 가장 타이트한 범위가 추론됨
// helloBasic<number>('38'); (X)

function helloBaisc1<T, U>(message: T, comment: U):T{
	return message
}
helloBasic1<string, number>("Mark", 39);
helloBasic1(36,39) // 해당 방식은 추론됨

Generics Array & Tuple

function helloArray<T>(messages: T[]): T {
  return messages[0];
}

function helloTuple<T, K>(messages: [T, K]): T {
  return messages[0];
}

console.log(helloArray(['Hello', 'World'])); // string[]
console.log(helloArray(['Hello', 1])); // Array<string | number>
console.log(helloTuple(['Hello', 'World'])); // [string, string]
console.log(helloTuple(['Hello', 1])); // [string, number]
// console.log(helloTuple(['Hello', 'world', 1])); // Error

Generics Function

type HelloFunctionGeneric = <T>(message: T) => T;

const helloFunction: HelloFunctionGeneric = <T>(message: T): T => {
  return message;
};

console.log(helloFunction<string>('Hello').length);

Generics Class

class Person<T> {
  private _name: T;

  constructor(name: T) {
    this._name = name;
  }
}

new Person('Mark');
// new Person<string>(38); (X)

Generic with multiple types

class Person7<T, K> {
  private _name: T;
  private _age: K;

  constructor(name: T, age: K) {
    this._name = name;
    this._age = age;
  }
}

new Person7('Mark', 38);

Generics with extends

class Person6<T extends string | number> {
  private _name: T;

  constructor(name: T) {
    this._name = name;
  }
}

new Person6('Mark');
new Person6(38);
// new Person6(true); // T 가 string 또는 number 를 상속받기 때문에 boolean 은 X

keyof & type lookup system

interface Person8 {
  name: string;
  age: number;
}

const person8: Person8 = {
  name: 'Mark',
  age: 36
};

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

setProperty(person8, 'name', 'Anna');
// setProperty(person8, 'name', 27);
console.log(getProperty(person8, 'name'));
// console.log(getProperty(person8, 'fullname'));

keyof & type lookup system

interface IPerson{
	name: string;
	age: number;
}

const person: IPerson = {
	name: "Mark",
	age: 39,
};

// keyof의 타입을 만들어줌
type Keys = keyof IPerson;
// keyof는 해당 값에 대해서 그 중 한개의 타입으로 추론해준다.

function getProp(obj: IPerson, key: "name" | key){
	return obj[key];
}

function setProp(
	obj: IPerson,
	key: "name" | "age",
	value: string | number
): void{
	obj[key] = value;
}
// return을 받는 값은 union 값이 아닌 name, age 등 특정한 값이어야만 한다.

// extends를 사용해서 K를 T로 제한한다.
function getProp<T, K extends keyof T>(obj: T, key: K): T[K]{
	return obj[key];
}

function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]):void {
	obj[key] = value;
}


#TypeScript #JavaScript