타입스크립트의 목적
런타임에러를 방지하기 위해, 코드를 실행하기 전에 에러를 잡아내기 위해 사용함
타입스크립트 = Stronly Typed Programming Language
그러나 브라우저는 타입스크립트가 아닌 자바스크립트를 이해하기 떄문에 자바스크립트로 코드를 변환해줘야 함
참고로 Node.js 는 JS와 TS 모두 이해할 수 있음
일단 타입스크립트 코드를 작성해서 그 코드를 컴파일하면 보호장치 없는 자바스크립트가 되지만, 만약 타입스크립트 코드에 에러가 있으면 그 코드는 자바스크립트 코드로 컴파일되지 않음
이런 보호 장치는 유저가 코드를 실행하는 런타임에 발생하는 것이 아님
자바스크립트는 타입 추론을 하기 떄문에 문제가 됨
#2.1 Implicit Types vs Explicit Types (06:51)
자바스크립트는 그 변수가 어떤 타입인지 지정하지 않아도 됨
타입스크립트는 변수 타입을 명시해야 함
타입스크립트는 두 가지 접근 방식을 결합함 \
데이터와 변수의 타입을 명시적으로 정의할 수도 있고
아니면 그냥 Javascript처럼 변수만 생성하고 넘어가도 됨
여기서 좋은 점은 Typescript가 타입을 추론해준다는 것임
아래와 같이 변수와 타입 함께 선언 가능
let a = "hello"
let b : boolean = false
let c : number[] = []
그러나 코드의 가독성을 위해 타입을 작성하지 않고 Typescript가 타입을 추론할 수 있게 두는 것이 더 나음
#2.2 Types of TS part 1
optional type
name은 필수 parameter이고, age는 선택적으로 parameter일 때 아래와 같이 작성
const player: {
name: string,
age?: number // 물음표 붙이면 optional parameter 됨
} {
name=: "nico"
}
비슷한 코드 여러 번 작성해야 할 때 Alias (별칭) 타입 생성하여 코드 작성을 줄일 수 있음
type Age = number
type Player = {
name: string,
age?: Age
}
const playerA : Player {
name=: "A"
}
const playerB : Player {
name=: "B",
age: 12
}
함수가 리턴하는 타입 지정
function playerMaker(name:string) : Player {
// string 타입의 name을 argument로 받고 Player 타입의 객체를 return함
return { // object with name return
name: name // name = name일 경우 name 으로 생략 가능
}
}
//화살표 함수 ver
const playerMaker = (name:string) : Player => ({name})
2.3 Types of TS part Two
원하면 readonly 속성을 타입에 추가할 수도 있음
ㄴ 요소들을 '읽기 전용'으로 만들 수 있음
type Player = {
readonly name: string,
age?: number
}
// Player type 오브젝트의 name을 수정할 수 없게 됨
const numbers: readonly number[] = [1,2,3]
// numbers.push(4) not working
Tuple : array를 생성하는데 최소한의 길이를 가져야하고 특정 위치에 특정 타입이 있어야 함
const player: [string, number, boolean] = ["nico", 1, true]
3개의 요소가 있어야 하고 위 순서대로의 타입이어야 함
그 외의 타입들
let a : undefined = undefined
let b : null = null
// 참고로 optional type은 선언한 type | undefined 로 설정됨
any : 비어있는 값들을 쓰면 기본값이 any가 됨
=> any는 타입스크립트를 빠져나오고 싶을 때 씀 (타입스크립트의 보호 장치 제거)
=> any는 아무 타입이나 될 수 있음
any 사용을 막기 위해 추가할 수 있는 몇가지 규칙이 있음
그럼에도 불구하고 any는 Typescript에 존재하고 우린 그것을 사용하는 방법을 배울 필요가 있음
2.4 Types of TS part3
Typescript에만 존재하는 타입
unknown 어떤 타입인지 모르는 변수
unknown 타입은 어떤 작업을 할 때 이 변수의 타입을 먼저 확인해야 하는 방식으로 보호받게 됨
let a: unknown;
if (typeof a === number){
let b = a + 1
}
void: 아무것도 return하지 않는 함수를 대상으로 사용함
function hello() {
console.log('x')
}
// 이 함수는 아무것도 return하지 않으므로 void 함수임
// 따로 타입 지정하지 않아도 자동으로 인식됨
never: 함수가 절대 return하지 않을 때 발생함, 예를 들어 함수에서 예외가 발생할 때
function hello():never{
throw new Error("xxx")
} // 리턴하지 않고 오류를 발생하는 함수에서 never을 사용함
또한 never은 타입이 두가지일 수도 있는 상황에 발생할 수 있음
function hello(name:string|number){
if (typeof name === "string") {
name // tyep = string
} else if (typeof name === "number") {
name // type = number
} else {
name // type = never
// 만약 타입이 알맞게 지정된다면 이 코드는 절대 실행되지 않을 것임
}
}
#3.0 Call Signatures
Call Signature: 함수 위에 마우스를 올렸을 때 보게 되는 것 (함수의 인자 타입과 반환 타입 알려줌)
// 함수의 call signature 생성
type Add = (a: number. b: number) => number;
// 타입 Add의 함수 add 생성
const add: Add = (a, b) => a + b
#3.1 Overloading
Overloading은 함수가 서로 다른 여러 개의 call signature을 가지고 있을 때 발생됨
// call signature 길게 작성
type Add = {
(a: number, b: number) : number
(a: number, b: string) : number
}
const add: Add = (a,b) => {
if (type of b === "string") return a
reutrn a + b // if type of b === number
}
// 이는 call signature 수가 적을 때만 사용 가능한 나쁜 코드임
// 라우터를 사용법
// #1 객체 형식으로 전달
Router.push({
path: "/home",
state: 1
})
// #2 string 형식으로 전달
.push("/home")
// 위를 typescript로 작성하면 아래와 같다
type Config = {
path: string,
state: objec
}
type Push = {
(path:string):void
(config: Config):void
}
const push:Push = (config) => {
if (typeof config === "string") { console.log(config) }
else {
console.log(config.path)
}
}
type Add = {
// parmeter 개수 다름
(a: number, b: number) : number
(a: number, b: number, c: number) : number
}
// c parameter은 선택적이므로 optional parmeter로 지정해줌
const add:Add = (a,b,c?:number) => {
if (c) return a + b + c // c가 있으면
return a + b // c가 없으면
}
#3.2 Polymorphism
다형성: 여러가지 다른 형태
배열을 받고 그 배열의 결과를 print해주는 함수 만들 것임
concrete type: 우리가 이미 아는 타입들 (ex. number, string, unknown ... )
generic type: 타입의 placeholder 같은 것, concrete 타입 대신 쓸 수 있음
제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법
ㄴ 타입스크립트는 그 타입을 유추할 것임
generic type을 이용하는 이유: call signature을 작성할 때 여기 들어올 확실한 타입을 모를 때 generic 을 사용함
generic type 사용법
1. 먼저 타입스크립트에 generic을 사용하고 싶다고 알려줘야 함
generic을 사용하면 타입스크립트가 알아낸 타입으로 Call Signature 타입을 바꿔줌
type SuperPrint = {
<Potato>(arr: Potato[]): Potato // 인자로 받은 타입 중 하나를 리턴함
}
const superPrint: SuperPrint = (arr) => arr[0]
a = superPrint([1,2,true,false]) // 정상 작동
// call signature 가 아래와 같이 자동 대체 됨
// (arr: (number|boolean)) : void
// typeof a === number
#3.3 Generics Recap
제너릭을 보굿로 지정하고 싶을 때
type SuperPrint = {
<T,M>(a: T[], b: M): T
}
const superPrint: SuperPrint = (arr) => arr[0]
a = superPrint([1,2,true,false], "x") // 정상 작동
#3.4 Conclusions
Call Signature 외에서 Generic을 사용하는 경우
(1) 함수
function superPrint<V>(a: V[]) { // 제너릭 선언 후 인자에 제너릭 지정
return a[0]
}
(2) 제너릭을 사용해 타입을 생성할 수도 있고, 어떤 경우는 타입을 확장할 수 있음
type Player<E> = { // 제너릭 E 선언
name: string
extraInfo:E
}
type NicoExtra = {
favFood: string
}
type NicoPlayer = Player<NicoExtra>
const nico: NicoPlayer = {
name= "nico",
extraInfo: {
favFood: "kimchi"
}
}
const lynn: Payer<null> = {
name: "lynn",
extraInfo: null
}
Array를 입력했을 때 자동완성으로 뜨는 것을 확인하면 <T> 제너릭을 받는 것을 확인할 수 있음
type arrNumbers = Array<number>
let a:A = [1,2,3,4]
function printAllNumbers(arr: number[]){} // 이것도 되고
function printAllNumbers(arr: Array<number>){} // 이것도 됨
useState<number>() // ReactJS에서도 유용하게 사용됨
#4.0 Class
class Player {
constructor( // 이 안에 파라미터들을 써주기만 하면, TypeScript가 알아서 Constructor 함수 만들어줌
private firstName: string,
private lastName: string,
public nickname: string
) {}
}
const nico = new Player("nico","las","니꼬");
// nico.firstName ... not working
추상클래스
- 다른 클래스가 상속받을 수 있는 클래스
- 추상 클래스는 직접 새로운 인스턴스를 만들 수는 없음
abstract class User {
constructor( // 이 안에 파라미터들을 써주기만 하면, TypeScript가 알아서 Constructor 함수 만들어줌
private firstName: string,
private lastName: string,
public nickname: string
) {}
getFullName(){
return `${this.firstName} ${this.lastName}`
}
}
class Player extends User {
}
const nico = new Player("nico","las","니꼬");
nico.getFullName();
// nico.firstName ... not working
추상메소드
추상 메소드를 만들려면, 메소드를 클래스 안에서 구현하지 않으면 됨 ( 코드가 없는 함수 )
*메소드: 클래스 안에 있는 함수
추상클래스 안에서도 추상메소드를 만들 순 있는데, 대신 메소드를 구현해서는 안되고 메소드의 call signature만 적어둬야 함
추상메소드는 추상 클래스를 상속받는 모든 것들이 구현해야하는 메소드를 의미함
기본적으로 프로퍼티는 public임
만약 너가 클래스 안 프로퍼티를 private으로 만든다면, 너가 그 클래스를 상속하였을지라도 너는 그 프로퍼티에 접근할 수 없음 (당연히 인스턴스 밖에서 접근할 수 없고, 다른 자식 클래스에서도 접근할 수 없음)
만약 프로퍼티가 외부로부터 보호되지만 다른 자식 클래스에서 사용되기를 원한다면 protected를 쓰면 됨
abstract class User {
constructor( // 이 안에 파라미터들을 써주기만 하면, TypeScript가 알아서 Constructor 함수 만들어줌
protected firstName: string,
protected lastName: string,
protected nickname: string
) {}
abstract getNickName():void
getFullName(){
return `${this.firstName} ${this.lastName}`
}
}
class Player extends User { // getNickName 추상메소드 구현해야 함
getNickName(){
console.log(this.nickname) // protected 프로퍼티이기 때문에 사용 가능
}
}
const nico = new Player("nico","las","니꼬");
nico.getFullName();
4.1 Recap
딕셔너리 만들기
type Words = {
[key:string]: string
// Words 타입이 위와 같은 타입만을 가지는 오브젝트임을 말해준 것
// 제한된 양의 property 혹은 key를 가지는 타입을 정의해주는 방법
// ex
// [key:number]: string
// let dict : Words = {
// 1: "food",
// 2: "animal"
//}
}
class Dict {
private words: Words
constructor(){
this.words = {}
}
add(word: Word){
if (this.words[word.term] === undefined){
this.words[word.term] = word.def;
}
}
def(term: string){
retrun 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.def("kimchi")