맵드 타입

📌기존의 객체 타입을 기반으로 새로운 객체 타입을 만든다.

interface Animal {
    type:string;
    name:string;
    age:number;
    color:string;
}

 

동물의 정보를 Animal 타입과 같이 저장해놓는다.

그리고 동물의 정보를 수정 가능한 함수가 있다면

function UpdateAnimal(animal:Animal){
 ...내부함수 }

UpdateAnimal( {color:"점박이"} )

 

이와같이 내가 color만 변경하고 싶다고 해서 color 프로퍼티만 함수에 넣을 수는 없다.

이미 이 함수의 매개변수 타입은 Animal 타입으로 정해져있기때문이다.

여기에 수정하고 싶은 프로퍼티만 골라담을 수 있는 타입이 하나 더 필요하다.

type AnimalModi = {
    typename?:string;
    name?:string;
    age?:number;
    color?:string;
}

 

새롭게 생긴 AnimalModi 타입은 모든 프로퍼티가 선택적이기때문에 원하는 프로퍼티만 골라 쓸 수 있다. 

 

 

여기서 중복되는 코드를 피하기 위해 간단하게 맵드타입을 사용할 수 있다.

Map 함수는 배열에서 요소를 하나씩 꺼내 콜백함수를 적용한 뒤 다시 배열을 뱉어내는 역할인데 이와 비슷하다.

 

맵드타입은 interface에서는 사용 X

type AnimalModi = {

    [key in " type " | " name " | "age" | "color"]?: Animal[key]

}

 

key가 type,name,age,color 를 한 번씩 돌면서 그 역할을 한다.

여기서 key 말고 k,i,value 등 변수의 이름은 다양하게 설정할 수 있다.

우측은 인덱스드 엑세스 타입, 즉 프로퍼티의 타입을 추출해준다.

그런데 이 코드 역시 원본타입인 Animal이 수정or삭제or추가 등 변동사항이 있다면 일일히 찾아다니면서 수정해야하는 번거로움이 있다.

 

또 줄여보자면 

type AnimalModi = {

    [key in keyof Animal]?: Animal[key]

}

 

keyof 연산자를 이용해 Animal 타입의 프로퍼티 키의 스트링 리터럴타입에 각각 한번씩 접근할 수 있도록 해준다.

 

 

➕이런식으로 모든 프로퍼티 타입을 불리언으로 바꿀 수도 있다.

type AnimalModi = {

    [key in " type " | " name " | "age" | "color"]?: boolean;

}

 


템플릿 리터럴 타입

📌특정 패턴을 만드는 string 타입을 만드는 것

 

type Main = "빵" | "떡" | "밥"

type Sub = "면" | "국" | "음료"


type Dinner = `빵-면` | `빵-국` |  `빵-음료` ...

 

가짓수가 많을수록 경우의 수도 늘어난다.

 

이를 한 줄로 정리하는 템플릿 리터럴 타입은

 

type Dinner = `${Main} - ${Sub}`

 

 

 

 

keyof 연산자

📌객체 타입에 이용된다.

📌객체타입에 있는 모든 프로퍼티의 key들을 string literal union으로 추출해낸다.

 

예를들어 

type objKey = {

    name:string;

    age:number;

    position:string

}

이라는 타입의 프로퍼티는 세 개다.

 

각각의 프로퍼티 키 네임은 각각 name, age, position이고 타입을 따져보면 각각 name,age,position이라는 고유한 값을 가지는 string literal이다.

여기서 string literal union이라하면 name | age | position 이 된다.  

 

interface Person {
	name:string;
	age:number;
}

function getKey(person: Person,key: "name" | "age"){
	return person[key]
}

 

더보기

💫객체 접근방법

 


    let obj = {
        name:"사용자1"
    }
    - obj.name
    - obj["name"]
 

 

이런 코드를 짜면 역시나 타입의 프로퍼티가 추가or삭제or수정됐을 경우 하나하나 찾아다니며 수정해야하는 번거로움이 있다.

 

✔️keyof 타입명

function getKey(person: Person,key: keyof Person){
	return person[key]
}

 

이렇게 코드를 변경하면 Person 타입에 있는 모든 프로퍼티의 키 값들을 추출해 유니온타입으로 만들어준다.

 

keyof typeof 연산자

 

여기에서 typeof는 특정 변수의 타입을 추론해준다.

function getKey(person: Person,key: keyof typeof person){
	return person[key]
}

const person = {
	name:"고영이",
	age:22
}

