Typescript_스터디 9일차 (23/02/14)

yhwa·2023년 2월 14일
0
post-thumbnail

📝 Typescript로 블록체인 만들기 : From #2.2 to #3.1

1) 선택적 변수 (optional parameter)
2) 타입 Alias 생성하기
3) 함수의 리턴 type
4) 함수의 파라미터 type
5) 화살표 함수의 type
6) readonly type
7) 튜플 (Tuple)
8) undefined, null
9) any
10) unknown
11) void
12) never
13) TS에서의 함수의 type
14) call signatures
15) overloading
16) 댜형성
17) concrete type
18) generic type

⛳️ 선택적 변수 (optional parameter)

- 필수가 아닌 선택적으로 갖는 함수의 파라미터를 말합니다.
- 파라미터 이름 바로 뒤에 물음표(?)를 붙여 선택적 변수 설정을 합니다.
const user = {
	name:string,
    age?:number //type이 number or undefined의 상태
} : {
  name:"Tom"
}

//user.age값이 true이면서 10미만인 조건
if(user.age && user.age < 10){ }

👉 user.age가 선택적 변수이므로, 일차적으로 존재하는지 확인하는 과정이 필요합니다.
(user.age의 type이 undefined 일수도 있는 케이스를 제거)

⛳️ Alias 타입

- 변수에 type을 할당하여, 공통된 타입을 여러번 재사용 하는 것을 말합니다.
type Name = string;
type Age = number;

type User = {
	name:Name,
  	age?:Age
}

const tom:User = {
	name:"tom",
  	age:12
}

const john:User = {
	name:"john"
}

⛳️ 함수의 파라미터 type

- 함수의 파라미터 이름 뒤에 :type 를 입력하여 타입을 정의합니다.

⛳️ 함수의 리턴 type

- User type 오브젝트를 생성합니다.
- 함수 파라미터 선언부 뒤에 :User 를 입력하여, User type 오브젝트를 리턴합니다.
- string 타입으로 name을 파라미터로 받고, User타입을 return하여 함수 리턴값의 type으로 적용합니다.
type User = {
	name:string,
  	age?:number
}

function userMaker(name:string):User{
	return {
    	//name:name
      	name
    }
}

const tom = userMaker("tom");
tom.age = 12;

⛳️ 화살표 함수의 type

- 마찬가지로, 함수의 파라미터 이름 영역 바로 뒤에 :User 를 입력하여, type User 오브젝트를 리턴합니다.
type User = {
	name:string,
  	age?:number
}

const userMaker = (name:string):User => ({
	name
})

⛳️ readonly type

- 파라미터 이름 앞에 readonly를 입력하여 적용합니다.
- JS에는 없는 기능입니다, 컴파일된 JS에서는 보이지않습니다.
- 함수 인자(argument)의 값이 수정되지 않도록 데이터를 보호합니다.
//type object
type User = {
	readonly name:string,
  	age?:number
}

//call signature
const userMaker = (name:string) : User => ({name});

const john = userMaker("john");

//john.name = "tom"; //readonly의 보호장치로 인하여 error
john.age = 12;
- 파라미터 배열의 타입을 readonly처리할 경우
const numbers = readonly number[] = [1,2,3,4];
//numbers.push("a"); //readonly의 보호장치로 인하여 error

const names = readonly string[] = ["a","b","c"];
//names.push(1); //readonly의 보호장치로 인하여 error

👉 readonly을 통해 파라미터 배열의 push, map, filter의 기능을 막음으로서, 데이터의 불변성(immutability)을 확보할 수 있습니다.

⛳️ 튜플(Tuple)

- 배열을 생성할 수 있게 합니다.
- 최소한의 길이를 가져야하며, 특정 위치에 특정 타입이 있어야합니다.
- readonly와 같이 JS로 컴파일되면 흔적이 사라집니다.
- 튜플은 컴파일되면 단순 배열로 보여집니다.
- TS의 보호장치가 필요하며, 지정된 배열 길이와 순서에 맞는 요소의 위치를 전달해야 할때 사용합니다.
- 
const user:[string, number, boolean] = ["john", 12, true];
//user[0] = 1; //tuple에의한 error, 첫번째 index는 항상 string입니다.

