[타입스크립트] Do it! 타입스크립트 프로그래밍

June·2021년 10월 30일
0

1장

타입스크립트란?

세 종류의 자바스크립트

자바스크립트는 세 종류가 있다.

  • 웹 브라우저에서 동작하는 표준 자바스크립트 ES5
  • 2015년부터 매년 새로운 버전을 발표하는 ESNext
  • ESNext에 타입을 추가한 타입스크립트

트랜스 파일

ESNext 자바스크립트 소스코드는 바벨이라는 트랜스 파일러를 거치면 ES5 자바스크립트가 된다. 타입스크립트 소스코드는 TSC라는 트랜스파일러를 통해 ES5 자바스크립트 코드로 변환된다.

타입스크립트 주요 문법

ESNext의 주요 문법

1. 비구조화 할당

let person = {name: "Jane", age: 22}
let {name, age} = person // name = "jane", age = 22

let array = [1,2,3,4]
let [head, ...rest] = array // head = 1, rest = [2,3,4]

let a = 1, b = 2;
[a, b] = [b,a] // a = 2, b = 1

클래스

모듈

생성기

promies와 async/await

타입스크립트 고유의 문법

1. 타입 주석과 타입 추론

let n: number = 1
let m = 2

2행처럼 타입 부분을 생략할 수도 있다. 생략되면 타입 추론을 통해 결정한다. 타입 추론 덕분에 자바스크립트로 작성된 '.js' 파일을 확장자만 '.ts'로 바꾸면 타입스크립트 환경에서도 바로 동작한다.

2. 인터페이스

interface Person {
  name: string
  age?: number
}
  
let person: Person = {name: "Jane"}

3. 튜플

배열에 저장되는 아이템의 데이터 타입이 모두 같으면 배열, 다르면 튜플이다.

let tuple: [boolean, number, string] = ]true, 1, 'Ok']

4. 제네릭 타입

class Container<T> {
  constructor(public value: T) {}
}
let numberContainer: Container<number> = new Container<number>(1)

5. 대수 타입 (algebraic data)

다른 자료형 값을 가지는 자료형을 의미한다.

type NumberOrString = number | string;
type AnimalAndPerson = Animal & Person;

타입스크립트 개발 환경 만들기

nodejs 설치, 비주얼 스투디오 코드, 브라우저 설치

scoop 이라는 설치 프로그램으로 최신 버전 유지 가능

powershell ->

Set-ExecutionPolicy RemoteSigned -scope CurrentUser (이후 a 입력)
$env:SCOOP='C:\Scoop'
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
scoop install aria2
scoop install git

비주얼 스튜디오 코드 설치
... 생략

vscode 터미널에서

touch hello.ts

코드 작성후

tsc hello.ts

원래 hello.js 파일을 만들어야 하는데 오류 났다.
그래서

node hello.ts

하면 바로 실해오딘다.

tsc는 타입스크립트 코드를 ES5 형식의 자바스크립트 코드로 변환만 할 뿐 실행하지는 않는다. 만약 타입스크립트 코드를 ES5로 변환하고 실행까지 하려면 ts-node 설치해야 한다.

npm install nodejs-lts
npm i -g npm
npm i -g typescript ts-node
ts-node hello.ts

https://cafe.naver.com/doitstudyroom

2. 타입스크립트 프로젝트 생성과 관리

타입스크립트 개발은 노드제이에스 프로젝트를 만든다음, 개발 언어를 타입스크립트로 설정하는 방식으로 진행한다. 노드제이에스 프로젝트는 디렉토리를 하나 만들고 여기에 package.json이란 이름의 파일을 만드는 것으로 시작한다. 보통 package.json은 터미널에서 npm init 명령어를 실행해서 생성한다.

프로젝트 생성자 관점에서 패키지 설치하기

package.json 파일을 만들었으면 프로젝트 구현에 필요한 다양한 오픈소스 패키지를 npm install 또는 간단히 npm i 명령으로 설치한다.

