이 글은 노마드코더 챌린지 진도에 맞게 노마드코더 Typescript로 블록체인 만들기 강의를 듣고 그 내용을 정리한 것입니다. 챌리지의 과제는 노마드코더 챌린지 규칙상 공유드릴 수 없음을 알려드립니다.
1. Introduction
누가 타입스크립를 사용해야할까?
- 더 나은 생산성과 더 나은 사용자 경험을 원하는 Javascript 개발자
- 타입 사용에 익숙한 Java, C 등을 사용하는 개발자
왜 자바스크립트는 문제인가?
1. 자바스크립트는 매우 유연한 언어이다. ⇒ 개발자가 잘못된 코드를 입력해도 최대한 이해하려고함.
- []+false ⇒ string
- 함수에서 인자를 하나만 보내도 실행됨.
function devide(a, b) {
return a / b
}
divide(2, 3) // 0.66666666666666
divide('XXXXXXXX') //NaN
- 객체에 존재하지 않는 함수도 호출할 수 있음.
const nico = { name:'nico' }
nico.hello() //Uncaught TypeError: nico.hello is not a function
- 그래서 코드를 실행하기 전까지는 에러를 알 수 없다. 실행 후 에러가 발생하는 문제 빈번 (런타임에러) → 유저가 직접 에러를 마주하게 됨.
2. Overview of Typescript
- 코드를 실행하기 전에 에러를 잡고싶다!
- 타입스크립트는 자바스크립트로 변환된다. 타입스크립트가 에러를 감지하면 javascript로 컴파일 되지 않는다.
Implicit Types vs Explicit Types
- 타입스크립트가 저절로 추론하여 타입을 정한다.
- 명시적으로 타입을 알려준다.
- 대개의 경우 타입스크립트가 타입을 추론하도록 두는 것은 좋다. 그러나 때로 직접 명시해줘야할 때가 있음
const c = [1, 2, 3] c.push("1") // 이미 c가 숫자들을 담는 배열임을 추론하고 있으므로 저절로 오류 발생시킴 const c : number[] = [] c.push("1") //빈배열일 경우 직접 명시해주어야함.
- 대개의 경우 타입스크립트가 타입을 추론하도록 두는 것은 좋다. 그러나 때로 직접 명시해줘야할 때가 있음
Types of Typescript part.I
- 객체 타입 선언하기, 선택적으로 사용하고 싶으면 type뒤에 ?붙이면 됨.
const nicoPlayer : {
name:string,
age?:number
} = {
name: "nico"
}
- type Alias(별칭)
: 1번에서 player가 여러명이라면 똑같으 타입 코드를 반복해서 작성해야한다. 이럴 때 Alias를 사용하면 코드를 재사용해 간결하게 할 수 있다.
type Player = {
name:string;
age?:number;
}
const nico : Player = {
name: "nico"
}
const sujin : Player = {
name: "sujin",
age: 27
}
- 함수 타입
// string타입의 name 인수를 받아 Player 타입을 리턴하는 함수
function playerMaker(name:string):Player {
return {
name: name
}
}
//화살표함수
const playerMaker => (name:string):Player =>({name:name})
Types of Typescript part.II
readonly
: 변경 불가능
type Player = {
readonly name:string;
age?:number;
}
const numbers : readonly number[] = [1, 2, 3, 4];
numbers.push(1) //Error발생
Tuple
: array를 만듦, 최소한의 길이, 특정위치에 특정타입이 있어야함.
// 이름, 나이, 챔피언 여부 [string, number, boolean]
const player: [string, number, boolean] = ["nico", 20, true];
undefined
,null
,any
- 선택적 타입은 undefined가 될 수 있다.
let a : undefined = undefined;
let b : null = null;
- 비어있는 값을 쓰면 any가 default
- any는 어떤 타입이든 될 수 있다. Typescript를 빠져나오는 방법. 사용지양
Types of Typescript part.III
unknown
: 변수타입을 미리알 수 없을 때 사용. 사용하기 전에 타입을 확인하도록 보호받을 수 있음.
let a:unknown;
let b = a + 1; //Error
if (typeof a === 'number') {
let b = a + 1;
}
void
: 아무것도 return하지않는 함수. 대개는 void를 직접 지정할 필요는 없음.
never
- 함수가 절대 return하지 않을 때 발생. (가령 오류를 내뱉는 함수처럼)
function hello():never { reutrn "X"; } //Error function hello():never { throw new Error("xxx"); }
- 타입이 두가지일 수도 있는 상황에서 발생.
function hello(name:string|number) { if( typeof name === "string") { // name type string } else if (typeof name === "number") { // name type number } else { name // name type never } }
- 함수가 절대 return하지 않을 때 발생. (가령 오류를 내뱉는 함수처럼)
3. Functions
Call signature
type Add = (a:number, b:number) => number; //call signature
type Add = {
(a:number, b:number) : number
}
const add:Add = (a, b) => a + b;
Overloading
: 여러개의 서로다른 call signature를 갖고 있는 함수
: overloading 된 함수를 작성할일은 많지 않지만 외부 패키지, 라이브러리에서 굉장히 많이 사용.
type Add = {
(a:number, b:number) : number;
(a:number, b:string) : number;
}
- Next.js 예시 - 둘다 작동함
Router.push("/home");
Router.push({
path: "/home",
state: 1
});
//type
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)
else {
console.log(config.path, config.state)
}
}
- 파라미터의 갯수가 다른 경우
type Add = {
(a:number, b:number):number,
(a:number, b:number, c:number):number
}
const add:Add = (a, b, c?:number) => {
if(c) return a + b + c
return a + b
}
Polymorphism(다형성) : Generic(제네릭)
: many + structure
type SuperPrint = {
(arr:number[]):void
(arr:boolean[]):void
(arr:string[]):void
}
const superPrint:SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
superPrint([1,2,3,4])
superPrint([true,false,true])
superPrint(["a","b","c"])
superPrint([1,2,true,false])//Error
제네릭(Generic)
이란 type의 placeholder 같은 것. call signiture를 작성할 때 들어올 타입을 정확히 모를때 사용하면, 타입스크립트가 상황에따라 타입을 유추한다.
- 대문자로 시작한다면 어떤 이름이라도 상관없음.
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","c"])
superPrint([1,2,true,false])
- 제네릭을 여러개 사용할 경우
type SuperPrint = {
<T,M>(arr:T[], b:M):void
}
- 제네릭 확장예시
type Player<E> = {
name: string
extraInfo: E
}
type NicoPlayer = Player<{favFood:string}>
const nico:NicoPlayer = {
name: "nico",
extraInfo: {
favFood: "kimchi"
}
}
const lynn:Player<null> = {
name: "lynn",
extraInfo: null
}
- 다양한 제네릭의 활용 : 타입스크립트의 타입들은 대부분 제네릭으로 선언되어있다. ex) Array<T>, useState<T>
number[] == Array<number>
useState<number>()
4. Classes and Interfaces
Classes
- 타입스크립트로 클래스 선언하기
class Player { constructor( private firstName:string, private lastName:string, public nickname:string ) {} } const nico = new Player("nico", "las", "니꼬"); nico.firstName //Errror : firstName is private // 자바스크립트에선 에러 안남
- Abstract class(추상클래스) **Typescript 기능, Javascript에 없음
: 다른 클래스가 상속받을 수 있는 클래스, 직접 새로운 인스턴스를 만들 수는 없음
abstract class User { constructor( private firstName:string, private lastName:string, public nickname:string ) {} getFullName() { return `${this.firstName} ${this.lastName}` } private getFirstName() { return this.firstName } //private나 public은 property뿐만 아니라 method에서도 작동한다. } class Player extends User {} const nico = new Player("nico", "las", "니꼬"); nico.getFullName(); nico.getFirstName(); //Errror : getFirstName is private
- Abstract method(추상 메소드) **Typescript 기능, Javascript에 없음
: 추상클래스를 상속받는 모든 것들이 구현해야하는 메소드를 의미
abstract class User { constructor( -private +protected firstName:string, private readonly lastName:string, // readonly : 읽을 순 있지만 수정은 안됨. public nickname:string ) {} abstract getFirstName():void getFullName() { return `${this.firstName} ${this.lastName}` } } // User를 상속받은 클래스이므로 getFirstName을 구현하지 않으면 에러남 class Player extends User { getFirstName() { //Player가 User를 상속받았으나 private 필드에는 여전히 접근할 수 없음 //firstName을 private대신 protected를 사용하면됨. console.log(this.firstName) //Error } } const nico = new Player("nico", "las", "니꼬");
Interfaces part.I (타입 vs 인터페이스)
- type
- type은 모든 될 수 있다. 특정값, string, object 등등
- property 축적되지 않음
type Team = "red" | "blue" | "yellow"; type Health = number; type Player = { nickname: string, team: Team, health: Health } const nico :Player = { nickname: "nico", team: "pink", //Error : Team에서 정한 특정값만을 가져야함. health: 10 } //상속 type User = { name:string; } type Player = User & { } const nico:Player = { name: "nico" }
- interfaces
- object나 class 모양을 특정할 때 사용.
- 상속시 클래스와 유사한 인터페이스
- property가 축적됨
type Team = "red" | "blue" | "yellow"; type Health = number; interface Player { nickname: string, team: Team, health: Health } const nico :Player = { nickname: "nico", team: "pink", //Error : Team에서 정한 특정값만을 가져야함. health: 10 } //상속 interface User { name:string; } interface Player extends User { } const nico:Player = { name: "nico } //property 축적 interface User { name:string; } interface User { lastName:string; } interface User { health:number; } const nico: User = { name: "nic0", lastName: "ge", health:10 }
Interfaces part.II (추상클래스 대신 인터페이스 사용하기)
- 추상클래스
abstract class User { constructor( protected firstName:string, protected lastName:string ) {} abstract sayHi(name:string):string abstract fullName():string } // User를 상속받는 클래스는 sayHi와 fullName을 반드시 구현해야함 // firstName과 lastName을 갖고있음 class Player extends User { fullName() { return `${this.firstName} ${this.lastName}` } sayHi(name:string) { return `Hello ${name}. My name is ${this.fullName()}` } }
- 추상클래스는 상속받을 클래스들이 꼭 가져야하는 메서드 등을 정해둘 수 있어 좋지만, 자바스크립트로 컴파일되면 일반 클래스로 바뀌기때문에 의미가 사라짐. 이때 interface를 사용하면 유용. interface는 컴파일되면 사라짐.
- 추상클래스 대신 인터페이스로 구현하기
- 추상클래스와 동일하게 원하는 property를 가지도록 강제하고 싶음.
- 인터페이스를 상속받은 property는 private이나 protected사용불가
interface User {
protected firstName:string,
protected lastName:string,
sayHi(name:string):string
fullName():string
}
interface Human {
health:number
}
class Player implements User, 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()}`
}
}
- 추상클래스 대신 타입으로 구현하기 : 인터페이스 방식과 동일
type PlayerA = { firstName:string; } class User implements PlayerA { constructor( public firstName:string ){} }
Polymorphism(다형성)
: 다른모양의 코드를 가질 수 있게 해 주는 것 → 제네릭 사용
interface IStorage<T> {
[key:string]:T
}
class LocalStorage<T> {
private storage:IStorage<T> = {}
set(key:string, value:T){
this.storage[key] = value;
}
remove(key:string){
delete this.storage[key]
}
get(key:string):T {
return this.storage[key]
}
clear() {
this.storage = {}
}
}
const stringsStorage = new LocalStorage<string>()
stringsStorage.get("ff")
const booleanStorage = new LocalStorage<boolean>()
booleanStorage.get('ssss')
5 [2022 UPDATE] TYPESCRIPT BLOCKCHAIN
Targets
// tsconfig.json
{
"include": ["src"],
"compilerOptions": {
"outDir": "build",
"target": "ES6"
}
}
- include 배열: 자바스크립트로 컴파일하고 싶은 모든 디렉토리
- outDir: 자바스크립트 파일이 생성될 디렉토리
- target: 컴파일될 ES 버전
Lib Configuration
- lib: 합쳐진 라이브러리의 정의 파일을 특정해주는 역할
- 코드가 어디서 동작할 것인지 알려줄 수 있음. ex) “DOM”을 입력하면 브라우저API 자동완성 사용가능
// tsconfig.json
{
...
"lib": ["ES6", "DOM"]
}
}
Declaration Files
- strict mode를 설정하면 엄격한 타입검사 옵션이 활성화되고, d.ts 파일이 없는 경우도 에러 발생시킴.
// tsconfig.json
{
...
"strict": true
}
}
- 자바스크립트 라이브러리들의 타입을 정의하는 파일
d.ts
//myPackage.js : 외부 라이브러리라고 가정
export function init(config) {
return true;
}
export function exit(code) {
return code + 1;
}
//myPackage.d.ts : myPackage의 타입 선언
interface Config {
url: string;
}
declare module "myPackage" {
function init(config: Config): boolean;
function exit(code: number): number;
}
- lib.d.ts: 타입스크립트의 기본적인 타입 정의를 포함하고 있음.
JSDoc
- allowJS : ts와 js를 혼용해서 사용할 경우 사용.
// tsconfig.json { ... "strict": true, "allowJs" : true } }
- js에서 ts 마이그레이션할 때 ts로 바꾸기 곤란한 js파일의 타입을 체크하고 싶다면? →
//@ts-check
: type error 발생시켜줌//myPackage.js // @ts-check export function init(config) { return true; } export function exit(code) { return code + 1; }
- js에서 ts 마이그레이션할 때 ts로 바꾸기 곤란한 js파일의 타입 보호를 받고싶다면? → JSDoc :
/** */
안에 작성// @ts-check /** * initializes the project * @param {object} config * @param {boolean} config.debug * @param {string} config.url * @returns {boolean} */ export function init(config) { return true; } /** * Exits the program * @param {number} code * @returns {number} */ export function exit(code) { return code + 1; }
Uploaded by N2T
'FE > typescript' 카테고리의 다른 글
[Typescript] AxiosError의 response받아오기, AxiosError Type (0) | 2023.02.26 |
---|
댓글