[TYPESCRIPT] - 01. 타입스크립트의 시작

THOVY·2022년 10월 5일
0

LEARNING

목록 보기
6/15

나는 여태 js 를 이용했다. js 만 배웠기 때문이다. 주위에서 매우 많은 분들이 ts를 배우고 사용하라 하셨다.

코스도 끝났고, 이제 나만의 커리큘럼을 따라갈 수 있기 때문에 미뤄뒀던 TypeScript를 배워보려합니다.

첫 커리큘럼으로 타입스크립트를 선택한 이유

지난 프로젝트에서 js 를 사용했다. 다양한 객체 내에 다양한 함수들이 있었다. user.name, user.nickname, user.password, attraction.name, attraction.address, stay.stayname,post.userid 등등

왜인지 모르게 팀으로 작업하면서 의사소통의 문제인지 함수이름이 통일되지 않은 부분이 많았다. attraction 에서는 해당 객체의 이름이 name 인데 stay 에서는 stayname 이라고 한다던가. 비슷한 객체임에도 모두 달랐다. 심지어 같은 user 임에도 어떤 부분에서는 user.id 이지만 어떤 부분에서는 user.userId 라고 했다.

그럼 어떡해? 어떡하긴 그냥 받아보고 어디선가 문제가 생기면 일일이 console.log 를 찍어보고 어느 부분에서 undefined 가 나오는 걸 보고, 이건 왜 또 달라! 하면서 스트레스 받고 그랬던 거지.

하지만 TypeScript

이런 문제들을 코드를 짤 때 미리 말해준다고 한다.

사실 내가 아직 ts 의 시작이라, 백엔드에서 만들어준 객체까지 체크를 해주는지는 모르겠다만... 그랬으면 좋겠지만.. 어쨌든

코드를 실행하기 전에 문제가 있다고 얘기를 해준다면 방금 마주했던 스트레스는 이제 다시 볼 일이 없을 거다.

그래서 가장 먼저 배워놓는다면 후에 어떤 프로젝트를 하던지 간에 다시 사용할 수 있을 것이다. 다시 일일이 log 찍으며 찾아 해매는 일은 없는 거지.

부푼 기대를 안고 배워봅시다.


새 프로젝트를 시작하기에 앞서 컴퓨터를 포맷했기 때문에
타입스크립트를 시작하면서 설치해줄 것들이 많다.

설치

  1. VSCODE 설치

  2. NODE.JS 설치

NODEJS 들어가서 설치해준다. TYPESCRIPT 강의에서 사용하는 버전이 17.3 이라 17.3을 설치할까 했는데, 그냥 최신버전 설치했다.

선생님이 17.3 이상이면 된다고 했다. 뭐든 쌔거가 좋은 거 아니겠어요?

사실 안정버전이 17.3. 이상이었으면 그거 설치했을 텐데 홈페이지에 나와있는 안정버전이 16버전이라 18 버전 설치함.

그러면 이제 준비끝

본격적으로 강의를 들으며 시작해봅시다.

강의

타입스크립트의 특징

타입 지정, 타입 추론

매우 중요한 특징 중 하나가 타입 지정이다.
이름조차 typescript 인 만큼 타입이 정해져있다.

// js
const name = "thovy"

이렇게 선언하면 되는 js 와 달리 ts

// ts
const name : string = "thovy"

타입을 함게 명시해줘야한다.

하지만 타입스크립트는 타입을 추론해주기 때문에, 첫 변수 선언에서 js 처럼 작성했다고 해서 걱정하지 않아도 된다. ts는 그 변수의 타입을 추론해 뒤에 그 변수의 타입이 변하지 않는다면, 오류를 발생시키지 않는다. 하지만 뒤에 그 변수의 타입을 잘못 적는다면, 문제가 발생한다.

// ts
let name = "thovy"
// 이렇게 적어도 name 이 string 이라고 추론하고

let name = "harry"
// 라고 하면 에러가 나지 않는다.