타입스크립트 프로젝트는 보통 typescript와 ts-node 패키지를 설치한다.

이 프로젝트를 전달받아서 이용하는 다른 개발자에는 두 패키지가 설치되어 있지 않을 수 있다. 따라서

npm i -D typescript ts-node
npm i -D @types/node

프로젝트 이용자 관점에서 패키지 설치

다른 사람에게 프로젝트를 전달할 때는 node_modules 디렉터리를 모두 지운다.
따라서 다른 사람이 작성한 프로젝트를 받아 이용할 때에는 가장 먼저 package.json 파일이 있는 디렉토리에서

npm i

그러면 pcakge.josn에 등록된 패키지들이 node_modules에 자동으로 설치된다.

tsconfig.json 파일 만들기

타입스크립트 프로젝트는 타입스크림트 컴파일러 설정 파일인 tsconfig.json 파일이 있어야 한다. 이 파일은 tsc --init 명령으로 만들 수 있다.

tsconfig.json

{
  "compilerOptions": {
    "module" : "Commonjs",
    "esModuleInterop": true,
    "target": "ES5",
    "moduleResolution": "node",
    "outDir": "dist", 
    "baseUrl": ".",
    "sourceMap": true,
    "downlevelIteration": true,
    "noImplicitAny": false,
    "paths": {"*": ["node_modules/*"]}
  },
  "include": ["stc/**/*"]
}
mkdir -l src/utils
touch src/index.ts src/utils/makePerson.ts

makePerson.ts

export function makePerson(name: string, age: number) {
    return {name: name, age: AbstractRange}
}

export function testMakePerson() {
    console.log(
        makePerson('Jane', 22),
        makePerson('Jack', 33)
    )
}

index.ts

import { testMakePerson } from "./utils/makePerson";
testMakePerson()

시작 소스 파일명을 index로 짓는 이유
node나 ts-node 소스 파일을 실행하려면 ts-node ./src/index.ts 명령을 사용합니다. 하지만 소스 파일명이 index이면 파일명을 생략하고 ts-node ./src로 실행할 수 있습니다. 이 때문에 프로젝트의 시작 함수(엔트리 함수)가 있는 소스 파일명은 보통 index로 짓습니다.

package.json 수정

{
  "name": "ch02-1",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "dev": "ts-node src",
    "build": "tsc && node dist"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^16.11.6",
    "ts-node": "^10.4.0",
    "typescript": "^4.4.4"
  }
}

모듈 이해하기

타입스크립트에서는 index.ts 같은 소스 파일을 모듈이라고 한다. export는 기능을 제공하는 쪽에서 사용하고 import는 다른 모듈의 기능을 사용하는 쪽에서 사용한다.

index.ts

let MAX_AGE = 100

interface IPerson {
    name: String
    age: number
}

class Person implements IPerson {
    constructor(public name: string, public age: number) {}
}

function makeRandomNumber(max: number = MAX_AGE): number {
    return Math.ceil((Math.random() * max))
}

const makePerson = (name: string,
    age:number = makeRandomNumber()) => ({name, age})

const testMakePerson = (): void => {
    let jane: IPerson = makePerson('Jane')
    let jack: IPerson= makePerson('Jack')
    console.log(jane, jack)
}
testMakePerson()

모듈화를 진행해보자.

index.ts 파일의 모듈화
Person.ts

let MAX_AGE = 100

interface IPerson {
    name: String
    age: number
}

class Person implements IPerson {
    constructor(public name: string, public age: number) {}
}

function makeRandomNumber(max: number = MAX_AGE): number {
    return Math.ceil((Math.random() * max))
}

const makePerson = (name: string,
    age:number = makeRandomNumber()) => ({name, age})

index.js

const testMakePerson = (): void => {
    let jane: IPerson = makePerson('Jane')
    let jack: IPerson= makePerson('Jack')
    console.log(jane, jack)
}
testMakePerson()

export 키워드

export 키워드는 function, interface, class, type, let, const 키워드 앞에도 붙일 수 있다.

