자바스크립트(JavaScript)를 기반으로 정적 타입 문법을 추가한 프로그래밍 언어입니다.
자바스크립트는 동적 타입의 인터프리터 언어로 런타임 환경에서 오류를 발견합니다. 이에 반해 타입스크립트는 정적타입의 컴파일 언어로 타입스크립트 컴파일러 또는 바벨을 통해 자바스크립트 코드로 변환되어 코드 작성 단계에서 타입을 체크해 오류를 확인할 수 있습니다.
npm install -g typescript
타입스크립트를 사용하기 위해서는 타입스크립트 컴파일러를 설치하고 .ts 파일을 만들어 사용하면 됩니다.
브라우저는 타입스크립트 코드를 그대로 해석할 수 없기 때문에 배포시 반드시 코드를 자바스크립트로 변환해야합니다. .ts파일을 .js 파일로 변환하려면 에디터에서 터미널을 열어 'tsc-w'를 입력하면 됩니다.
타입스크립트의 컴파일을 세부적으로 설정하기 위해 tsconfig 파일을 생성합니다.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
}
}
여기에서 파일들을 변환할 때 어떻게 변환할 것인지 세부적으로 설정합니다. 예를 들어'target'은 타입스크립트파일을 어떤 버전의 자바스크립트로 바꿔줄지 정하는 부분이고, 'module'은 자바스크립트 파일간 import 문법을 구현할 때 어떤 문법을 쓸지 정하는 부분입니다.
let name :string = 'coco'
변수를 만들 때 타입 지정이 가능합니다. 변수명 : 타입명의 형식으로 작성합니다. 타입으로는 string, number, boolean, bigint, null, undefined,[], {} 등을 쓸 수 있습니다.
name = 606;
타입을 지정한 뒤 위와 같이 의도치 않게 타입이 변경될 경우,
'number' 형식은 'string' 형식에 할당할 수 없습니다.
에러메시지가 뜹니다.
let member:string[] = ['mark', 'haechan']
array 자료 안에 들어갈 타입은 타입명[] 으로 지정합니다. member 배열의 경우 array 안에 string 타입만이 들어올 수 있습니다.
let cat: {name: string} = {name: 'nana'}
object 자료안에 들어갈 타입은 만들 object와 똑같은 모습으로 지정하면 됩니다.
변수의 type이 number 혹은 string일 경우에는 어떻게 할까요?
let user: number | string = 'MarkLee'
user = 802
변수에 여러가지 타입의 데이터가 들어올 수 있다면 | 기호를 통해 or 연산자를 표현할 수 있습니다. 타입 두 개 이상을 합쳐서 새로운 타입을 만드는 것을 union type이라고 합니다.
array, object 자료에서도 마찬가지입니다.
let numbers: (number | string)[] = [1, '2', 3]
이렇게 | 기호를 통해 number 또는 string으로 이루어진 배열의 타입을 지정할 수 있습니다.
let members: number | string[] = ['1', '2']
하지만 소괄호를 하지 않을 경우 number 혹은 string이 들어간 배열로 타입이 지정되기 때문에 주의해야 합니다.
function add (x:number) :number {
return x + 2
}
add(1) // 가능
add() // 불가능
함수로 들어오는 파라미터의 타입은 파라미터 옆에 적습니다. 함수가 실행된 후 리턴되는 값의 타입은 함수명() 우측에 적습니다.
return할 자료가 없는 함수의 타입으로는 void를 사용하고, 파라미터에 타입을 지정하면 필수 파라미터가 됩니다.
function add (x?:number) :number {
return x + 2
}
add(1) // 가능
add() // 가능
파라미터가 옵션일 경우, 파라미터 오른쪽에 물음표를 붙이면 됩니다.
function sayHi (name: string | undefined) :void {
if(typeof name === 'string') {
console.log('안녕하세요' + name)
} else {
console.log('이름이 없습니다')
}
}
함수에서도 마찬가지로 union type을 사용할 수 있습니다. 하지만 이 경우 type narrowing을 통해 타입을 세분화해야합니다. type narrowing은 if문 등으로 타입을 하나로 정해주는 것을 뜻합니다.
sayHi의 함수는 if문과 typeof 키워드로 현재 파라미터의 타입을 검사해 string일 경우와 undefined일 경우를 세분화합니다. 타입이 확실하지 않아 생기는 에러를 방지하기 위해서 위와 같이 코드를 작성해야 합니다.
function numArr (x: string | number) {
let array: number[] = [];
array[0] = x as number;
}
'변수명 as 타입명'의 형식으로 type assertion을 통해 타입을 지정할 수도 있습니다. assert는 '주장하다'는 뜻으로, numArr 함수는 x의 타입을 number라고 주장합니다. 다만 as 키워드는 narrowing 할 때 그리고 어떤 타입이 인자로 들어올지 확실히 알 때 사용해야합니다.
만일 numArr('123')이라고 인자로 string 타입을 넣어도 오류가 발생하지 않습니다. type assertion은 주장만 할 뿐 실제로 타입을 바꿔주는건 아니기 때문입니다. 따라서 as 문법은 왜 타입에러가 나는지 정말 모르겠는 상황에 임시로 사용하거나, 어떤 타입이 들어올지 정말 확실하게 알고 있는데 컴파일러 에러가 방해할 때 사용해야 합니다.
type MyType = string | number
let name:MyType = 'coco'
type 키워드를 이용해 타입을 변수처럼 사용할 수 있습니다. 타입 작문은 일반 변수와 구분하기 위해 보통 대문자로 시작합니다.
const hometown = {region : 'Seoul'}
hometown.region = 'Busan'
console.log(hometown.region) //Busan
object 자료의 경우, const 변수로 재할당은 불가하지만 object 속성은 수정할 수 있습니다. object 속성을 바뀌지 않게 막고 싶을 때는 타입스크립트 문법을 사용하면 됩니다.
type Boy = {readonly name : string}
const boy:Boy = {name: 'Mark'}
boy.name = 'Haechan'
//읽기 전용 속성이므로 'name'에 할당할 수 없습니다.
특정 속성을 변경불가능하게 하고 싶을 경우, readonly 키워드를 속성 왼쪽에 붙이면 됩니다.
type Obj = {
color? : string,
size: number,
readonly position: number[]
}
속성 일부가 선택 사항일 때는 함수와 마찬가지로 물음표연산자를 추가하면 됩니다.
type MyFunc = (x: string) => number
let myFunc :MyFunc = function (x) {
return 10
}
함수에 type alias를 지정하고 싶을 경우, 화살표 함수를 이용해 파라미터와 리턴값의 타입을 지정합니다. 그리고 함수표현식을 통해 변수에 타입을 지정하면 됩니다.
type UserInfo = {
age: number,
add: (a: number) => number,
changeName : () => void
}
let userInfo : UserInfo = {
age: 26,
add(a) {
return a+1
},
changeName : () => {}
}
객체 안에 함수가 있는 경우에도 위와 같이 타입을 지정할 수 있습니다.
string, number 등 뿐 아니라 일반 문자열도 타입이 될 수 있습니다.
let name : 'kim' | 'lee';
name = 'kim'
name = 'park'
//"park"' 형식은 '"kim" | "lee"' 형식에 할당할 수 없습니다.
name이라는 변수는 kim 또는 lee라는 문자열만 할당할 수 있습니다. 이렇게 엄격하게 특정 글자나 숫자만 가질 수 있게 제한을 두는 타입을 literal type 이라고 부릅니다.
const user = {
name : 'kim'
}
function callUser(a : 'kim') {
}
callUser(user.name)
//Argument of type 'string' is not assignable to parameter of type '"kim"'.
user.name은 'kim'이지만 callUser 함수를 호출할 때 파라미터로 user.name을 넣으면 오류가 납니다. 왜일까요?
user.name의 타입은 string 타입이지 'kim'타입이 아니기 때문입니다.
'a : 'kim'는 ''kim이라는 자료만 들어올 수 있습니다'라는 뜻이 아니라, 'kim이라는 타입만 들어올 수 있습니다'라는 뜻입니다.
const user = {
name : 'kim'
} as const
이러한 문제를 해결하기 위해서는 object 뒤에 as const를 붙이면 됩니다. 이렇게 하면 user의 타입을 object의 value로 바꾸고, object 안에 있는 모든 속성을 readonly로 바꿉니다.
class Person {
name: string;
age: number;
constructor (name:string, age:number) {
this.name = name;
this.age = age;
}
sayHi() {
console.log('Hi ' + this.name)
}
}
const hyuk = new Person('Lee', 22)
console.log(hyuk) //Person {name: 'Lee', age: 22}
hyuk.sayHi() //Hi Lee
타입스크립트는 자바스크립트와 다르게 필드값을 미리 정의해야합니다. constructor 함수에는 함수와 마찬가지로 파라미터에 타입을 지정해야 합니다.
interface Student {
name: string,
}
interface Teacher extends Student {
age: number
}
let student:Student = {name: 'Lee'}
let teacher:Teacher = {name: 'Park', age: 32}
interface 문법을 통해 object 자료형의 타입을 쉽게 지정할 수 있습니다. type alias와 마찬가지로 대문자로 작명하고 {}안에 타입을 명시합니다. 중복사항이 있을 경우 class처럼 extends도 가능합니다.
interface는 type alias와 달리 타입 이름 중복선언이 가능하며, 중복시 extends와 동일하게 작동합니다.
interface Animal {
name :string
}
interface Animal {
legs :number
}
이 경우 Animal 타입은 name, legs 속성을 가집니다.
interface Animal {
name :string
}
interface Dog extends Animal {
name :number
}
//'Dog' 인터페이스가 'Animal' 인터페이스를 잘못 확장합니다.
//'name' 속성의 형식이 호환되지 않습니다.
//'number' 형식은 'string' 형식에 할당할 수 없습니다.
하지만 extends할 때 object 안의 속성이 중복될 경우 오류가 발생합니다.
코드를 작성하다보면 변수나 파라미터에 undefined나 null이 들어오는 경우도 생깁니다. 이 때 if문을 undefined일 경우, string일 경우 두 개 작성하지 않고도 type narrowing을 할 수 있는 방법이 있습니다.
function myFunc (a :string | undefined) {
if( a && typeof a === 'string') {
console.log(a)
}
}
위와 같이 && 연산자를 이용해서 if문을 생략할 수 있습니다.
alert(1 && 2 && null && 3); //null
alert(1 && 2 && 3); //3
&&연산자는 value가 false일 경우 멈추고 해당 value값을 리턴합니다. value가 모두 true일 때만 마지막 value의 값을 리턴합니다.
따라서 myFunc의 경우 a가 undefined라면 undefined가 반환되어 if문이 실행되지 않고, string 타입일 때만 실행됩니다.
type Dog = { swim: string }
type Bird = { fly: string }
function animal (a: Dog | Bird) {
if('swim' in a) {
return a.swim
}
return a.fly
}
animal 함수는 Dog 타입 또는 Bird 타입을 가집니다. 이때 타입들이 서로 다른 유니크한 속성들은 가지고 있다면 in 연산자를 통해 narrowing을 할 수 있습니다. if('swim' in a)는 a가 속성으로 swim을 가지고 있니? 라는 뜻입니다.
let today = new Date()
if(today instanceof Date) {
console.log('It is true')
}
어떤 class로부터 new 키워드로 생성된 object의 경우, instanceof 키워드를 통해 부모 class가 누구인지 검사할 수 있습니다.
type Chan = {
position : 'vocal'
age : number
}
type Mark = {
position : 'rap'
age : number
}
function member (x : Chan | Mark ) {
if(x.position === 'vocal') {
console.log(`It's type if Chan`)
} else {
console.log(`It's type if Mark`)
}
}
member({position: 'rap', age: 24}) //It's type if Mark
만약 object에 같으 속성을 가지고 있는 경우 어떻게 타입을 구분할 수 있을까요? literal type을 통해 속성에 저장된 값과 일치하는 경우를 찾아 narrowing을 할 수 있습니다.
함수에 unknown, any, union 등 불확실한 타입을 입력하면 출력되는 값도 unknown, any, union이기 때문에 일어나는 문제들이 많습니다.
function unknownArr (x: unknown[]) {
return x[0]
}
const a = unknownArr([1,2,7])
console.log(a + 1) //'a'은(는) 'unknown' 형식입니다.
예를 들어 이렇게 파라미터에 unknown[] 타입을 지정한 경우, 리턴 타입 또한 unknow이기 때문에 오류가 발생합니다.
이 경우 타입을 파라미터 함수에 미리 입력해 원하는 곳에 가변적으로 타입을 지정할 수 있습니다. 이 방법을 generic 이라고 부릅니다.
function firstName<Type> (x: Type[]) :Type {
return x[0]
}
const a = firstName<number>([1,2,7])
const b = firstName<string>(['lee', 'park'])
console.log(a) //1
console.log(b) //lee
함수에 <>를 열어 타입파라미터를 입력할 수 있습니다. 괄호 안에는 타입만 입력할 수 있습니다.
function addNum<Type> (x: Type) {
return x -1
}
const a = addNum<number>(100)
//산술 연산의 왼쪽은 'any', 'number', 'bigint' 또는 열거형 형식이어야 합니다.
하지만 이 경우 Type에 number 외에도 다른 타입이 지정될 수 있어 오류가 발생합니다.
이럴 땐 Type에 넣을 수 있는 타입을 미리 제한해 문제를 해결할 수 있습니다.
function addNum<Type extends number> (x: Type) {
return x -1
}
const a = addNum<number>(100)
console.log(a)//99
extends 문법을 통해 타입을 제한할 수 있습니다. interface 문법에서 쓰는 extends와는 다릅니다. 그 extends는 복사의 개념으로 사용되었습니다. 하지만 여기에선 number와 비슷한 속성을 가지고 있는지를 확인하는 if문의 역할을 합니다.