[Typescript]Typescript 정리
타입스크립트(Typescript)를 사용하는 이유
우리가 사용하는 브라우저는 타입스크립트를 이해하지 못한다. 타입스크립트를 브라우저에서 실행하기 위해서는 브라우저가 이해할 수 있는 자바스크립트로 코드를 변경하는 트랜스파일(transpile) 과정을 거친다. 그럼에도 불구하고 굳이 자바스크립트가 아닌 타입스크립트로 코드를 작성하는 이유는 무엇일까?
타입스크립트는 자바스크립트의 문제점을 보안하기 위해 만들어진 언어이다. 타입스크립트의 가장 큰 특징인 정적타입은 개발자들의 실수를 미연에 방지할 수 있다는 장점이 있다. 기본의 자바스크립트와는 달리 타입스크립트는 타입을 명시해야 한다. 명시하지 않으면 자체적으로 에러 표시를 해준다.
transpile VS compile
컴파일(compile)
자바와 같은 컴파일 언어에서 사람들이 이해할 수 있는 .java 파일을 기계(OS)가 이해할 수 있는 .class 파일로 변환하는 과정
트랜스파일(transpile)
타입스크립트를 브라우저에서 실행하기 위해 브라우저가 이해할 수 있는 자바스크립트로 변환하는 과정
타입(Type)의 종류
기본 타입
let firstname:string = 'dahye'
let age:number = 30;
let isAdult:boolean = true;
let array1:number[] = [1,2,3];
let array2:Array = [1,2,3];
let week1:string[] = ['mon', 'tue', 'wed'];
let week2:Array = ['mon', 'tue', 'wed'];
// week1.push(3) -> 다른 타입 추가하려고 하면 에러
타입 명시
let car1:string = 'bow';
재선언
car1 = 'benz'
// car1 = 1 -> 다른 타입으로 재선언하면 에러
튜플(Tuple)
한 가지 타입의 배열이 아닌 여러 타입의 배열을 만들 수 있게 만들어준다.
let b:[string, number];
b = ['z', 1];
// b = [1, 'z']; -> 타입 일치하지 않으면 에러
b[0].toLowerCase();
// b[1].toLowerCase(); -> 숫자를 소문자로 바꿀 수 없으므로 에러
void
함수에서 아무거도 반환하지 않을 때 주로 사용
function sayHello():void{
console.log('hello');
}
never
에러를 반환하거나 영원히 끝나지 않는 함수를 반환할 때 사용
function showErr():never{
throw new Error();
}
function isfLoop():never{
while(true){
// do something...
}
}
enum
비슷한 것끼리 묶어두는 것
아무 값을 넣지 않으면 0부터 차례대로 값을 넣어준다.
enum OsString {
Window, // 0
Ios, // 1
Android // 2
}
첫번째 값만 넣어주면 그 다음 값부터 차례대로 전값 + 1을 넣어준다.
enum OsString {
Window = 3,
Ios = 10,
Android // 11
}
문자 타입의 경우 양방향 매핑이 가능하다
enum OsString {
Window = 3,
Ios = 10,
Android
}
console.log(OsString['Window']) // 3
console.log(OsString[3]) // Window
enum 타입의 변수를 선언할 수 있다.
특정 값만 입력 받고 싶을 때 혹은 그 값들에 공통점이 있을 때 사용하면 좋다.
let myOs:OsString;
null, undefined
let x:null = null;
let y:undefined = undefined;
Interface(인터페이스)
인터페이스 사용 용도
let user:object;
user = {
name : 'dahye',
age : 30
}
console.log(user.name); // name에서 에러남
프로퍼티를 정의해서 객체를 표현하고자 할 때는 인터페이스를 사용하는 것이 좋다.
인터페이스 정의 방법
interface User {
name : string;
age : number;
gender? : string; // 옵션(값을 넣어도 되고 안 넣어도 된다.)
readonly birthYear : number; // 읽기 전용
[grade:number] : String; // 문자열 index 서명
}
let user : User = {
name : 'dahye',
age : 30,
birthYear : 2000,
1 : 'A',
2 : 'B'
}
user.age = 10; // 값의 재할당 가능
user.gender = "male"; // 옵션은 나중에 추가할 수 있다.
// user.birthYear = 1990; -> readonly가 적용되어 있어 값을 변경 할 수 없다.
console.log(user.age); // 접근가능
문자열 리터럴 타입
type Score = 'A' | 'B' | 'C' | 'F'; // 문자열 리터럴 타입
interface User {
name : string;
age : number;
gender? : string;
readonly birthYear : number;
[grade:number] : Score; // 해당 값 이외의 값이 들어오면 에러
}
let user : User = {
name : 'dahye',
age : 30,
birthYear : 2000,
1 : 'A',
2 : 'B'
}
인터페이스로 함수 정의
interface 인터페이스명 { (인자:인자타입): 반환타입;}
interface Add {
(num1:number, num2:number): number;
}
const add : Add = function(x, y){
return x + y;
}
add(10, 20);
interface IsAdult {
(age:number):boolean;
}
const a:IsAdult = (age) => {
return age>19;
}
a(33); // true
인터페이스로 클래스 정의
interface를 implement
interface Car{
color:string;
wheels:number;
start():void;
}
class Bmw implements Car {
color = "red";
wheels = 4;
start(){
console.log('go...');
}
}
interface 확장하기(extends)
interface Car{
color:string;
wheels:number;
start():void;
}
interface Benz extends Car {
door: number;
stop(): void;
}
const benz : Benz = {
door : 5,
stop(){
console.log('stop');
},
color:'black',
wheels:4,
start(){
console.log('goooooooo');
}
}
확장받은 interface의 프로퍼티는 무조건 정의해야한다.
두가지 interface 확장하기
interface Car{
color:string;
wheels:number;
start():void;
}
interface Toy{
name:string;
}
interface ToyCar extends Car, Toy{
price :number;
}
interface Benz extends Car {
door: number;
stop(): void;
}
함수(function)
함수 타입 정의
function add(num1 :number, num2 : number):number{
return num1 + num2
}
function isAdult(age:number):boolean{
return age > 19;
}
선택적 매개 변수
function hello(name?:string){
return `Hello, ${name || "world"}`;
}
const result1 = hello();
const result2 = hello('dahye');
// const result3 = hello(123); -> 타입 불일치시 에러
함수의 매개 변수도 옵션으로 지정할 수 있다.
매개변수 디폴트 값
function hello2(name = "world"){
return `Hello, ${name}`;
}
매개 변수에 디폴트 값을 사용할 수도 있다.
매개변수 순서 변경
function hello(name:string, age?:number):string{
if(age!==undefined){
return `Hello, ${name}. You are ${age}.`;
}else{
return `Hello, ${name}`;
}
}
console.log(hello("Sam"));
console.log(hello("Sam", 30));
function hello(age:number | undefined, name:string):string{
if(age!==undefined){
return `Hello, ${name}. You are ${age}.`;
}else{
return `Hello, ${name}`;
}
}
console.log(hello(30, "Sam"));
console.log(hello(undefined, "Sam"));
undefined로 명시적으로 표시
나머지 매개 변수
function add(...nums:number[]){
return nums.reduce((result,num) => result + num, 0);
}
add(1,2,3); // 6
add(1,2,3,4,5,6,7,8,9,10); // 55
나머지 매개 변수의 타입은 배열로 넣어준다.
this의 타입 선언
interface User {
name : string;
}
const Sam: User = {name:'Sam'}
function showName(this:User){
console.log(this.name)
}
const a = showName.bind(Sam);
a();
interface User {
name : string;
}
const Sam: User = {name:'Sam'}
function showName(this:User, age:number, gender:'m'|'f'){
console.log(this.name, age, gender)
}
const a = showName.bind(Sam);
a(30, 'f');
첫번째 매개변수 자리에 this의 타입을 선언할 수 있다.
함수의 오버로드(overload)
interface User {
name : string;
age : number;
}
function join(name:string, age:number):User; // number 타입 오버로드
function join(name:string, age:string):User; // string 타입 오버로드
function join(name:string, age:number|string):User|string{
if(typeof age === "number") {
return {
name,
age
};
}else{
return "나이는 숫자로 입력해주세요.";
}
}
const sam:User = join("Sam",30);
const jane:User = join("Jane","30");
전달 받은 매개변수의 개수나 타입에 따라 다른 동작을 하게 만들 수 있다.
리터털(Literal) 타입
값이 변화하지 않는 타입
const userName1 = "Bob"; // 변하지 않는 값 -> 문자형 리터럴 타입
let userName2:string|number = "Tom"; // 변하는 값
userName2 = 3;
type Job = "police" | "developer" | "teacher";
interface User {
name : string;
job : Job;
}
const user: User = {
name : "Bob",
job : "developer"
}
interface HighShoolStudent{
name:string;
grade:1|2|3; // 해당 값만 사용가능(변하지 않는 값) -> 숫자형 리터럴 타입
}
유니온(Union) 타입
다양한 값을 선택할 수 있게 하는 타입
interface Car {
name:"car";
color:string;
start():void;
}
interface Mobile{
name:"mobile";
color:string;
call():void;
}
function getGift(gift:Car|Mobile){
console.log(gift.color);
// 식별가능한 유니온 타입
if(gift.name === "car"){
gift.start();
}else{
gift.call();
}
}
교차(Intersection) 타입
모든 값을 가져오게 하는 타입
interface Car {
name:string
start():void;
}
interface Toy{
name:string
color:string;
price:number;
}
const toyCar: Toy & Car = {
name : "타요",
start(){},
color:"blue",
price:1000
}
클래스(Class)
class Car{
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
const bmw = new Car("red");
맴버 변수를 미리 선언해아한다.
class Car{
// color:string;
constructor(public color:string){
this.color = color;
}
start(){
console.log("start");
}
}
const bmw = new Car("red");
class Car{
// color:string;
constructor(readonly color:string){
this.color = color;
}
start(){
console.log("start");
}
}
const bmw = new Car("red");
맴버 변수를 미리 선언하지 않는 방법으로 매개변수에 public 혹은 readonly를 붙인다.
접근제한자
접근제한자는 변수명 앞에 아무거도 적지 않으면 디폴트가 public이다.
public
자식 클래스, 클래스 인스턴스에서 모두 접근 가능하다.
class Car2 {
name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name);
}
}
class Car2 {
public name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name);
}
}
private
private 혹은 #을 적으면 해당 클래스에서만 접근할 수 있고 자식 클래스에서는 접근할 수 없다.
class Car2 {
private name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name); // err: private 변수를 자식 클래스에서 사용할 수 없다.
}
}
class Car2 {
#name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name); // err: private 변수를 자식 클래스에서 사용할 수 없다.
}
}
protected
자식 컴포넌트에서 접근할 수 있으나 클래스 인스턴스로는 접근할 수 없다.
class Car2 {
protected name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name); // 여기서는 에러가 생기지 않는다.
}
}
const z4 = new Bmw("black");
console.log(z4.name); // 하지만 여기서는 에러가 생긴다.
readonly
readonly를 사용하면 public과 같이 접근 가능하나 데이테 변경을 막을 수 있다.
class Car2 {
readonly name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string){
super(color);
}
showName(){
console.log(super.name);
}
}
const z4 = new Bmw("black");
console.log(z4.name);
z4.name = 'zzzz4'; // readonly를 사용하면 데이터의 변경을 막을 수 있다.
class Car2 {
readonly name:string = "car";
color:string;
constructor(color:string, name:string){
this.color = color;
this.name = name;
}
start(){
console.log("start");
}
}
class Bmw extends Car2{
constructor(color:string, name:string){
super(color, name);
}
showName(){
console.log(super.name);
}
}
const z4 = new Bmw("black", "dahye");
console.log(z4.name);
readonly인 상태에서 변수 값을 변경하기 위해서는 constructor 내부에서 선언해 주어야 한다.
static
static으로 선언된 정적 멤버변수나 메서드는 this로 가져 올 수 없다.
class Car2 {
readonly name:string = "car";
color:string;
static wheels = 4;
constructor(color:string, name:string){
this.color = color;
this.name = name;
}
start(){
console.log("start");
console.log(this.wheels); // err
}
}
class Bmw extends Car2{
constructor(color:string, name:string){
super(color, name);
}
showName(){
console.log(super.name);
}
}
const z4 = new Bmw("black", "dahye");
console.log(z4.wheels); // err
class Car2 {
readonly name:string = "car";
color:string;
static wheels = 4;
constructor(color:string, name:string){
this.color = color;
this.name = name;
}
start(){
console.log("start");
console.log(Car2.wheels); // 클래스 명으로 접근할 수 있다.
}
}
class Bmw extends Car2{
constructor(color:string, name:string){
super(color, name);
}
showName(){
console.log(super.name);
}
}
const z4 = new Bmw("black", "dahye");
console.log(Car2.wheels); // 클래스 명으로 접근할 수 있다.
추상 클래스
클래스 앞에 abstract 선언하여 추상 클래스를 정의할 수 있다.
abstract class Car3 {
readonly name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
}
const caar = new Car3("red"); // err : 추상 클래스는 new하여 객체를 만들 수 없다.
// 추상클래스는 상속해서 사용할 수 있다.
class Benz extends Car3{
constructor(color:string){
super(color);
}
}
const v4 = new Benz("black");
abstract class Car3 {
readonly name:string = "car";
color:string;
constructor(color:string){
this.color = color;
}
start(){
console.log("start");
}
abstract doSomething():void; // 추상 클래스 내부의 추상 메서드는 반드시 상속 받은 곳에서 구체적인 구현을 해야한다.
}
class Benz extends Car3{
constructor(color:string){
super(color);
}
doSomething(){
alert('구체적인 기능 구현');
}
}
const v4 = new Benz("black");
제너릭 타입(Genric)
한가지 타입으로 정의 내릴 수 없을 때 어떤 타입의 데이터도 유연하게 받을 수 있는 것이 바로 제네릭 타입니다.
제네릭 타입을 사용하지 않은 경우
function getSize(arr:number[] | string[] | boolean[] | object[]):number {
return arr.length;
}
const arr1 = [1,2,3];
getSize(arr1); // 3
const arr2 = ["a","b","c"];
getSize(arr2);
const arr3 = [false, true, true];
getSize(arr3);
const arr4 = [{},{},{name:"Dahye"}];
getSize(arr4);
제네릭 타입을 사용한 경우
function getSize<T>(arr:T[]):number {
return arr.length;
}
const arr1 = [1,2,3];
getSize<number>(arr1); // 특정 타이븡로 제한 하고 싶을 때 <>안에 타입을 선언
const arr2 = ["a","b","c"];
getSize(arr2);
const arr3 = [false, true, true];
getSize(arr3);
const arr4 = [{},{},{name:"Tim"}];
getSize(arr4);
// 하나의 객체만을 선언하여 각기 다른 타입으로 선언하여 사용할 수 있다.
interface Mobile<T> {
name: string;
price: number;
option: T;
}
const m1: Mobile<Object> = {
name: "s21",
price: 10000,
option: {
color: "red",
coupon: false
}
}
// Option 객체의 모습이 정해져 있다면
const m1: Mobile<{color:string; coupon:boolean}> = {
name: "s21",
price: 10000,
option: {
color: "red",
coupon: false
}
}
const m2: Mobile<string> = {
name: "s20",
price: 9000,
option: "good"
}
제네릭 확장
interface User {
name:string;
age: number;
}
interface Car {
name:string;
color:string;
}
interface Book {
pirce:number;
}
const user: User = {name:"Dahye", age:10};
const car: Car = {name:"bmw", color:"red"};
const book: Book = {pirce:3000};
function showName<T extends {name:string}>(data:T):string{
return data.name;
}
showName(user);
showName(car);
showName(book); // name을 extends로 확장하였기 때문에 무조건 name이 있어야한다.
유틸리티(Utility) 타입
keyof
interface User {
id: number;
name:string;
age:number;
gender:"m"|"f";
}
type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender'
const test1:UserKey = "age";
const test2:UserKey = "birthday"; // User 인터페이스에 없는 값을 넣으면 에러가 난다.
선언된 특정한 값만 선택할 수 있게 만들어준다.
Partial
// User 인터페이스에 선언된 값을 모두 적어주지 않아도된다.
let admin: Partial<User> = {
id: 1,
name: "Choi",
job: "teacher" // User 인터페이스에 선언되지 않은 값을 새로 적어줄 수 없다.
}
선언된 특정한 값을 모두 사용하지 않아도 된다.
Required
let admin: Required<User> = {
id: 1,
name: "Choi",
age: 30,
gender: 'f'
필수로 값을 입력 받게 만든다.
Readonly
interface User {
id: number;
name: string;
age?: number;
}
let admin: Readonly<User> = {
id: 1,
name : "Choi"
}
admin.id = 4;
처음에 할당만 가능하고 변경이 불가능하다.
Record
Record<Key,Type> 형태로 선언하여 사용할 수 있다.
interface Score {
'1' : 'A' | 'B' | 'c' | 'D';
'2' : 'A' | 'B' | 'c' | 'D';
'3' : 'A' | 'B' | 'c' | 'D';
'4' : 'A' | 'B' | 'c' | 'D';
}
const score = {
1: 'A',
2: 'C ',
3: 'B',
4: 'D'
}
이렇게 사용하는 것 보다
type Grade = '1' | '2' | '3' | '4'
type Score = 'A' | 'B' | 'C' | 'D'
const score: Record<Grade,Score> = {
1: 'A',
2: 'C',
3: 'B',
4: 'D'
}
이렇게 Record를 사용한 코드가 훨씬 확장성이 좋다.
interface User {
id: number;
name: string;
age: number;
}
function isValid(user:User){
const result: Record<keyof User, boolean> = {
id: user.id > 0,
name: user.name !== '',
age: user.age > 0
}
return result;
}
Pick
interface User {
id: number;
name: string;
age: number;
gender: 'm' | 'f'
}
const admin: Pick<User, 'id' | 'name' > = {
id: 0,
name: 'choi'
user에서 제외할 것을 선언하여 필요한 것만 사용할 수 있다.
Omit
interface User {
id: number;
name: string;
age: number;
gender: 'm' | 'f'
}
const admin: Omit<User, 'age' | 'gender' > = {
id: 0,
name: 'choi'
}
user에서 제외할 것을 선언하여 필요한 것만 사용할 수 있다.
Exclude
Exclude<T1, T2> 형식으로 선언해서 사용하는데 T1에서 T2와 겹치는 타입을 제외한다.
type T1 = string | number;
type T2 = Exclude<T1, number>;
T2는 string만 남게 된다.
NonNullable
NonNullable<T> 형식으로 선언해서 사용한다. Null, undefined를 제외한 타입을 생성한다.
type T1 = string | null | undefined | void;
type T2 = NonNullable<T1>;
Null, undefined를 제외한 string, void만 남는다.