- 타입스크립트는 type을 추가함으로써 자바스크립트를 확장한다.
(Dynamic type -> Static type)- 타입스크립트는 당신이 코드를 run하기 전에 error를 캐치하고 fixes를 제공하여 시간을 절약한다.
- 타입스크립트는 complied language 이다.
- 완전히 오픈 소스 이다.
> npm i typescript -g -------------------(global 설치)
> tsc --init -------------------------------(tsconfig.json 파일 생성)
> tsc -w ----------------------------------(watch 모드)
> npm init -y ----------------------------(npm 프로젝트 시작)
> npm i typescript -D -------------------(원하는 버전, devDependency로 설치)
> npx tsc --init --------------------------(tsconfig.json 파일 생성)
> npx tsc -w ---------------------------- (watch 모드)
npx 대신 package.json의 script를 추가하여 사용.
// package.json
{
"scripts": {
"build": "tsc"
"build:watch": "tsc -w"
}
> npm run build
> npm run build:watch
Object 또는 reference 형태가 아닌 실제 value를 저장하는 data type
literal 값으로 primitive type의 sub type을 나타낼 수 있다.
또는 래퍼 객체로 만들 수 있다.
그러나 타입스크립트에서는 래퍼 객체를 type으로 작성하는 것을 명시적으로 반대한다.
function reverse(s: String): String {
return s.split("").reverse().join("");
}
reverse("hello typescript")
String -> string 으로 바꾸어 작성
function reverse(s: string): string {
return s.split("").reverse().join("");
}
reverse("hello typescript")
let isDone: boolean = false;
isDone = true;
console.log(typeof isDone); // 'boolean'
const decimal: number = 9;
const hex: number = 0xf00d;
const binary: number = 0b1011;
const octal: number = 0o542;
const notANumber: number = NaN;
const underscoreNum: number = 1_000_000;
const myName: string = 'beomjin_97';
myName = 'lemon';
const fullName: string = 'Mark Lee';
const age: number = 20;
const sentence: string = `Hello, My name is ${fullname}.
I'll be ${age + 1} years old`
new Symbol로 사용할 수 없다.
Symbol을 함수로 사용해서 symbol타입을 생성한다. Symbol('foo')
const sym = Symbol();
const obj = {
[sym]: "value"
};
obj[sym] //으로 접근
let u: undefined = undefined;
let n: null = null;
default 설정에 따르면 undefiend와 null은 모든 타입의 subtype이다.
그러나 '--strictNullCheck' 에 true를 할당하면 null과 undefined는 자기 자신의 타입에만 할당할 수 있다. (단, undefined는 void 타입에 할당 가능)
// tsconfig.json
{
"compilerOptions": {
"strictNullChecks": true,
}
}
이때, 다른 타입에 null과 undefined를 명시적으로 할당할 수 있게 하려면, union type을 사용한다.
let name: string | null = null;
union = 'Mark';
non-primitive type을 나타내고 싶을 때 type으로 object를 할당한다.
declare function create(o: object | null): void;
create({prop: 1});
create(null);
create('string'); //Error
create(true); //Error
let list1: number[] = [1, 2, 3]; // 권장되는 방식
let list2: Array<number> = [1, 2, 3];
let list3: (number | string)[] = [1, 2, 3, "a"];
let tup: [string, number];
tup = ['seohwa', 24]
tup = [24, 'seohwa'] //Error
tup[2] = 'el' //Error, length도 타입에 맞게 맞춰야한다.
const person: [string, number] = ['seohwa', 24];
const [first, second] = person;
// first는 string type
// second는 number type
any는 전체적인 type system에 문제를 일으킬 가능성(compile time에 type check가 정상적으로 이뤄지지 않음)이 있기 때문에 any 타입에 대해 정확히 알고 제한된 상황에서만 사용해야 한다.
any는 어떤 타입을 가지고 있는지 알 수 없음(어느 타입이어도 상관없음)을 나타내고 어느것도 할 수 없다는 것이 아닌 어떤 것이든 할 수 있다는 의미를 나타낸다.
let comp: any = 25;
comp.length //compile time에 error가 발생하지 않음
let comp: number = 25;
comp.length //error 발생
any는 object를 통해 지속적으로 전파된다.
따라서 타입 안정성을 잃지 않기 위해 필요하지 않은 경우에는 any를 사용하는 것을 지양해야 한다.
let looselyTyped: any = {};
let d = looselyTyped.a.b.c.d; // type적으로 에러가 나지 않음
function leakingAny(obj: any) {
const a: number = obj.num; //any 누수를 막는 지점
const b = a + 1;
retrun b;
}
const c = leakingAny({num: 0});
c.indexOf("0"); // type error 발생
any보다 type-safe한 타입으로 모르는 변수의 타입의 변수를 묘사할 때 any 대신 사용한다.
compiler가 타입을 추론할 수 있게 타입의 유형을 좁히거나 확정시키지 않으면 type error를 발생시킨다.
declare const maybe: unknown;
const aNumber: number = maybe; // type error
// type guard
if (maybe === true) {
const aBoolean: boolean = maybe; // if문 안의 maybe는 boolean 타입
const aString: string = maybe; // type error
}
if (typeof maybe === 'string') {
const aString: string = maybe; // if문 안의 maybe는 string 타입
const aBoolean: boolean = maybe; // type error
일반적으로 return에 사용되는 type.
function error(message: string): never {
throw new Error(message);
}
function fail(): never {
return error("failed");
}
function infiniteLoop(): never {
while(true) {}
}
never 타입은 모든 타입의 subtype이며, 모든 타입에 할당 할 수 있다.
그러나, never에는 그 어떤 것도 할당할 수 없다. (any도 할당할 수 없다.)
let a: string = 'hello'
if (typeof a !== 'string') {
a; // never type
}
declare const b: string | number;
if (typeof a !== 'string') {
a; // number type
}
type Indexable<T> = T extends string ? T & { [index: string]: any} : never;
const c:Indexable<{}> = ''; // 잘못된 타입을 입력하면 never가 되게한다.
undefined와 같은 의미(상태).
함수의 return값을 가지고 무언가를 하지 않겠다는 명시적 표시
function returnVoid(message: string) {
console.log(message);
return ;
}
function returnVoid(message: string): void {
console.log(message);
return undefined;
}
// void, undefined가 있든 없든 두 함수는 동일하게 작동한다.
const res = returnVoid("리턴"); //void type
서브타입은 슈퍼타입에 할당할 수 있지만 슈퍼타입은 서브타입에 할당 할 수 없다.
(함수의 포함 관계 또는 정수, 유리수, 실수의 관계를 생각하자.)
let sub1: 1 = 1;
let sup1: number = sub1;
sub1 = sup1 // error
let sub2: number[] = [1];
let sup2: object = sub2;
sub2 = sup2; //error
let sub3: [number, number] = [1, 2];
let sup3: number[] = sub3;
sub3 = sup3; // error
let sub4: number = 1;
let sup4: any = sub4;
sub4 = sup4 //error가 발생하지 않는다.
let sub5: never = 0 as never; //일반적인 사용방법은 아니다
let sup5: number = sub5;
sub5 = sup5; //error
class Animal {}
class Dog extends Animal {
eat() {}
}
let sub6: Dog = new Dog();
let sup6: Animal = sub6;
sub6 = sup6 //error
let sub7: string = '';
let sup7: string | number = sub7;
let sub8: {a: string; b: number } = {a: '', b: 1};
let sup8: {a: string | number; b: number } = sub8;
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() {}
}
function tellme(f: (d: Developer) => Developer) {}
tellme(function dToD(d: Developer): Developer {
return new Developer();
});
tellme(function pToD(d: Person): Developer {
return new Developer();
}) // Person의 모든것이 Developer를 범위를 벗어나지 않기 때문에 할당 가능
tellme(function sToD(d: StartupDeveloper): Developer {
return new Developer();
}) // StartupDeveloper의 일부가 Developer의 범위를 벗어나기 때문에 할당 불가능
type StringOrNumber = string | number;
let another: StringOrNumber = 0;
another = 'A';
type PersonTuple = [string, number];
let another: PersonTuple = ['A', 0];
type EatType = (food: string) => void;
tsconfing.json의 최상위 Properties
{
"compileOnSave" : true,
}
{
"extends" : [],
}
{
"files" : ["./src/main.ts",],
}
{
"exlude" : ["/dist"],
}
{
"include" : [".ts", ".tsx"],
}
files, exclude, include 모두 설정이 없으면, 모든 파일을 compile한다.
{
"compilerOptions": {
"typeroot": ["./path",],
"types": ["package name",]
}
}
자바스크립트 기반의 외부 모듈 또는 라이브러리의 type definition 시스템을 위한 option
{
"compilerOptions": {
"target": "es6",
"lib": ["dom","es6","dom.iterable","scripthost"]
}
}
target
lib
https://kangax.github.io/compat-table/es6/
{
"compilerOptions": {
"outFile": ,
"outDir": "./dist",
"rootDir": "./src"
}
}
outFile
rootDir : 입력 파일의 루트 디렉토리(출력 구조)를 지정한다.
outDir : rootDir에서 지정한 출력 구조를 해당 디렉토리로 리다이렉트한다.
{
"compilerOptions": {
"strict":true,
//"noImplicitAny":true,
//"noImplicitThis":true,
//"strictNullChecks":true,
//"strictFunctionTypes":true,
//"strictPropertyInitialization":true,
//"strictBindCallApply":true,
//"alwaysStrict":true,
//"noImplicitReturns":true
}
}
타입이 명시적으로 지정되어 있지 않은 경우, 컴파일러가 추론중 타입이 'any'라고 판단하게 되면 (또는 타입 추론에 실패하게 되면), 컴파일 에러를 발생시킨다.
'any'가 정말 맞는지 확인하고 맞다면 명시적으로 'any'라고 지정해야한다.
function f1 (a) { //error
return a * 38;
}
this 표현식에 타입이 명시적으로 지정되어 있지 않은 경우 컴파일 에러를 발생시킨다.
이를 해결하기 위해서는 첫번째 매개변수에 this를 지정하고 type을 지정해야한다.
function noInpliitThisFunc (this: any, name: string, age: number) {
this.name = name;
this.age = age;
return this;
}
this에 any타입을 할당하는 것은 합리적일 수도 있다.
Class에서는 this를 사용하면서 noImplicitThis 에러가 발생하지 않는다.
정의되지 않은 Class의 속성이 constuctor에서 초기화되었는지 확인한다.
Class Person {
private _name: string; //error
private _age: number; //error
//constructor(name: string, age: number) {
// this._name = name;
// this._age = age;
}
비동기 처리를 위해서 constructor 대신 다른 함수에서 초기화하는 경우, !로 에러를 무시할 수 있다.
Class Person {
private _name!: string;
private _age!: number;
public async initialize (name: string, age: number) {
this._name = name;
this._age = age;
}
bind, call, apply에 대한 더 엄격한 검사 수행
각 소스 파일에 대해 strict mode로 코드를 분석하고, 컴파일된 자바스크립트 파일에 "use strict"를 추가한다.
함수 내에서 모든 코드(경로)가 값을 리턴하지 않으면 컴파일 에러를 발생시킨다.
function f4 (a: number): number {
if (a > 0) {
return a * 38;
} //else {
// return a * 23;
}
console.log(f4(5));
console.log(f4(-5) + 5) //error
반복되는 타입을 하나의 인터페이스로 지정
interface Person1 {
name: string;
age: number;
}
function hello1(person: Person1): void {
console.log(`h1 ${person.name}`);
}
const p1: Person1 = {
name: 'mary`,
age: 40
};
hello1(p1);
interface Person2 {
name: string;
age?: number; // age는 없어도 된다.
}
function hello2(person: Person2): void {
console.log(`h1 ${person.name}`);
}
hello2({ name: 'mary',age: 39 })
hello2({ name: 'mary' }) // compile error가 발생하지 않는다.
객체에 추가될 key와 value의 타입을 지정한다.
interface Person3 {
name: string;
age?: number;
[index: string]: any;
}
function hello3(person: Person2): void {
console.log(`h1 ${person.name}`);
}
const p31: Person3 = {
name: 'mark',
age: 39, // ?와 마찬가지로 indexable type도 없어도 상관없다.
};
const p32: Person3 = {
name: 'Anna',
sisters: ["sung","chan"]
};
const p33: Person3 = {
name: 'mary',
father: p31,
mother: p32, // 타입만 지키면 개수는 상관없다.
};
hello3(p31);
hello3(p32);
hello3(p33);
interface의 속의 함수의 타입을 지정.
interface Person4 {
name: string;
age: number;
hello(): void;
}
const p41: Person4 = {
name: 'Mark',
age: 39,
hello: function(): void {
console.log(`hi ${this.name}`)
}
};
const p42: Person4 = {
name: 'Mark',
age: 39,
hello(): void {
console.log(`hi ${this.name}`)
}
};
const p43: Person4 = {
name: 'Mark',
age: 39,
hello: (): void => {
//console.log(`hi ${this.name}`) arrow function 내에서는 this= global.this
}
};
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 {
//throw new Error("Method not implemented.");
console.log(`h1 ${this.name});
}
}
const person: IPerson1 = new Person('Mark');
person.hello();
interface은 상속을 내려주고 상속 받을 수 있다.
interface IPerson2 {
name: string;
age?: number;
}
interface IKorean extends IPerson2 {
city: string;
}
const k: IKorean = {
name: 'sujin',
city: 'seoul',
};
함수의 타입을 인터페이스로 표현
interface HelloPerson {
(name: string, age?: number): void;
}
const helloPerson: HelloPerson = function (name: string) {
console.log(`hi ${name}`);
}
helloPerson("Mark", 39)
interface Person8 {
name: string;
age: number;
readonly gender: string;
}
const p81: Person8 = {
name: 'Mark',
gender: 'male'
}
p81.gender = 'female' // compile error: 읽기전용
type의 이유와 목적이 명확하면 Interface, 그저 별칭이 필요한 경우는 Alias.
물론 기술적인 부분도 차이점이 존재한다.
// type alias
type EatType = (food: string) => void;
// interface
interface iEat {
(food: string): void
}
// type alias
type PersonList = string[];
// interface
interface iPersonList {
[index: number]: string;
}
interface ErrorHandling {
success: boolean;
error?: { message: string }
}
interface ArtistsData {
artist: { name: string } [];
}
//type alias
type ArtistsResponseType = ArtistsData & ErrorHandling;
//interface
interface IArtistResponse extends ArtistsData, ErrorHandling {} // 다중 상속으로 intersection 구현
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swin(): void;
layEgg(): void;
}
type PetType = Bird | Fish;
// interface로는 구현하기 어려움
같은 이름의 interface를 작성해도 merging 된다.
type alias에서는 같은 이름을 사용할 수 없다.
interface MergingInterface {
a: string;
}
interface MergingInterface {
b: string;
}
let mi: MergingInterface;
클래스는 ES6 이상에서도 사용하는 기능이다. 타입스크립트는 이를 보강하여 사용자가 만드는 타입의 하나로 클래스를 사용한다.
class Person {
name
constructor (name: string) {
this.name = name
}
}
const person = new Person('beomjin')
console.log(person.name)
class Person {
name: string = 'mark'; // constructor없이 직접 할당
age: number; // 초기화하지 않으면 typeError
}
런타임 상에 값을 의도적으로 할당하는 경우 !를 붙여준다.
class Person {
name: string = 'mark';
age!: number;
}
const p1: Person = new Person();
p1.age = 39 // 런타임에 할당
console.log(p1.age)
값을 선택적으로 할당하고 싶으면 ?를 붙여준다.
class Person {
name: string = 'mark';
age: number;
constructor(age?) {
if (age === undefined) {
age: number = 20
} else {
this.age = age
}
}
}
const p1: Person = new Person(43);
const p2: Person = new Person(); //error가 발생하지 않는다.
접근 제어자에는 public, private, protected 가 있다.
class Person {
public name: string = 'mark';
private _age: number;
public constructor(age?) {
if (age === undefined) {
age: number = 20
} else {
this.age = age
}
}
}
const p1: Person = new Person(43); //접근 가능
console.log(p1.name); // 접근 가능
console.log(p1._age); // 접근 불가
클래스 필드 선언과 constructor 내부에 this 바인딩을 통한 값을 할당하는 코드 대신에
접근제어자를 통해 아래와 같이 작성할 수 있다.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
대신에
class Person {
public constructor(public name: string, public age: number) {}
}
타입스크립트에서도 접근자 프로퍼티를 사용할 수 있다.
class Person {
public constructor(private _name: string, private _age: number) {}
get fullName () {
return this._name + "Lee"
}
set fullName(name: string) {
this._name = n;
}
}
const p1: Person = new Person("Mark", 40);
console.log(p1.fullName); // getter
p1.fullName = "hejin"; //setter
class Person {
public readonly name: string = 'Mark';
private readonly country: string = 'korea';
public constructor( private age: number) {
}
hello() {
this.country = 'japan' // Error: read-only property
}
}
const p1: Person = new Person(30)
console.log(p1.country) // Error: private property
class Students {
[index: string]: "male" | "female";
jin: "male" = "male"
}
const a = new Student();
a.mark = "male"
a.jade = "male"
const b = new Student();
b.chloe = "female"
b.alex = "male"
b.anna = "female"
단 하나의 객체만 만드는 class
class ClassName {
private static instance: null | ClassName = null
public static getInstance(): ClassName {
if (ClassName.instance === null) {
ClassName.instance = new ClassName();
}
return ClassName.instance
}
private constructor() {} //외부에서의 생성을 막는다.
}
const a = ClassName.getInstance();
const b = ClassName.getInstance();
console.log(a === b) // true
ref. class inheritnace in javascript
class Parent {
constructor(protected _name: string, private _age: number) {}
public print(): void {
console.log(`이름은 ${this._name}이고, 나이는 ${this._age}이다.`)
}
}
const p = new Parent('mark', 24)
// p._name // Error
p.print();
class Child extends Parent {
//public _name = 'Mark Jr.' // 부모의 프로퍼티를 오버라이드
public gender = "male" // 프로퍼티 추가
constructor(age: number) {
super('Mark Jr.',age)
super.print();
}
}
const c = new Child(5)
추상 클래스: class의 기능들이 완전하지 않은 채로 class를 정의한다.
이후, 상속을 통해 완전하게 구현한다.
abstract class APerson {
protected _name: string = "Mark";
abstract setName(name: string): void
}
new APerson() // 추상클래스의 인스턴스를 만들 수 없다.
class Person extends APerson {
setName(name: string): void {
this._name = name;
}
}
const p = new Person();
p.setName('envy');
들어오는 타입에 따라 그것의 가장 좁은 타입을 지정하여 함수(또는 class, array등) 내에서 이용할 수 있다.
function helloGeneric<T>(message: T): T {
return message;
}
console.log(helloGeneric('marin').length)
console.log(helloGeneric(22).length) //Error
여러개를 지정할 수도 있고 함수를 호출할 때 명시적으로 타입을 지정할 수도 있다.
function helloBasic<T, U>(message: T, comment: U): T {
return message;
}
helloBasic<string, number>('Mark', 39); // 타입 지정
helloBasic(36, 39); // 타입 추론
함수의 인수로 array를 받을 때, 리턴 타입
function helloArray<T>(message: T[]): T {
return message[0];
}
helloArray(['Hello', 'World']);
helloArray(['Hello', 5]) // string | number 유니온타입
function helloTuple<T, K>(message: [T, K]): T {
return message[0]
}
helloTuple(['Hello', 5]) // string으로 정확하게 지정된다.
함수의 타입만 지정하고 나중에 함수 구현
type HelloFunctionGeneric1 = <T>(message: T) => T
const helloFunction1: HelloFunctionGeneric1 = <T>(message: T): T => {
return message;
}
interface HelloFunctionGeneric2 {
<T>(message: T): T;
}
const helloFunction2: HelloFunctionGeneric2 = <T>(message: T): T => {
return message
}
class Person<T, K> {
private _name: T;
private _age: K;
constructor (name: T, age: K) {
this._name = name;
this._age = age;
}
}
new Person ("Mark", 39);
new Person<string, number>("Mark", "age") // Error
new Person<string, number>("Mark", 20)
class PersonExtends<T extends string | number> {
private _name: T;
constructor(name: T) {
this._name = name;
}
}
new PersonExtends("Mark");
new PersonExtends(39);
new PersonExtends(ture); //Error
generic을 이용하면 아래와 같이 안전한 타이핑을 할 수 있다.
interface IPerson {
name: string;
age: number;
}
const person: IPerson = {
name: "Mark",
age: 39,
}
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getProp(person, 'age')
function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
setProp(person, 'name', 'Anna');