Person.ts

let MAX_AGE = 100

export interface IPerson {
    name: String
    age: number
}

class Person implements IPerson {
    constructor(public name: string, public age: number) {}
}

function makeRandomNumber(max: number = MAX_AGE): number {
    return Math.ceil((Math.random() * max))
}

export const makePerson = (name: string,
    age:number = makeRandomNumber()) => ({name, age})

import 키워드

어떤 파일이 export 키워드로 내보낸 심벌을 받아서 사용하려면 import 키워드로 해당 심벌을 불러와야 한다.

import {심벌목록} from "파일의 상대경로"

index.ts

import { IPerson, makePerson } from "./person/Person"

const testMakePerson = (): void => {
    let jane: IPerson = makePerson('Jane')
    let jack: IPerson= makePerson('Jack')
    console.log(jane, jack)
}
testMakePerson()

import * as 구문

Person.ts

import * as U from '../utils/makeRandomNumber'

export interface IPerson {
    name: String
    age: number
}

class Person implements IPerson {
    constructor(public name: string, public age: number) {}
}

export const makePerson = (name: string,
    age:number = U.makeRandomNumber()) => ({name, age})

makeRandomNumber.ts

let MAX_AGE = 100

export function makeRandomNumber(max: number = MAX_AGE): number {
    return Math.ceil((Math.random() * max))   
}

export default 키워드

IPerson.ts

export default interface IPerson {
    name: String
    age: number
}

export default 키워드는 한 모듈이 내보내는 기능 중 오직 한 개에만 붙일 수 있다. export default 가 붙은 기능은 import 문으로 불러올 때 중괄호 {} 없이 사용할 수 있다. export defaultexport등이 있는 파일에서도 사용할 수 있다.

Person.ts

import * as U from '../utils/makeRandomNumber'
import IPerson from './IPerson'

export default class Person implements IPerson {
    constructor(public name: string, public age: number = U.makeRandomNumber()) {}
}

export const makePerson = (name: string,
    age:number = U.makeRandomNumber()) => ({name, age})

index.ts

import IPerson from "./person/IPerson"
import Person, { makePerson } from "./person/Person"

const testMakePerson = (): void => {
    let jane: IPerson = makePerson('Jane')
    let jack: IPerson= makePerson('Jack')
    console.log(jane, jack)
}
testMakePerson()

외부 패키지를 사용할 때 import 문

npm i -S chance ramda
npm i -D @types/chance @types/ramda

package.json이 변경된다.

change 패키지는 가짜데이터를 만들어주는데 사용되고, ramda는 함수형 유틸리티 패키지다.

index.ts

import IPerson from "./person/IPerson"
import Person, { makePerson } from "./person/Person"
import Change, { Chance } from 'chance'
import * as R from 'ramda'

const chance = new Chance()

let persons: IPerson[] = R.range(0, 2).map((n: number) => new Person(chance.name(), chance.age()))
console.log(persons)

chance 패키지는 Chance 클래스 하나만 export default 형태로 제공하므로 import 문을 3행처럼 사용한다. 그리고 ramda 패키지는 다양한 기능을 제공하므로 import 문을 4행처럼 사용한다. 또한 change와 ramda는 외부 패키지므로 node_modules 디렉토리에 있다. 따라서 경로에서 ./ 등을 생략한 채 chance, ramda처럼 사용한다.

tsconfig.json 파일 살펴보기

책 참고!

3. 객체와 타입

타입스크립트 기본 타입

  • number
  • boolean
  • string
  • object

any 타입

자스와 호한을 위해 any 타입 제공

let a: any = 0
a = 'hello'
a = true
a = {}

undefined 타입

undefined 타입으로 선언되면 undefined 값만 가질 수 있다.

객체와 인터페이스

인터페이스 선언문

interface 인터페이스 이름 {
  속성 이름[?]: 속성 타입
}

선택 속성 구문

