인프런 타입스크립트 기초를 통해 공부중이며 제로초님의 유료강좌입니다.

코드를 통으로 가져오지는 않겠습니다.(내가 글을 쓰면서 복습하는게 목적이기때문에 필요한 부분만)

 

타입스크립트에 익숙하지않다면 자바스크립트로 먼저 코드를 작성하고 그 후에 타입스크립트로 변환하도록하자.

 

타입스크립트 (Typescript)

 

오 드디어 강의가 거의 끝이 나 보인다.!!!!

강의보고 이해하고 글한번 적고,, 다른것도 하다보니 되게 오래걸렸다.

다보고나면 뭐 하나 만들어보고 간단히 강의 훑고 react랑 typesctipt 같이 써봐야지!

 

진짜 의식의 흐름대로 글을쓰다보니 1회차부터 보니까 ㅋㅋㅋ 내가 헷갈림..ㅎㅎ

나중에는 공식문서보면서 좀 제대로 알아봐야겠다.

 

Typescript를 지원하는 패키지, 아닌 패키지

일단 typescript로 제작된 패키지도 있고 아닌애들도 있는데, redux같은 경우에는 typescript로 되어있어서 가져다쓰기 좋다고한다. 

redux git에 가서 보면 이렇게 Typescript로 작성되었다는 것을 알 수 있다.

 

DefinitelyTyped

만약 내가 쓰려는 패키지가 Typescript로 작성되어있지 않다면 definitelyTyped를 이용하자.

https://github.com/DefinitelyTyped/DefinitelyTyped

 

이 레퍼지토리는 많은 사람들이 모여서 지원안하는 패키지의 type을 만들어둔 곳이라고한다.

공식적으로 지원하는 것이 없을 경우 여기서 찾아보면된다고!

유명하지 않은 패키지같은 경우에는 없을 수도 있다고 한다.

types 폴더에서 react나 jQuery들을 확인할 수 있다.

install하는 방법은 cmd 창에 @types/ 로 시작하면된다.

npm i @types/jquery

 

Triple Slash 

패키지를 다운로드 받고 나면 d.ts파일에 아래와 같은 코드를 확인할 수 있는 경우가 있다.

이 경우는 다른 패키지를 참조하는 것을 나타내는데, 속성 값으로는 types와 path가 있다.

types에는 패키지의 이름을 적을 수 있고, path에는 경로를 적을 수 있다.

아래 코드와 같이 적혀있는 경우에는 symbol-observable을 참조하고 있다는 뜻이다.

/// <reference types="symbol-observable" />

왜 import를 안하냐고 할 수 있는데, import도 가능하나 다른 패키지를 참조할때는 저렇게 사용한다고 한다.

보통 프로젝트 코드가 아닌 라이브러리를 만들때 사용한다고 한다.

 

 

Namespace

export as namespace d3;

namespace는 export한 것들을 d3라는 네임으로 묶어주는 것을 뜻한다.

export as namespace d3;

export * from 'd3-array';
export * from 'd3-axis';

아래와 같이 되어있다면 d3-array에 export한 함수중에 max라는 함수가 있다면 아래와 같이 접근 할 수 있다.

d3.max

 

 

import * as 

import하자마자 export한다는 뜻이다.

import * as d3 from 'd3';

 

 

declare

타입이 없는 것을 새로 선언할 경우에 사용할 수 있다.

types라는 폴더를 생성하고 그 아래 d.ts파일을 생성하여 아래와 같이 can-use-dom 패키지의 d.ts파일을 생성해 줄 수 있다. 

// d.ts 파일
declare module 'can-use-dom'{
	const canUseDOM: boolean;
    export default canUseDOM;
}
// config 파일
{
	"compilerOptions" : {
    	"strict" : true,
        "lib" : ["EX5", "ES2015", "ES2017", "ES2018", "ES2019"],
        "typeRoots" : ["./types", "./node_modules/@types"]
    },
    "exclude" : ["*.js"]
}

types는 내가 만든 파일이 들어있는 폴더의 경로이며 node_modules는 definitelyTyped에서 받은 d.ts파일들이 있다.

 

아래와 같이 다양한 작업을 할 수 있다.

전역 객체 선언