const user: readonly [string, number, boolean] = ["john", 12, true];
//user[0] = "hey" //readonly설정으로 인한 error
//readonly를 적용하여 어떤것도 수정할 수 없게 설정가능합니다.

👉 항상 정해진 갯수의 요소를 가져야하는 배열을 지정할 수 있습니다. 원하는 순서에 맞는 타입을 가져와야할 때, 즉 API가 object로 값을 주지 않고 배열로 데이터를 내려줄 경우 유용합니다.

⛳️ undefined 와 null type

- JS에도 있는 type입니다.
- 선택적 타입은 undefined가 될 수 있습니다.
type User = {
	age?:number
}

👉 선택적 타입으로서, User.age는 number이거나 undefined가 됩니다.

⛳️ Any type

- 비어있는 값을 사용하면 기본값이 any타입 입니다.
- TS는 기본적으로 a를 any의 array라고 간주합니다.
- any를 사용하면, 타입체커 기능이 사라지면서 JS와 환경이 똑같아집니다.
- TS에서 빠져나오고싶을때 사용합니다.타입스크립트의 보호장치에서 빠져나옵니다.
- any는 모든 TS의 보호장치를 비활성화합니다.
- JS와 같은 상태가 됩니다.
let a = []; // let a: any[];

⛳️ unkown

- 어떤타입인지 모르는 변수의 처리법
- TS에서 가장 중요한 것은 type checker와 소통하는 것 입니다.
- API로부터 응답을 받을때, 그 응답의 타입을 모를때, 
- 변수의 타입을 미리 알지 못할 때 unkown을 사용합니다.
let a:unknown;
//let b = a + 1; //error

//a의 타입이 number라는게 확인이 된 케이스만 작업을 허용합니다.
if(typeof a === 'number'){
  
  	//해당 범위 안에서 a는 number입니다.
	let b = a + 1;
}
- typescript로부터 보호를 받게됩니다.
- 어떤 작업을 하려면 이 변수의 타입을 먼저 확인해야하는 방식입니다.
- a의 타입이 unknown이기때문에, type이 확인된 상태에서만 error가 일어나지 않습니다.

void

- void는 아무것도 return하지 않는 함수를 대상으로 합니다.
- 통상적으로 void는 따로 지정할 필요는 없습니다.
- TS는 함수가 아무것도 return하지 않는다는 것을 자동으로 인식합니다.
function hello():void {
	console.log("hello");
}

never

- 함수가 절대 return하지 않을때 발생합니다.
- 함수에서 exception(예외)가 발생할때를 말합니다.
- never는 에러를 일으키면 정상적으로 작동합니다.
- never는 return하지 않고, 오류를 발생시키는 함수를 만들때 never를 사용합니다.
- never는 타입이 2가지일수도 있는 상황이 생길수도 있습니다.
//error code
function func01():never{
	return "X"
}

function func02():never {
	throw new Error("error");
}
function func03(name:string|number){
	if(typeof name === "string"){
    	name //string
        
    } else if(typeof name === "number"){
    	name //number
        
    } else {
    	name //name의 type은 never입니다.
    }
}
- name인자의 type은 string이거나, number라고 알려줬고 모든 타입이 앞에서 확인이 되었습니다.
- 남은 타입 조건은 never가 됩니다.
- 즉 이 코드는 절대 실행되지 않아야한다는 의미입니다. 
- (앞에서 조건을 다 충족하고 남은 케이스이기때문에)
- 거의 사용되지 않습니다.

TS에서의 함수

//방법1 : type을 모두 영역마다 작성
function addFunc(a:number, b:number){
	return a + b;
}

const addArrowFunc = (a:number,b:number) => a + b;

//방법2 : 함수만의 call signature를 만들어서 함수 뒤에 입력
//Add라는 타입을 TS가 인지하기때문에, TS가 유추를 할 수 있습니다.
//인자, 파라미터의 type과 리턴값의 type까지 call signature를 통해 TS가 유추가능
type Add = (a:number, b:number) => number;

