What are Interfaces
Interface
타입을 만들어 내는 방식이다.
자바스크립트에는 전혀 없는 문법이다.
interface는 컴파일 타임에만 필요하다. 컴파일 타임에 interface를 이용해서 문제가 없는지 관계를 규명해서 체크해주는 역할을 한다. 이로 인해, 에러 발생 여부를 알게 되어 문제가 있는지를 확인 할 수 있다.
Optional property (1)
name은 항상 있어야 하는 타입이지만, age는 있을 수도 있고 없을 수도 있다면 ‘물음표’를 달아주면 된다.
hello2를 출력할때 age를 설정하지 않아도 오류가 발생하지 않는다.
interface Person1 {
name: string;
age?: number;
}
function hello2(person: Person2 ): void {
console.log(`안녕하세요! ${person.name}입니다.`)
}
hello2({ name: 'Mark', age: 39 });
hello2({ name: 'Anna' });
Optional property (2)
Indexable type
말 그대로 index를 사용하여 만드는 타입이다.
[]안에 index의 원하는 타입을 지정하고 : 뒤에는 해당 인덱스 안에 들어올 타입을 지정해준다.
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,
};
-
string ⇒ sister / any ⇒ ‘Sung’, ‘Chan’
const p32: Person3 = {
name: 'Anna',
sister: ['Sung', 'Chan'],
}
-
string ⇒ father, mother / any ⇒ p31, p32
any이기 때문에 객체가 들어가도 상관이 없다.
const p33: Person3 = {
name: 'Bromang',
father: p31,
mother: p32,
}
-
물음표와 indexable의 차이점
물음표: property의 이름이 명시되어 있고 해당 property가 있는 지 없는 지를 명시한다.
indexable: 어떤 이름의 property가 와도 괜찮다. 어떤 property이던지 추가로 설정할 수 있다.
function in interfaces
Interface안에서 함수를 정의하고 함수를 만들어서 사용 하기
interface Person4 {
name: string;
age: number;
hello(): void;
}
-
function 키워드를 이용해서 함수를 만든 후, 해당 함수를 hello 변수에 할당
const p41: Person4 = {
name: 'Mark',
age: 39,
hello: function (): void {
console.log(`안녕하세요! ${this.name}입니다.`)
},
}
-
function 키워드 없이 함수 만들기
const p42: Person4 = {
name: 'Mark',
age: 39,
hello(): void {
console.log(`안녕하세요! ${this.name}입니다.`)
},
}
-
화살표 함수로 만들기 → 사용 불가능
단, 해당 방법은 오류 발생! 화살표 함수 내부에서는 this 사용이 불가능하다.
const p43: Person4 = {
name: 'Mark',
age: 39,
hello: () void => {
console.log(`안녕하세요! ${this.name}입니다.`)
},
}
class implements interface
Interface를 이용해서 class 만들기
-
implements 키워드
해당 키워드를 사용해서 interface를 class로 사용한다.
interface의 내용을 바탕으로 class를 생성하는 것이다.
해당 클래스는 인터페이스의 구성을 만족하는 형태로 만들어야 한다.
interface IPerson1 {
name: string;
age?: number;
hello(): void;
}
class Person implements IPerson1 {
name: string;
age?: number | undefined;
constructor(name: string) {
this.name = name;
}
hello(): void {
console.log(`안녕하세요! ${this.name}입니다.`)
}
}
const person = new Person("Mark");
// const person: IPerson1 = new Person("Mark");
person을 확인하면 class Person이라고 나온다. 그런데 해당 Person은 interface IPerson1이 implement되어서 만들어진 것이기 때문에 주석 부분의 코드처럼 작성해도 된다.
interface extends interface
존재하는 interface를 가져와서 추가 interface를 넣기. 즉 상속이다!
IKorean은 IPerson2를 상속받았기 때문에 IPerson2가 가지고 있는 내용을 기본적으로 가지게 된다.
interface IPerson2 {
name: string;
age?: number;
}
interface IKorean extends IPerson2 {
city: string;
}
const k: IKorean = {
name: "Mark",
city: "Seoul",
}
compileOptions - typeRoots, types
"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`."
}
"types": {
"description": "Specify type package names to be included without being referenced in a source file.",
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
},
"markdownDescription": "Specify type package names to be included without being referenced in a source file."
}
@types
- TypeScript 2.0부터 사용 가능해진 내장 type definition 시스템이다.
이전에는 내장이 아닌 서드파티 type definition이었다. 이제는 내장으로 통합.
- 아무 설정을 하지 않으면
- node_modules/@types 라는 모든 경로를 찾아서 사용한다.
- typeRoots를 사용하면
- 배열 안에 들어있는 경로들 아래에서만 가져온다.
나는 node_modules 안의 @types 도 쓰겠지만 내 프로젝트 안의 @types도 쓸거라면 typeRoots를 지정해 주면 편하게 사용할 수 있다.
- types를 사용하면
- 배열 안의 모듈 혹은 ./node_modules/@types/ 안의 모듈 이름에서 찾아온다.
- 빈 배열을 넣는다는 건 이 시스템을 이용하지 않겠다는 것이다.
- typeRoots 와 types를 같이 사용하지 않는다.
compileOptions - target 과 lib
"target": {
"description": "Set the JavaScript language version for emitted JavaScript and include compatible library declarations.",
"type": "string",
"default": "ES3",
"anyOf": [
{
"enum": [
"ES3",
"ES5",
"ES6",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ES2021",
"ES2022",
"ESNext"
]
},
{
"pattern": "^([Ee][Ss]([356]|(20(1[56789]|2[012]))|[Nn][Ee][Xx][Tt]))$"
}
],
"markdownDescription": "Set the JavaScript language version for emitted JavaScript and include compatible library declarations."
}
"lib": {
"description": "Specify a set of bundled library declaration files that describe the target runtime environment.",
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"anyOf": [
{ ... }
]
},
"markdownDescription": "Specify a set of bundled library declaration files that describe the target runtime environment."
}
target
- 빌드의 결과물을 어떤 버전으로 할 것인지 설정한다.
- 지정은 안하면 es3이다.
lib
- 기본 type definition 라이브러리를 어떤 것을 사용할 것인지 결정한다.
- lib를 지정하지 않을 때, (target에 따라 자동으로 설정됨)
- target이 ‘es3’이고, default로 lib.d.ts 를 사용한다.
- target이 ‘es5’이면, default로 dom, es5, scripthost 를 사용한다.
- target이 ‘es6’이면, default로 dom, es6, dom.iterable, scripthost 를 사용한다.
- lib를 지정하면 그 lib 배열로만 라이브러리를 사용한다.
- 빈 [ ] (배열) ⇒ error! ‘no definition found ~~’
compileOptions - outDir, outFile, 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."
}
"outDir": {
"description": "Specify an output folder for all emitted files.",
"type": "string",
"markdownDescription": "Specify an output folder for all emitted files."
}
"rootDirs": {
"description": "Allow multiple folders to be treated as one when resolving modules.",
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
},
"markdownDescription": "Allow multiple folders to be treated as one when resolving modules."
}
outFile
단일 파일로 합쳐서 출력한다.
outDir
해당 디렉토리로 결과 구조를 보낸다.
rootDir
입력 파일의 루트 디렉토리 설정으로, –outDir로 결과 디렉토리 구조를 조작할 때 사용됩니다.
-
rootDir 설정했을 때
파일 구조 설명
"rootDir": "./src"
"outDir": "./dist"
컴파일 시(npx tsc), dist 폴더가 생성되면 해당 폴더 안에 test.js와 hello.js 파일이 생성된다.
-
rootDir 설정하지 않을 때
파일 구조 설명
// "rootDir": "./src"
"outDir": "./dist"
컴파일 시, dist 폴더가 생성되면서 해당 폴더 안에 src폴더가 만들어진다.
컴파일 이후, 폴더 구조 설명
- dist/src/tset.js
- dist/hello.js
rootDir을 따로 설정하지 않았기 때문에 루트(시작점)이 해당 폴더(compilation-context, 최상위)가 된 것이다. rootDir을 설정했을 때에는 해당 설정이 루트(시작점)이 된다.
compileOptions - strict
앞으로 작업할때는 strict를 true로 설정하고 작업하는 것이 기본이다.
"strict": {
"description": "Enable all strict type checking options.",
"type": "boolean",
"default": false,
"markdownDescription": "Enable all strict type checking options."
}
모든 strict type checking option을 모두 키는 것
- —nolmplicitAny
- —nolmplicitThis
- —strictNullChecks
- —strictFunctionTypes
- —strictPropertyInitialization
- —strictBindCallApply
- —alwaysStrict
-
–nolmplicitAny
명시적이지 않게 any 타입을 사용하여 표현식과 선언에 사용하면 에러를 발생시킨다. (직접으로 any로 표현하지 않았는데 any로 추론이 되는 것)
- 타입스크립트가 추론을 실패한 경우(추론 결과가 어떤 것이든 들어올 수 있는 상황) any가 맞으면 any라고 지정하라는 의미를 가지고 있다.
- 아무것도 쓰지 않으면 에러를 발생시킨다.
- 해당 오류를 해결하면, 타입 추론이 되었으므로 any라고 지정되어 있지 않은 경우는 any가 아닌 것이다.
nolmplicitAny를 사용할 때, 인덱스 객체에 인덱스 signature가 없는 경우 오류가 발생하는데 이를 예외처리하는 옵션이 있다.
obj['foo'] = 10; // Error: Index signature of ~~
obj[’foo’] 로 사용할 때, 인덱스 객체라 판단하여 타입에 인덱스 시그니처가 없는 경우, 에러를 발생시킨다.
이때, suppressImplictitAntIndexErrors 옵션을 사용하면 해당 경우는 예외로 간주하여 에러를 발생시키지 않는다.
-
–noImplicitThis
명시적이지 않게 any 타입을 사용하여, this 표현식에 사용하면 에러를 발생시킨다.
(this에 아무거나 들어올 수 없게 들어올 수 있는 형태를 제한해주는 것이 좋다.)
- 첫번째 매개변수 자리에 this를 놓고, this에 대한 타입을 어떤 것이라도 표현하지 않으면 nolmplicitAny가 오류를 발생시킨다.
- JavaScript 에서는 매개변수에 this를 넣으면 이미 예약된 키워드라 SyntaxError를 발생시킨다.
- call / apply / bind와 같이 this를 대체하여 함수 콜 용도로도 쓰인다.
- 그래서 this를 any로 명시적으로 지정하는 것이 합리적이다. (단, 구체적인 사용처가 있는 경우 타입을 표현하기도 한다.)
-
–strictNullChecks
strictNullChecks에서는 null 및 undefined 값이 모든 유형의 도메인에 속하지 않으며, 그 자신을 타입으로 가지거나 any일 경우에만 할당이 가능하다.
즉, 해당 옵션을 키지 않으면 모든 타입에 null과 undefined가 포함된 상태로 사용하게 되는 것이다.
한가지 예외는 undeifined에 void 할당이 가능하다.
- strictNullChecks를 적용하지 않으면,
- 모든 타입은 null, undefined 값을 가질 수 있다.
- string으로 타입을 지정해도 null 혹은 undefined 값을 할당할 수 있다. → 엄청 큰 오류
- strictNullChecks 적용하면,
- 모든 타입은 null, undefined 값을 가질 수 없고, 가지려면 union type을 이용해서 직접 명시 해야 한다.
- any 타입은 null과 undifined를 가진다. 예외적으로 void 타입의 경우 undefined를 가진다.
-
–strictFunctionTypes
함수 타입에 대한 bivariant 매개변수 검사를 비활성화한다.
( Animal → Greyhound ) <: (Dog → Dog)
- 반환 타입은 공변적(covariant)
- 인자 타입은 반공변적(contravariant)
- 타입스크립트에서 인자 타입은 공변적이면서, 반공변적이다 → 문제!
- 해당 문제를 해결하는 옵션이 strictFunctionTypes이다.
- 옵션을 키면, 에러가 나지 않던 걸 나게 한다.
-
–strictPropertyInitialization
정의되지 않은 클래스의 속성이 생성자에게 초기화되었는지 확인한다.
해당 옵션을 사용하려면 —strictNullChecks를 사용하도록 설정해야 한다.
- constructor에서 초기값을 할당한 경우 ⇒ 정상
- constructor에서 안하는 경우
- 보통 다른 함수로 이니셜라이즈 하는 경우 (async 함수)
- constructor에는 async를 사용할 수 없다.
-
–strictBindCallApply
bind, call, apply에 대한 더 엄격한 검사 수행을 한다.
- Function의 내장 함수인 bind, call, apply를 사용할 때 엄격하게 체크하도록 하는 옵션이다.
- bind는 해당 함수안에서 사용할 this와 인자를 설정해주는 역할을 하고, call과 apply는 this와 인자를 설정한 후 실행까지 한다.
- call과 apply는 인자를 설정하는 방식에서 차이점이 있다.
call은 함수의 인자를 여러 인자의 나열로 넣어서 사용하고, apply는 모든 인자를 배열 하나로 넣어서 사용한다.
-
–alwaysStrict
각 소스파일에 대해 자바스크립트의 strict mode로 코드를 분석하고 “엄격하게 사용”을 해제한다.
그렇게 하면 syntax에러가 ts error로 나오고, 컴파일된 자바스크립트 파일에 “use strict”가 추가된다.
Compilation Context
타입스크립트를 자바스크립트로 변환하는 프로그램이 타입스크립트 컴파일러이다.
이 타입스크립트 컴파일러가 어떤 파일과 어떤 방식으로 변환할 것인지 규명해 둔 맥락이 compilation context이다.
이러한 것들은 tsconfig.json파일에 적혀있다.
tsconfig schema
tsconfig.json의 전체적인 스키마(구조)를 보는 사이트 링크
최상위 프로퍼티
- compileOnSave
- extends
- compileOptions → 어떤 설정으로 컴파일 할 것인지
- files, include, exclude → 어떤 파일을 컴파일 할 것인지 결정
- reference
typeAcquisition
tsNode
compileOnSave
파일을 저장하면 컴파일 하겠다는 설정이다.
"compileOnSaveDefinition": {
"properties": {
"compileOnSave": {
"description": "Enable Compile-on-Save for this project.",
"type": "boolean"
}
}
}
- true 혹은 false로 default값은 false이다.
- true로 켰을때 누가 컴파일을 해주냐? → Visual Studio 2015 with TypeScript 1.8.4 이상 혹은 atom-typescript 플러그인
- vsc의 경우 ‘저장할때 뭐를 한다’라는 기능이 추가적으로 들어있기 때문에 그닥 중요한 옵션은 아니다.
extends
상속할 때 사용하는 키워드로 tsconfig 파일도 다른 파일을 상속 받고 그 안에 추가를 해서 사용하는 방법이 있다.
"extendsDefinition": {
"properties": {
"extends": {
"description": "Path to base configuration file to inherit from. Requires TypeScript version 2.1 or later.",
"type": "string"
}
}
}
- 파일 (상대) 경로명: string
- TypeScript 2.1 이상에서만 사용이 가능
- in Project/base.json
{
"compilerOptions": {
"strict": true
}
}
- in project/tsconfig.json
{
"extends": "./base.json"
}
files, include, exclude
어떤 파일을 컴파일 할 것인지 결정한다.
"filesDefinition": {
"properties": {
"files": {
"description": "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'. 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 excluded from compilation. The 'exclude' property only affects the files included via the 'include' 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, exclued
- glob 패턴 (.gitignore 같은 형식)
- include
- exclude 보다 약하다. (include 안에 있는 것 중에 exclude에 해당하는 것이 있으면 제외된다.)
- “*” 같은 걸 사용하면 .ts/ .tsx/ .d.ts 만 include한다. 만약 해당 파일들 말고도 js파일도 같이 하고 싶으면 allowJS 옵션을 켜야된다.
- exclude
- 설정을 안 하면 4가지(node_modules, bower_components, jspm_packages, )를 default로 제외한다.
-
은 include가 있어도 항상 제외한다.
Structural Type System vs Nominal Type System
-
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; // 구조가 동일하기 때문
Iperson과 PersonType은 똑같은 구조를 가지고 있기 때문에 한쪽 타입을 한쪽 타입으로 대입할 수 있다.
이러한 원리를 이용해서 타입스크립트에서는 해당 타입이 무엇인지 명시적으로 설정할 필요 없이 있던 것을 사용해도 된다. 구조가 동일하면 그냥 가져다가 쓰면 되기 때문이다.
-
Nominal Type System
구조가 같아도 이름이 다르면 다른 타입이다.
대표적인 언어로 C, Java가 있다.
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-aaaaaa');
// error: 아무 문자열이나 넣을 수 없고 PersonID 형식으로 치환된 타입만 넣을 수 있다.
-
Duck Typing
런타임에 발생하는 타이핑방식이다.
만약 어떤 새가 오리처럼 걷고, 헤엄치고, 소리를 낸다면 우리는 그 새를 오리라고 부를 것이다. 라는 철학을 가지고 만든 것이다.
대표적으로 Python이 있다.
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)
- 서브타입
-
같거나 서브타입인 경우에는 할당이 가능하다. 이러한 것을 공변이라고 부른다.
// primitive type
let sub7: string = '';
let sup7: string | number = sub7;
sup7은 string과 number의 조합이기 때문에 sub7보다 더 넓은 개념이다. 따라서 sub7을 넣을 수 있다. 이것이 공변이다.
// object - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
let sub8: { a: string; b: number } = { a: '', b: 1 };
let sup8: { a: string | number; b: number } = sub8;
a끼리 비교하고 b끼리 비교했을 때, sup8의 a가 더 넓은 개념을 가진다. 따라서 sub8을 sup8에 넣을 수 있다.
// array - object와 마찬가지
let sub9: Array<{ a: string; b: number }> = [{ a: '', b: 1 }];
let sup9: Array<{ a: string | number; b: number }> = sub9;
- 슈퍼타입
- 함수의 매개변수 타입만 같거나 슈퍼타입인 경우 할당이 가능하다. 이것을 반변이라고 부른다.
class Person {} // 가장 적다. 최상위
class Developer extends Person {
coding() {}
} // 두번째로 많다.
class StartupDeveloper extends Developer {
burning() {}
} // 안에 들은 함수가 가장 많다. 최하위
// 인자 f에 함수가 들어간다. Developer를 인자로 받고 Developer를 리턴하는 형태
function tellme(f: (d: Developer) => Developer) {}
// Developer => Developer에다가 Developer => Developer를 할당하는 경우
tellme(function dToD(d: Developer): Developer {
return new Developer();
});
// Developer => Developer에다가 Person => Developer를 할당하는 경우
tellme(function pToD(d: Person): Developer { // Person이 슈퍼타입인 경우
return new Developer();
});
// Developer => Developer에다가 StartupDeveloper => Developer를 할당하는 경우
tellme(function sToD(d: StartupDeveloper): Developer { // StartupDeveloper 서브타입
return new Developer();
});
tellme에 인수 Developer와 Person이 들어왔을 때는 문제가 없다. Developer와 Developer는 같고 Person은 Developer보다 상위이기 때문이다.
인수가 StartupDeveloper는 코드 자체에는 문제가 없다. 그러나 해당 코드를 function tellme~~
코드에 넣었을 때 f에 맞지 않다. StartupDeveloper는 Developer보다 하위이기 때문에 해당 코드의 f는 burning()을 모른다. 그렇기에 논리적으로 문제가 있다.
하지만, 타입스크립트는 해당 부분에 대해서 사용자에게 선택을 준다.
-
strictFunctionTypes
해당 옵션을 키면, 함수를 할당할 때 함수의 매개변수 타입이 같거나 슈퍼타입이 아닌 경우(서브타입인 경우)에 에러를 통해서 경고한다.
strictFunctionTypes 옵션을 키지 않으면 해당 코드 정도는 에러가 발생하지 않는다. (융통성을 준달까)
타입 별칭(별명) (Type Alias)
어떤 타입에 이름을 붙여준 것이다.
Interface와 비슷하지만 다르다.
Primitive Type, Union Type, Tuple, Function 등 어떤 형태의 타입을 길게 작성하지 않고 따로 빼서 이름을 만들어서 사용하는 것이다.
만들어진 타입의 별명(refer)으로 사용하는 것이지 타입을 만드는 것은 아니다.
-
Aliasing Primitive
Primitive Type을 다른 이름으로 불러 보는 것이다.
별다른 의미는 없다..
type MyStringType = string;
const str = 'world';
let mystr: MyStringType = 'hello';
mystr = str;
-
Aliasing Union Type
유니온 타입은 A도 가능하고 B도 가능한 타입이다.
길게 쓰는걸 짧게 쓸 수 있고 반복되는 것을 줄여준다.
// 사용 x
let person: string | number = 0;
person = 'Mark';
// 사용 o
type StringOrNumber = string | number;
let another: StringOrNumber = 0;
another = 'Anna';
-
Aliasing Tuple
튜플 타입에 별칭을 줘서 여러군데서 사용할 수 있게 한다.
반복과 타이핑을 줄여준다.
let person: [string, number] = ['Mark', 35];
// 사용 o
type PersonTuple = [string, number];
let another: PersoonTuple = ['Anna', 24];
-
Aliasing Fuction
매개변수를 별명을 만들어서 사용해준다.
type EatType = (food: string) => void;
-
Aliasing과 Interface 구분
어떤 타입이 목적이나 존재가치나 명확하면 interface.
그렇지 않고, 어떤 대상을 가리키거나 별명으로서만 존재하면 type aliasing.