export{}
declare global{
	interface Window{
    	//...
    }
    
    interface Error{
    	//...
    }
}

export{}를 한 이유는 글로벌인 경우 external모듈이어야하기 때문에(규칙) export를 사용하여 꼼수로 에러가 안나도록한다.

글로벌 객체를 확장하기 위해서는 external, ambient 모듈이어야한다.

아래처럼 ambient 모듈로 만들어도된다. namespace, interface를 통해 express의 request를 확장할 수 있다.

declare module 'connect-flash'{
	global {
    	namespace Express{
        	interface Request{
            	//...
            }
        }
    }
}

import express = require('express');

(모듈관련해서는 좀 더 자세히 알아봐야할 듯 싶다)

 

 

잘못된 d.ts파일이라면?

1.먼저 패키지 d.ts파일을 install해서 복사해준다.

// 예시는 connect-flash 모듈
npm i @types/connect-flash

2.패키지를 제거한다.

npm rm @types/connect-flash

3.내가 작성하려는 d.ts파일을 모아둔 폴더에 빈 파일을 생성하여 붙여넣는다.

4.잘못된 부분만 수정한다.

 

 

d.ts파일 옵션 - 라이브러리 만들때 사용하는방법

// config 파일
{
	"compilerOptions" : {
    	"strict" : true,
        "lib" : ["EX5", "ES2015", "ES2017", "ES2018", "ES2019"],
        "typeRoots" : ["./types", "./node_modules/@types"],
        
        // 옵션추가
        "declaration" : true,
        "declarationDir" : "./types"
    },
    "exclude" : ["*.js"]
}

그 후 npx tsc 하게되면 declarationDir 경로에 d.ts파일을 자동으로 생성해준다.

 

 

intersection

& 이것을 말한다.

둘다 만족하는 것을 뜻한다.

type A = string & number;

예제

interface A { 
	a : true
};

interface B{
	b : false
};

const c: A & B = {
	a: true,
    b: false
}

interface나 type aliases에서 사용할 수 있다.

interface A { 
	a : true
};

interface B{
	b : false
};

type C = {
	c : false
}

const d: A & B & C= {
	a: true,
    b: false,
    c: false
}

|는 하나만 만족해도된다.

interface A { 
	a : true
};

interface B{
	b : false
};

const c: A | Bㅌ= {
	a: true,
    b: true
}

코드의 중복을 막고 각각의 변수를 재사용하기 위해 좋다.

 

 

call, bind, apply

타입추론이 잘 안되는 메서드들이다.

타입스크립트가 업데이트되면서 새로운 옵션이 생겼다고 한다.

// config 파일
{
	"compilerOptions" : {
    	"strict" : true,
        
		// 옵션 추가
        "strictBindCallApply" : true,	
        
        "lib" : ["EX5", "ES2015", "ES2017", "ES2018", "ES2019"],
        "typeRoots" : ["./types", "./node_modules/@types"],
        "declaration" : true,
        "declarationDir" : "./types"
    },
    "exclude" : ["*.js"]
}

타입스크립트가 엄격해진다고한다.

 

 

TS유틸리티

Partial

// interface의 일부만 가져다가 사용할 때 사용한다. 
interface A {
	a: 'a',
    b: 123
}

const a: A = {
	a: 'a',
    b: 123
};

// b가없어도 에러가 나지 않는다.
const b: Partial<A> = {
	a: 'a'
}

Readonly

// 하나하나 넣어주면 너무 비효율적
interface A{
	readonly a: 'a',
    readonly b: 'b',
    readonly c: 'c'
};

// Readonly
interface B{
	a: 'a'
};

const b: Readonly<B> = {
	a: 'a'
}

그 외에 공식문서보고 쭉 읽어보자.

pick, omit 등..

 

 

데코레이터

클래스나 클래스의 기능을 수정할 수 있다.

중복되는 코드가 생겼을때 함수를 하나 만들어서 사용할 수 있다.

아직 정식 문법이 아니어서 옵션 값을 추가해준다.

