타입스크립트 코드 테스트
https://www.typescriptlang.org/play
타입스크립트 핸드북
https://typescript-kr.github.io/pages/basic-types.html
타입스크립트란?
- TypeScript는 JavaScript에 추가적인 구문을 추가하여 editor와의 단단한 통합을 지원합니다. editor에서 초기에 오류를 잡을 수 있습니다.
- TypeScript 코드는 JavaScript가 실행되는 모든 곳(브라우저, Node.js 또는 Deno 및 앱 등)에서 JavaScript로 변환될 수 있습니다
- TypeScript는 JavaScript를 이해하고 타입 추론(type inference)을 사용하여 추가 코드 없이도 훌륭한 도구를 제공합니다.
Types of TS(기본)
- 배열: 자료형[]
- 숫자: number
- 문자열: string
- 논리: boolean
- optional
// Alias 타입
const player : {
name: string,
age?:number // 선택적 타입
} = {
name: "nico"
}
if(player.age && player.age >10) {
console.log("어린이")
}
예시 설명: name은 반드시 string, age는 undefined | number임
if문에서 player.age를 조건으로 쓰려면 player.age가 undefind가 아닐 경우를 상정해서 조건을 걸어야
// Alias 타입
type Player = {
name: string,
age?: number // 선택적 타입
}
const player : Player = {
name: 'nico'
}
const player2 : Player = {
name: 'meme',
age: 15
}
if(player.age && player.age >10) {
console.log("어린이")
}
// aregument의 타입을 지정하는 방법 => (name: string)
// return값의 타입을 지정하는 방법 => : Player
function playerMaker(name: string) : Player {
return {
name
}
}
//위와 같은 함수
const playerMaker = (name:string) : Player => ({item})
const nico = playerMaker("nico")
nico.age = 12
readonly
// Alias 타입
type Player = {
readonly name: string,
age?: number // 선택적 타입
}
nico.name = "ds"
const numbers: readonly number[] = [1,2,3,4]
number.push(5)
예시 설명: nico.name은 readonly라서 저렇게 하면 오류남
number.push(5) 안 됌
unknown
let a: unknown;
if(typeof a === 'number') {
let b = a + 1
}
if(typeof a === "string") {
let b = a.toUpperCase()
}
변수의 타입을 미리 알지 못 할 때 unknown을 사용
void
void는 아무것도 return하지 않는 함수를 대상으로 사용
function hello() {
console.log('x')
}
never
함수가 절대 return하지 않을 때 발생
function hello():never {
throw new Error("xxx") //에러를 발생시키는 함수
}
function hello(name:string|number) {
if(typeof name === "string") {
name //string
} else if (typeof name === "number") {
name //number
} else {
name //never
}
}
call signatures
type Add = (a:number, b:number) => number;
const add:Add = (a, b) => a + b
overloading
직접 작성하기보다 외부 라이브러리에 자주 보이는 형태로, 하나의 함수가 복수의 Call Signature를 가질 때 발생
type Config = {
path: string,
state: object
}
type Push = {
(path:string):void
(config: Config):void
}
const push: Push = (config) => {
if (typeof config === "string") {console.log(config)} //string
else {
console.log(config.path) //config
}
}
type Add = {
(a:number, b:number) : number
(a:number, b:number, c:number) : number
}
const add:Add = (a,b,c?:number) => { //c는 선택사항
if(c) return a + b + c
return a + b
}
Polymorephism(다형성)과 Generic(제네릭)
type SuperPrint = {
<T>(arr:T[]):void //제네릭
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
superPrint([1, 2, 3, 4])
superPrint([true, false, true])
superPrint(["a", "b"])
superPrint(["a", 1, 2, false])
type SuperPrint = {
<T>(arr:T[]):T
}
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1, 2, 3, 4])
const b = superPrint([true, false, true])
const c = superPrint(["a", "b"])
const d = superPrint(["a", 1, 2, false])
type SuperPrint = {
<T, M>(arr:T[], b:M):T //타입스크립트에게 T는 함수의 첫번째 파라미터로 배열이 올거라고 말해주고 M은 두번쨰 파라미터로 들어온다고 말해
}
const superPrint: SuperPrint = (arr) => arr[0]
const a = superPrint([1, 2, 3, 4], true)
const b = superPrint([true, false, true], "a")
const c = superPrint(["a", "b"], 1)
const d = superPrint(["a", 1, 2, false], 1)
// 타입을 만드는 방법
type Words = {
[key:string]:string //프로퍼티의 이름은 모르지만 타입은 알 때 씀
}
class Dict {
private words: Words //프로퍼티를 만들고 원하는 대로 초기화를 해주면 됌
constructor(){
this.words = {}
}
add(word: Word){ //클래스는 타입처럼 쓸 수 있음
if(this.words[word.term] === undefined) {
this.words[word.term] = word.def
}
}
def(term:string){
return this.words[term]
}
}
class Word {
constructor(
public term:string,
public def:string
) {}
}
const kimchi = new Word("kimchi", "한국의 음식")
const dict = new Dict()
// dict.add(kimchi)
dict.def("kimchi")
type Words = { // 해시
[key: string]: (string | string[])
//객체의 property에 대해 모르지만 타입만을 알 때 유용하다
}
class Dict {
private words: Words
constructor() {
this.words = {}
}
add(word: Word) { // word는 Word 클래스의 인스턴스 타입.
if(!this.words[word.term]) {
// 사전에 없는 단어이면
this.words[word.term] = word.def;
}
}
find(term: string) {
return this.words[term];
}
// 단어를 삭제
rmv(term: string) {
delete this.words[term];
}
// 단어 이름 업데이트
update(oldTerm: string, newTerm: string) {
if(this.words.hasOwnProperty(oldTerm)) {
this.words[newTerm] = this.words[oldTerm];
delete this.words[oldTerm];
}
}
// 사전에 저장된 단어의 개수
size() {
return Object.keys(this.words).length;
}
// 모든 사전의 이름과 뜻 출력
all() {
for(let [key, value] of Object.entries(this.words)) {
console.log(`${key}: ${value}`)
}
}
}
// words는 initializer 없이 선언해주고 contructor에서 수동으로 초기화
// constructor에 인자로 넣어 constructor가 지정해주길 바라는 게 아니므로
// 각각의 단어에 대한 클래스
class Word {
constructor(
public term: string, public def: (string| string[])
) {}
// 단어 출력하는 메소드
toString() {
console.log(`${this.term}: [뜻] ${this.def}`);
}
// 단어 정의 추가
addDef(newDef : string) {
if(typeof this.def === 'string') {
this.def = [this.def, newDef]
} else {
this.def = [...this.def, newDef];
}
}
// 단어 정의 수정
updateDef(oldDef : string, newDef: string) {
if(typeof this.def === 'string') {
if(oldDef === this.def) this.def = newDef
} else {
this.def.filter(val => val !== oldDef);
this.def.push(newDef);
}
}
}
/** 출력 */
const kimchi = new Word("kimchi", "한국의 음식");
const tang = new Word("연근 갈비탕", "중국의 음식");
const sushi = new Word("스시", "일본의 음식");
kimchi.addDef("고춧가루로 배추를 버무려 숙성 및 발효시킨 음식")
kimchi.toString(); // kimchi: 한국의 음식,고춧가루로 배추를 버무려 숙성 및 발효시킨 음식
tang.toString() // 연근 갈비탕: 중국의 음식
sushi.updateDef("일본의 음식", "밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식");
sushi.toString(); // 스시: 밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식
const dict = new Dict();
dict.add(kimchi);
dict.add(tang);
dict.add(sushi);
dict.all()
// kimchi: 한국의 음식,고춧가루로 배추를 버무려 숙성 및 발효시킨 음식
// 연근 갈비탕: 중국의 음식
// 스시: 밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식
dict.find("kimchi");
// (2) ['한국의 음식', '고춧가루로 배추를 버무려 숙성 및 발효시킨 음식']
dict.size()
// 3
dict.update("kimchi", "김치")
dict.all()
// 연근 갈비탕: 중국의 음식
// 스시: 밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식
// 김치: 한국의 음식,고춧가루로 배추를 버무려 숙성 및 발효시킨 음식
dict.rmv("연근 갈비탕");
dict.all()
// 스시: 밥을 뭉쳐놓고 그 위에 재료를 얹어낸 음식
// 김치: 한국의 음식,고춧가루로 배추를 버무려 숙성 및 발효시킨 음식
interface
interface와 type의 차이
- 둘은 타입스크립트에서 오브젝트의 구조를 알려주는 같은 작업을 수행할 수 있지만 다른 기능 들이 있음
- 추상클래스는 유용한데, 다른 클래스가 가져야 할 property랑 메소드를 명시할 수 있도록 도와줌
- 인터페이스는 추상클래스와 비슷한 보호를 제공하지만, 인터페이스는 자바스크립트 파일에서 보이지 않음
파일 크기자 좀 더 커지고, 추가 클래스가 만들어짐 - 만약 추상클래스를 다른클래스들이 특정 모양을 따르도록 하기 위한 용도로 쓴다면 인터페이스를 쓰는 것이 나음
- 첫번 째, 기억할 것. 타입을 쓰고 싶다면 type 키워드를 쓰기
한 가지 옵션은 오브젝트의 모양을 설명하는 것이고 다른 옵션은 alias를 만드는 것, 그리고 또 다른 타입을 특정된 값으로 만드는 거
abstract class User { //이 추상클래슨 두 개의 메소드를 가지고 있음, 자바스크립트에서 보임
constructor (
protected firstName: string,
protected lastName: string
) {}
abstract sayHi(name:string): string
abstract fullName():string
}
//상속받은 클래스가 어떻게 동작해야할 지 일러주기 위해서 추상클래스를 사용함
class Player extends User {
fullName() {
return `${this.firstName} ${this.lastName}`
}
sayHi(name:string) {
return `Hello ${name}. My name is ${this.fullName}`
}
}
//파일 사이즈를 줄임 //자바스크립트 코드로 컵파일 되지 앉음(자바스크리브에서 안 보임)
interface User2 { //인터페이스는 오브젝트나 클래스의 모양을 묘사하도록 해줌
firstName: string,
lastName: string,
sayHi(name:string): string
fullName():string
}
interface Human {
health:number
}
class Player2 implements User2, Human {
constructor (
public firstName: string,
public lastName: string,
public health: number
) {}
fullName() {
return `${this.firstName} ${this.lastName}`
}
sayHi(name:string) {
return `Hello ${name}. My name is ${this.fullName}`
}
}
function makeUser(user: User2) {
return "Hi"
}
makeUser({
firstName: "nico",
lastName: "las",
fullName: () => "xx",
sayHi: (name) => "string"
})
같은 기능을 추상클래스와 interface로 만드려면~
type PlayerA = {
name: string
}
type PlayerAA = PlayerA & {
lastName: string
}
//type PlayerAA는 이미 정의되어서 이렇게 하면 안 됌
// type PlayerAA = PlayerA & {
// health: number
// }
const playerA: PlayerAA = {
name:"nico",
lastName: "las"
}
////
interface PlayerB {
name:string
}
interface PlayerBB extends PlayerB {
lastName:string
}
interface PlayerBB {
health:number
}
const playerB: PlayerBB = {
name:"nico",
lastName:"las",
health: 10
}