const add:Add = (a, b) => a+b; 


호출 시그니처 (call signatures)

- 함수위에 마우스를 올렸을때, 보이게되는 함수의 인자,리턴값 type을 말합니다.
- 함수가 어떻게 구현되는지를 보여주는 것이 아니라, 
- 함수의 인자(=파라미터), 리턴값의 type을 알려주는 역할입니다. 
- 함수를 구현하기 전에, 함수가 어떻게 작동하는지 작성해놓을수 있습니다.
- 이로써 첫번째로 TS가 개발자가 작성한 type을 생각하도록 하게 합니다.
- 타입을 먼저 개발자가 설명하고 그 다음에 코드 구현을 합니다.
- 타입과 함수구현을 분리하여 운영할 수 있습니다.
- ReactJS에서 props로 함수를 보내면, TS에게 설명을 해줘야합니다.
- 프로그램을 디자인할때 타입을 먼저 생각하게 합니다.

오버로딩 (overloading)

- function overloadin 이나 method overloading이라고 합니다.
- 외부 라이브러리에서 자주 볼 수 있습니다.
- 패키지나 라이브러리에서 오버로딩을 많이 사용합니다.
- call signature를 모아서 보관하는 역할
- 함수가 여러개의 call signatures를 가지고있을때 발생시킵니다.
- 서로 다른 여러 개의 call signatures를 가지고 있을 경우
- 여러개의 call signatures가 있는 함수를 말합니다.
type Add = {
	(a:number, b:number) : number,
  	(a:number, b:string) : number
}

//TS는 파라미터b가 number, string 2가지 값을 동시에 만족할수 있는 것을 압니다.
const add:Add = (a, b) => a+b;
//오버로딩 예시 > Next.js
//object 보내기
Router.push({
	path: "/home",
  	state: 1
});

//string 보내기
Router.push("/home");
//string을 보내거나 Config객체를 보내는 경우
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)
    }
}
- 다른 call signature에 파라미터 개수도 다른 경우
- 파라미터 개수가 다를 경우
- ?으로 선택사항,옵션이라는 의미를 부여합니다.
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)

- concrete type: number, boolean, string, void, unkown...type을 말합니다.
- type 종류나 조합이 다양해지면 모든 가능성을 다 조합해서 만들게됩니다. 
//call signatures
type SuperPrint = {
	(arr: number[]):void
  	(arr: boolean[]):void
  	(arr: string[]):void
  	(arr: (number|boolean)[]):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])

제네릭 (generic)

- type의 placeholder와 같은 역할을 합니다.
- concrete type 대신 사용할 수 있습니다.
- TS로 type의 placeholder를 사용할 수 있으며, 이를 추론하여 함수를 사용합니다.
- call signature을 작성시, 여기에 들어올 확실한 타입을 모를때 사용합니다.
- concrete type을 적용해야하지만, 
- call signature을 작성시, concrete type을 모를 수도 있습니다.
- 그때 generic을 사용합니다.
- generic을 사용함으로서 각각의 코드라인에서 사용하는 type을 유추합니다.
- TS는 받은 값을 generic을 이용해서 type을 유추합니다.
- 그 유추한 타입으로 call signature를 개발자에게 보여줍니다.
- generic의 placeholder대신 TS가 감지한 타임으로 코드를 변경해줍니다.
- generic을 사용하면 일일이 concrete type을 작성할 필요가 없습니다.
- TS는 모든 call signature를 대체하고 다르게 만듭니다.
- 제너릭은 개발자가 요구하는대로 signature를 생성해줄 수 있는 도구입니다.
- 제네릭을 처음 인식했을때와 제네릭의 순서를 기반으로 타입을 알게됩니다.
- any가 아니라 개발자의 요청에 따라 call signature를 생성하는 것입니다.
//1. <T> generic을 사용하겠다. TS에게 generic으로 사용할 이름을 등록합니다.
// ** 제네릭을 사용할때, TS가 해당 함수의 call signature를 만들어줍니다.
//2. 인자의 type을 generic을 이용해서 유추하겠다.
//3. 리턴값의 type을 generic을 이용해서 유추하겠다.
//==> 이것이 바로 다형성입니다.
//any를 쓰지않고 generic type을 사용합니다.
type SuperPrint = {
	<T> (arr: T[]):T
}