getKey(person, "name") //고영이

 

person 객체의 타입을 추론해서 이 객체에 포함된 각각의 프로퍼티에 접근한다.

 

 

 

인덱스드 엑세스 타입

인덱스를 이용해 다른 타입내의 특정 프로퍼티 타입을 추출한다.

객체,배열,튜플에 사용 가능

 

📌객체 프로퍼티의 타입 추출

interface Post {
	title:string,
	content:string,
	author: {
		id:number,
		name:string
	}
}

 

만약 여기에서 author 프로퍼티의 타입에 관심이 있다면

function printInfo(author: {id:number, name:string}){
	console.log(`${author.id} - ${author.name}`)
}

 

printInfo 함수의 인자는 author 프로퍼티이고, id와 name의 타입을 직접 명시해주면 나중에 타입 프로퍼티가 추가or삭제or수정되었을 때 귀찮아진다.

 

뭐든 중복되는 코드는 피하는 것이 좋다.

function printInfo2(author:Post["author"]){
	console.log(`${author.id} - ${author.name}`)
}

 

Post ["author"]

Post타입 으로부터 [프로퍼티] 의 타입추출한다.

여기서 [ 인덱스 ] 내부에는 타입만 들어갈 수 있고, 값(ex.변수)은 들어갈 수 없다.

 

+

interface Post {
	title:string;
	content: {
		contentnumber:number,
		contenttitle:string
	};
	author : {
		id: number;
		name: string;
	}
}

function printinfor (content : Post["content"]){ //여기서 인수로 들어가는 프로퍼티는
	console.log(`${content.contentnumber}-${content.contenttitle}`) // 함수 구현부에서 언급되어야함
}

 

 

✔️또 인덱스를 중첩해 조금 더 구체적인 프로퍼티에까지 접근할 수 있다.

function printInfo2(author:Post["author"]["name"]){
	console.log(`글쓴이는 ${author}`)
}

 

 

📌배열 요소의 타입 추출

type PostList = {
	title:string,
	content:string,
	author: {
		id:number,
		name:string
	}
}[];

 

이런 객체로 이루어진 배열 타입이 있다고 가정했을 때

const post2:PostList= {  // 오류
	title:"제목",
	content:"내용",
	author: {
		id:1,
		name:"글쓴이"
	}
}

const post2:PostList[number]= {  // O
	title:"제목",
	content:"내용",
	author: {
		id:1,
		name:"글쓴이"
	}
}

 

이 때 [number]대신 [0], [1], [2] ... 등등 숫자를 넣어도 똑같다.

 

+만약 배열타입의 객체는 그대로 유지한 채 [number] 라는 인덱스드 엑세스 타입을 지운다면 오류메시지가 뜬다.

 

type List = [
	{
		id:number,
		name:string
	},
	{
		id:number,
		name:string
		skill:boolean
	},
]

type Listone = List[0]["id"] //number
type Listtwo = List[1]["skill"] //boolean

 

이런 방식으로도 접근 가능하다.

 

 

📌튜플의 요소 타입 추출하기

type tup = [number, string, boolean]

type tup0 = tup[0] // number

type typ1 = tup[1] // string

type typ2 = tup[2] //boolean

type typ3 = tup[number] //number|string|boolean

 


 

 

강의에서는 자주 쓰이는 문법이라고 하는데 사실 굳이 이걸 쓰지 않더라도 코드 작성은 가능할 것 같다..

실제로 어떤 경우에 쓰이는지 아직 감이 안온다.

프로미스 기초

📌const promise = new Promise(실행함수)

📌비동기처리를 위한 내장 객체

📌매개변수로는 resolve / reject 두 가지를 받는다. 

      ✔️resolve - 비동기 작업의 상태를 성공으로 바꾸는 함수

      ✔️reject - 비동기 작업의 상태를 실패로 바꾸는 함수

📌then과 catch는 각각 비동기처리가 성공했을 때, 실패했을 때 실행되는 함수이다.

 


const promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{
    	resolve(10) // 10은 비동기작업의 결과값
    },3000)
})

 

3초 후 resolve를 호출해 비동기 작업을 성공 상태로 바꾼 후 

작업의 결과값을 "인수"로 전달한다.

 

  • then은 resolve가 실행됐을 때, 즉 비동기 작업이 성공했을 때 실행된다.

promise.then((res)=>{
	console.log(res) // 10
})

 

  • catch는 반대로 reject가 실행됐을 때 실행된다. 

