Today I Learned_230403

6 분 소요

TypeScript

인프런 타입스크립트 강의 수강

섹션 8. 이넘

이넘 : 특정 값들의 집합을 의미하는 자료형. 집합의 데이터 타입

드랍다운, 정해진 값의 목록을 지정할 때 이넘을 사용.

숫자형 이넘

enum Shoes {
    Nike,   // 0
    Adidas, // 1
    Vans    // 2
}

var myShoes = Shoes.Nike;
console.log(myShoes);   // 0 출력
enum Shoes {
    Nike = 10,   // 0
    Adidas, // 11
    Vans    // 12
}

숫자형 이넘은 초기화하지 않아도 자동 초기화가 된다.

문자형 이넘

enum Shoes {
Nike= '나이키',
Adidas= '아디다스',
Vans= '반스'
}

var myShoes = Shoes.Nike;
console.log(myShoes);// '나이키' 출력

문자형 이넘은 자동초기화가 안 되기 때문에 모두 지정해 주어야 한다.

이넘의 활용

이넘 활용 전..

function askQuestion (answer: string) {
    if (answer === 'yes') {
        console.log('정답입니다.');
    }
    if (answer === 'no') {
        console.log('오답입니다.');
    }
}

askQuestion('yes'); //제대로 된 호출
askQuestion('Y');   //파라미터로 넣을 수는 있지만 이러면 처리를 하지 못한다.

이넘을 적용한다면…

enum Answer {
    Yes = 'Y',
    No = 'N'
}

function askQuestion (answer: Answer) {
    if(answer === Answer.Yes) {
        console.log('정답입니다.');
    }
    if (answer === Answer.No) {
        console.log('오답입니다.');
    }
}

askQuestion(Answer.Yes);
askQuestion('YES'); // enum에서 제공하는 데이터만 넣을 수 있기에 호출 실패.

섹션 9. 클래스

javascript의 클래스

ES2015 (ES6)에서 소개된 새로운 문법

역할 : 인스턴스를 만들어 주는 것. 첫번째 로직은 보통 constructor 사용

class Person {
    // 클래스 로직
    constructor(name, age) {
        console.log('생성 되었습니다.');
        this.name = name;
        this.age = age;
    }
}

var heejung = new Person('희정', 29);   // 생성 되었습니다.
console.log(heejung)

prototype 스펙 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

var user = {name : 'capt', age: 100}
var admin = {name: 'capt', age: 100, role: 'admin'};

중복되는 코드들을 지우고 싶음 → javascript의 프로토타입을 통해 상속해서 지워보자.

image

admin으로 아무것도 받지 않아도 proto를 지정해 주면, admin에서 상위 user의 값인 name, age를 접근해서 사용할 수 있다.

admin의 기본적인 객체의 모양을 user로부터 내려받아 사용.

image

이렇게 추가적인 데이터를 넣어서 사용할 수 있음. image

javscript에서 객체를 만들면, 해당 객체의 최상위 prototype은 Object가 된다.

Object에 매핑된 속성들을 사용할 수 있음.

array를 선언하면 Array[] 프로토타입으로 지정된다

image

Built-in Javascript API 또는 Javascript Native API라고 함

프로토타입은 객체 정보를 확장하는 것 뿐만 아니라, 여러 기능을 활용하기 위해 이미 사용하고 있었다.

클래스 : 추가적으로 기능을 제공하거나 성질을 바꾸는 것이 아니라 단순히 문법만 바꾸는 것.

문법 : 생성자 패턴을 통해 생성

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

var heejung = new Person('희정', 29);   // 생성 되었습니다.
function Person(name, age) {
    this.name = name;
    this.age = age;
}
var hee = new Person('희정', 29)

두 코드는 같은 코드라고 할 수 있다.

ES6 이전 → 하단과 같이 사용

class를 바벨을 돌려서 보면 하단과 같이 사용하고 있다는 것을 알 수 있음

