const player : {
name:string,
age?:number
}= {
name: "nico"
}
문제1)
=> player라는 객체가 있는지 없는지부터 써줘야 한다.
if(player.age && player.age < 10){
}
type Player = {
name:string,
age?:number
}
const yudang : Player = {
name:"leeyujeong"
}
const minsu :Player = {
name: "kimminsu",
age: 27
}
사용빈도 void > unknown >>>> never
=> 함수의 call signature 타입을 만들어보자.
예시1) 이런식으로 {} 를 쓰면, 이런 에러가 나온다. 타입이 void가 돼서 에러다. void는 함수인데 아무 값도 반환하지 않는 건데 number타입이 되어야해서.
위 call signature을 이렇게 써도 된다.
오버로딩이란?
문제1) 똑똑한 타입스크립트는 숫자와 문자를 더하면 안된다고 경고한다.
해결)
이렇게 조건을 걸어서 b 타입이 number일때 return 값을 구분해줘서 써주면 해결된다.
문제2) 실제 개발할 때 마주할 overloading / 이런식으로 라이브러리나 패키지를 디자인한다.
문제3) call signature의 파라미터 개수가 다를때는?
해결3) c는 optional 하기 때문에 c?:number 이렇게 적어줘야 한다.
이런식으로 조건을 걸어준다.
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 //c가 있다면 return a+b+c
return a+b
}
generic이란?
=> 타입스크립트에게 이 call signature가 제네릭을 받는다는 걸 알려줘야 한다.
예시1) 이런식으로 제네릭을 써준다. 어떤 단어든 상관없음
제네릭 타입으로 call signature 타입 추론한 예시1)
제네릭 타입으로 call signature 타입 추론한 예시1)
return 값이 제네릭 타입으로 지정하고 싶을 때)
위 제네릭 사용 방법은 여러개 중 하나며 좀 어려운 편임. 이것보다 다른 방법이 더 자주쓰임
T는 배열에서 오고, 함수의 첫번째 파라미터에서 오는거라고 타입스크립트에게 알려준것.
제네릭 사용법) 빨간색 부분에서 나 제네릭 사용할거야~ 타입스크립트한테 알려주고,~ 알려준거임.!
파란색 부분에서 사용할거야
generic을 이용해서 call signature을 직접 만드는 경험은 적을 것임
일반함수에서)
function superPrint<T>(a:T[]){
return a[0]
}
const a = superPrint([1,2,3,4])
const b = superPrint([true, false, true])
const c = superPrint(["이건", "어때"])
const d = superPrint([1, false, "메롱"])
제네릭을 이용해 타입을 생성할 수도 있고, 확장시킬 수도 있고, 어떤 경우에는 코드를 저장한다.
type Player<E> = {
name:string,
extraInfo:E
}
const nico: Player<{favFood:string}> = {
name: "nico",
extraInfo: {
favFood:"kimchi"
}
}
위 코드를, 이렇게 확장시킬 수 있음
확장1)
확장2)
타입을 재사용할 수 있다) lynn에게도 Player 타입 적용
const lynn:Player<null> = {
name: "lynn",
extraInfo: null
}
제네릭 사용하는 또 다른 방법1) 기본적으로 타입스크립트 타입은 제네릭으로 만들어져 있다.
type A = Array<number>
const a : A = [1,2,3,4]
또 다른 방법2) <>
이걸보면 제네릭을 사용했다고 보면된다.
또 다른 방법3) useState의 call signature가 number 타입의 useState가 되는 것
useState<number>()
class Player {
constructor(
private firstName:string,
private lastName: string,
public nickName: string
){}
}
const nico = new Player("nico", "las", "니꼬")
nico.firstName //private이기 때문에 안됨
//js로 어떻게 컴파일 되어있나
class Player {
constructor(firstName, lastName, nickName) {
this.firstName = firstName;
this.lastName = lastName;
this.nickName = nickName;
}
}
const nico = new Player("nico", "las", "니꼬");
nico.firstName; //private이기 때문에 안됨
Abstract Class
다. 예시) private로 만든 메소드는 이렇게 쓸 수 없다. / 근데 자바스크립트에서는 에러를 못 발견해낸다. 타입스크립트는 에러가 나기 전에 나를 보호해준다.
에러를 확인해보자) 추상 메소드 getNickName() 을 실행시키지 않은 문제
문제 해결) 추상 메소드를 실행시키는데 nickName에 접근하기 위해서 private => protected 로 바꿨다.
type Words = {
[key: string] : string
}
let dict: Words = {
"딸기": "맛있어요",
"수박" : "씨 좀 빼줘요"
}
어떤 에러일까? ) words는 intializer가 없고 Contructor에서 정의된 sign이 아니라는 에러
//이렇게 안썼다고 뭐라 하는건데 하지만 나는 constructor가 words를 지정해주길 바라지 않는다.
class Dict {
constructor(private words : Words){
}
}
class Dict {
private words : Words //words를 initializer없이 선언해주고
constructor(){
this.words = {} //constructor에서 수동으로 초기화해줬다.
}
}
위 예시에서, 누군가가 kimchi.def = "김치 중국꺼"
임의로 데이터를 바꿀 수 없게 readonly 를 붙여서도 방지할 수 있다.
class Word {
constructor(
public readonly term: string,
public readonly def: string
){}
}
type Team = "red" | "blue"
type Health = 1 | 5
interface Player {
nickname: string,
team: Team,
health: Health
}
이처럼 type은 객체의 모양을 특정하는데 사용되기도 하고, 특정 값을 가지게 하기도 하고 , alias로 사용되기도 한다.
그러나, interface는 딱 한가지 용도로만 쓰인다.
: 객체의 모양을 특정하는데 사용된다!!!
=> interface를 쓰는이유? 더 객체지향 프로그래밍처럼 보여서 이해가 쉽다.
interface User {
readonly name: string
}
interface Player extends User{
}
const nico : Player = {
name: "nico"
}
=> 이걸 type을 이용해서 코드를 쓰려면?
type User = {
name: string
}
type Player = User &{
}
const nico : Player = {
name: "nico"
}
인터페이스를 3번 각각 만들어도, 타입스크립트가 알아서 하나로 합쳐준다.
interface User {
name: string
}
interface User {
lastName: string
}
interface User {
health: number
}
const nico: User = {
name:"nico",
lastName: "lee",
health: 3
}
fullName과 sayHi 메소드를 구현해야 한다고 에러 뜸 !!!
abstract class User {
constructor(
protected firstName: string,
protected lastName: string
){}
abstract sayHi(name:string): string
abstract fullName(): void
}
class Player extends User {
fullName(){
return `${this.firstName} ${this.lastName}` //protected는 추상 클래스로부터 상속받은 클래스들이 property에 접근하도록 도와준다.
}
sayHi(name: string){
return `Hello ${name}. My name is ${this.fullName()}`
}
}
인터페이스는 가볍다. 컴파일함면 js로 바뀌지 않고 사라진다.
interface User {
firstName: string,
lastName: string
sayHi(name:string): string
fullName(): void
}
class Player implements User {
constructor(
public firstName: string,
public lastName: string
){}
fullName(){
return `${this.firstName} ${this.lastName}` //protected는 추상 클래스로부터 상속받은 클래스들이 property에 접근하도록 도와준다.
}
sayHi(name: string){
return `Hello ${name}. My name is ${this.fullName()}`
}
}
이런식으로 추가도 가능하다. (한 클래스에서 여러 개의 인터페이스를 상속할 수 있다.)
인터페이스를 만들어두고 팀원이 원하는 각자의 방식으로 클래스를 상속하도록 하는건 멋진 방법이다!!! 만약 모두가 같은 인터페이스를 사용한다면, 같은 property와 method를 가지게 된다.
생성한 인터페이스를 당연히 타입으로도 사용가능하다.
타입스크립트에게 오브젝트의 모양을 알려주기 위해서는 인터페이스를 쓰고, 나머지 상황에서는 타입을 쓴다!!
타입스크립트에 의해 이미 선언된 자바스크립트의 웹 스토리지 API를 위한 인터페이스다.
제네릭을 사용해서 나만의 로컬 스토리지 API를 가상으로 만들어보자.
제네릭을 클래스로 보내고,
클래스는 제네릭을 인터페이스로 보낸 뒤에
인터페이스는 제네릭을 사용한다.
메소드를 만들어보자.
interface SStorage<T>{
[key: string] : T
}
class LocalStorage<T> {
private storage:SStorage<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 = {}
}
}