//하지만
let name = true // boolean 타입
// 라고 변수의 타입이 잘못되면 에러가 난다. 타입설정을 해주지 않았지만 타입을 추론해놓은 거다.

타입스크립트가 타입을 추론해기 때문에 타입을 명시해주지 않는 것을 기본으로 하고 최소한으로 필요할 때만 명시해주는 것을 권장한다는데,
내 생각엔 단순한 변수가 아니면 타입을 명시해주는 게 낫다고 생각한다. 코드가 조금 길어지고 가독성은 조금 떨어지지만...일주일 전에 작성한 코드를 까먹어버리는 나의 댕청한 머리로 여러 사람과 함께 코드를 짜야하는 상황에서는 변수의 타입을 명시해주는 것이 깔끔하고 빠른 코드 작성과 이해에 큰 도움이 될 거라 생각한다.

let array = []
// 라고 하면 array의 타입은 any[] 가 되어 무엇이든 들어갈 수 있게 되고,
array.push('1')
// 이라는 string 을 넣어도 된다.
let array = [1,2,3]
// 이라고 하면 array 의 타입이 number[] 로 추론되어 숫자만 들어갈 수 있게되고,
array.push('1')
// 이라는 string 은 넣을 수 없게 된다.
let array : number[] = []
// 이라고 하면 array 의 타입이 명시적으로 number[] 가 되므로,
// array.push('1') 같은 바보같은 명령어는 쓰지 않겠지

읽기 전용 (readonly)

const name: readonly string = 'thovy'
// 라고 readonly 선언하면
const name = 'harray'
// 라고 변경할 수가 없다. 저렇게 변경하려하면 빨간 밑줄이 생길 거다.

변경하면 안되는 값들을 선언할 때 readonly 를 사용하면 실수로라도 변경되는 것을 방지 할 수 있을 거다.

튜플 (tuple)

파이썬에서 들어본 것 같은 tuple 이라는 것도 있단다.

평범한 array 같은데 각 위치별로 타입을 지정해놓을 수 있는 거다.

배열에 프로그래머의 [이름, 생년월일, 활동여부] 를 넣어보자.

const programmer: [string, number, boolean] = ['thovy', 590703, true]

이렇게 짜여진 배열을 변경하려고 하면

programmer[0] = 950703	// error

잘못 된 위치에 잘못된 값을 넣으려 하면 에러가 발생된다. 왜냐면 첫번째[0] 자리는 string 이니까.
여기에 readonly 를 사용하면 알맞은 타입을 넣더라도 수정되는 것을 막을 수 있겠지?

const programmer: readonly [string, number, boolean] = ['thovy', 590703, true]
programmer[0] = 'harray'	// error

만약 null 이나 undefined 를 넣어야한다면?

하지만 타입을 정해주기 애매할 때는?
undefined 나 null 이 들어갈 수도 있다면?

type Programmer = {
  name:string
  age?:number
}

이렇게 ? 물음표를 쓰면 age 의 타입은 number 혹은 undefined 가 된다.
age 를 넣지 않는다면 undefined 가 되겠지? 그러니가 없어도 된다는 거지.

변수의 타입을 모른다면?

API 에서 응답을 받는데 그 변수의 타입을 모른다면?

UNKNOWN

let a : unknown;
// a 의 타입을 모를 때

if (typeof a === 'number') {
  let b = a + 1
}
if (typeof a === 'string') {
  let b = a.toUpperCase();
}

type 을 알 수 없으니 저렇게 적어놓을 수 있는 것.

만약에 a 가 어떤 타입으로든 한 번 사용되면, 그 뒤 부터는 앞서 사용된 타입으로 사용해야한다.

함수 선언

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

라고 했을 때 이상한 건 없지만 ts 는 빨간 밑줄을 표시할 거다.
왜냐면 a b 가 뭔지 모르기 때문.

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