인터페이스를 설계할 때 기본적으로 모든 속성은 반드시 있어야하지만, 선택적인 속성도 만들 수 있다.

interface IPerson2 {
  name: string // 필수
  age: number // 필수
  etc?: boolean // 선택
}
let good1: IPerson2 ={name: 'Jack', age: 32}

익명 인터페이스

interface 키워드도 사용하지 않고, 인터페이스 이름도 없는 인터페이스를 익명인터페이스라고 한다.
주로 함수를 구현할 때 사용한다.

let ai: {
  name: string
  age: number
  etc?: boolean
} = {name: 'Jack', age:32}

function printMe(me: {name: string, age: nuimber, etc?: boolean}) {
  console.log(
    me.etc?
    `${me.name} ${me.age} ${me.etc}`:
    `${me.name} ${me.age}`
  )
}
printMe(ai)

객체와 클래스

생성자

class Person2 {
  constructor(public name: string, public age?: number) {}
}
let jack2: Person2 = new Person2('Jack', 32)
console.log(jack2)

인터페이스

인터페이스는 규약이므로 클래스 몸통에 반드시 인터페이스가 정의하고 있는 속성을 멤버 속성으로 포함해야 한다.

interface IPersron4 {
  name: string,
    age?: number
}

class Person4 implements IPerson4 {
  constructor(public name: string, public age?: number) {}
}
let jack4: IPerson4 = new Person4('Jack', 32)

추상 클래스

abstract class AbstractPerson5 {
  abstract name: string
  constructor(public age?: number) {}
}

name 속성 앞에 abstract가 붙었으므로 new 연산자를 적용해 객체를 만들 수 없다.

클래스 상속

...
class Person5 extends AbstractPerson5 {
  constructor(public name: string, age?: number) {
    super(age)
  }
}

static 속성

class A {
  static initValue = 1
}

let initVal = A.initValue // 1

객체의 비구조화 할당문

인터페이스나 클래스를 사용해 관련 정보를 묶어 새로운 타입으로 표현하는 것을 구조화라고 한다.

export interface IPerson {
  name: string
  age: number
}

비구조화란?

구조화된 데이터를 분해하는 것을 비구조화라고 한다.

let name = jack.name, age = jack.age

비구조화 할당

let {name, age} = jack

잔여 연산자

Rest 연산자를 의미하낟.

전개 연산자

점 3개 연산자가 비구조화 할당문이 아닌 곳에서 사용될 때 이를 전개 연산자라고 한다.

let coord = {...{x:0}, ...{y:0}}

객체의 속성을 모두 전개해 새로운 객체를 만들어 준다.

객체의 타입 변환

타입 변환

let person: object = {name: "Jack", age: 32};
person.name // 오류

person 변수의 타입은 object인데 objcet 타입은 name 속성을 가지지 않으므로 오류 발생. 타입 변환 구문을 이용해서 해결할 수 있다. 일시적으로 name 속성이 있는 타입으로 변환해 person.name 속성값을 얻어온다.

(<{name: string}>person).name

타입 단언

타입스크립트는 타입 변환이 아닌 타입 단언(type assertion)이라는 용어를 사용한다.

타입 단언문은 두 가지 종류가 있다.

(<타입>객체)
(객체 as 타입)

INameable.ts

export default interface INameable {
  name: string
};

type-assertion.ts

import INameable from './INameable'
let obj: object = {name: 'Jack'}

let name1 = (<INameable>obj).name
let name2 = (obj as Inameable).name
console.log(name1, name2) 

서로 형태만 다를 뿐 내용은 같다.

4. 함수와 메서드

함수 선언문

타입스크립트 함수 선언문은 자바스크립트 함수 선언문에서 매개변수와 함수 반환값에 타입 주석을 붙인다.

function 함수이름(매개변수1: 타입1, 매개변수2: 타입2, ...): 반환값 타입 {
  함수 몸통
}

type 키워드로 타입 별칭 만들기

