Today I Learned_230403
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의 프로토타입을 통해 상속해서 지워보자.
admin으로 아무것도 받지 않아도 proto를 지정해 주면, admin에서 상위 user의 값인 name, age를 접근해서 사용할 수 있다.
admin의 기본적인 객체의 모양을 user로부터 내려받아 사용.
이렇게 추가적인 데이터를 넣어서 사용할 수 있음.
javscript에서 객체를 만들면, 해당 객체의 최상위 prototype은 Object가 된다.
Object에 매핑된 속성들을 사용할 수 있음.
array를 선언하면 Array[] 프로토타입으로 지정된다
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);
제네릭 사용 예시
드롭다운 메뉴에 대한 것을 제네릭으로 만들어서 사용하고 싶음
우선 드롭다운 메뉴에 들어갈 것들 정의..
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');