이렇게 명시해줘야한다. 어떤 인자들을 받는지.
add(a:number,b:number):number{ ... 라고 또 명시할 수도 있지만 당연한 건 알아서 추론한다.

화살표 함수

화살표 함수도 같다. 명시해줘야한다.

const addd = (a:number, b:number) => a+b

명시하기 싫을 땐?

함수마다 명시를 해야하는게 귀찮다? 그럼 타입을 만들어보자!

type Add = (a:number, b:number) => number;
// Add 라는 type 을 만들어 준 것.

const add: Add = (a,b) => a+b
// add 라는 게 Add 타입이라고 선언해주고 나머지 인자들은 선언해주지 않았음.
// 그래도 Add 타입을 미리 선언해놓았기 때문에 알아서 number 로 알아들음.

polymorphism (다형성)

type Logging = {
  arr: number[] :void
}

const log: Logging = (arr) => {
  arr.forEach(i => console.log(i))
}

이런 식으로 Logging 이라는 타입을 만들어 log 라는 함수를 사용하려고 할 때,
지금 log 라는 함수의 인자로는 number[] 만 들어갈 수 있다.

그래서

log([1, 2, 3])	// 1 2 3
log(['떼잉','고양이','치즈'])	// error

string[] 을 넣으면 에러가 나올 거다.
그런데 우리는 다양한 값을 넣고 싶어

그럼 overloading 을 해보자

type Logging = {
  arr: number[] :void
  arr: string[] :void
}

이렇게 되면 number[], string[] 모두 잘 출력 될 거다.
하지만

log([1, 2, '고양이', '4마리'])

는 또다시 에러가 날거다.

그럼

arr:(number[] | string[]) :void

를 추가해주면 되지만, 이렇게 하나하나 추가해주는 것보다 좋은 방법이 바로

generic polymorphism 을 이용하는 것(다형성)

type Logging = {
  <T>(arr: T[]):void
}

라고 한다면 어떤 것을 넣던 간에, 그 때에 맞춰서 타입스크립트가 인자로 받을 타입을 알아서 바꿔?준다.

number[] 타입만 왔으면 그렇게 설정되어있다고 알려주고,
같이 들어오면 | 표시를 사용해 같이 사용할 수 있다고 알려주고,
때에 따라 다양하게 변형된다. 우리는 타입에 신경쓰지 않고 사용하면 되는 거지.

이런식으로.

<T> 는 뭐라고 적던 상관 없음. TV 를 많이 사용한단다. TypePlaceholder

그럼 generic 과 any 와 다른점이 뭐지?

any 를 사용하면

const log: Logging = (arr) => {arr[0]}
  
const a = log([1,2,'cat'])
a.toUpperCase()

라고 했을 때 에러가 표시 되지 않은채 실행했을 때 에러가 남.
1 는 uppercase 가 없잖아.
그런데 any 는 뭐든 가능하다고 하니, 에러표시를 하지 않을 거고.

근데 generic 은 이런 경우에 빨간 밑줄로 에러를 표시 해줄 거임.

private

class User {
  constructor(
    private firstName:string,
    private lastName:string,
    private id:string,
    public nickname:string
  )
}

const thovy = new User('King', 'Thovy','thovy123','thovy');

이렇게 private 으로 해놓으면 함부로 가져다 쓸 수 없다.
객체지향 언어에서 볼 수 있는 특징. private

마치 자바에서 사용하는 거랑 비슷한 거 같다.

그래서

thovy.nickname	// 'thovy'
thovy.name	// error

privatename 은 빨간 밑줄이 그이며 사용할 수 없을 거다.

js 로는 할 수 없는 부분.

abstract class (추상클래스)

abstract class User {
  constructor(
    private firstName:string,
    private lastName:string,
    private id:string,
    public nickname:string
  )
}

class Customer extends User{
  
}

이렇게 되면 User 를 바로 사용할 수 없다. User 는 오로지 상속만 할 수 있는 클래스가 된다.


const thovy = new Customer('King','Thovy','thovy123','thovy');

라고 해야함. 직접적으로 인스턴스를 만들 수 없다.

abstract class User {
  constructor(
    private firstName:string,
    private lastName:string,
    private id:string,
    public nickname:string
  ){}
  getFullName()
    return `${this.firstname} ${lastname}`
}

class Customer extends User{
}

추상 클래스에 getFullName 이라는 걸 추가해주고, 그걸 상속받았다면,

thovy.getFullname()

도 가능하다.

하지만

private getFullName(){...}

이 된다면( private 이 된다면) 사용할 수 없다.

private 은 프로퍼티 뿐만 아니라 메서드에도 적을 수 있다.

자바같다.

protected

private 으로 하면 상속받은 클래스에서도 private 객체를 사용할 수 없다.
하지만 protected 라면 클래스 밖에서는 사용할 수 없지만, 상속받은 클래스 안에서는 객체를 사용할 수 있다.

abstract class User {
  constructor(
    protected firstName:string,
    private lastName:string,
    private id:string,
    public nickname:string
  ){}
  getFullName()
    return `${this.firstname} ${lastname}`
}

class Customer extends User{
  getFullname(){
    console.log(this.firstName)		// firstName 이 출력된다
    console.log(this.lastName)		// error
  }
}

읽는 건 되는데 수정은 안되게 하고 싶다? 그럼 앞서 배운 "readonly"

interface

type Words = {
  [key:string]:string
}

class Dict {
	private words:Words;
}

이렇게 하면 private words 부분에서 빨간 밑줄이 생기는데,

이니셜라이즈가 없으니,
선언한 뒤에 컨스트럭터를 만들어 초기화 해주자.

type Words = {
  [key:string]:string
}

class Dict {
	private words:Words;
  constructor(){
    this.words ={}
  }
}

class Word {
  constructor(
  	public term:string,
    public def :string,
  ){}
}

const kimchi = new Word("kimchi","한국의 음식");

이렇게 되면 kimchi 라는 변수는 kimchi 라는 Dict 의 term이 kimchi 고, def 가 한국의 음식Word 를 담은 변수가 되는 거다. 그래서 kimchi 변수를 호출하면 자연스럽게 Dict 오브젝트에 {kimchi,한국의음식} 이 담겨 응답되겠지?

type Words = {
  [key:string]:string
}

class Dict {
	private words:Words;
  constructor(){
    this.words ={}
  }
  add(word:Word){		// dict 에 add라는 객체 추가 기능 추가
    if(this.words[word.term] === undifined){
      this.words[word.term] = word.def
    }
  }
  def(term:string){		// dict 에 def 라는 검색?기능 추가
    return this.words[term]
  }
}

class Word {
  constructor(
  	public term:string,
    public def :string,
  ){}
}

const kimchi = new Word("kimchi","한국의 음식");

const dict = new Dict()

dict.add(kimchi);		// dict 이라는 Dict 타입의 새 변수를 만들고 거기에 kimchi 변수를 넣음(add)
dict.def("kimchi");		// 거기에서 def 라는 메서드를 이용함


그러면 이렇게 나올 거다.

그럼 앞으로 단어를 삭제하고, 수정하는 메서드도 추가하기 쉬울 거다.

implements

extends 와는 다르게 implements 는 js 에는 없다. 그래서 코드가 더욱! 가벼워진다.

그래서 interfaceimplements 를 사용하면 js 에서는 어떤 것도 나오지 않고 상속받는 클래스만 표시된다.

그럼 코드가 더욱 가벼워진다.

type 과 interface 의 차이

type PlayerA = {
  name:string
}

const playerA: PlayerA ={
  name:'thovy'
}

////////

interface PlayerB = {
  name:string
}

const playerB: PlayerB ={
  name:'thovy'
}

이렇게 보면 똑같아 보이지만, 타입을 상속할 때는 달라진다.

lastName이라는 속성을 추가하고 싶다면

type PlayerA = {
  name:string
}

type PlayerAA = PlayerA & {
  lastName:string
}

const playerA: PlayerA ={
  name:'thovy'
  lastname:'King'
}

////////

interface PlayerB = {
  name:string
}

interface PlayerB {
  lastName:string
}

const playerB: PlayerB ={
  name:'thovy'
  lastName:'King'
}

종합 마무리


interface SStorage { // Storage는 이미 ts 기본 interface 가 있으므로 SStorage로
  [key:string] : T
}
  

class LocalStorage<T> {
  private storage: SStorage<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 = {}
  }
}

