npm install typescript --save-dev
: 컴파일러가 node_modules 에 설치된다. (실행: npx tsc
)npx tsc --init
: tsconfig.json 파일을 만들어서 추천세팅을 적용한다.Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
{
"include": ["src"],
"compilerOptions": {
"outDir": "./build"
}
}
tsconfig.json
파일에 추가하면 src/
에 있는 타입스크립트 파일들이 build/
폴더로 트랜스파일된다.mkdir [폴더명]
: 프로젝트 폴더를 만든다.npm init -y
npm i -D typescript
: 타입스크립트를 설치한다.touch tsconfig.json
: 타입스크립트 설정 파일을 만든다.src/index.ts
: 파일을 만들어서 코드를 작성한다.package.json
에서 "test"
스크립트는 제거"build": "tsc"
스크립트 추가 : 해당 스크립트가 실행되면 ts를 js로 컴파일한다."start" : "node build/index.js"
스크립트추가. npm run build && npm run start
를 실행하면 ts를 js로 번역하고 프로젝트를 빌드해준다.npm i -D ts-node
ts-node 를 설치하면 빌드 없이 ts 를 실행할 수 있게 해준다.(개발환경에서 사용)"dev" : "ts-node src/index.ts"
스크립트 추가.npm i nodemon
nodemon 은 저장하면 자동으로 커맨드를 재실행해준다.dev
스크립트 앞에 nodemon --exec
을 추가한다"dev": "nodemon --exec ts-node src/index.ts"
옵션키 | 설명 | 옵션벨류 | 의미 |
---|---|---|---|
"include" | 컴파일하고싶은 디렉토리들을 넣는다. | ["src"] | src 폴더 내 모든ts파일을 컴파일 |
"compilerOptions" | js파일이 생성될 디렉토리 | {"outDir":"build", "target":"ES3"} | build폴더에 ts를 ES3버전으로 컴파일한 js파일을 넣는다. |
"lib" | 라이브러리의 정의파일(.d.ts )을 특정해준다 | ["ES6","dom"] | ES6를 지원하는 환경에서 실행된다. 브라우저위에서도 실행된다.(lib.dom.d.ts 파일을 읽어와서 document등에 대한 자동완성기능 제공) |
"stirct" | strict모드를 켤지 말지에 대한 옵션 | true | strict mode를 켠다 |
"allowJs" | js도 import하게 할지 여부 | true | ts파일에서 js파일을 import하기 허용 |
"esModuleInterop" | es6 모듈사양을 준수하여 commonJS 모듈을 가져오는걸 허용할지 여부 | true | import abc from "abc" 문법을 사용할수 있게 해준다. 허용하지 않으면 require(~)나 import * as ~ from 을 사용해야된다. |
"module" | 모듈시스템 | "CommonJs" | CommonJs 모듈시스템을 사용한다. |
// /src/myPackage.js
export function init(config) {
return true;
}
export function exit(coe) {
return code + 1;
}
// /src/myPackage.d.ts
interface Config {
url: string;
}
declare module "myPackage" {
function init(config: Config): boolean;
function exit(code: number): number;
}
// src/index.ts
imoprt { init, exit } from "myPackage"; // myPackage 에서 init과 exit 을 import
//타입스크립트에 타입 설명을 했으므로 불평하지 않는다.
init({
url: "abaaa"
})
// src/index.ts
imoprt { init, exit } from "./myPackage"; // myPackage.js 에서 init과 exit 을 import. allowJs 가 꺼져있으면 에러가 발생한다.
// (alias) function init(config: any): boolean -> ts가 추론한 타입
init({
url: "abaaa"
})
자바스크립트 파일은 그대로 보존하고, 타입스크립트의 보호를 받고 싶은 경우
allowJs
옵션을true
로 바꾼뒤,
>JSDoc
문법으로@ts-check
주석을 달아주면 해결이 된다.
>@ts-check
는 ts파일에게 js 파일을 확인하라는 의미
이 방식으로 js 파일에 선언된 함수도typescript
의 보호를 받을 수 있게 된다.
// /src/myPackage.js (자바스크립트 파일)
/**
* 프로젝트를 초기화한다.
* @param {object} config
* @param {boolean} config.debug
* @param {string} config.url
* @returns boolean
*/
export function init(config) {
return true;
}
/**
* 프로그램을 종료한다.
* @param {number} code
* @returns number
*/
export function exit(code) {
return code + 1;
}
npm i -D @types/패키지명
npm i -D @types/node
를 터미널에 입력하면 된다.참고) object 타입은 존재하지 않음
let a : number = 10
이런식으로 변수 뒤에 : 타입
을 선언해준다.?
를 붙여준다.age?: number
의미는 age 가 number
거나 undefined
임을 의미한다.const playerJohn: {
name: string;
age: number;
} = {
name: "john",
age: 20,
};
type Player = {
name: string;
age?: number;
};
const playerJohn = {
name: "john",
age: 20,
};
type Player = {
name: string;
age?: number;
};
function playerMaker(name: string): Player {
return {
name,
};
}
const john = playerMaker("john");
type Player = {
name: string;
age?: number;
};
const playerMaker = (name: string): Player => ({ name });
const john = playerMaker("john");
player
가 name
요소를 가지고 있다고 해보자name
요소는 readonly
(읽기전용) 가 된다고 하자type Age = number;
type Name = string;
type Player = {
readonly name: Name;
age?: Age;
};
const playerMaker = (name: string): Player => ({ name });
const john = playerMaker("john");
readonly
인 name
을 수정할 수 없게된다!const numbers: readonly number[] = [1, 2, 3, 4];
numbers.push(1); // 에러난다. push 메소드를 readonly number[] 타입에서 제거해버렸기 때문
배열을 바꾸지 않는 filter 나 map 등은 readonly 여도 사용할 수 있다.
["John", 12, false]
의 타입을 만들자const player: [string, number, boolean] = ["John", 12, false];
const player: readonly [string, number, boolean] = ["John", 12, false];
let a: unknown;
let b = a + 1; // 에러발생. a가 unknown 이므로
if (typeof a === "number") {
let b = a + 1; // 문제없음
}
void
로 적용된다.never
// 에러를 던지는 함수이므로 반환 타입을 never로 설정해야 한다.
function sayHi(): never {
throw new Error("error!");
}
function sayHi(name: string | number) {
if (typeof name === "string") {
// name : string
} else if (typeof name === "number") {
// name : number
} else {
// name : never
}
}
// Bad
function add(a, b) {
// a와 b에 에러발생. 타입을 모르기 때문에 any 타입이 되기 때문
return a + b;
}
// Good
function add(a: number, b: number) {
return a + b;
}
Call signature
를 작성하면 된다// 방법1
type Add = (a: number, b: number) => number;
// 방법2
type Add = {
(a: number, b: number): number;
};
// Good 에러없음
const add: Add = (a, b) => a + b;
polymorphism : 여러가지 다른 형태
type Add = {
(a: number, b: number): number;
(a: number, b: string): number;
};
// 에러발생. call signatures가 두개라서 b 가 어떤타입인지 모르기 때문
const add: Add = (a, b) => a + b;
// Good 어떤타입인지 몰라도 작동하는 로직이기 때문
const add: Add = (a, b) => {
if (typeof b === "string") return a;
if (typeof b === "number") return a + b;
};
여러 call signatures 가 존재할 때, parameter 의 타입이 달라질 수는 있지만, parameter 의 개수가 동일했다.
parameter 의 개수가 서로 다른
call signatures
가 존재할 경우
call signature
만으로 typescript 가 만족하지 않기 때문에, 옵셔널
변수와 타입
을 알려줘야 한다// c 파라미터가 optional 인 경우
type Add ={
(a:number, b:number) : number
(a:number, b:number, c:number) : number
}
// add 에서 에러
const add: Add (a,b,c) =>{
return a+b
}
// c 가 옵셔널인것과 타입을 명시해야 한다.
const add: Add (a,b,c?:number) =>{
return a+b
}
generic
을 통해서도 polymorgphism
을 준다.type SuperPrint = {
(arr: number[]): void;
(arr: boolean[]): void;
(arr: string[]): void;
};
const superPrint: SuperPrint = (arr) => {
arr.forEach((i) => console.log(i));
};
superPrint([1, 2, 3, 4]);
superPrint([false, false, true, true]);
superPrint(["a", "b", "c"]);
superPrint(1,2, true, "a");
와 같은 다양한 방법으로 함수를 이용하고 싶을 때마다 타입을 다시 명시해야 하는 문제가 생긴다.concrete type
인 number
, boolean
, string
, void
들을 일일히 적어주는 것보다도 더 좋은 방식이 있다generic
은 타입의 placeholder 같은 개념generic
이 처음 사용되는 지점을 기반으로 타입을 추론한다.generic
은 입력된 arguments
를 보고 타입을 추론하도록 타입스크립트에게 지시해준다.type SuperPrint = {
(arr: number[]): void;
(arr: boolean[]): void;
(arr: string[]): void;
};
const superPrint: SuperPrint = (arr) => {
arr.forEach((i) => console.log(i));
};
superPrint([1, 2, 3, 4]);
superPrint([false, false, true, true]);
superPrint(["a", "b", "c"]);
generic
을 사용한다고 알려준다.generic
을 사용한다고 알려준다.// 방법1
type SuperPrint = {
<T>(arr: T[]): void;
};
// 방법2
type SuperPrint<T> = {
(arr: T[]): void;
};
const superPrint: SuperPrint = (arr) => {
arr.forEach((i) => console.log(i));
};
// const superPrint: <number>(arr: number[]) =>void
superPrint([1, 2, 3, 4]);
// const superPrint: <boolean>(arr: boolean[]) =>void
superPrint([false, false, true, true]);
// const superPrint: <string>(arr: string[]) =>void
superPrint(["a", "b", "c"]);
// const superPrint: <number>(arr: number[]) =>void
superPrint(1, 2, true, "a");
// Bad ( Error: T 에는 .length 를 할수가 없다. )
function echo<T>(text: T): T {
console.log(text.length);
return text;
}
// Good : text 가 배열이라는 것을 알려줘서 length를 구할수 있게 한다.
function echo<T>(text: T[]): T {
console.log(text.length);
return text;
}
// Good
function echo<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
Player<T>
이면 T
자리에 또다른 타입을 넣을 수 있다.number[]
타입도
을 이용하면 Array<number>
로 쓸 수 있다. (타입스크립트가 Array
라는 타입을 generic
을 이용해 Array<T>
로 정의하고 있기 때문)useState<number>()
를 사용하여 call signature 를 정의하는 generic 에 number를 전달할 수있다.useState
함수의 타입을 제네릭을 이용해 정의하기 떄문이다.type Player<T> = {
name: string
extraInfo: T
}
const john = Player<{favFood:string}> = {
name: "john"
extraInfo: {
favFood: "banana"
}
}
// 타입선언
type Player<T> = {
name: string
extraInfo: T
}
type JohnExtra = {
favFood: string
}
type JohnPlayer<T> = Player<{favFood:string}>
// 타입적용
const john : JohnPlayer = {
name: "john"
extraInfo: {
favFood: "banana"
}
}
const bob : Player<null> = {
name: "bob"
extraInfo: null
}
type GenericExample = <T>(a: T[]) => T;
type GenericExample = <T, M>(a: T[], b: M) => T;
interface GenericExample {
<T>(a: T): T;
}
// 함수 선언시 generic 문법 적용
function echo<T>(a: T): T {
return a;
}
const greeting = echo("hello"); // good 타입스크립트가 유추하게 해서 좋다.
const greeting = echo<string>("Hello"); // 더 명시적이다. T 에 string 이 대입된다.
// 방법1
interface IEcho {
<T>(text: T): T;
}
// 방법2
interface IEcho<T> {
(text: T): T;
}
function echo<T>(text: T): T {
return text;
}
let myString: IEcho = echo;
let myString: IEcho<string> = echo; // 더 명시적 <string>
// 클래스
class GenericMath<T> {
pi: T;
sum: (x: T, y: T) => T;
}
let math = new GenericMath<number>();
any 는 타입스크립트로의 보호를 벗어난다.
generic 은 타입스크립트의 보호를 받을 수 있다.
a 의 타입이 generic 에 의해 string|boolean|number|void|undefined 등 모든 타입이 될수 있다고 해보자.
a.toUpperCase() 는 에러가 난다. 왜냐면 string 일 경우에만 허용하려고 하기 때문이다.
반면 a 의 타입이 any 인 경우 typescript 의 보호에서 벗어나있어서 에러를 내주지 않는다.
private
과 public
도 제공한다. (property
뿐만 아니라 method
에 대해서도 작동한다.)property
에 private
를 설정해두면, 해당 클래스를 상속받은 클래스여도 그 property에 접근할수가 없고 인스턴스 밖에서도 접근할 수 없다.// 타입스크립트에서 클래스 선언
class Player {
constructor(
private firstName: string,
private lastName: string,
private nickname: string
){};
}
const john = new Player("john", "brown", "johnny");
// 자바스크립트 버전
class Player {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
const john = new Player("john", "brown", "johnny");
abstract class User {
constructor(
private firstName: string,
private lastName: string,
private nickname: string
){};
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
class Player extends User {}
const john = new Player("john", "brown", "johnny");
abstract class User {
constructor(
private firstName: string,
private lastName: string,
private nickname: string
);
abstract getNickName(): void;
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
class Player extends User {
getNickName() {
console.log(this.nickname);
}
}
const john = new Player("john", "brown", "johnny");
john.getNickName(); // 에러 : nickname 은 private 로 보호받는 property 인데 자식클래스에서 정의된 메소드로 접근하기 떄문
john.getFullName(); // 정상 출력됨. getFullName 은 추상클래스 내에서 정의됐고 & private 함수가 아니기 때문
protected
가 적용되면 외부로부터 보호되지만, 다른 자식 클래스에서는 접근할 수 있다! (private 보다 약한 보안)abstract class User {
constructor(
private firstName: string,
private lastName: string,
protected nickname: string
);
abstract getNickName(): void;
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
class Player extends User {}
const john = new Player("john", "brown", "johnny");
[whatever:string]
을 사용한다.[key:number] : string
} 는 { 1: "food" , 2 : "apple" }
를 의미한다.파라미터
가 특정 클래스의 인스턴스
라는 것을 typescript 에게 말해줄 때, 클래스명
을 타입
처럼 명시해줄 수 있다.class Word {
constructor(
public term: string,
public def: string
){}
}
type Words ={
[key:string] : string // Words 타입은 string 만을 property로 갖는 오브젝트
}
class Dict{
private words : Words // words가 어떻게 생겼는지 초기화를 해줘야 함
constructor (){
this.words = {}
}
add(word:Word){ // 클래스인 Word 를 마치 타입처럼 넣어줬다!!
if(this.word[word.term] === undefined){
this.words[word.term] = word.def;
}
}
def(term:string){
return this.words[term]
}
}
{
"potato": "food"
}
const kimchi = new Word("kimchi", "한국 음식")
const dict = new Dict()
dict.add(kimchi)
dict.def("kimchi") // "한국의 음식"
Word
클래스의 term
과 def
property 는 public
으로 설정됐다. 왜냐면 Dict
클래스의 add
메소드를 만들 때 words.term
과 word.def
에 접근해야 되기 때문이다.public term: string
-> public readonly term: string
class Dict {
private words: Words; // words가 어떻게 생겼는지 초기화를 해줘야 함
constructor() {
this.words = {};
}
add(word: Word) {
// 클래스인 Word 를 마치 타입처럼 넣어줬다!!
if (this.word[word.term] === undefined) {
this.words[word.term] = word.def;
}
}
def(term: string) {
return this.words[term];
}
static hello() {
return "hello";
}
}
type 은 원하는 타입을 정의할 수 있게 해준다.
// 타입스크립트에게 object가 어떻게 생겼는지 알려준다.
type Player = {
nickname: string;
age: number;
};
type Friends = Array<string>; // string[] 과 같은 표현인데, Array 의 generic 을 활용했다는 특징이 있다.
type Food = string; // alias
type Team = "red" | "blue" | "yellow"; // 타입제한
type age = 10 | 20 | 30;
interface
는클래스
또는오브젝트
의 모양을 설명해주는 방법이다.
interface
는 객체지향프로그래밍(oop) 의 개념으로 디자인 된 개념.
다용도인type
과 다르게interface
는 오직object
에 대해서만 설명할 수 있다.
>interface
는 타입명 뒤에=
기호를 붙이지 않는다.
>interface
는extends
로 타입을 상속 할 수 있다.
>interface
는implements
로 클래스에 상속시킬수 있다.
>interface
는 js 로 컴파일 되지 않고 사라진다. 이말은interface
에서 상속 받는 방식으로 클래스를 만들면.ts
파일내에서 타입스크립트의 보호를 받음과 동시에js
파일이 가벼워 진다는 의미이다.
// 타입스크립트에게 object가 어떻게 생겼는지 알려준다.
interface Player {
// Player ={ ... } 형태(type 방식)가 아니라 Player { ... }
nickname: string;
age: number;
}
interface Food = string // 에러. 인터페이스는 alias 로 쓸수도 없고 = 도 붙이지 않는다
인터페이스는 개발자에게 클래스를 다루는 느낌을 줄 수 있다.
// interface 를 사용한 경우
interface User {
name: string;
}
interface Player extends User {}
const john: Player = {
name: "john",
};
// 클래스
abstract class User {
constructor(name: string) {}
}
class Player extends User {}
const john = new Player("john");
타입을 사용하면 위의 둘과는 좀 다르게 보인다.
type User = {
// 타입을 쓴 경우 = 기호를 붙여야 함
name: string;
};
type Player = User & {
// type 을 쓴 경우 extends 키워드를 사용x
};
const john: Player = {
name: "john",
};
interface
를 사용하면property
를 축적시킬 수 있다.type
은 불가능
interface User {
name: string;
}
interface User {
nickname: string;
}
interface User {
age: number;
}
const john: User = {
name: "john",
nickname: "johnny",
age: 20,
};
// Bad: type 으로는 객체의 타입을 중첩시키지 못하기 때문에 에러가 발생한다.
type User = {
name: string;
};
type User = {
nickname: string;
};
interface
를 implement
한다!"추상클래스를 인터페이스로 바꿔보자.
abstract class User {
constructor(protected firstName: string, protected lastName: string) {}
// abstract 메소드가 선언되었기 때문에 User를 상속하여 만들어지는 클래스는 해당 메소드를 정의해줘야 한다.
abstract sayHi(name: string): string;
abstract fullName(): string;
}
class Player extends User {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}`;
}
}
const john = new Player("john", "brown");
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
class Player implements User {
constructor(
public firstName: string, // User 를 상속할 때는 private 로 만들 수 없다. User 의 형태를 따라 private여야 한다.
public lastName: string
) {}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}`;
}
}
const john = new Player("john", "brown");
장점
abstract class
에서interface
로 바꾸면서 js 코드를 압축할 수 있다.
협업시interface
를 사용하여 각자의 방식으로 클래스를 상속하면,객체지향적
프로그래밍을 하기 좋다.
abstract class
를 사용하는 용도가 다른 클래스들이 특정 모양을 따르게 하기 위한 용도로 쓴다면interface
는 완벽한 대체제가 될 수 있다.
단점
private
,public
property 를 사용할 수 없다. (클래스쓰면 constructor 에서 보호여부를 정할수가 있다는 점과 차이가 있음)
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
interface Human {
height: number;
}
class Player implements User, Human {
constructor(
public firstName: string,
public lastName: string,
public height: number
) {}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}`;
}
}
const john = new Player("john", "brown");
interface User {
firstName: string,
lastName: string,
sayHi(name: string): string,
fullName(): string
}
// Bad: User 는 인터페이스로 정의된 녀석이라 클래스사용하듯이 하면 안된다.
function makeUser(user: User) : User{
return new User {
firstName: user.firstName,
lastName: user.lastName,
sayHi: (name: string) => "hi",
fullName: ()=> user.firstName + user.lastName
}
}
//Good : User 처럼 생긴 오브젝트를 넣고, 리턴해야 함
function makeUser(user: User) : User{
return {
firstName: user.firstName,
lastName: user.lastName,
sayHi: (name: string) => "hi",
fullName: ()=> user.firstName + user.lastName
}
}
makeUser({
firstName: "john",
lastName: "brown",
sayHi : (name) => "hi~",
fullName : () => "john brown"
})
type | interface | |
---|---|---|
타입정의 | 모든 타입을 정의한다 | object 타입을 정의한다 |
타입상속 | type A = B &{...} | interface A extends B{...} |
클래스상속 | X | class A implements B {} |
용도 | 타입정의,제한,콜시그니처 | 객체 타입정의, 클래스 상속 |
특징 | 누적선언 불가 추상클래스 대체가능 alias타입 | 객체지향적 누적선언 가능 추상클래스 대체가능 |
interface IStorage<T> {
[key: string]: T;
}
class LocalStorage<T> {
private storage: IStorage<T> = {};
set(key: string, value: T) {
this.storage[key] = value;
}
remove(key: string) {
delete this.storage[key];
}
get(key: string): T {
return this.storage[key];
}
clear() {
this.storage = {};
}
}
const stringStorage = new LocalStorage<string>();
stringStorage.get("aaa");
stringStorage.set("hello", "how are you");
const booleansStorage = new LocalStorage<boolean>();
booleansStorage.get("bbb");
booleansStorage.get("hello", true);
x
type categories = "TO_DO" | "DOING" | "DONE" ;
export interface IToDo{
text: string;
id: number;
category: categories;
}
export const categoryState = atom<categories>({
key: "category",
default: "TO_DO",
})
<select value={category} onInput={onInput}>
<option value="TO_DO">To Do</option>
<option value="DOING">Doing</option>
<option value="DONE">Done</option>
</select>
export enum Categories {
"TO_DO",
"DOING",
"DONE",
}
export interface IToDo{
text: string;
id: number;
category: Categories;
}
export const categoryState = atom<Categories>({
key: "category",
default: Categories.TO_DO,
})
<select value={category} onInput={onInput}>
<option value={Categories.TO_DO}>To Do</option>
<option value={Categories.DOING}>Doing</option>
<option value={Categories.DONE}>Done</option>
</select>
//
export enum Categories{
"TO_DO", //0
"DOING", //1
"DONE", //2
}
category !== Categories.DOING // category !== 2 랑 똑같다.
<button name = {Categories.TO_DO}> // 문제발생 (name은 스트링이어야됨)
<button name = {Categories.TO_DO + "" }> // 문제 해결. name 에 "0" 이 들어간다.
export enum Categories{
"TO_DO" = "TO_DO", //TO_DO
"DOING" = "DOING", //DOING
"DONE" = "DONE", //DONE
}
<button name = {Categories.TO_DO}> // 문제 없음.