type 새로운 타입 = 기존 타입
type stringNumberFunc = (string, number) => void
let f: stringNumberFunc = function(a: string, b: number): void {}

(string, number) => void 함수 시그니처를 stringNumberFunc라는 이름으로 타입 별칭을 만들었다. 2행에서 타입 주석을 쉽게 붙일 수 있다.

undefined 관련 주의 사항

undefined 타입은 타입스크립트 타입 계층도에서 가장 하위 타입이다. 따라서 매개변수로 넣을 수 있다. 그래서 판별코드를 넣어야 한다.

function getName(o: INameable) {
  return o != undefined ? o.name : 'unknown name'
}

선택적 배개변수

function fn(arg1: string, arg?: number): void {}

선택적 매개변수가 있는 함수 시그니처는 타입 뒤에 물음표를 붙인다.

type OptionalArgFunc = (string, number?) => void

함수 표현식

함수 선언문에서 함수 이름을 제외한 function(a,b) {return a + b}와 같은 코드를 함수 표현식이라고 한다. 함수 표현식은 함수형 언어의 핵심 기능이다. '일등 함수'라는 용어를 시작으로 함수 표현식이 무엇인지 살펴보자.

일등 함수

일등 함수란 함수와 변수를 구분하지 않는다는 의미다.

표현식

표현식이라는 용어는 리터럴, 연산자, 변수, 함수 호츨딩이 복합적으로 구성된 코드 형태다.

익명 함수

let value = (function(a,b) {return a+b;})(1,2) // 3

const 키워드와 함수 표현식

함수 표현식을 담는 변수는 let보다는 const가 맞다.

화살표 함수와 표현식 문

const arrow1 = (a: number, b: number): number => {return a + b} // 실행문 방식 몸통
const arrow2 = (a: number, b: number): number => a + b // 표현 문 방식 몸통

중괄호 사용 여부에 따라 실행문 방식과 표현식 문 방식으로 달라진다.

실행문과 표현식 문

자바스크립트는 ES5는 실행문 지향 언어, ESNext와 타입스크립트는 실행문과 표현식문 동시 지원. 다중 패러다임 언어

프로그래밍 언어에서 실행문은 cpu에 실행되는 코드의미. 실행문은 cpu에서 실행만될 뿐 결과를 알려주지 않는다. 결과를 알려면 return 키워드를 사용해야 한다. 표현식 문은 굳이 return 안써도 알려준다.

변수 대입은 대표적인 실행문

let x
x= 1

x > 0처럼 return 키워드 없이 결과값을 반환하는 실행문이 필요하다. 이를 표현식 문이라 구분해서 부른다.

표현식 문 스타일의 화살표 함수 구현

일등 함수 살펴보기

콜백 함수

고차 함수와 클로저, 그리구 부분 함수

책 참고할 것!

const add2 = (a: number): (number) => number => (b:number) : number => a+b
const result = add(1)(2)
console.log(result) // 3

함수 구현 기법

매개변수 기본값 지정하기

export const makePerson = (name: string, age: number = 10): Person => {
  const person = {name: name, age: age}
  return person
}

객체 생성 시 부분을 생략할 수 있는 타입스크립트 구문

타입스크립트는 매개변수의 이름과 똑같은 이름의 속성을 가진 객체를 만들 수 있다. 이때 속성값 부분을 생략할 수 있다.

const makePerson = (name: string, age: number) => {
  const person = {name, age} // {name: name, age: age}의 단축 표현
}

객체를 반환하는 화살표 함수 만들기

export const makePerson = (name: string, age: number = 10): Person => ({name, age})

컴파일러가 {}를 객체로 해석하게 하려면 다음처럼 객체를 소괄호로 감싸주어야 한다.

ㅅ맥인 키와 값으로 객체 만들기

const makeObject = (key, value) => ({[key]: value})
console.log(makeObject('name', 'Jack')) // {name: 'Jack'}

타입스크립트에서는 {[key]: value} 형ㅌ의 타입을 '색인 가능 타입'이라고 하며, 다음과 같이 key와 value 타입을 명시한다.

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

