레퍼런스로 사용하는 동료의 캡스톤 과제가 ts로 되어있어서 코드 이해를 위해 ts를 배우기로 했다.
단기간에 사용할 수 있도록 간단히만 요약함.
< 시작 >
- npm install -g typescript로 설치
- ts 파일이 위치한 위치로 가서 'tsc -w' 실행하면 코드 작성 후 저장할 때마다 자동으로 js로 변환함
< 질문 >
1. tuple 이 필요한 이유가 뭘까 (지정된 위치에 지정된 타입의 원소를 다뤄야 하는 경우가 뭘까) :
2. TS에서 interface와 type 의 사용방법이 굉장히 유사한데 각각 어느 상황에서 써야 적절한가?
-> object나 class 의 모양을 강제하고 싶다면 interface를 사용하고 그 외 변수타입을 지정하거나 tuple을 사용하는 등 다른 목적이라면 type을 사용한다
3.
// -------------------------- TYPES ---------------------------
// ts 타입 종류: string, number, boolean, null, undefined, bigint, [], {} ...
let type1: string = "hello";
let type2 = "bye";
type1 = "hi"; type2 = "good bye";
let type3: number = 1;
let type4 = 2;
type3 = 3; type4 = 4;
let type5: boolean = false;
let type6 = false;
type5 = true; type6 = true;
let type7: null = null;
let type8 = null;
let type9; // undefined
let type10: undefined; // undefined
let type11: string[] = ["hi", "I'm fine", "and you?"];
let type12 = ["this", "is", "string", "array"];
type11.push("I'm Andrew"); type12.pop();
let type13: any = 123; // any에는 어떤 타입이든 올 수 있다 -> 제약이 없어지므로 js와 비슷한 상황
let type14: unknown; // 아직 어떤 타입일지 모름
// void: 아무것도 return 하지 않는 함수
const voidFunction = () => {
console.log("this is void type function");
}
// never: 1.에러를 발생시키는 함수 2. 절대 반환될 수 없는
// 1.
const errorFunction = () => {
throw new Error("error");
}
// 2.
const numOrString = (type15: number|string) => {
if(typeof type15 === "number"){
type15 += 1;
}
else if(typeof type15 === "string"){
console.log(type15);
}
else{
type15; // never
}
}
// unknown: 타입에 따라 다른 결과
let thing: unknown;
if(typeof thing === "string"){
let sentence = thing;
}
if(typeof thing === "number"){
let size = thing;
}
// -------------------------- CUSTOM TYPE ---------------------------
// 나만의 타입 만들기
// 변수명은 대부분 대문자로 시작
type Age = string | number; // union type: 두 타입 중 하나면 ok
const myAge: Age = 27;
const yourAge: Age = "27";
// type 에 concrete type의 특정 값을 지정할 수도 있음
type Color = "red" | "blue" | "yellow";
type Clothes = {
size: string,
color: Color
}
const shirt: Clothes = {
size: "XL",
color: "red" // ex: "black" 은 에러
}
// 기존 type에 새로운 property 를 추가할 때
type Phone = {
kind: string,
version: string,
price: number
}
type MyPhone = Phone & {
// Phone 타입에 추가할 property 작성
phone_number: string
}
const myPhone: MyPhone = {
kind: "iPhone",
version: "XR",
price: 1000000,
phone_number: "010-1111-2222"
}
// 선언한 타입의 여러가지 경우의 수에서 한 가지를 선택해서 사용
// 1. 개수는 같고 종류는 다른 인자
type Subtraction = {
(a: number, b: number): number;
(a: number, b: string): number;
}
const sub: Subtraction = (a, b) => {
if(typeof b === "string") return a;
return a - b;
}
// 2. 개수가 다른 인자
type Menu = {
(a: string): string;
(a: string, b: string): string;
}
const menu: Menu = (a: string, b?: string) => {
if(b) return b;
return a;
}
// -------------------------- FUNCTION ---------------------------
// 함수에서 타입지정하기: parameter, return value
function plus(x: number, y: number): number{
return x + y;
}
plus(1, 2);
// 화살표 함수에서 타입 지정하기: parameter, return value
const minus = (x: number, y: number): number => {
return x - y;
}
// Call Signature: 함수의 파라미터, 반환타입 미리 지정
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => (a + b); // 타입을 미리 지정했기 때문에 a, b 타입을 안써줘도 됨
const result = add(1, 2); // number
// -------------------------- ARRAY ---------------------------
// array 에서 사용가능한 tuple: 지정한 자리에 지정한 타입 원소가 있어야함
// type으로 변수 선언 후 []에 원하는 타입 지정
type Member = [number, boolean];
let john: Member = [13, false];
// let emily: Member = ["13", true]; // 에러
// -------------------------- OBJECT ---------------------------
// object 속성에 type 지정
type Obj = {
age: string,
}
let jameson: Obj = {
age: "hello",
}
// object property의 타입은 알지만 이름은 아직 모를때
type Member2 = { // 글자로 된 모든 속성의 타입은 string 으로 지정
[key: string]: string, // call signature
}
// obj 속성 key에 ""를 사용하면 식별자규칙을 따르지 않아도됨
// 식별자규칙: 변수 이름을 만드는데 지켜야할 규칙
// 1. 문자, 숫자, _, & 로 작성 2. 숫자로 시작하지 않기 3. 예약어는 변수명 될 수 없음 ...
let tom: Member2 = {
"name": "tom",
"age": "123",
}
// obejct 선언시 타입과 함께 한번에 작성 (비추천)
const playerEx: {
// 타입 부분을 중괄호 처리해서 사용
name: string,
age?: number, // optional -> age는 존재해도 되고 안해도 됨
}={
name: "nico"
}
// -------------------------- CLASS ---------------------------
// 추상 클래스 작성
abstract class Pet {
// JS에는 접근제한자 없음, TS에서만 사용
constructor(
protected name: string,
private age: number,
){}
abstract getName(): string;
public getAge(){
return this.age;
}
}
// 자식 클래스 작성
class Dog extends Pet{
getName(){
return this.name;
}
}
// 클래스 인스턴스
const pet1 = new Dog("콩이", 7);
pet1.getName();
pet1.getAge();
// -------------------------- INTERFACE ---------------------------
// class, object 의 구성을 나타내기 위해 사용
// 마치 type처럼 사용되지만 type 기능이 좀 더 많음 => argument나 return 타입으로 지정가능
// 구성
interface Gear {
name: string;
kind: string;
getName(): string;
}
// interface 끼리 상속 가능
interface Laptop extends Gear{
getPrice(): number;
}
// 클래스로 구현
class MyLaptop implements Laptop{
constructor(
public name: string,
public kind: string,
private price: number // 인터페이스에서 선언된 변수는 반드시 public 이다
){}
public getName(): string{
return this.name;
}
public getPrice(){
return this.price;
}
}
// instance 생성
const myLaptop = new MyLaptop("gram", "ultrabook", 1500000);
// obj 생성
const myLaptopObj: Laptop = {
name: "gram",
kind: "utlrabook",
getName: function(){ return this.name; },
getPrice: function(){ return 1500000; }
}
const name2 = myLaptopObj.getName(); // Gear, Laptop 인터페이스에서 타입지정해줘도 MyLaptop에서 지정안하면 any 표시됨..
// TS interface는 중첩이 됨(...)
interface Hi{
Hi: string
}
interface Hi{
Hello: string
}
const greeting: Hi = {
Hi: "Hi",
Hello: "Hello"
}
// -------------------------- ABSTRACT VS INTERFACE ---------------------------
// abstract class 와 interface 사용시 차이점
// class 의 기본적인 골자를 알려주기 위해 abstract, inteface 사용
// abstract 는 JS 변환시 class로 남아있고 interface 는 type 처럼 코드 남지 않음
// 1. abstrace example
abstract class Food{
constructor(
private name: string,
private price: number
){}
public getName(): string{
return this.name;
}
public getPrice(): number{
return this.price;
}
abstract showInfo(): string;
}
// Food 상속받은 Snack 클래스
class Snack extends Food{
public showInfo(){
return `This snack is ${this.getName()}, cost is ${this.getPrice()}`;
}
}
const myFood = new Snack("Apple", 3000); // name, price는 getter로만 접근가능, 수정불가
// 2. 위와 같은 내용을 interface로 만들어보기
interface Food2{
name: string,
price: number,
// interface 안에서 함수 구현 불가능 <-> abstract class
getName(): string,
getPrice(): number,
showInfo() : string
}
class Snack2 implements Food2{
constructor(
// 인터페이스를 구현한 필드는 반드시 public 접근제한자 <-> abstract class
public readonly name: string, // private, getter 사용 못함 -> readonly로 대체 (뇌피셜)
public readonly price: number // private ,getter 사용 못함 -> readonly로 대체 (뇌피셜)
){}
// 외부에서 name, price 접근 가능하므로 getter로 보호 불가 -> readonly 사용
public getName(){
return this.name;
}
public getPrice(){
return this.price;
}
public showInfo(){
return `This snack is ${this.name}, cost is ${this.price}`;
}
}
const myFood2 = new Snack2("신라면", 1000); // name, price 직접 접근가능 but 수정 불가
// -------------------------- READONLY ---------------------------
// Javascript 에는 없는 개념
// 1. object property
type Fruit = {
readonly color: string,
name: string
}
const fruit1: Fruit = { color: "red", name: "apple"};
fruit1.name = "banana";
// fuirt1.color = "yellow"; // readonly 는 변경 못함: 에러
// 2. array
const array: readonly string[] = ["1", "2"];
// array.push("3"); // readonly 는 변경 못함: 에러
// -------------------------- GENERIC ---------------------------
// 1. call signature로 너무 많은 타입을 지정해야할 때, 어떤 타입이 올지 모를 때
type MyArray = {
<T>(array: T): T;
}
const printArray: MyArray = (array) => {
console.log(array);
return array;
}
// number, string, boolean 등 다른 타입이와도 처리가능
const arr1 = printArray([1, 2, 3]); // return number[]
const arr2 = printArray(["1", "2", "3"]); // return string[]
const arr3 = printArray([1, 2, "3", true, false]); // return number | string | boolean[]
// 2. 결과적으로 any와 같다고 생각할 수 있지만 다르다 => 같은 코드를 여러 type에서 사용할 수 있게 재활용
// generic은 각 call signature를 생성
type AnyArray = {
(array: any): any;
}
const anyArray: AnyArray = (array) => {
console.log(array);
return array;
}
const any1 = anyArray([1, 2, 3]); // return any not number
const any2 = anyArray(["1", "2", "3"]); // return any not string
// const any3 = anyArray(1, 2, "3", true, false); // 에러
// 3. 함수에서 generic 사용
function printArray2<V> (array: V){
console.log(array);
return array;
}
const arr4 = printArray2([1, 2, 3]); // return number[]
const arr5 = printArray2(["1", "2", "3"]); // return string[]
const arr6 = printArray2([1, "2", true]); // return number | string | boolean
// 4. generic으로 타입 확장
type Person<E> = {
name: string,
extraInfo: E
}
type MyExtra = {
favFood: string
}
type DavidPerson = Person<MyExtra>
const david: DavidPerson = {
name: "david",
extraInfo: {
favFood: "kimchi"
}
}
const Tim: Person<null> = {
name: "Tim",
extraInfo: null
}
// -------------------------- 응용 ---------------------------
// 1. function 으로 type이 지정된 object 를 반환하는 예
type Player = { // 특정 객체를 위한 타입 선언
name: string,
age?: number // optional (number OR undefined)
}
function playerMaker(name: string): Player{
return {
name // name: name
}
}
const nico = playerMaker("nico");
nico.age = 30; // Player type 지정 안하면 Error
// 2. 간단한 Dictionary 만들어보기
type Words = {
[key: string]: string;
}
class Word{
constructor( // 클래스 필드 자동 초기화
// 접근제한자 꼭 적기
public name: string, // this.name = name;
public def: string // this.def = def;
){}
}
class Dict{
private words:Words;
constructor(){
// 빈 obj 생성
// this.words = words 로 자동초기화를 원하지 않기때문에 수동초기화
this.words = {};
};
public addWord(word: Word){
if(this.words[word.name] === undefined){
this.words[word.name] = word.def;
}
}
public definition(name: string){
return this.words[name];
}
}
const whiteboard = new Word("whiteboard", "글쓰는 흰 보드");
const dictionary = new Dict();
dictionary.addWord(whiteboard);
dictionary.definition("whiteboard");
// 3. 다형성 : generic, class, interface ...
// generic : concrete type이 아닌 placeholder 사용 가능하게 함, 같은 코드를 다른 타입에서 사용할 수 있도록 함
interface SubStorage<G>{
[key: string]: G
}
// 다양한 type에서 쓸 수 있는 LocalStorage 간단실습
class LocalStorage<G>{
private storage: SubStorage<G> = {}
set(key: string, value: G){
this.storage[key] = value;
}
remove(key: string){
delete this.storage[key];
}
get(key: string): G{
return this.storage[key];
}
clear(){
this.storage = {};
}
}
const stringStorage = new LocalStorage<string>();
stringStorage.set("greeting", "hello");
stringStorage.get("gretting");
stringStorage.remove("greeting");
stringStorage.clear();
const numberStorage = new LocalStorage<number>();
numberStorage.set("dozen", 12);
numberStorage.get("dozen");
numberStorage.remove("dozen");
numberStorage.clear();
// ...