// config 파일
{
	"compilerOptions" : {
    	"strict" : true,
        "strictBindCallApply" : true,	
        "lib" : ["EX5", "ES2015", "ES2017", "ES2018", "ES2019"],
        "typeRoots" : ["./types", "./node_modules/@types"],
        
        // 옵션 추가
        "experimentalDecorators" : true,
        
        "declaration" : true,
        "declarationDir" : "./types"
    },
    "exclude" : ["*.js"]
}

위에 적으면 클래스 데코레이터

메서드 옆에적으면 메서드 데코레이터라고 한다.

프로퍼티에 적으면 프로퍼티 데코레이터, 파라미터 옆이면 파라미터 데코레이터가 된다고 한다.

@makeGender
class Person{}

class Person{
	@validate title : string
}
// typeof Person안하고 Person만 적으면 인스턴스가 되버려서 꼭 typeof Person해야한다.
function makeGender(target: typeof Person){
	
    // 원본을 인자로 받아서 새로운 것을 extends 시켜서 내보낸다.
    return class extends target{
    	//...
    }
}

@makeGender
class Person{
	//....
}

@makeGender
class Person2{
	//....
}

 

데코레이터의 각각의 위치에 따라서 매개변수가 달라진다고 한다.

3번째 인자 값이 달라진다.

// 클래스 데코레이터
function readonly(target: any, key: any, descriptor: propertyDescriptor){
	//....
}

// 파라미터 데코레이터
function readonly(target: any, key: any, index: number){
	//....
}

 

 

Q&A 타입스크립트는 왜써요?

노드로 개발할 경우 서버가 죽는 경우가 생긴다.

보통어이없는 실수가 대부분이다.

타입스크립트는 코드 작성 시 에디터에서 한번 걸러주고 빌드시 두번 걸러줘서 안정성이 올라갈 수 있다.

자바스크립트는 런타입에서 오류가 나야 알 수 있다.

퍼포먼스가 좋아지는 일은 없다. 

안좋아지는 일도 없다 빌드하는 시간이 걸리기는 하지만 컴파일된 코드를 런타임에서 실행하기 때문에 퍼포먼스에는 이상이 없다.

 

 

 

인프런 타입스크립트 기초를 통해 공부중이며 제로초님의 유료강좌입니다.

코드를 통으로 가져오지는 않겠습니다.(내가 글을 쓰면서 복습하는게 목적이기때문에 필요한 부분만)

 

타입스크립트에 익숙하지않다면 자바스크립트로 먼저 코드를 작성하고 그 후에 타입스크립트로 변환하도록하자.

 

타입스크립트 (Typescript)

 

타입스크립트의 모듈 시스템은 자바스크립트의 최신문법을 개승했기 때문에

common.js를 쓰던 노드js의 방식과는 다르다.

common.js와 ES2015의 차이를 이해해야한다.

 

export default

자바스크립트에서는 exports와 module.exports가 같기 때문에 exports가 중복되면 값을 덮어쓰게된다. 

 

const hello = 'module';

exports.a = 'a';
exports.b = 'b';

module.exports = function(){

}

이런 문제를 극복하고자 ES2015에서는 export default를 도입했다.

export와 export default는 서로 다른 것이기 때문에 덮어씨여지지않는다.

// module.js
const hello = 'module';

// 선언을 먼저하고 아래에서 export해도된다.
const b = 'b';

// 바로 선언하면서 export해도 된다.
export const a = 'a';
export{b};

// 다른 js파일에서 가져다 쓸 수 있다.
import {a, b} from './module';

 

착각할 수 있는 부분은 export default와 module.exports가 같다고 생각하는 것이다. 

두개는 다른것이다.

그래서 타입스크립트에서 모듈을 처리할때에 module.exports로된 모듈이라면 아래와 같이 문법적으로 * as를 사용해서 가져와야한다.

// module.js
module.exports = function(){
	//....
}

// import하는 js
import * as hi from './module';

* as가 없을 경우 module.exports를 통해 가져오는 것이기 때문에 문법에 맞춰 * as를 추가해준다.

만약에 * as없이 module.exports와 export default를 같이 import하고 싶다면 tsconfig에 esModuleInterop을 true로 추가해주면된다. 하지만 비추하며 문법에 맞춰 사용한다.

 

 

ts 모듈 시스템 주의사항

타입 정의용 코드 (인터페이스, 클래스 등) 들은 모듈로 분리하는 경우가 많다.

타입선언이나 로직을 재사용할 수 있기 때문이다.

또는 타입선언과 로직을 구분하기 위해 분리를 하기도 한다.

꼭 분리를 해야하는 것은 아니다. 파일내에서 export 키워드만 추가해도 모듈로서 사용할 수가 있는데,

export interface Test{
    //...
}

스크립트 파일과 모듈을 구분할 수 있어야하기에 구분하는것이 좋다.

import와 export가 들어가면 모듈이 되며 그렇지 않다면 스크립트라고 할 수 있다.

 

타입스크립트에서는 common.js에 해당하는 파일을 import하려고 할때 .ts파일이나 .dts파일을 만들어서 선언파일을 만들어 준다.

// common.js
module.exports = function(){
    //...
}

// common.ts나 common.dts 파일을 생성한다.
declare function a(){}
export = a;

// import하는법
import A = require('./common');

// 또는 위에서 말한 * as를 사용
import * as A from './common';

* as는 그 파일안에 있는 모든 것을 가져오는 것을 의미한다.

.ts는 보통 내 프로젝트가 라이브러리일때 사용한다고 한다.

 

 

 

인프런 타입스크립트 기초를 통해 공부중이며 제로초님의 유료강좌입니다.

코드를 통으로 가져오지는 않겠습니다.(내가 글을 쓰면서 복습하는게 목적이기때문에 필요한 부분만)

 

타입스크립트에 익숙하지않다면 자바스크립트로 먼저 코드를 작성하고 그 후에 타입스크립트로 변환하도록하자.

 

타입스크립트 (Typescript)

 

매개변수를 구조분해할때 타입 작성 방법

// (X)
function test({ a: boolean, b: number}){
    //....
}

// (O)
fuction test({ a, b }: { a: boolean, b: number}){
    //....
}

뒤에 별도로 써야 에러가 나지 않는다.

아래와 같이 쓸 수 있다.

// 호출할때 객체로 인자값을 전달하고
connectCardDOM({ data: player.data, DOM : player.hero, hero: true});

// 구조분해를 이용해 인자를 이렇게 처리한다.
function connectCardDOM({ data, DOM, hero = false }: { data: Card, DOM: HTMLDivElement, hero: boolean}){
    //...
}

만약 너무 길다 싶을때에는 인터페이스로 분리하는 방법이 있다.

개인적인 견해라고 말했지만 인터페이스는 보통 객체를 만들때 사용한다고 한다.

interface A{
	data: Card, 
    DOM: HTMLDivElement, 
    hero: boolean
}

// 인터페이스 A를 타입으로 선언해준다.
function connectCardDOM({ data, DOM, hero = false }: A){
    //...
}

 

 

타입가드

넓은 타입을 좁은 타입으로 좁혀 사용할 수 있다.

아래 예제는 Animal을 implements한 cat과 dog 클래스에서 더 좁은 타입으로 만들기 위해 isCat을 선언한 것을 확인할 수 있다.

true일 경우 data는 cat이 된다.

class dog implements Animal{
    public a: number;
}

class cat implements Animal{
    public a: number;

    // b는 cat 만있음
    pibloc b: string;
}

// cat에는 b가 있어서 cat일 경우 true가 리턴 됨.
// return true일 경우 data is Cat이 된다.
// 이것을 타입 가드라고 한다.
function isCat(data: Animal): data is Cat{
    if(data.b){
        return true;
    }else{
        return false;
    }
}

Animal이라는 큰 클래스가 있다면 그 안에서 cat과 dog를 구분할 수 있다.

예제는 물론 간단히 두개로만 나누었지만 함수가 커지게 된다면 각각의 경우에 대한

타입가드를 만드는 것이 좋다.

위에서 만든 isCat을 사용하는 예제를 보겠다.

class dog implements Animal{
    public a: number;
}

class cat implements Animal{
    public a: number;
    pibloc b: string;
}

function isCat(data: Animal): data is Cat{
    if(data.b){
        return true;
    }else{
        return false;
    }
}

// 이런식으로 사용한다.
btn.addEventListener('click', () => {
    
    // isCat 타입 가드로 data의 타입을 리턴받는다.
    if(isCat(data) && 다른 조건을 추가해줘야 안전){
        //....
    }
});

 