promise.catch((error)=>{
	console.log(error)
})

 

 

제네릭과 프로미스

여기서 주의할 점은

비동기작업의 결과값에 타입을 정의해주지 않았을 때,

 

✔️resolve의 결과값은 unknown 타입으로 추론된다.

물론 이후 따라오는 then 함수에 전달된 resolve의 결과값 역시 unknown 타입으로 정의된다.

unknown 타입은 추후 메서드 사용이 불가하기때문에 오류가 발생 할 가능성이 높아 타입 정의를 분명히 해두는게 좋다.

 

✔️reject는 any타입으로 추론된다.

때문에 catch 함수에서 타입좁히기를 사용해 코드를 짜볼 수 있다.

 

<예시>


const promise = new Promise<number>((resolve,reject)=> {
	setTimeout(()=>{
		reject(20) //20은 비동기작업의 결과값
	}, 3000)
});

promise.then((response)=>{
	console.log(response) // 20
})

promise.catch((error)=>{ //error -> any 타입
	if(typeof error === "string"){[
		console.log(error)
	]}
})

 

 

프로미스를 반환하는 함수의 타입


interface Post {
	id:number;
	title:string;
	content:string;
}

function fetchPost(){
	return new Promise((resolve,reject) => {
		setTimeout(()=>{
			resolve({
				id:1,
				title:"title",
				content:"contents"
			})
		},3000)
	})
}

const postrequest = fetchPost();

postrequest.then((post)=>{
	post.id // 이 경우 매개변수가 unknown 타입으로 추론되어 메서드를 쓸 수 없어 오류 발생 
})

 

비동기작업 결과값에 타입을 정의해주지 않았을 경우, resolve의 인수는 unkown으로 추론된다.

때문에 unknown 타입인 post에서 id 프로퍼티를 찾을 수 없어 오류메시지가 뜬다.

 

 

이 경우 반환값의 타입을 명확히 정의해야한다.


function fetchPost(){
	return new Promise<Post>((resolve,reject) => {
		setTimeout(()=>{
			resolve({
				.........
			})
		},3000)
	})
}

 

혹은

function fetchPost():Promise<Post>{
	return new Promise((resolve,reject) => {
		setTimeout(()=>{
			resolve({
				.........
			})
		},3000)
	})
}

 

이렇게 첫 줄에 타입을 선언할 수 있다.

제네릭 인터페이스


interface Test<T,V> {
    name:T;
    age:V;
}

let testValue:Test<string,number> = {
    name: "이름",
    age:20
}

 

제네릭 인터페이스를 사용 할 경우 꼭 사용할 타입을 명시해줘야한다.

함수는 전달하는 매개변수의 타입으로 자동추론하지만 인터페이스는 자동추론 할 방법이 없기때문이다.

 

인덱스 시그니처 활용

인터페이스보다 훨씬 유연하게 프로퍼티와 값 정의 가능

 


interface Test<T> {
    [key:string] : T;
}


let test:Test<number[]> = {
    numberlist: [100,10,20]
}

 

제네릭 타입 별칭

type Test<T> = {
    [key:string] : T
}

let test:Test<string> = {
    name: "username1"
}

 

타입별칭도 인터페이스 활용과 마찬가지로 사용하려하는 타입을 명시해야한다.

 

제네릭 클래스

클래스 역시 마찬가지로 타입이 달라서 생기는 중복 코드의 문제점을 해결하기 위해 타입 변수를 활용할 수 있다.

아래와 같은 class가 있고, list라는 변수를 넘버타입의 배열로 타입 정의 하는 경우

나중에 스트링 타입 배열이나, 여러 타입이 혼합된 배열을 인수로 넣고싶을 때 

같은 모양의 클래스를 또 만들어야 하는 경우가 생긴다.

class NumberList {
	constructor(private list:number[]){}

	push(data: number){
		this.list.push(data)
	}
	pop(){
		return this.list.pop();
	}
	print(){
		console.log(this.list)
	}
}

let numberlist = new NumberList([1,2,3]);

 

중복코드를 피하기 위해 타입변수를 만들어 list에 들어가는 매개변수를 기준으로 타입추론이 가능하게 만들 수 있다.

 

class List<T> {
	constructor(private list:T[]){}

	push(data: T){
		this.list.push(data)
	}
	pop(){
		return this.list.pop();
	}
	print(){
		console.log(this.list)
	}
}

let numberlist = new List([1,2,3]);

 

+ Recent posts