type SuperPrint = <T, M>(a: T[], b:M) => T
//TS는 제네릭이 처음 사용되는 지점을 기반으로 해당 타입이 무엇인지 알게됩니다.

제네릭 이용 사례

- 일반 개발자가 제네릭을 사용해서 
직접 call signature를 만들일은 거의 드문 일입니다.
- 보통 다른 라이브러리를 사용하고, 
그 라이브러리들이 제너릭을 통해서 생성됩니다.
- 라이브러리를 만들거나 
다른 개발자가 사용하게 만드는 무언가를 개발 할 때는 유용합니다.
- 그 외 대부분의 경우 작성할 일이 없습니다.
- 대부분의 경우 제너릭을 사용만 합니다.
- 항상 타입스크립트가 타입을 유추하도록하는 것이 좋습니다.
nextJS, nestJS, reactJS의 제너릭
- 제너릭을 사용할것이며, TS에게 제너릭을 보내게됩니다.
- 제너릭을 사용하는 call signature를 작성하게 됩니다.

제너릭은 call signature 생성 외에도 사용하는 곳이 있습니다.

- 제너릭을 이용하여 타입을 생성할수도 있고 
- 타입을 확장할 수도 있습니다.
- 코드를 저장하기도 합니다.
- 제너릭은 함수뿐이 아니라 다양한 곳에서 사용됩니다.
- ex) 대부분의 기본적인 타입스크립트 type은 generic으로 만들어졌습니다.
//call signature를 생성하고 type으로 활용
type SuperPrint = <T>(a:T[]) => T;
const superPrint: SuperPrint = (a) => a[0]

//위의 것을 일반함수로 대체하기
function superPrint<V>(a: V[]){
	return a[0]
}
function superPrint<T>(a:T[]){
	return a[0]
}

//<number>는 있어도 되고, 없어도 됩니다.
const a = superPrint<number>([1,2,3,4]);
//데이터가 TS으로 보호받기위해 any가 아닌 generic을 활용합니다.
//제너릭을 확장하여 사용하기
type User<T> = {
	name:string,
  	extraInfo:T
}

type JohnExtra = {
	favFood:string
}

type JohnUser = User<JohnExtra>

const john:JohnUser = {
	name:"john",
  	extraInfo: {
    	favFood: "kimchi"
    }
}

//type들끼리 일종의 상속이 가능합니다.
//type의 재사용 가능합니다.
//많은 것들이 있는 큰 타입을 하나 가지고있는데, 
//그 중 하나가 달라질 수 있는 type이라면, 그곳에 generic을 사용합니다.
//그렇다면 타입을 많이 재사용할 수 있게됩니다.
//또한 커스텀한 타입을 보낼 때도 사용할 수 있습니다.
const tom:User<null> = {
	name: "tom",
  	extraInfo: null
}
//generic을 사용하는 또 다른 방법
type A = Array<number>
let a:A = [1, 2, 3, 4]

function printAllNumbers(arr: number[]){
	//같은 형식
}

function printAllNumbers(arr: Array<number>){
	//같은 형식
}

ReactJS와 타입스크립트

- ReactJS를 TS와 함께 사용할 경우, useState라는 함수는 제너릭을 받습니다.
- 그렇지 않으면 TS는 React의 state타입을 알 수 없습니다.
//제너릭을 보내면, 
//useState의 call signature이 number타입의 useState가 되는 것 입니다.
useState<number>()
  • 모두 다른 개발자가 쓸 라이브러리나 코드를 디자인할때 제너릭을 사용합니다.
profile
📌 FE 공부 정리 공간입니다.

0개의 댓글