클래스 메서드

메서드란?

타입스크립트에서 메서드는 function으로 만든 함수 표현식을 담고 있는 속성이다.

export class A {
  value: number = 1
  method: () => void = function(): void {
    console.log(`value: ${this.value}`)
  }
}

...
import {A} from './A'
let a: A = new A
a.method() // value: 1

위에 것은 가독성이 떨어진다. 타입스크립트는 클래스 속성 중 함수 표현식을 담는 속성은 function 키워드를 생략할 수 있게 하는 단축 구문을 제공한다.

export class B {
  constructor(public value: number = 1) {}
  method(): void {
    console.log(`value: ${this.value}`)
  }
}

...
import {B} from './B'
let b: B = new B(2)
b.method() // value: 2

5장. 배열과 튜플

자바스크립트에서 배열은 Array 클래스의 인스턴스다.

let 배열이름 = new Array(배열 길이)

단축구문

let numbers = [1,2,3]

자바스크립트에서 배열은 객체다

배열의 타입

타입스크립트에서 배열의 타입은 아이템 타입[]이다.

문자열과 배열 간 변환

타입스크립트에는 문자 타입이 없고 문자열의 내용 또한 변경할 수 없다. 이러한 특징 때문에 문자열을 가공하려면 먼저 문자열을 배열로 전환해야 한다.

배열의 비구조화 할당

let array: number[] = [1,2,3,4,5]
let [first, second, third, ...rest] = array

for ... in 문

for ... in문은 배열의 인덱스 값을 순회한다.

let names = ['Jack', 'Jane', 'Steve']

for (let index in names) {
  const name = names[index]
}

for ... in 문에 객체를 사용할 때는 객체가 가진 속성을 대상으로 순회한다.

let jack = {name: 'Jack', age: 32}
for (let property in jack)
  console.log(`${proprty}: ${jack[property]}`)

for ... of 문

for ... in문은 배열의 인덱스 값을 대상으로 순회하지만 for ... of 문은 배열의 아이템 값을 대상으로 순회한다.

for (let name of ['Jack', 'Jane', 'Steve'])
  console.log(name)

제네릭 방식 타입

배열을 다루는 함수를 작성할 때는 number[]와 같이 타입이 고정된 함수를 만들기보다는 T[]형태로 배열의 아이템 타입을 한꺼번에 표현하는 것이 편리하다. 타입을 T와 같은 일종의 변수로 취급하는 것을 제네릭 타입이라고 한다.

const arrayLength = (array: T[]): number => array.length

배열의 길이를 얻는 함수다. number[], string[], IPerson[] 등 다양한 아이템 타입을 가지는 배열에 똑같이 적용되게 하려고 한다. 컴파일러가 T의 의미를 알 수 있게, 타입 변수라고 알려줘야 한다.

export const arrayLength = <T>(array: T[]): number => array.length
export const isEmpty = <T>(array: T[]): boolean => arrayLength<T>(array) == 0

제네릭 함수의 타입 추론

const identity = <T>(n: T): T => n
console.log(
  identity<boolean>(true),
  identity(true)
)

제네릭 형태로 구현된 함수는 원칙적으로는 3행처럼 타입 변수를 명시해야 한다. 하지만 타입스크립트는 4행처럼 타입 변수 부분을 생략할 수 있게 한다.

순수 함수와 배열

타입 수정자 readonly

타입스크립트는 순수 함수 구현을 쉽게하도록 readonly 키워드를 제공한다.

function forcePure(array: readonly number []) {
  array.push(1) // error
}

타입스크립트에서 인터페이스, 클래스, 함수의 매개 변수 등은 let 이나 const 없이 선언한다. 따라서 이런 심벌에 const 같은 효과를 주려면 readonly라는 타입 수정자가 필요하다.

전개 연산자와 깊은 복사

전개 연산자를 사용해 배열을 복사하면 깊은 복사를 할 수 있다.

0개의 댓글