03-1 타입스크립트 변수 선언문

타입스크립트 타입

let name:string = '이희재'

let age:number = 29

let isAdult:boolean = true

let a:null = null

let b:undefined = undefined // undefined은 타입이면서 값이고, undefined 값만 가질 수 있다.

// 배열
let arr1:number[] = [1, 2, 3]
let arr2:Array<number> = [1, 2, 3]

let arr3:string[] = ['hello', 'world']
let arr4:Array<string> = ['hello', 'world']

// 튜플
let tuple:[string, number]

tuple = ['hello', 100]
tuple = [10, 100] // Type 'number' is not assignable to type 'string'.

tuple[0].toLowerCase()
tuple[1].toLowerCase() // Property 'toLowerCase' does not exist on type 'number'.

// void: 함수에서 아무것도 반환하지 않을 때 사용
function sayHello():void {
  console.log('hello')
}

// never: error를 반환하거나 영원히 끝나지 않는 함수일 때 사용
function infLoop():never {
  throw new Error()

  while(true) {
	
  }
}

// enum: 자바스크립트에는 없는 타입, 아무것도 정의하지 않으면 0부터 1씩 증가하면서 할당
enum Os1 {
  Window, // 0
  Ios,    // 1
  Android // 2
}

enum Os2 {
  Window = 5,
  Ios,    // 6
  Android // 7
}

// 숫자가 아닐 때는 단방향 맵핑만 된다.
enum Os3 {
  Window = 'win',
  Ios = 'ios',
  Android = 'and'
}

console.log(Os1) // Window: 0, Ios: 1, Android: 2
console.log(Os2[5]) // Window
console.log(Os3.Window) // win

 

any 타입

any 타입은 변수의 값이 타입과 무관하게 어떤 종류의 값도 저장할 수 있다.

let random:any = '랜덤'

random = 100
random = true
random = null
random = undefined
random = [1, 2, 3]

 

undefined 타입

undefined 타입은 타입이면서 값이고, 오직 undefined 값만 가질 수 있다.

변수를 선언하고, 초기화를 하지 않으면 undefined 값을 가지는 것이 아니라 에러가 발생하기 때문에 주의해야 한다.

초기값이 없을 경우, undefined으로 선언하고 null safe(?)를 사용하는 방식으로 해결할 수도 있지만, 변수 초기화를 해주는 것이 좋다.

let num:number
console.log(num) // Variable 'num' is used before being assigned.

let num:undefined
console.log(num) // undefined

 

let과 const 키워드

  • var

var 변수는 재선언(중복 선언)되고, 재할당(업데이트) 된다.

var 변수로 선언할 경우, 유지보수의 어려움이 있어 ESNext에서 var 키워드는 사용하지 말라고 권고한다.

 

  • let

let 변수는 재할당은 가능하지만, 재선언은 불가능하다.

let으로 선언한 변수는 코드에서 그 값이 수시로 변경될 수 있음을 암시한다.

 

  • const

const 변수는 재선언, 재할당 모두 불가능하다.

const로 변수를 선언할 때는 반드시 초깃값을 명시해야 하고, 변숫값이 절대로 바뀌지 않는다.

 

타입 주석

타입 주석(type annotation)은 자바스크립트 변수 선언문을 확장해 타입을 명시하는 것을 의미한다.

let으로 선언한 변수는 값을 변경할 수 있지만, 선언된 타입과 다른 타입의 값으로 바꾸려고 하면 오류가 발생한다.

let 변수 이름: 타입
const 변수 이름: 타입 = 초깃값

let name:string = '이희재'
let age:number = 29
let isAudult:boolean

age =  30
age = '삼십' // Type 'string' is not assignable to type 'number'.

 

타입 추론

타입스크립트는 자바스크립트와 호환성을 위해 타입 주석 부분을 생략할 수 있다.

타입 추론(type inference)은 대입 연산자(=)가 오른쪽 값에 따라 변수의 타입을 지정하는 것을 의미한다.

let name = '이희재' // name을 string로 판단
let age = 29 // age를 number로 판단

 

템플릿 문자열

템플릿 문자열은 변수에 담긴 값을 조합해 문자열을 만들 수 있게 한다.

`${변수 이름}`

let count = 10
let message = 'Your count'
let result = `${message} is ${count}`

console.log(result) // Your count is 10

 


03-2 객체와 인터페이스

object 타입은 인터페이스와 클래스의 상위 타입이다.

object 타입으로 선언된 변수는 속성 이름이 다른 객체를 자유롭게 담을 수 있다.

 