class를 써도 기존의 프로토타입 기반의 성질이 유지된다. 클래스를 사용하지 않아도 충분히 관련 기능들을 사용할 수 있음.

타입스크립트의 클래스

class Person {
    // 클래스의 최상단에 멤버 변수를 정의해 주어야 함.
    private name: string;   // private: 변수 내부에서만 접근
    public age: number;     // public: 변수 외부에서도 접근 가능. 기본값
    readonly log: string;   // 접근만 할 수 있고 값을 변경할 수 없다.

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

섹션 10. 제네릭

제네릭 : 타입이 들어가는 언어 중 가장 많이 활용되는 문법

재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징

함수의 파라미터 개념으로 받게 되는 것.

// 제네릭 사용하지 않음
function logText(text) {
    console.log(text);
    return text;
}

// 타입을 지정하지 않았을 때는 자유자재로 넘길 수 있음
logText(10);    // 반환 : 숫자 10
logText('안녕');  // 반환 : 문자열
logText(true);  //반환 : 진위값

// 제네릭 사용
function log<T>(text: T):T {
    console.log(text);
    return text;
}

// 함수 호출 시 파라미터의 타입을 지정해 주는 것
log<string>('안녕');

제네릭을 사용하지 않고 함수로만 해결해 볼 때..

//text는 any -> 어떤 타입을 다 받을 수 있음
function logText(text) {
    console.log(text);
    text.split('').reverse().join('');  // 이건 string만 사용 가능. 숫자와 boolean을 받을 수 없다.
    return text;
}
logText(10);
logText('안녕');
logText(true);

숫자를 받기 위해서 number용 함수를 구현해 준다.

function logNumber(num: number) {
    console.log(num);
    return num;
}

여러 타입을 받으려고 여러 함수를 만드는 것이 좋은 것인가..? 중복을 만들기에 그닥 좋지는 않은 듯…

불필요한 코드가 생산되는 문제…

union을 사용하면 함수 하나에 여러 타입의 인자를 받을 수 있음.

function logText(text: string | number) {
    console.log(text);
    return text;
}
logText(10);
logText('안녕');

문제점 1. union의 경우, 공통된 속성값만 안에서 사용할 수 있기에, string과 number 각각에만 존재하는 속성들을 사용할 수 없고 공통된 속성만 사용할 수 있다.

문제점 2. 타입을 정확히 추론할 수 없음 - input 파라미터는 해결할 수 있어도 리턴값은 해결할 수 없다.

const a = logText('a');  //return값은 string|number
a.split('') // string을 넣었음에도 split 사용 불가.

제네릭의 사용 - 함수가 호출될 때 정의된다.

function logText<T>(text: T): T {
    console.log(text);
    return text;
}
const str = logText<string>('abc');
str.split('');
const flag = logText<boolean>(true);

제네릭 사용 예시

image

드롭다운 메뉴에 대한 것을 제네릭으로 만들어서 사용하고 싶음

우선 드롭다운 메뉴에 들어갈 것들 정의..

const emails: Email [] = [
    // value : select box에 들어가는 값
  { value: 'naver.com', selected: true },
  { value: 'gmail.com', selected: false },
  { value: 'hanmail.net', selected: false },
];

const numberOfProducts: ProductNumber[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

value가 각각 string과 number로 다르다.

interface 생성

interface Email {
  value: string;
  selected: boolean;
}

interface ProductNumber {
  value: number;
  selected: boolean;
}

union type을 사용하면 우선 해결할 수 있음

// 배열의 값들을 넘겨받아서 태그를 만들고 각각의 값들을 속성에 연결해주기
function createDropdownItem(item: Email | ProductNumber) {
  const option = document.createElement('option');
  option.value = item.value.toString();
  option.innerText = item.value.toString();
  option.selected = item.selected;
  return option; // option이라는 태그를 생성
}

// 이메일과 Number 모두 수용할 수 있는 드롭다운 목록을 만들어야 함
emails.forEach(function (email) {
  const item = createDropdownItem(email);
  const selectTag = document.querySelector('#email-dropdown');
  selectTag.appendChild(item);
});

numberOfProducts.forEach(function (product) {
  const item = createDropdownItem(product);
})

단점 → interface에 중복이 너무 많이 생긴다.

value의 타입이 달라진다면 계속 만들어야 한다..

interface에 제네릭 선언

// 타입 선언 시점에 제네릭으로 넘겨서 바꿔 보겠다.
interface Dropdown<T> {
    value: T;
    selected: boolean;
}

일반적인 인터페이스 사용

interface Dropdown {
    value: string;
    selected: boolean;
}
const obj: Dropdown = {value: 'abc', selected: false};

제네릭 사용

interface Dropdown<T> {
    value: T;
    selected: boolean;
}
const obj: Dropdown = {value: 'abc', selected: false}; //이러면 사용 불가
const obj: Dropdown<string> = {value: 'abc', selected: false}; //이러면 사용 가능

제네릭을 사용하여 코드 리팩토링

interface DropdownItem<T> {
  value: T;
  selected: boolean;
}

const emails: DropdownItem<string> [] = [
    // value : select box에 들어가는 값
  { value: 'naver.com', selected: true },
  { value: 'gmail.com', selected: false },
  { value: 'hanmail.net', selected: false },
];

const numberOfProducts: DropdownItem<number>[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false },
];

function createDropdownItem<T>(item: DropdownItem<T>) {
  const option = document.createElement('option');
  option.value = item.value.toString();
  option.innerText = item.value.toString();
  option.selected = item.selected;
  return option;
}

emails.forEach(function (email) {
  const item = createDropdownItem<string>(email);
  const selectTag = document.querySelector('#email-dropdown');
  selectTag.appendChild(item);
});

numberOfProducts.forEach(function (product) {
  const item = createDropdownItem<number>(product);
})

제네릭의 타입 제한

제네릭을 엄격하게 쓴다거나 다른 옵션들을 쓰고싶을 때

function logTextLength<T>(text: T[]):T[] {
    console.log(text.length);   //현재 타입스크립트 입장에서는 여기에 어떤 타입이 들어올 지 알 수 없음. length는 개발자만 안다.
    return text;
}
logTextLength(['hi', 'abc']);

배열 힌트를 주는 것 같이, 제네릭에 간단한 힌트를 제공할 수 있음

extends를 통해 제한을 할 수 있음

// 제네릭 타입 제한 2 - 정의된 타입 이용하기
interface LengthType {
    length: number;
}

// 제네릭으로 받은 T는 항상 LengthType의 하위이기 때문에, LengthType에서 제공하는 속성들을 받을 수 있다.
function logTextLength<T extends LengthType>(text:T):T {
    text.length;
    return text;
}

logTextLength('a');     //가능
logTextLength(1);       //불가능
logTextLength([1, 2, 3]);   //가능
logTextLength({length: 1});     //가능
interface ShoppingItem {
    name: string;
    price: number;
    stock: number;
}

// 쇼핑 아이템에서 특정 옵션만 받을 수 있도록
function getShoppingItemOption<T>(itemOption: T):T {
    return itemOption;
}

//제네릭을 쓰고 있기에 어떤 타입이든 사용할 수 있음
getShoppingItemOption(10);
getShoppingItemOption('a');

keyof를 사용한 제네릭 타입 제한

interface ShoppingItem {
    name: string;
    price: number;
    stock: number;
}

// 쇼핑 아이템에서 특정 옵션만 받을 수 있도록
// ShoppingItem의 key들 중 한가지가 이 타입이 된다.
// 파라미터로는 name, price, stock 중 한 가지가 들어가야 함.
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T):T {
    return itemOption;
}

//제네릭을 쓰고 있기에 어떤 타입이든 사용할 수 있음
getShoppingItemOption('name');
getShoppingItemOption('price');
getShoppingItemOption('stock');

카테고리:

업데이트: