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

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

 

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

 

타입스크립트 (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 타입스크립트는 왜써요?

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

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

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

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

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

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

 

 

몇일 전에 크레인인형뽑기 문제를 풀고 난 후에 다른 사람의 코드에서 reduce 메서드를 사용하는 것을 확인했고 궁금해져서 메서드에 대해 공부해봤습니다.

 

reduce

reduce 구문

arr.reduce(callback(acc, cur, curIndex, arr), initVal]);

 

reduce 인자

누적 값(acc)

현재 값(cur)

현재 인덱스 (curIndex)

원본 배열(arr)

 

그리고 initialValue라는 callback의 최초 acc에 제공하는 누적 값의 초기 값.

만약 초기 값이 없다면 자동으로 누적 값은 원본배열(arr)의 0번째 인덱스 값으로 지정됩니다.

 

간단한 예제를 보겠습니다.

// 초기 값이 없을때
[1,2,3,4,5].reduce((acc, cur, curIndex, arr) => {
	// acc는 초기값이 없으므로 [1,2,3,4,5]에서 첫번째 인덱스의 값인 1이 됩니다.
    
    // cur는 acc가 초기 값을 사용했으므로 [1,2,3,4,5]에서 그 다음 인덱스의 값인 2가 됩니다.

	// curIndex는 cur값이 2이므로 2의 인덱스인 1이 됩니다.
    
    // arr는 원본 배열인 [1,2,3,4,5]가 됩니다.
});

// 초기 값이 있을때
[1,2,3,4,5].reduce((acc, cur, curIndex, arr) => {
	// acc는 초기값이 있으므로 0이 됩니다.
    
    // cur는 [1,2,3,4,5]에서 1이 됩니다.

	// curIndex는 cur값이 1이므로 1의 인덱스인 0이 됩니다.
    
    // arr는 원본 배열인 [1,2,3,4,5]가 됩니다.
}, [0]);

이때 빈 배열에서 초기 값 없이 reduce를 호출하면 오류가 발생합니다.

 

 

초기 값이 없는 경우의 예제를 보겠습니다.

반복문의 첫번째라고 가정해보겠습니다.

var arr = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){

  // 초기 값이 없기에 배열의 첫번째 인자값이 된다 = 0
  console.log(acc);

  // 첫번째 배열이 초기값이 되어서 cur값은 그 다음 값인 1이 된다. 
  console.log(cur);

  // cur이 1인 값을 가졌으니 1의 값의 index가 찍힌다 = arr의 1은 1번째 인덱스에 있다.
  console.log(curIndex);

  // arr은 현재 호출된 배열이므로 [0,1,2,3,4]가 찍힌다.
  console.log(arr);
});

두번째라고 가정해보겠습니다.

var arr = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){

  // 리턴 값이 없기 때문에 undefined가 된다.
  console.log(acc);

  // 이전의 값은 1이었으니 그 다음 값이 현재 값이 되므로 2가 찍힌다. 
  console.log(cur);

  // cur이 2인 값을 가졌으니 2의 값의 index가 찍힌다 = arr의 2는 2번째 인덱스에 있다.
  console.log(curIndex);

  // arr은 현재 호출된 배열이므로 [0,1,2,3,4]가 찍힌다.
  console.log(arr);
});

이런식으로 반복이되며 배열의 길이-1 만큼 반복을 합니다.

 

 

초기 값이 있는 경우의 예제를 보겠습니다.

반복문의 첫번째라고 가정해보겠습니다.

var arr = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){

  // 초기 값이 1이 있기 때문에 acc는 1이다.
  console.log(acc);

  // 초기값이 있었기 때문에 acc가 원본 배열의 첫번째 인덱스의 값을 가져가지 않았기 때문에 cur은 0이 된다. 
  console.log(cur);

  // cur이 0인 값을 가졌으니 0의 값의 index가 찍힌다 = arr의 0은 0번째 인덱스에 있다.
  console.log(curIndex);

  // arr은 현재 호출된 배열이므로 [0,1,2,3,4]가 찍힌다.
  console.log(arr);
}, 1);

두번째라고 가정해보겠습니다.

var arr = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){

  // 리턴 값이 없기 때문에 undefined가 된다.
  console.log(acc);

  // 이전의 값은 0이었으니 그 다음 값이 현재 값이 되므로 1가 찍힌다. 
  console.log(cur);

  // cur이 1인 값을 가졌으니 1의 값의 index가 찍힌다 = arr의 1은 1번째 인덱스에 있다.
  console.log(curIndex);

  // arr은 현재 호출된 배열이므로 [0,1,2,3,4]가 찍힌다.
  console.log(arr);
}, 1);

 

만약 첫번째 두번째 반복에서 return이 있다면 결과가 어떻게될까?

return 값이 있는 예제를 보겠습니다.

두번째라고 가정해보겠습니다.

var arr = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){

  // 첫번째 반복에서 acc는 1이었고 cur는 0이었다. 
  // return 1 + 0이 현재의 acc값이므로 acc는 1이다.
  console.log(acc);

  // 이전의 값은 0이었으니 그 다음 값이 현재 값이 되므로 1이 찍힌다. 
  console.log(cur);

  // cur이 1인 값을 가졌으니 1의 값의 index가 찍힌다 = arr의 1은 1번째 인덱스에 있다.
  console.log(curIndex);

  // arr은 현재 호출된 배열이므로 [0,1,2,3,4]가 찍힌다.
  console.log(arr);
  
  // acc는 1이고 cur도 1이다. 1+1로 return 2가 된다.
  return acc + cur;
}, 1);

 

이런식으로 return을 통한 인수의 총 값을 구하는 것도 가능하다.

var array = [0,1,2,3,4];

arr.reduce(function(acc, cur, curIndex, arr){
	return acc + cur;
});

 

좀 더 심화되는 코드로 배열에 중복되지 않는 숫자를 추가하는 예제를 확인해보자.

var arr = [0,1,0,2];

arr.reduce(function(acc, cur){

  // 초기값은 빈배열이다.
  // 초기값이 있기 때문에 cur은 0부터 시작한다.
  // 배열에서 cur값을 indexOf 메서드를 통해 찾는다.
  if(acc.indexOf(cur)< 0 ) 

  // acc에 cur 값을 push한다.
  acc.push(cur);

  // acc 값을 리턴한다.
  return acc;
}, []);

 

비동기 프로그래밍에서도 유용하게 쓸 수 있습니다.

const timeArr = [1000, 2000, 3000, 4000, 5000];

const factory = (time) => {
    return new Promise((resolve, reject) =. {
        setTimeout(resolve, time);
    }
};

timeArr.reduce((acc, cur) => {

  // 이전에 리턴받은 결과가 then되고나서 현재 값 전달
  return acc.then(() => factory(cur));

  // 초기값
}, Promise.resolve());

 

 


참고
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 

 

 

 

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

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

 

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

 

타입스크립트 (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 });

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

 

 

+ Recent posts