함수 (Function)
웹 애플리케이션을 구현할 때 자주 사용되는 함수는 타입스크립트로 크게 다음 3가지 타입을 정의할 수 있습니다.
- 함수의 파라미터(매개변수) 타입
- 함수의 반환 타입
- 함수의 구조 타입
함수의 기본적인 타입 선언
자바스크립트
function sum(a, b) {
return a + b;
}
타입스크립트 - 1
// any type 오류를 나게 하자
// tsconfig.json => "noImplicitAny": true
function sum(a, b) {
return a + b;
}
타입스크립트 - 2
function sum(a: number, b: number): number {
return a + b;
}
기존 자바스크립트 함수의 선언 방식에서 매개변수 와 함수의 반환 값에 타입을 추가하였습니다.
💡 TIP
함수의 반환 값에 타입을 정하지 않을 때는 void라도 사용
함수의 인자
타입스크립트에서는 함수의 인자를 모두 필수 값으로 간주합니다. 따라서, 함수의 매개변수를 설정하면 undefined나 null이라도 인자로 넘겨야하며 컴파일러에서 정의된 매개변수 값이 넘어 왔는지 확인합니다. 달리 말하면 정의된 매개변수 값만 받을 수 있고 추가로 인자를 받을 수 없다는 의미입니다.
function sum(a: number, b: number): number {
return a + b;
}
sum(10, 20); // 30
sum(10, 20, 30); // error, too many parameters
sum(10); // error, too few parameters
위와 같은 특성은 정의된 매개변수의 갯수 만큼 인자를 넘기지 않아도 되는 자바스크립트의 특성과 반대됩니다. 만약 이러한 특성을 살리고 싶다면 ? 를 이용해서 아래와 같이 정의할 수 있습니다.
function sum2(a: number, b?: number): number {
return a + b;
}
sum(10, 20); // 30
sum(10, 20, 30); // error, too many parameters
sum(10); // 타입 에러 없음
매개변수 초기화는 ES6 문법과 동일합니다.
function sum(a: number, b = 100): number {
return a + b;
}
const result1 = sum(10, undefined); // 110
const result2 = sum(10, 20, 30); // error, too many parameters
const result3 = sum(10); // 110
console.log({ result1, result3 });
REST 문법이 적용된 매개변수
ES6 문법에서 지원하는 **Rest 문법**은 타입스크립트에서 다음과 같이 사용할 수 있습니다.
function sum1(a: number, ...nums: number[]): number {
let totalOfNums = 0;
for (let key in nums) {
totalOfNums += nums[key];
}
return a + totalOfNums;
}
const result = sum1(10, 20, 30, 40); // 100
console.log(result);
This
타입스크립트에서 자바스크립트의 this가 잘못 사용되었을 때 감지할 수 있습니다. 만약 this가 뭔지 모른다면 일단 다음 글을 먼저 확인하고 읽어주세요.
Understanding Javascript Function Invocation and this https://velog.io/@padoling/JavaScript-화살표-함수와-this-바인딩
타입스크립트에서 this가 가리키는 것을 명시하려면 아래와 같은 문법을 사용합니다.
function 함수명(this: 타입) {
// ...
}
그럼 위 문법을 실제 예제에 적용해보겠습니다.
interface Node {
count: number;
init(this: Node): () => {};
}
let nd: Node = {
count: 10,
init: function (this: Node) {
return () => {
return this.count;
};
},
};
let getCount = nd.init();
let count = getCount();
console.log(count); // 10
리터럴 타입(Literal)
리터럴 타입은 집합 타입의 보다 구체적인 하위 타입입니다. 이것이 의미하는 바는 타입 시스템 안에서 "Hello World"는 string이지만, string은 "Hello World"가 아니란 것입니다.
오늘날 TypeScript에는 문자열과 숫자, 두 가지 리터럴 타입이 있는데 이를 사용하면 문자열이나 숫자에 정확한 값을 지정할 수 있습니다.
문자열 리터럴 타입 (String Literal Types)
// @errors: 2345
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
} else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else {
// 하지만 누군가가 타입을 무시하게 된다면
// 이곳에 도달하게 될 수 있습니다.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy");
숫자형 리터럴 타입 (Numeric Literal Types)
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}
const result4 = rollDice();
유니언과 교차 타입(Union, Intersection)
Union Type ⇒ 여러 타입들을 조합하여 사용하는 방법
function printId(id: number | string) {
console.log(id.toUpperCase());
// string | number' 형식에 'toUpperCase' 속성이 없습니다.
// 'number' 형식에 'toUpperCase' 속성이 없습니다.
// type narrowing
// if(typeof id === "string"){
// console.log(id.toUpperCase());
// }
}
Intersection Type ⇒ 유니언 타입은 조합해서 사용한다고 하면 교차 타입은 여러 가지 타입을 결합해서 사용한다고 생각하면 된다.
type Common = {
name: string,
age: number,
gender: string
}
type Animal = {
howl: string
}
type Cat = Common & Animal;
type Dog = Common | Animal;
let dog: Dog = {
howl: 'dogggg'
}
let cat: Cat = {
age: 3,
gender: 'C',
name: 'CC',
howl: 'cattttt'
}
type, interface 차이
type ⇒ interface는 타입과 마찬가지로 객체의 타입의 이름을 지정하는 또 다른 방법입니다.
interface AnimalInterface {
species: string;
height: number;
weight: number;
}
const tiger: AnimalInterface = {
species: "tiger",
height: 200,
weight: 300,
};
type AnimalType = {
species: string;
height: number;
weight: number;
};
const lion: AnimalType = {
species: "lion",
height: 180,
weight: 400,
};
- typescript type과 interface의 차이 - https://bny9164.tistory.com/48
Class
readonly
클래스의 속성에 readonly 키워드를 사용하면 아래와 같이 접근만 가능합니다.
class Developer {
readonly name: string;
constructor(theName: string) {
this.name = theName;
}
}
let john = new Developer("John");
john.name = "John"; // error! name is readonly.
Accessor
타입스크립트는 객체의 특정 속성의 접근과 할당에 대해 제어할 수 있습니다. 이를 위해선 해당 객체가 클래스로 생성한 객체여야 합니다. 아래의 간단한 예제를 봅시다.
class Developer {
name: string;
}
const josh = new Developer();
josh.name = "Josh Bolton";
export { Developer };
위 코드는 클래스로 생성한 객체의 name 속성에 Josh Bolton이라는 값을 대입한 코드입니다. 이제 josh라는 객체의 name 속성은 Josh Bolton이라는 값을 갖겠죠.
여기서 만약 name 속성에 제약 사항을 추가하고 싶다면 아래와 같이 get과 set을 활용합니다.
class Developer {
private _name: string;
get name(): string {
return this._name;
}
set name(newValue: string) {
if (newValue && newValue.length > 5) {
throw new Error("이름이 너무 깁니다");
}
this._name = newValue;
}
}
const josh = new Developer();
josh.name = "Josh";
console.log(josh.name); //Josh
export { Developer };
<aside> 💡 TIP
get만 선언하고 set을 선언하지 않는 경우에는 자동으로readonly로 인식됩니다.
</aside>
- TypeScript - 클래스, 접근제한자 - https://iagreebut.tistory.com/269
Abstract Class
추상 클래스(Abstract Class)는 인터페이스와 비슷한 역할을 하면서도 조금 다른 특징을 갖고 있습니다. 추상 클래스는 특정 클래스의 상속 대상이 되는 클래스이며 좀 더 상위 레벨에서 속성, 메서드의 모양을 정의합니다.
abstract class Developer {
abstract coding(): void; // 'abstract'가 붙으면 상속 받은 클래스에서 무조건 구현해야 함
drink(): void {
console.log("drink sth");
}
}
class FrontEndDeveloper extends Developer {
coding(): void {
// Developer 클래스를 상속 받은 클래스에서 무조건 정의해야 하는 메서드
console.log("develop front");
}
design(): void {
console.log("design front");
}
}
class BackEndDeveloper extends Developer {
coding(): void {
// Developer 클래스를 상속 받은 클래스에서 무조건 정의해야 하는 메서드
console.log("develop server");
}
design(): void {
console.log("design server");
}
}
// const dev = new Developer(); // error: cannot create an instance of an abstract class
const josh = new BackEndDeveloper();
const kai = new FrontEndDeveloper();
josh.coding(); // develop server
josh.drink(); // drink sth
josh.design(); // design server
console.log("");
kai.coding(); // develop front
kai.drink(); // drink sth
kai.design(); // design front
export { Developer };
- 추상클래스 - 추상 클래스를 정의할 때는 class 앞에 abstract라고 표기합니다. 또한 추상 메서드를 정의할 때도 abstract를 메서드 이름 앞에 붙입니다. 추상 메소드는 정의만 있을 뿐 몸체(body)가 구현되어 있지 않습니다. 몸체는 추상 클래스를 상속하는 클래스에서 해당 추상 메소드를 통해 필히 구현해야 합니다.그리고 추상 클래스는 추상 메서드 뿐만 아니라, 실 사용이 가능한 메서드도 정의할 수 있습니다. 추상 클래스를 상속하는 클래스를 통해 생성된 인스턴스는 이 메서드를 사용할 수 있습니다. 추상 클래스는 말 그대로 추상이므로 클래스와 달리 인스턴스를 생성하지 않습니다. 생성 구문을 사용하면 오류가 발생합니다.
- 추상 클래스(Abstract) 용도 완벽 이해하기 - https://inpa.tistory.com/entry/JAVA-☕-추상-클래스Abstract-용도-완벽-이해하기
- 메소드 오버라이딩 - http://www.tcpschool.com/java/java_inheritance_overriding
포켓몬 모음집 만들기
README.md
# 설치 모듈
- npm i typescript -D (타입 스크립트 사용)
```bash
npm i typescript -D
tsconfig.json
{
// include : 컴파일할 파일 경로를 설정합니다. [ src폴더 하위의 모든 .ts 확장자를 가진 파일 ]
"include": ["src/**/*.ts"],
"compilerOptions": {
// es5 에서는 promise 사용 불가 => es6(es2015)추가 및 새로운 값 추가시 dom도 같이 추가
"lib": ["ES2015", "dom"],
// esModuleInterop 속성이 위의 코드 처럼 true로 설정될 경우, ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 됩니다.
// e.g. typescript-test.ts => import express from "express"
"esModuleInterop": true,
"target": "ES5",
// rootDir : 컴파일될 타입스크립트 코드들의 위치를 명시합니다.
// "rootDir": "src",
// outDir : 컴파일 후 생성되는 js파일이 생성될 폴더명
"outDir": "dist",
// strictNullChecks
"strictNullChecks": true,
// 암시적 any 타입을 허용하지 않는다
"noImplicitAny": true
}
}
lib 값 - https://www.typescriptlang.org/tsconfig#lib
dom 추가 이유 - https://github.com/ynab/ynab-sdk-js/issues/96
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>포켓몬스터</title>
</head>
<body>
<main>
<h1>포켓몬스터 카드</h1>
<div id="app"></div>
</main>
<script src="./dist/app.js"></script>
</body>
</html>
style.css
@import url("<https://fonts.googleapis.com/css2?family=Caveat:wght@400;700&display=swap>");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #333;
line-height: 1.5;
font-family: "Caveat", cursive;
}
main {
padding: 1rem;
max-width: 1100px;
margin: auto;
}
main > h1 {
text-align: center;
color: #e4c439;
margin-bottom: 2rem;
font-size: 2rem;
text-transform: uppercase;
}
#app {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
grid-gap: 1.5rem;
justify-content: center;
align-items: center;
}
.card {
width: 12rem auto;
background: #444;
color: #e4c439;
padding: 1rem;
border-radius: 10px;
border-top: 0.5px solid #cebf7b;
border-bottom: 0.5px solid #cebf7b;
text-align: center;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
position: relative;
overflow: hidden;
cursor: pointer;
transition: 0.2s ease-in-out all;
}
.card:hover {
transform: scale(1.05);
}
.card--id {
background: #cebf7b;
width: 3rem;
color: #333;
padding: 0.1rem;
font-weight: 700;
position: absolute;
border-radius: 0 0 10px 0;
top: 0;
left: 0;
}
.card--name {
text-transform: capitalize;
color: #fff;
font-size: 2rem;
font-weight: 700;
}
.card--image {
width: 150px;
display: block;
margin: auto;
}
.card--details {
font-size: 1.3rem;
color: #dbca80;
}
src/app.ts
app 엘리먼트를 담을 container 변수생성
보여줄 포켓몬 최대 수를 지정하는 pokemons 변수 생성
IPokemon - 우리가 api에서 가져올 포켓몬 객체 형식을 지정하는 인터페이스 생성
// getElementById 정의로 가보면 HTMLElement, null을 리턴 받는걸 확인
const container: HTMLElement | null = document.getElementById("app");
const pokemons: number = 100;
interface IPokemon {
id: number;
name: string;
image: string;
type: string;
}
- element type - https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts (HTMLElementTagNameMap)
src/app.ts
포켓몬 정보를 불러오는 getPokemon 함수를 100번 실행시킬 fetchData 함수 구현
const fetchData = (): void => {
for (let i = 1; i <= pokemons; i++) {
getPokemon(i);
}
};
src/app.ts
인자값으로 1~100 값을 받고 포켓몬 api를 요청하고 데이터를 가공 한 후(getPokemon) html에 포켓몬 card를 추가하는 showPokemon 함수 구현
const getPokemon = async (id: number): Promise<void> => {
const data: Response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const pokemon: any = await data.json();
const pokemonType: string = pokemon.types
.map((poke: any) => poke.type.name)
.join(", ");
const transformedPokemon: IPokemon = {
id: pokemon.id,
name: pokemon.name,
image: `${pokemon.sprites.front_default}`,
type: pokemonType,
};
showPokemon(transformedPokemon);
};
const showPokemon = (pokemon: IPokemon): void => {
let output: string = `
<div class="card">
<span class="card--id">#${pokemon.id}</span>
<img class="card--image" src=${pokemon.image} alt=${pokemon.name} />
<h1 class="card--name">${pokemon.name}</h1>
<span class="card--details">${pokemon.type}</span>
</div>
`;
if (container) {
container.innerHTML += output;
}
};
src/app.ts
fetchData 실행
fetchData();
tsc 명령어로 app.ts => app.js 변환 후 결과 확인!
💡 포켓몬 결과 순서가 동기적으로 안 나오는 문제는 각자 해결해보세요! 강의에서 설명드린대로 엄청 쉽습니다!
'코딩캠프 > 내일배움캠프' 카테고리의 다른 글
[ TIL ] 01.20(금) 50일차 (0) | 2023.01.20 |
---|---|
[ TIL ] 01.19(목) 49일차 (0) | 2023.01.19 |
[ TIL ] 01.17(화) 47일차 (0) | 2023.01.17 |
[ TIL ] 01.16(월) 46일차 (0) | 2023.01.16 |
[ WIL ] 01.09~14 9주차 (0) | 2023.01.15 |