Type Annotation
- Type Annotation은 JavaScript에서는 없었던 type을 지정해주는 문법이다.
- 아래와 같이 타입을 지정하거나, 혹은 typescript가 타입을 추측하여 지정하는 경우도 있다 아래의 예시를 보면
let a = "Mark";
a = 39;
// JavaScript 상에는 문제가 없는 코드 이지만,
// TypeScript 에서는 Type이 위에서 String으로 명시되어있기에, 해당 코드는 에러를 발생한다.
let b:number
a = 39;
//b의 변수 선언과 같이, 초기 값이 없으면 무조건 type을 지정해줘야한다.
Primitive Type
- 오브젝트와 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형이다.
- primitive 형의 내장 함수를 사용 가능한 것은 JavaScript 처리 방식 덕분이다.
- boolean
- number
- string
- symbol
- null
- undefined
- literal 값으로 Primitive 타입의 서브 타입을 나타낼 수 있다.
Type Casing
- Number, String, Boolean 또는 Object 유형이 위에서 권장한 소문자 버전과 동일하다고 생각하고 싶을 수 있다.
- 그러나 이러한 유형은 primitives를 나타내지 않으며, 타입으로 더 이상 사용해서는 안된다.
- 아래는 잘 못된 primitive type이다. -> 모두 소문자여야 한다.
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
- Javascript와 같이 TypeScript의 모든 숫자는 부동 소수점 값이다.
- TypeScript는 16진수 및 10진수 리터럴 외에도 ECMAScript 2015에 도입된 2진수 및 8진수도 지원한다.
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
- 다른 언어에서와 마찬가지로이 텍스트 형식을 참조하기 위해 string 형식을 사용합니다.
- JavaScript와 마찬가지로, TypeScript는 문자열 데이터를 둘러싸기 위해 큰 따옴표(")나, 작은 따옴표(') 를 사용합니다.
let name: string = "mark";
name = 'anna';
- 행에 걸쳐 있거나, 표현식을 넣을 수 있는 문자열
- 이 문자열은 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
- primitive type의 값을 담아서 사용한다.
- 고유하고 수정 불가능한 값으로 만들어 준다.
- ECMAScript 2015의 Symbol 입니다.
new Symbol
로 사용 할수 없다.- Symbol을 함수로 사용해서 symbol 타입을 만들 수 있다.
Symbol을 적용하기 위해서 해야할 사전 작업
...
"lib" : [
"ES2015",
"DOM"
]
...
Undefined & Null
- TypeScript에서, undefined와 null은 실제로 각각 undefined와 null이라는 타입을 가진다.
- void와 마찬가지로, 그 자체로 그다지 유용하지 않다.
- 둘다 소문자만 존재한다.
let u: undefined = undefined
let n: null = null
- number에 null 또는 undefine를 할 당 할 수 있다.
- 하지만, 컴파일 옵션에서
--stricNullChecks
를 사용하면, Null과 nudefined는 void나 자기 자신들에게만 할당 할 수 있다.- 이 경우, null과 undefined를 할당 할 수 있게 하려면, union type을 이용 해야한다.
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
이라는 값으로 할당된 것을 null 이라고 한다.- 무언가가 있는데, 사용할 준비가 덜 된 상태
- null 이라는 타입은 null이라는 값만 가질 수 있다.
- 런타임에서
typeof
연산자를 이용해서 알아내면, object 입니다.
- 값을 할당하지 않은 변수는 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
- 본래 JS에서 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();
- any는 되로록이면 안쓰는게 좋다.
- 컴파일 타임에 타입 체크가 정상적으로 이루어지지 않기 대문
- 그래서 컴파일 옵션 중에 any를 써야하는데 쓰지 않으면 오류를 뱉도록 하는 옵션도 있음
numplicityAny
unknown
- 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;
}
- 변수, 상수, 함수 또는 클래스가 어딘가에 이미 선언되어 있음을 알린다.
- 즉, JS코드로 컴파일 되지 않고, TypeScript 컴파일러에게 타입 정보를 알리기만 한다.
never
예시
- never는 일반적으로 return에서 사용된다.
- 어떠한 형태로도 return 되지 않는다
function error(message: string): never{
throw new Error();
}
function fail(){
return error("failed");
}
function infiniteLoop(): never{
while(true){}
}
- 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
- 위의 코드를 보게 된다면, a는 any라는 타입이 된다.
- 해당 코드는 NaN과 같은 에러를 발생시키게 될 수도 있다
- 이를 막기 위해 아래의 옵션이 있다.
- 타입을 명시적으로 지정하지 않는 경우, 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
- 위의 코드 처럼 타입을 명시적으로 작성한 경우에서 undefined라는 타입을 출력했습니다.
- 에러를 뱉은것이 아닌
undefined
가 나온 이유는 결국 모든 타입에 자동으로null
과undefined
가 포함 되어있기 때문입니다.
- 모든 타입에 자동으로 포함되어 있는
null
과undefined
를 제거해준다.
- 해당 옵션을 킨 후 아래의 코드 결과
// 매개변수의 타입은 명시적으로 지정했습니다.
// 명시적으로 지정하지 않은 함수의 리턴 타입은 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;
}
}
- 함수 내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생시킨다.
// 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 입니다. -> 해당 경우는 잘못된 경우 입니다.
- TypeScript의 경우
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 타입이기 때문이다.
- 해당 옵션을 키게 된다면, 함수를 할당할 시에 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우, 에러를 통해 경고한다.
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
tsconfig schema
- compileIOnSave
- extends
- compileOption
- files
- include
- exclude
- references
tsconfig 구조
compileOnSave
{
...,
"compileOnSaveDefinition" : {
"properties" : {
"compileOnSave" : {
"description" : "Enable Compile-on-Save for this project.",
"type": "boolean"
}
}
},
...
}
- 해당 기능은 파일을 Save하게 되면 자동 compile되는 옵션이다.
- true / false (default false) 로 설정할 수 있다.
- Vs 2015 with TypeScript 1.8.4 이상 해당 기능을 지원한다.
- 혹은 atom-typescript Plugin가 설치 되어야 한다.
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
- 위와 같이 구성하게 된다면,
base.json
에 의해서tsconfig.json
에서 활성화 되지 않던"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
- 상대 혹은 절대 경로의 리스트 배열 입니다.
- exclude 보다 상위의 개념이다.
include, exclude
- glob 패턴(마치
.gitignore
)
include
- exclude 보다 하위 개념이다.
-
- 같은걸 사용하면,
.ts
,.tsx
,.d.ts
만 include(allow JS)
- 같은걸 사용하면,
exclude
- 설정 안하면 4가지(
node_modules
,bower_components
,jspm_packages
,<outDir>
)를 default로 제외합니다. <outDir>
은 항상 제외합니다.(include에 있어도)
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"
},
...
}
- TypeScript 2.0 부터 사용 가능해진 내장 type definition 시스템
- 아무런 설정을 안한다면,
node_modules/@types
라는 모든 경로를 찾아서 사용 typeRoots
를 사용한다면, 배열 안에 들어있는 경로들 아래서만 가져온다.\types
를 사용하면, 배열 안의 모듈 혹은 ./node)modules/@types 안의 모듈 이름에서 찾아온다.- [] 빈 배열을 넣는 다는 것은 이 시스템을 이용하지 않겠다는 것이다.
typeRoots
와types
를 같이 사용하지 않는다.
target과 lib
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
- 기본 typedefinition 라이브러리를 어떤 것을 사용할 것인지
target
이es3
이고, default로lib.d.ts
를 사용한다.target
이es5
이면, default로dom
,es5
,scripthost
를 사용한다.target
이es6
이면, default로dom
,es6
,dom.interable
,scripthost
를 사용한다.
- lib를 지정하면 그 lib 배열로만 라이브러를 사용한다.
- 빈 [] =>
no definition found
어쩌궁,,,
- 빈 [] =>
outDir, outFile, rootDir
- 쉽게 말하면, Ts -> Js로 빌드 시에, 해당 작업을 통해서 나오는 파일의 경로를 말한다.
- 우리가 지정한 실제 프로젝트 파일의 위치를 말한다.
{
"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"
}
}
--noImplicitAny
--noImplicitThis
--stricNullChecks
--strictFunctionTypes
--strictPropertyInitialization
--strictBindCallApply
--alwaysStrict
--noImplicitAny
Raise error on expressions and declarations with an implied any type
- 명시적이지 않게 any 타입을 사용하여, 표현식과 선언에 사용하면, 에러를 발생
- 타입스크립트가 추론을 실패한 경우, any가 맞으면, any 라고 지정하라.
- 아무것도 쓰지 않으면, 에러를 발생
- 이 오류를 해결하면, any라고 지정되어 있지 않은 경우는 any가 아닌 것이다.
--noImplicitThis
Raise error on this expressions with an implied any type
- 명시적이지 않게 any 타입을 사용하여, this 표현식에 사용하면, 에러를 발생합니다.
- 첫번째 매개변수 자리에 this를 놓고, this에 대한 타입을 어떤 것이라도,
noImplicatAny
가 오류를 발생시킨다. - JavaScript에서는 매개변수에 this를 넣으면, 이미 예약된 키워드라
SyntaxError
를 발생시킨다. - call / apply /bind 와 같이 this를 대체하여 함수 콜 하는 용도로 쓰인다.
- 그래서 this를 any로 명시적으로 지정하는 것은 합리적입니다.(물론 구체적인 사용처가 있는 경우 타입을 표현한다.)
- Class 에서는 this를 사용하면서,
noImplicitThis
와 관련한 에러가 나지 않는다. - Class 에서 constructor를 제외한 멤버 함수의 첫번재 매개변수도 일반 ㅎ마수와 마찬가지로 this를 사용할 수 있다.
--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)
strictNullChecks
모드에서는,null
및undefined
값이 모든 유형의 도메인 에 속하지 않으며, 그 자신을 타입으로 가지거나, any경우에만 할당이 가능합니다.- 한 가지 예의는
undefined
에void
할당 가능
strictNullChecks
를 적용하지 않으면,- 모든 타입은
null
,undefined
값을 가실 수 있습니다. string
으로 타입을 지정해도,null
혹은undefined
값을 가질 수 있다.
- 모든 타입은
stictNullChecks
를 적용하면,- 모든 타입은
null
,undefined
값을 가질 수 없고, 가지려면 union type을 이용해서 직접 명시 해야 합니다. any
타입은null
과undefined
를 가집니다.- 예외적으로 void 타입의 경우
undefined
를 가집니다.
- 모든 타입은
strictNullChecks
를 적용하지 않고, 어떤 값이null
과undefined
를 가진다는 것은 암묵적으로 인정하고 계속 사용하다 보면, 정확히 어떤 타입이 오는 지를 개발자가 스스로 간과할 수 있다.- 이 옵션을 켜고 사용하는 경우,
- 사용하려는 함수를 선언할 때부터 매개변수와 리턴 값에 정확한 타입을 지정하려는 노력을 기울여야 하고, 기울이게 될 것입니다.
--strictFunctionTypes
Disable bivariant parameter checking for function types
- 함수 타입에 대한 bivariant 매개변수 검사를 비활성화
- Question : Which of the following types could be subtypes of
Dog -> Dog
?Greyhound -> Greyhound
Greyhound -> Animal
Animal -> Animal
Animal -> Greyhound
- Question : Which of the following types could be subtypes of
(Animal -> Greyhound <: (Dog -> Dog))
- 반환 타입은 공변적(convariant)
- 인자 타입은 반공변적(contravariant)
- 그런데 Ts에서 인자 타입은 공변적이면서, 반공변적인게 문제
- 이 문제를 해결하는 옵션이
strictFunctionTypes
- 옵션을 켜면, 에러가 안나던걸 에러 나게 함
- 이전에는 위와 같은 코드도 에러를 발생시키지 않았지만, 이제는 에러가 발생하게 됩니다.
--strictPropertyInitialization
Ensure non-undefined class properties are initialized in the constructor
- 정의되지 않는 클래스의 속성이 생성자에서 초기화 되었는지 확인합니다.
- 이 옵션을 사용하려면
--stitNullChecks
를 사용하도록 설정해야 합니다.
constructor
에서 초기 값을 할당한 경우 => 정상
constructor
에서 안하는 경우- 보통 다른 함수로 이니셜라이즈 하는 경우 (async 함수)
constructor
에는async
를 사용할 수 없다.
--strictBindCallApply
Enable stricter checking of of the bind, call, and apply methods on functions
- bind, call, apply 에 대한 더 엄격한 검사 수행
- Function 의 내장 함수인 bind / call / apply 를 사용할 때, 염격하게 체크하도록 하는 옵션입니다.
- bind는 해당 함수 안에서 사용할 this와 인자를 설정해주는 역활을 하고, call 과 apply는 this 와 인자를 설정한 후, 실행까지 합니다.
- call 과 apply 는 인자를 설정하는 방식에서 차이점이 있습니다.
- call은 함수의 인자를 여러 인자를 여러 인자의 나열로 넣어서 사용하고, apply는 모든 인자를 배열 바하로 넣어서 사용 합니다.
--alwaysStrict
Parse in strict mode and emit "use strict" for each source file
- 각 소스 파일에 대해 JavaScript의 strict mode 로 코드를 분석하고, "엄격하게 사용"을 해제합니다.
- syntex 에러가 ts error로 나온다.
- 컴파일된 JavaScript 파일에 "use strict" 추가 됨
Interfaces
What are Interfaces?
- 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)
- 어떤 타입이 오더라도 혹은, 어떤 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로 적용하게 된다면, 값을 변경할 수 없다.
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
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 MergingInterface {
a: string;
}
interface MergingInterface {
b: string;
}
let mi: MergingInterface;
mi.
Declaration Merging - type alias
type MergingType = {
a: string;
};
type MergingType = {
b: string;
};
Classes
- object를 만드는 blueprint(청사진, 설계도)
- 클래스 이전에 object를 만드는 기본적인 방법 function
- JavaScript 에도 class 를 es6 부터 사용 가능
- OOP을 위한 초석
- TypeScript 에서는 클래스도 사용자가 만드는 타입의 하나
Quick Start - Class
class Person {}
const p1 = new Person();
console.log(p1);
Class
는 es6 부터 나온 문법이기에, ES6와 똑같이 출력된다.- 하지만, ES5밑 버전은
class
라는 개념이 없기 때문에, 해당 부분은function
으로 대체된다.
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(); // 위와 같이 지원하려면 ?를 매개변수에 붙여야한다.
- 생성자 함수가 없으면, 디폴트 생성자가 불린다.
- 프로그래머가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.
- 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 으로 설정하면 클래스 외부에서 접근할 수 없다.
- 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){}
}
- ReadOnly는 말 그대로 read만 가능한 함수이다.
- 즉, 중간에 값을 변경하는 건 허용되지 않는다.
- 절대 변하지 않는 값에 할당하면 된다.
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);
- 프로퍼티 값이 동적으로 변경되어야할 때 유용한 문법이다.
- 해당 부분은 남발하기에는 조금 리스크가 있을 것 같다
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
- 어플리케이션이 생성되는 중간에 클래스에서 단 하나의 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();
- 상속 관계에 있을 때만 접근 가능
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 타입의 Class 생성자를 생성할 수 없다.
- Abstract 타입의 Class는 Abstract type의 함수가 있어야하며, 상속 받은 Class는 해당 함수를 제일 먼저 설정 해야한다.
Generics
Generics, Any와 다른 점
- hello의 리턴이 any이기 때문에 타입 헬퍼가 제대로 되지 않음
- helloGeneric을 사용하면 정상적으로 사용 가능
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
- Generic 타입을 쓰지 않으면, T를 추론
- Generic 타입을 쓰면, T를 검증
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;
}