아래 코드는 오류가 나지 않는 정상 코드이다.

속성값이 달라져도 오류가 발생하지 않기 때문에 오류 검출 및 유지보수를 위해서 타입 스크립트의 인터페이스 구문이 생겨났다.

let obj:object = { name: '이희재', age: 29 }
obj = { first: 1, second: 2 }

 

인터페이스 선언문

interface 키워드는 객체의 타입을 정의할 수 있다.

인터페이스를 설계할 때 필수 속성과 선택 속성이 있고, 속성 이름 뒤에 물음표 기호를 붙이면 선택 속성이 된다.

// 속성들이 여러 개일 경우, 쉼표, 세미콜론 또는 줄바꿈을 구분자로 사용한다.
interface IPerson {
  name: string
  age: number
  isAdult?: boolean // 선택 속성
  readonly birthYear: number // 읽기만 가능하고, 값을 변경할 수 없다.
}


let person1:IPerson = {
  name: '이희재',
  age: 29,
  isAdult: true,
  birtyYear: 1995
}
person1.birthYear = 2000 // Cannot assign to 'birthYear' because it is a read-only property.


Type '{ name: string; }' is missing the following properties from type 'IPerson': age, birthYear
let person2:IPerson = {
  name: '이희재'
}


// 'number' does not exist in type 'IPerson'.
let person3:IPerson = {
  name: '이희재',
  age: 29,
  number: '010-1111-1111'
}

 

익명 인터페이스

익명 인터페이스는 interface 키워드도 사용하지 않고, 인터페이스 이름도 없는 인터페이스를 의미한다.

let ai: {
  name: string
  age: number
  etc?: boolean
} = { name: 'Tom', age: 20 }

console.log(ai) // { name: 'Tom', age: 20 }

 


03-3 객체와 클래스

클래스 선언문

타입스크립트는 C++나 자바 같은 객체지향 언어에서 흔히 볼 수 있는 class, private, public, protected, implements, extend와 같은 키워드를 제공한다.

 

타입스크립트에서 생성자 변수의 타입을 선언하지 않으면 에러가 발생한다.

class Person {
  name: string
  age?: number

  // 생성자: 클래스의 속성(name, age)를 선언할 수 있다.
  constructor(name: string, age?: number) {
    this.name = name
    this.age = age
  }
}

let jack:Person = new Person()
jack.name = 'Jack'
jack.age = 32

console.log(jack) // Person { name: 'Jack', age: 32 }

 

접근 제한자

  • public

클래스 내부, 자식 클래스, 클래스 인스턴스 모두 접근이 가능하다. 아무것도 작성하지 않으면 public으로 간주한다.

 

  • protected

클래스 내부, 자식 클래스에서 접근이 가능하다.

 

  • private

클래스 내부에서만 접근이 가능하다.  캡슐화

외부에서 필요하지 않은 메서드(ex. 비밀번호 암호화 로직) 또는 임의로 변경하면 안 되는 데이터(ex. 계산기에서의 값은 계산을 통해서만 변경, 직접 값을 변경 X)를 만들 때 주로 private로 선언한다.

 

 접근 가능성  public  protected  private
 클래스 내부  O  O   O
 자식 클래스 내부  O  O  X
 클래스 인스턴스  O  X  X

 

class Car {
  public color: string // Car { color: 'black' }
  protected color: string // Property 'color' is protected and only accessible within class 'Car' and its subclasses.
  private color: string // Property 'color' is private and only accessible within class 'Car'.
  
  constructor(color: string) {
    this.color = color
  }
}

let bmw:Car = new Car()
bmw.color = 'black'

console.log(bmw)

 

인터페이스 구현

implements 키워드는 클래스가 인터페이스를 구현할 때 사용한다.

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

 

인터페이스가 정의하고 있는 속성을 모두 포함하고 있지 않으면(name 또는 age가 하나라도 없으면) 해당 에러가 발생한다.

Class 'User' incorrectly implements interface 'IUser'.   Property 'age' is missing in type 'User' but required in type 'IUser'.

interface IUser {
  name: string
  age: number
}

class User implements IUser {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

let tom:IUser = new User('Tom')

console.log(tom) // User { name: 'Tom', age: undefined }

 

추상 클래스

abstract 키워드는 추상 클래스를 정의할 때 사용하고, 직접 인스턴스를 생성할 수 없고, 상속만을 위해 사용된다.

즉, 프로퍼티나 메서드의 이름만 선언해 주고(몸체가 구현되지 않았다.), 구체적인 기능은 상속받은 쪽에서 작성할 때 사용한다.

 

클래스의 상속

extends 키워드를 사용해서 상속 클래스를 만든다.

super 키워드는 부모 클래스의 생성자를 호출할 수 있다.

abstract class Car {
  color: string
  wheel: number