LocalStorage 에서 T 를 반환하도록 하므로, T 에 어떤 걸 넣어주느냐에 따라
새로운 변수를 만들 수 있음.

const stringSStorage =  new LocalStorage<string>()

const booleanSStorage = new LocalStorage<boolean>()

너무 어렵지만 이제 개론이 끝났습니다.

이제 본격적으로 ts 를 활용한 프로젝트를 만들어볼까요?

  1. vscode 를 열고 터미널에 npm init -y 를 입력해 node.js 프로젝트 생성.

  2. package.json 에서 main 지우기

  3. test 지우기

  4. 터미널에 npm i -D typescript 를 입력해 typescript 설치

  5. 설치 확인

  6. src 폴더 생성 - 그 안에 index.ts 파일 생성

7.tsconfig.json 파일 생성(touch tsconfig.json 은 왜 안 되지)

7-1. 코드 입력

{
    "include": ["src"],
    "compilerOptions": {
        "outDir": "build"
    }
}

include: ts 가 include 안에 있는 폴더를 모두 본다는 것. 여기선 src 를 지정해줬으니 src 에 있는 모든 파일을 확인해보고 compile 할 거다.

compilerOptions outDir: complie 된 파일을 어디에 저장할 건지. 여기선 build 라고 했으니 build 폴더안에 저장한다.

  1. 저장하고 npm run build 를 실행.

  2. buildindex.js 파일이 생성되는 지 확인

