
원래 JS밖에 안 써봤었기 때문에 TS로 프로젝트를 하게 돼서 JS에는 없지만 TS에만 있는 기능들을 정리해 보았다.
TypeScript를 사용하는 이유
선택적으로 타입을 정의
type Person = { // 사용자 정의 타입 생성
name : string,
age? : number // age 는 선택적 타입 이므로 type 이 undefined || number 이다.
}
→ 둘 다 사용자 정의 타입을 선언할 때 사용
interface는 주로 객체의 구조를 정의하는 데 사용됩니다. 객체의 속성과 타입을 선언하는 데에 강점이 있습니다.interface는 상속(extends)을 통해 다른 interface를 확장할 수 있습니다. 즉, 여러 개의 interface를 조합하여 하나의 인터페이스를 만들 수 있습니다.interface는 동일한 이름의 여러 개를 선언 해도 자동으로 합쳐집니다. 이는 같은 이름의 인터페이스를 여러 곳에서 확장하여 사용할 수 있다는 장점을 제공합니다.interface는 주로 클래스와의 상호작용에서 사용됩니다.// Interface로 정의
interface StudentInfo {
name: string;
age?: number;
curriculum?: number;
}
// Interface 상속
interface DetailedStudentInfo extends StudentInfo {
grade: number;
}
type은 객체 뿐만 아니라 유니온, 인터섹션 등 다양한 형태의 타입을 정의할 수 있습니다. 즉, 객체뿐만 아니라 원시 타입, 유니온 타입, 튜플 등 다양한 타입을 정의할 수 있습니다.type은 interface와 달리 상속이나 확장 기능이 없습니다. 하나의 type 정의가 다른 type에 기반하여 확장되지 않습니다.type은 조금 더 복잡한 타입을 만들 수 있도록 허용합니다. 조건부 타입과 같은 기능을 이용하여 더 다양한 유형의 타입을 만들 수 있습니다.type은 객체뿐만 아니라 리터럴 타입 등 다양한 상황에서 사용됩니다.// Type으로 정의
type StudentInfo = {
name: string;
age?: number;
curriculum?: number;
};
// 복잡한 타입 정의
type Grade = 1 | 2 | 3 | 4 | 5;
type DetailedStudentInfo = StudentInfo & { grade: Grade };
interface User {
name: string
}
// interface Player extends User{
// }
type Player = User & {}; // %(and) 연산자로 User 꺼를 가져오는 거.
// type 으로 상속관계를 정의하는 법.
const haram: Player = {
name: "haramjeong"
}
implements ⇒ typescript 에서의 extends
interface 의 상속에선 property 를 private, protected 로 만들 수 없다. → public 만 가능.
주로 객체의 구조 정의 → 인터페이스 → 알아서 속성 합쳐줌.
다양한 타입을 정의하거나 복잡한 조건 활용 → 타입 → 계속 새로 갱신됨.
대부분 상호 교체 가능.
// 얘를 추상클래스가 아닌 인터페이스로 상속받을 수 있게 한다면 파일 크기를 매우 줄일 수 있다
// -> js 에선 추상클래스가 없으므로 그대로 클래스를 가져오지만 인터페이스는 아예 가볍게 사라진다.
interface User{
firstName: string,
lastName: string,
fullName(): string,
sayHi(name:string): string
}
interface Human{
health: number
}
class Player implements User,Human{
constructor(
public firstName: string,
public lastName: string,
public health: number
) {}
fullName(){
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string){
return `hi ${name}. my name is ${this.fullName()}`;
}
}
(parameter : input type) : return type ⇒ (return value)
const studentMaker = (name : Name) : StudentInfo => ({name})
// 일반적인 함수 선언
function add(a: number, b: number): number {
return a + b;
}
// 람다 함수 (화살표 함수) 선언
const addLambda = (a: number, b: number): number => {
return a + b;
};
// 람다 함수 축약 형태 (단일 표현식의 경우 return 생략 가능)
const addLambdaShort = (a: number, b: number): number => a + b;
수정 불가능한 속성으로 만들어 줌
const player : readonly string[] = ["1","2"]
player[1] = 3 // error !!
서로 다른 type을 가진 요소들이 array의 형태로 저장될 수 있음.
const player : [string, number, boolean] = ["haram", 2, false]
player[0] = 9 // error !! -> type : string !!!! not change to int
Type
undefined, null, any
let a = [] // any[]
void, never, unknown
let a : unknown
if (typeof a === "number"){
let b = a + 1
}
function hello():never{
throw new Error("xxx") // error 발생
}
function hello(name:string|number){
if (typeof name === "string"){
name // name 은 string
} else if (typeof name === "number"){
name // name 은 number
} else {
name // name 은 never !!!!
}
}
function add1(a:number, b:number){
// call signature : function add1(a: number, b: number): number
return a + b;
}
const add2 = (a:number, b:number) : number => a+b
// call signature : const add2: (a: number, b: number) => number
// Call Signature 생성
// "add" 라는 call signature 는 number 타입 2개를 입력받고, number 를 리턴함.
type Add = **(a:number, b:number) => number** // call signation
// Call Signature 를 활용한 함수 생성.
const add:Add = (a,b) => a + b // type을 정해주지 않아도 됨.
type Add = {
(a:number,b:number) : void // type signature 1
(a:number,b:string) : void // type signature 2
}
const add : Add = (a,b) => {
if (typeof b == "number") console.log(a+b)
else console.log("can not add number and string")
}
add(1,3) // 4
add(1,"2") // can not add number and string
type Add = {
(a : number, b : number) : number
(a : number, b : number, c : number) : number
}
const add: Add = (a, b, **c?:number**) => {
if (c){
return a+b+c
}
else {
return a+b
}
}
console.log(add(1,2)) // 3
console.log(add(1,2,3)) // 6
poly - many, morphos - structure
⇒ 여러가지 다른 구조.
Concrete type ? → bool, string, number, …
generic type 사용 → like placeholder → 추론 후 함수 사용 → 들어오는 거
'Generic은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입 만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.’
type SuperPrint = {
<T>(arr:T[]) : void // 어떤 타입의 배열이든 알아서 타입 추정.
// 추가적으로 배열에서 모든 value들의 type이 통일 될 필요 없음.
// -> 이건 너무 좋다.
}
const superPrint:SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
// all compile
superPrint([1,2,3,4,5,6])
superPrint([1,"2",3,4,5,6])
superPrint([1,2,false,4,5,6])
superPrint([1,2,3,4.3,5,6])
type FirstPrint = {
<T>(arr : T[]):T
}
const firstPrint : FirstPrint = (arr) => {
return arr[0]
}
console.log(firstPrint([1,2,3,4,5,6])) //1
console.log(firstPrint([1,"2",3,4,5,6])) //1
console.log(firstPrint([1,2,false,4,5,6])) //1
console.log(firstPrint([1,2,3,4.3,5,6])) //1
// 2개 이상의 generic
type RequireValue = {
<T,M>(arr: T[], value: M) : T | undefined
}
const requireValue: RequireValue = (arr, value) => {
const returnValue = arr.find(i => (typeof i === typeof value))
return returnValue
}
console.log(requireValue([1,2,3,4,'2',1,24,5],'haram')) // '2'
// 콜 시그니쳐를 정의할 때 제네릭 타입을 정의하는 것뿐만 아니라
// 함수 자체에서 한 번에 제네릭 타입을 정의할 수 있다.
// Type Script 에게 타입을 유추하도록 코드를 작성하는 것이 좋다.
function superPrint<T>(arr: T[]){
return arr[0]
}
// Generic type을 확장시켜서 사용할 수 있음.
type **Player**<E> = {
name: string
extraInfo: E
}
type **NicoExtra** = {favFood: "kimchi"}
type **NicoPlayer** = **Player**<**NicoExtra**>
const nico: **NicoPlayer** = {
name: "nico",
extraInfo: {
favFood: "kimchi"
}
}
// 만일 Player 타입이지만 extraInfo의 속성을 가지지 않는 객체의 경우
const lynn: Player<null> = {
name: "lynn",
extraInfo: null
}
abstract class User { // 추상 클래스 생성
constructor( // TypeScript 에선 생성자를 더 편하게 작성할 수 있다.
private firstName:string,
protected lastName:string,
public nickName:string
) {}
// constructor(firstName, lastName, nickName) {
// this.firstName = firstName;
// this.lastName = lastName;
// this.nickName = nickName;
// }
abstract getNickname():void // 추상메소드 -> 콜 시그니쳐만 작성.
getFullName():string{
return `${this.firstName} ${this.lastName}`
// python 의 f string 을 js 에선 이렇게 사용.
}
}
class Player extends User{
getNickname(){ // 콜 시그니쳐만 사용된 추상 메소드를 자식 클래스에서 생성.
console.log(this.lastName)
}
}
const haram = new Player("haram","jeong","haram")
console.log(haram.getFullName())
console.log(haram.lastName) // Error !! -> property error
// 얘를 추상클래스가 아닌 인터페이스로 상속받을 수 있게 한다면 파일 크기를 매우 줄일 수 있다
// js 에선 추상클래스가 없으므로 그대로 클래스를 가져오지만 인터페이스는 아예 가볍게 사라진다.
// 그래서 여러 개의 인터페이스를 만들어서 상속할 수 있도록 설계한다.
interface User{
firstName: string,
lastName: string,
fullName(): string,
sayHi(name:string): string
}
interface Human{
health: number
}
class Player implements User,Human{
constructor(
public firstName: string,
public lastName: string,
public health: number
) {}
fullName(){
return `${this.firstName} ${this.lastName}`
}
sayHi(name: string){
return `hi ${name}. my name is ${this.fullName()}`;
}
}
TypeScript에서 == 연산자와 === 연산자는 값들을 비교하는 데 사용되는 두 가지 다른 연산자.
== 연산자 (동등 연산자):== 연산자는 값의 동등성을 비교함.예시:
// typescript ex
5 == 5 // true
'5' == 5 // true (형변환 이후 비교 => 값 자체는 동일 하므로 true)
'hello' == 'world' // false
=== 연산자 (일치 연산자):=== 연산자는 값과 데이터 타입 모두를 비교함.true를 반환함.false를 반환함.// typescript ex
5 === 5 // true
'5' === 5 // false
일반적으로 TypeScript 및 JavaScript에서는 === 연산자를 권장 (형 변환이 발생하지 않아 예기치 않은 동작을 방지)
== 연산자는 예기치 않은 형 변환이 발생할 수 있어서 버그의 원인이 됨.
⇒ 값을 비교할 때는 데이터 타입과 값을 모두 고려하는 === 연산자를 사용하는 것이 안전.