TIP

코딩 스타일
함수 호출할 때 인자 값을 객체로 넣는다면 이점이 뭘까?

test(true, 5);

// true와 5가 무엇인지 가독성이 높아졌다.
test({ start: true, max: 5});

보통 함수 정의부에서 확인을 해야하는 데 호출하는 부분만 봐도 알 수 있다.

인자가 한 없이 늘어났다면 더욱 효과적이다.

test({ start: true, puzzle: true, max: 5, currentIndex : 0 });

하지만 이건 개인적인 부분이라고 한다.

 

 

인프런 타입스크립트 기초를 통해 공부중이며 제로초님의 유료강좌입니다.

코드를 통으로 가져오지는 않겠습니다.(내가 글을 쓰면서 복습하는게 목적이기때문에 필요한 부분만)

 

타입스크립트에 익숙하지않다면 자바스크립트로 먼저 코드를 작성하고 그 후에 타입스크립트로 변환하도록하자.

 

타입스크립트 (Typescript)

 

클래스와 인터페이스를 사용할때 언제 써야할까?

만약 내가 작업할 때 new키워드를 쓰지 않고 상속의 구현만 할것이라면 인터페이스를 사용해도 무방하다.

(실제 사용은 클래스에서 extends 하는 방식으로 하고 구현만 할거라면 인터페이스 implements해준다.

interface Example{
    (a: number, b: number) : number;
}

const ex: Example = (a,b) => a + b;

함수나 클래스는 할당되는 문법이 따로 있기 때문에 보통 객체에서 인터페이스를 많이 쓴다고 한다.(취향일수도)

 

 

인터페이스로 객체 타입을 선언

interface Example{
    add: (a: number, b: number) => number;
}

const ex: Example = {
    add: (a,b) => {
        return a + b;
    }
}

 

 

제네릭(Generic)

인터페이스에서의 제네릭

// 인터페이스
interface Test<T>{
	//...
}

// 클래스
class Test<T> implements Example{ 
	//...
}

// 함수
function Test<T>(arr: T[], callback: (item: T) => void): void{
	//...
}

 

 

 

타입 여러개를 더하기하는 함수를 만들고 싶다고 했을때 어떻게 해야할까?

(아래 예제에 string 타입을 더하고 있는 함수하나가 있고 string과 number타입을 모두 포함하는 함수를 만들고 싶다. 그리고 같은 타입끼리만 더해져야한다.)

function add(a: string, b: string): string{
    return a + b;
}

두번 선언할 것인가?

자바스크립트에서는 같은 함수를 두 번 선언할 수 없다.

// (X)
function add(a: string, b: string): string{
    return a + b;
}

function add(a: number, b: number): number{
    return a + b;
}

이렇게 선언한다면 에러가 난다. 그렇다면 or 연산자를 써서 해결해야할까?

function add(a: number | string, b: number | string): number | string{
    return a + b;
}

타입 두개를 추가하는데에는 만족했지만 해당 타입끼리 더해야한다는 요구조건을 만족할 수 없다.

아래와 같이 string과 number의 더하기도 허용한다.

add(1 + 'abc');

이때 해결할 수 있는 방법은 제네릭을 사용하는것이다.

제네릭은 임의의 변수를 만들어서 함수를 생성하고 나중에 이 함수를 사용할때 타입값을 입력받는 것이다.

 

함수로는 생성할 수 없고 인터페이스를 통해 생성해야한다.

// (X)
function add<T>(a: T, b: T): T{
    return a + b;
}

// (O)
interface obj<T>{
    add: (a: T, b: T) => T;
}

 

인터페이스 선언 시에 T라는 임의의 타입을 작성해 둠으로써 여유롭게 선언한다.

(T는 임의의 이름이다. 다른 이름도 상관 없다)

// T라는 타입을 제네릭으로 바꾼다.
interface obj<T>{
    add: (a: T, b: T) => T;
}

// 인터페이스를 사용할때 타입을 정해준다.
// <T>에 number를 넣게되는 것
const a: obj<number> = {
    add: (a, b) => (a + b);
};

// <T>에 string을 넣게되는 것
const b: obj<string> = {
    add: (a, b) => (a + b);
};

a.add(1, 2);
b.add('a', 'b');

 

addEventListener를 사용할 때 보통 아래와 같이 사용하는데, 

타입스크립트가 어떤식으로 동작하는지 알기 위해 lib.dom.d.ts파일을 확인해보자.

document.addEvenListener<'submit'>('submit', () => {});

lib.dom.d.ts에는 <K extends keyof ...... 라고 적혀있다.

이때 저 K가 제네릭이다.

 

실제 내가 넣었던 'submit'이 K로 들어가게된다.

그리고 extends keyof DocumentEventMap을 확인한 뒤 DocumentEventMap -> ... -> 순서대로 따라가다보면 GlobalEventHandlerEventHandlersEventMap까지 다다를 수 있는데,

내부에 key를 확인해보면 된다.

key에 없는 값을 입력할 경우 타입스크립트에서는 유추할 수 없다.

만약 타입이 현저히 다를 경우 두번째 documentEventListener(type: string.... 선언에 맞춰간다.

(둘 중에 적합한 것을 찾는다)

이것을 function overloading이라고 한다.

 

제네릭에서 extends란

클래스에서 extends는 상속을 말하는데 타입스크립트에서 extends는 제한이라는 뜻이다.

즉 타입에 대한 제한을 두는 것이다.

아래 예시는 boolean을 타입으로 넣는 경우이다.

interface obj<T>{
    add: (a: T, b: T) => T;
}

const a: obj<boolean> = {
    add: (a, b) => (a + b);
};

boolean타입을 더한다니? 이렇듯 아무거나 타입을 넣게되면 원하는 결과가 나오지 않기 때문에 제한을 둔다.

만약 extends string을 넣는다면 string과 string을 extends한 타입들만 선언할 수 있도록 하는 것이다.

interface obj<T extends string>{
    add: (a: T, b: T) => T;
}

여기서 number를 넣게되면 에러가 난다.

 

아래처럼 작성된 경우 K는 HTMLElementEventMap에서 keyof로 제한을 두겠다는 의미다.

첫번째 인수로는 key로 포함된 이벤트만 들어와야 에러가 나지 않는다.

document.addEventListener<'submit'>('submit', 

 

제네릭에 기본 값을 넣을 수 있다.

// K가 넘겨오는 타입 extends는 제약사항
// HTMLElementTagNameMap에서 key를 제약으로둔다.
// = 'div'는 기본 값
createElement<K extends keyof HTMLElementTagNameMap = 'div'>(.....

 

+ 제네릭 안에 제네릭도 들어갈 수 있다.

 

 

lib.dom.d.ts파일의 find를 확인해보자.

이것은 인터페이스 내부에 들어있다.

아래와 같이 선언했을때

Array<T>에서 먼저 타입을 받고 있다.

그리고 Array로 'a', 'b', 'c'를 삽입했기 때문에 item의 타입이 string인 것을 타입스크립트는 유추할 수 있다.

['a','b','c'].forEach((item) => {});

 

만약 오만가지 타입을 다 넣는다면 any가 될까?

아니다. 

// item : string | number | boolean | undefined | null 형식으로 유추한다.
['a',1,true, undefined, null].forEach((item) => {});

 

빈 배열을 삽입할 경우에는 never가 된다.

[].find((item) => {
    //......
});

 

아래 find에는 predicate라는 텍스트가 있는데

true, false를 반환하는 콜백함수인 경우에 predicate라 부른다.  

 

forEach를 구현하여 제네릭에 대해 더 자세히 알아보자.

function forEach<T>(arr: T[], callback: (item: T) => void): void{
    for(let i: number = 0; i < arr.length; i++){
        callback(arr[i]);
    }
}

// 사용할때 제네릭 넣기
// item이 자동으로 유추된다.
// forEach 선언할 때 item의 타입에도 T를 넣었기 때문에 유추가 되는 부분이다.
forEach<string>(['a', 'b', 'c'], (item) => {
     //........
});

 

일단 타입스크립트에서 타입추론되는 부분이 있다면 그 중에 제네릭일 확률이 있고 콜백함수에서 타입추론되는 경우 제네릭일 확률이 높다고한다.

 

 

 

 

+ Recent posts