  constructor(color: string, wheel: number) {
    this.color = color
    this.wheel = wheel
  }

  abstract changeColor(color: string): string
  abstract wheelCount(wheel: number): number
}

class kia extends Car {
  constructor(color: string, wheel: number) {
    super(color, wheel)
  }

  changeColor(): string {
    return 'change color'
  }

  wheelCount(): number {
    return 4
  }
}

const k5: kia = new kia('white', 4)

console.log(k5) // kia { color: 'white', wheel: 4 }
console.log(k5.changeColor()) // change color
console.log(k5.wheelCount()) // 4

 

static 속성

static 키워드는 다른 객체지향 언어처럼 클래스 정적 메서드를 정의한다. (정적인 속성을 가질 수 있다.)

정적 메서드는 인스턴스가 아닌 클래스 이름으로 호출하고, 클래스의 인터페이스를 생성하지 않아도 호출할 수 있다.

정적 메소드는 this를 사용할 수 없다.

class A {
  static first: number = 1
  second: number = 2
}

let first = A.first
let second = A.second // Property 'second' does not exist on type 'typeof A'.

console.log(first) // 1

 


03-4 객체의 비구조화 할당문

구조화

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

// 구조화
export interface IPerson {
  name: string
  age: number
}

export interface ICompany {
  name: string
  age: number
}

let jack:IPerson = {
  name: 'Jack',
  age: 29
}

let naver:ICompany = {
  name: 'Naver',
  age: 10
}

console.log(jack) // { name: 'Jack', age: 29 }
console.log(naver) // { name: 'Naver', age: 10 }

 

비구조화

구조화된 데이터는 어떤 시점에서 일부만 사용해야 할 때가 있다.

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

// 비구조화
let jack_name = jack.name
let jack_age = jack.age

console.log(jack_name, jack_age) // Jack 29

// 비구조화 할당: 중괄호로 묶어 각각의 초깃값으로 할당받는다.
let {name, age} = naver

console.log(name, age) // Naver 10

 

잔여 연산자

... 잔여 연산자를 사용하면 country와 city를 제외한 나머지 속성들이 담긴다.

let address:any = {
  country: 'Korea',
  city: 'Seoul',
  address1: '양천구',
  address2: '목동서로',
  address3: '221'
}

const {country, city, ...detail} = address

console.log(country) // 대한민국
console.log(city) // 서울
console.log(detail) // { address1: '양천구', address2: '목동서로', address3: '221' }

 

전개 연산자

... 전개 연산자를 사용하면 객체의 속성을 모두 전개해 새로운 객체로 만들어 준다.

let name = {name: 'Jack'}
let age = {age: 30}

const userInfo = {...name, ...age}

console.log(userInfo) // { name: 'Jack', age: 30 }

 


03-5 객체의 타입 변환

타입 변환(type conversion)은 특정 타입의 변숫값을 다른 타입의 값으로 변환할 수 있는 기능이다.

let person: object = {name: 'Jack', age: 32};

person.name // Property 'name' does not exist on type 'object'.

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

 

타입 단언

타입 단언(type assertion)은 컴파일러에게 타입을 확실히 알려주기 위해 사용하고,

강제로 타입을 지정하는 것이기 때문에 타입을 만족하지 않더라도 오류를 무시한다.

 

타입을 변경하기 위해서 as 타입 단언을 사용하지 않는 것은 좋지 않지만, 원시 타입에서 원시 타입으로 변경하거나 response(then, catch)에서 받은 값의 데이터 타입을 모를 때, as 타입 단언을 통해 데이터 타입을 주입시키는 용도로도 사용할 수 있다.

 

콜론(:)을 사용해서 타입을 지정하는 것은 타입 선언이고, 타입 선언을 이용하면 할당되는 값이 선언된 타입을 만족하는지 검사한다.

주의: react의 JSX에서 <> 꺽쇠 괄호를 사용하기 때문에 리액트에서는 as를 사용한다.

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

export default interface INameable {
  name: string
}

let obj:object = {name: 'Jack'}

let name1 = (<INameable>obj).name
let name2 = (obj as INameable).name

console.log(typeof obj, obj) // object { name: 'Jack' }
console.log(typeof name1, name1) // string Jack
console.log(typeof name2, name2) // string Jack

 

 

참고자료

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

+ Recent posts