compile 버전 지정

package.json 에서 target 을 입력하면

이렇게 버전을 지정할 수 있다.

  1. 버전을 지정한 뒤 다시 npm run build

ES3 에서는 const 도 없고 화살표 함수도 없다. ES6 로 버전을 지정해주니 const 와 화살표 함수가 표현된다.

lib

lib 는 프로젝트가 어떤 환경에서 실행될지 정해주는 건데,

이렇게 DOM 을 적으면 웹브라우저 환경에서 실행된다는 것을 애기해준다.
그래서 ts 파일에 코드를 적을 때
document 라고 치면

이렇게 document 내부의 메서드들이 나오지만.

lib 에서 DOM 을 지우면

브라우저 환경이 아니라고 생각해 document 를 입력해도 알아듣지 못한다.
마치 백엔드 서버라고 생각하는 거지.




진짜 진짜 본격적으로 코인 만들어보기

npm i -D ts-node
실제 배포에서는 사용하지 않고 개발환경에서 사용. 계속 js 로 변환하면서 빌드하고 그러면 오래걸리니까.
ts-node 를 추가하면 컴파일 없이 ts 코드를 실행해준다.
script 에 'ts-node src/index'
(확장자는 없어도 되고 ts 라고 해도된다. ts를 실행하는 script 니까 js 는 안되겠지?)

npm i nodemon
커멘드를 재실행해줘서 서버를 재시작할 필요가 없다.
script 에 nodemon --exec

dependencies 에 우리가 설치한 친구들이 잘 설치됐나 확인하고


npm run dev

이렇게 되면, 코드를 바꿔서 저장하면 곧바로 서버가 재실행되며 변경된 사항이 적용된다. react 는 이걸 모두 포함하고 있던 거지.
정말 react 는 최고야. 사랑해요 저커버그

진짜진짜진짜 코인 만들기

...

강의를 전혀 이해하지 못함.

this.blockchain 이라고 한 거를
[...this.blockchain] 이라고 하니까 보호가 되던데?!

알아봐야겠다. 그거는 이번 react 프로젝트에서도 사용했었던 거니까.

어쨌든 끝

어쨋든 ts 를 배운 것 같다..?

시간이 없으니 바로 시작해보자.

profile
BEAT A SHOTGUN

0개의 댓글