반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- React #controlled component #비제어 컴포넌트 #제어 컴포넌트
- rate limit
- useState #Hooks
- 얕은 복사 #깊은 복사 #shallow copy #deep copy
- React #리액트 이벤트 주기 #리액트 이벤트
- donwstream #upstream #origin
- 플로이드 #c++
- RateLimit
- 버블링 #갭쳐링 #이벤트 #JS
- react #useCallback #react Hook
- React #Hook rules #Hook 규칙
- npm #not being able to find a file #npm install Error
- axios
- html entities
- 노마드 코더 #타입스크립트 #typescript #class
- 코드스테이츠 #알고리즘 #그리디
- React-Query
- interceptors
- DP #c++
- 다익스트라 #파티 #백준
- React #effect hook #useEffect
- react
- 백준 #적록색약
- 빡킹독
- react fragment
- JWT #토큰 #refreshToken #accessToken #Token #token #localStorage #sessionStorage
- #useRef #언제 쓰는데?
- 백준 #직각삼각형
- 이친수
- raect typescript #react #typescript #styled-component
Archives
- Today
- Total
꿈꾸는 개발자
10장 제네릭 본문
- TS는 제네릭을 사용해 타입 간의 관계를 알아낸다
- 제네릭 타입 매개변수를 원하는 만큼 선언 가능 ⇒ 타입 매개변수는 구조체의 각 인스턴스에 대해 타입 인수라고 하는 서로 다른 타입을 함께 제공 가능???????
- 타입 매개변수는 전형적으로 T or U 같은 단일 문자 이름 또는 Key와 Value 같은 파스칼 케이스 이름을 가짐
10.1 제네릭 함수
- 매개변수<타입별칭>의 형태로 함수를 제네릭으로 만듦 ⇒ 함수 내 매개변수 타입 애너테이션, 반환값 애너테이션으로 사용 가능!
function iden<T>(input:T){
return input;
}
const num=iden("me")//타입 me
const stringy=iden(123)//타입 123
//화살표 함수 형태의 제네릭(리액트 환경에서 JSX와 충돌이 있음=>13장에서 다룸)
const iden2=<U>(input:U)=>input;
- 위와 같은 방법으로 any타입을 사용하지 않고, 다른 입력을 수용하고, 타입 안정성을 유지할 수 있음
10.1.1 명시적 제네릭 호출 타입
- 제네릭 함수: 함수가 호출되는 방식에 따라 타입 인수 유추 ⇒ 위 코드에선 TS 검사기는 iden에 제공되는 인수를 사용해 ⇒ 매개변수의 타입 인수 유추!
function logWrapper<Input>(callback:(input:Input)=>void){
return (input:Input)=>{
console.log("Input",input);
callback(input);
}
}
//타입 (input:string)=>void
logWrapper((input:string)=>{
console.log(input.length);
})
//타입:(input:unknown)=>void;
logWrapper((input)=>{
console.log(input.length);//Error: length does not exists on type unknown
})
- TS는 매개변수 타입을 모르는 경우 Input이 무엇이 되어야 하는지 알 방법X
- 명시적 제네릭 타입 인수: unknown으로 설정되는 것을 파하기 위해 ⇒ 일치여부 확인을 위해 제네릭 호출에서 타입 검사 수행
logWrapper<string>((input)=>{
console.log(input.length);
})
- 위와 같이 명시할 수 있음 string 타입임을 명시 가능 ⇒ 필요한 경우에만 지정하는 것이 좋음(필요한 경우에 대해선 명시X)
10.1.2 다중 함수 타입 매개변수
- 임의의 수의 타입 매개변수를 쉼표로 구분해 함수를 정의함
function A<First,Second>(first:First,second:Second){
return[first,second]as const;
}
let tuple=A(true,"abc"); //value:readonly [boolean,string] 타입
- 위와 같이 여러 타입 매개변수를 선언하면, 둘 다 명시적 제네릭 타입으로 선언하거나 아니면 둘 다X(한 쪽만 유추X)
function make<Key,Value>(key:Key,value:Value){
return {key, value};
}
//ok 타입 둘 다 인수 제공X
make("abc",123); //타입: {key:string,value:number};
//ok 둘 다 타입 제공됨
make<string,number>("abc",123);//타입: {key:string;value:number};
make<"abc",123>("abc",123) //타입: {key:"abc",value:123};
make<string>("abc",123)//한 쪽만 제공X Error 밠애
<aside> 💡 제네릭 구조체에선 두 개 이상의 매개변수 사용X⇒코드 가독 하락
</aside>
10.2 제네릭 인터페이스
- 인터페이스에도 제네릭 선언 가능
interface Box<T>{
inside:T
}
let stringyBox:Box<string>={
inside:"abc",
}
let numberBox: Box<number>={
inside:123,
}
let incorrectBox:Box<number>={
inside:false, //당연히 타입 에러 발생!
}
- TS의 내장 Array method는 제네렉으로 정의됨 ⇒ Array는 타입 매개변수 T를 사용해서 배열 안에 저장된 데이터의 타입을 나타냄!
10.2.1 유추된 제네릭 인터페이스 타입
- 함수와 same ⇒ 인터페이스의 타입 인수도 사용법에서 유추 가능!
interface LinkedNode<Value>{
next?:LinkedNode<Value>;
value:Value;
}
function getLast<Value>(node:LinkedNode<Value>):Value{
return node.next? getLast(node.next):node.value;
}
let lastDate=getLast({
value:new Date("09-13-1993")
});
let lastFruit=getLast({
next:{
value:"ban",
},
value:"apple",
})
let lastMismatch=getLast({
next:{
value:123 //여기에서 이미 number로 지정됨!
},
value:false, //Error boolean => number 불가!
})
- 인터페이스 타입 매개변수 ⇒ 타입 애너테이션은 항상 상응하는 타입 인수를 제공해야 함! ⇒ 아니면 에러 발생!
10.3 제네릭 클래스
- 클래스도 멤버에서 사용할 임의의 수의 타입 매개변수 선언 가능 (클래스의 각 인스턴스는 타입 매개변수로 각자 다른 타입 인수 집합을 가짐)
class Sec<Key,Value>{
key:Key;
value:Value;
constructor(key:Key,value:Value){
this.key=key;
this.value=value;
}
getValue(key:Key):Value|undefined{
return this.key===key?this.value:undefined;
}
}
const storage= new Sec(1234,"abcd"); //타입 Sec<number,string>
storage.getValue(1987);//타입 string | undefined;
10.3.1 명시적 제네릭 클래스 타입
- 인스턴스 생성 시 함수 생성자에 전달된 매개변수의 타입으로 타입 유추 가능하면⇒TS는 타입 유추를 함 (기본값은 unknown)
class Cur<Input>{
#callback:(input:Input)=>void;
constructor(callback:(input:Input)=>void){
this.#callback=(input:Input)=>{
console.log("Input",input);
callback(input);
}
}
call(input:Input){
this.#callback(input);
}
}
//이 경우 Input=string 타입
new Cur((input:string)=>{
console.log(input.length);
})
//타입:unknown이라 에러 발생!
new Cur((input)=>{
console.log(input.length);
})
//명시적 제네릭 클래스 타입
new Cur<string>((input)=>{
console.log(input.length);
})
10.3.2 제네릭 클래스 확장
- 제네릭 클래스는 extends keywords 다음에 오는 기본 클래스에 사용 가능. TS는 기본 클래스에 대한 타입 인수 유추X
class Quote<T>{
lines:T;
constructor(lines:T){
this.lines=lines;
}
}
class SpokenQ extends Quote<string[]>{//string[]으로 extends
speak(){
console.log(this.lines.join("\\n"));
}
}
new Quote("the only real failure is the ").lines;//타입 string
new Quote([4,5,6,6,7]).lines //타입 number[];
new SpokenQ([
"a",
"b",
]).lines; //타입 string[];
new SpokenQ([4,5,6,7,7]); //Error: Type 'number' is not assignable to type 'string'
- 제네릭 파생 클래스는 자체 타입 인수를 기본 클래스에 번갈아 전달 가능(타입 이름 일치 필요X)
class Quote<T>{
lines:T;
constructor(lines:T){
this.lines=lines;
}
}
class SpokenQ extends Quote<string[]>{
speak(){
console.log(this.lines.join("\\n"));
}
}
new Quote("the only real failure is the ").lines;//타입 string
new Quote([4,5,6,6,7]).lines //타입 number[];
new SpokenQ([
"a",
"b",
]).lines; //타입 string[];
new SpokenQ([4,5,6,7,7]); //Error:
class AttributeQ<Value> extends Quote<Value>{
speak:string;
constructor(value:Value,speak:string){
super(value);
this.speak=speak;
}
}
new AttributeQ("a","b") //Quote<string> 확장하기???
10.3.3 제네릭 인터페이스 구현
interface ActingC<Role>{
role:Role;
}
class MoviePart implements ActingC<string>{
role:string;
speaking:boolean;
constructor(role:string, speaking:boolean){
this.role=role;
this.speaking=speaking;
}
}
const part=new MoviePart("aaaa",true);
part.role; //타입 string;
class BB implements ActingC<string>{ //string을 줬는데 boolean을 작성해서 오류!
role:boolean; //Error Acting<string>인데 role:boolean을 줘서 오류/boolean으로 수정하면 해당 오류는 해결
}
10.3.4 메서드 제네릭
- 클래스 메서드는 클래스 인스턴스와 별개로 자체 메서드 타입 선언 가능
class A<Key>{
key:Key;
constructor(key:Key){
this.key=key;
}
createPair<Value>(value:Value){
return {key:this.key,value};
}
}
//타입: A<string>
const factory=new A("role");
factory.createPair(20); //{key:string, value:number};
//타입: {key:string, value:number}
const numberPair=new A("role").createPair(10);
console.log(numberPair); //{key:string, value:number};
//{key:string,value:string}
const stringPair=factory.createPair("stringPair");
- 핵심: 메서드의 제너릭은 개별적으로 줄 수 있음
10.3.5 정적 클래스 제네릭
class Both<OnInstance>{
instanceLog(value:OnInstance){
console.log(value);
return value;
}
static staticLog<OnStatic>(value:OnStatic){
let fromInstance:OnInstance;//Error:static type cannot reference class type arguments
console.log(value);
return value;
}
}
const logger=new Both<number[]>;
logger.instanceLog([1,2,3]);
//유추된 OnStatic 타입 인수: boolean[];
Both.staticLog([false,true]);
//유추된 OnStatic 타입 인수: string
Both.staticLog<string>("aaaaa");
10.4 제네릭 타입 별칭
- 타입 별칭에서도 타입 인수를 제네릭으로 만들 수 있다.
type Nullish<T>= T | null | undefined;
- 제네릭 타입 별칭은 일반적으로 제네릭 함수의 타입을 설명하는 함수와 사용됨!
type CreatesVa<Input,Output>=(input: Input)=>Output;
let creator:CreatesVa<string,number>;
creator=text=>text.length; //사용 가능!
creator=text=>text.toUpperCase(); //Error:type 'string' is not assignable to type "number"
- 마지막 creator의 경우 출력값이 number타입인데, string타입으로 assign해서 에러가 발생한다.
10.4.1 제네릭 판별된 유니언
4장 객체에서 언급한 판별된 유니언은
type Result<Data>=FailureR | SuccessfulResult<Data>;
interface FailureR{
error:Error;
succeeded:false;
}
interface SuccessfulResult<Data>{
data:Data;
succeeded:true;
}
function handleRes(result:Result<string>){
if(result.succeeded){
//result SuccessfulResult<string>의 타입
console.log("we did it")
}else{
console.error(`${result.error} `);
}
result.data; //Property 'data' does not exist on type 'Result<string>'.
// Property 'data' does not exist on type 'FailureR'.
}
- result를 반환할 때 result의 succeeded의 값이 true/false인지 무조건 위처럼 확인해야 한다. ⇒ 위처럼 제네릭 타입 + 판별된 타입을 함께 사용하면 Result와 같이 재사용이 가능한 타입 모델링이 가능해진다.
10.5 제네릭 제한자
- TS는 제네릭 타입 매개변수의 동작을 수정하는 구문도 제공!
10.5.1 제네릭 기본값
interface Quote<T=string>{
value:T;
}
let explicit: Quote<number>={value:123};
let implicit:Quote={value:"aaaaaa"}; //string이 default
let mismatch:Quote={value:123}//error: Type 'number' is not assignable to type 'string'
interface KeyValuePair<Key, Value=Key>{
key:Key;
value:Value;
}
//타입 KeyValuePair<string, number> => 서로 다른 값을 가질 수 있음
let allExplicit:KeyValuePair<string,number>={
key:"aaa",
value:10,
}
//타입: KeyValuePair<string, string>
let oneDefault:KeyValuePair<string>={
key:"aaaa",
value:"ten",
}
//Error: 적어도 하나의 argument을 줘야한다.
let firstMissing:KeyValuePair={
key:"rating",
value:10,
}
- Key, Value ⇒ 서로 다른 값을 가질 수 있지만, 기본적으로 동일한 타입 유지됨(명시하지 않으면)
- Key는 기본값이 없음 여전히 유추 가능/제공되어야 함
- 주의: 기본 타입 매개변수는 항상 선언 목록 제일 마지막에 와야 한다
function inTheEnd<First,Second,Third=number,F=string>(){}; //ok
function inTheMiddle<F,S=boolean, T=number, F>(){}//Error 발생
10.6 제한된 제네릭 타입
- 일부 함수는 제한된 타입에서 작동하는 것 외에 제네릭 타입에는 대부분 타입 제공이 가능!
interface WithLength{
length:number;
}
function logWithLength<T extends WithLength>(input:T){
console.log(`length: ${input.length}`);
return input;
}
logWithLength("aaaa") //타입:string;
logWithLength([false,true]); //타입 boolean[];
logWithLength({length:123}) //타입 {length:number};
logWithLength(new Date()); //Error
// Argument of type 'Date' is not assignable to parameter of type 'WithLength'.
// Property 'length' is missing in type 'Date' but required in type 'WithLength'.
- 위와 같이 선언을 하면 T 제네릭에 대한 length를 가진 모든 타입이 받아들여짐 ⇒ length:number를 가진 객체까진 허용됨 ⇒ Date와 같은 타입 형태에는 숫자형 length 멤버X 오류가 발생한다.
- 객체도 length: (다른 타입)일 경우 바로 에러가 발생한다.
10.6.1 keyof와 제한된 타입 매개변수
- keyof 연산자는 제한된 타입 매개변수와도 잘 동작함
- extends와 keyof를 함께 사용하면 타입 매개변수를 이전 타입 매개변수의 키로 제한 가능(제네릭 타입 키를 지정하는 유일한 방법)
function get<T, Key extends keyof T>(container:T,key:Key){
//container에서 검색할 수 있는 T의 Key 중 하나의 key 이름을 받는다!?
return container[key];
}
const roles={
favorite:"AAA",
others:["aaa","bbb","ccc"],
}
const favorite=get(roles,"favorite"); //타입 string
const others=get(roles,"others"); //타입 string[];
//Error:Argument of type '"extras"' is not assignable to parameter of type '"favorite" | "others"'.
const missing=get(roles,"extras");
function get<T>(container:T,key:Key){
//container에서 검색할 수 있는 T의 Key 중 하나의 key 이름을 받는다!?
return container[key];
}
const roles={
favorite:"AAA",
others:["aaa","bbb","ccc"],
}
const favorite=get(roles,"favorite"); //타입 string
const others=get(roles,"others"); //타입 string[];
//Error:Argument of type '"extras"' is not assignable to parameter of type '"favorite" | "others"'.
const missing=get(roles,"extras");
function get<T>(container:T,key:keyof T){ //union type이다 => string | string[]으로 표현된다!
//container에서 검색할 수 있는 T의 Key 중 하나의 key 이름을 받는다!?
return container[key];
}
const roles={
favorite:"AAA",
others:["aaa","bbb","ccc"],
}
const favorite=get(roles,"favorite");
const others=get(roles,"others"); //타입 string | string[]
//Error:Argument of type '"extras"' is not assignable to parameter of type '"favorite" | "others"'.
const missing=get(roles,"extras");
- 위 둘의 차이의 경우: keyof T로 표현을 하면 ⇒ Container에 있는 모든 속성 값에 대한 유니언 타입이 반환된다
- 차이점: Key extends keyof T를 사용하면 타입이 string or string[] 둘 중 하나로 고정됨
- key: keyof T인 경우: string | string[] 타입 alias로 범위가 넓어진다.
10.7 Promise
- 임의의 타입에 대해 유사한 작업을 나타내는 promise의 기능은 타입스크립트의 제네릭과 자연스럽게 융합된다. (Promise클래스로 표현된다???)
추가 작성이 필요하다
10.7.1 Promise 생성
- TS에서 Promise 생성자는 단일 매개변수를 받도록 작성된다. (축소된 형식은 대략 밑과 같음)
class PromiseLike<Value>{
constructor(
exectutor:(
resolve:(value:Value)=>void,
reject:(reason:unknown)=>void,
)=>void,
){}
}
- 값을 resolve하려는 Promise를 만들려면, Promise의 타입 인수를 명시적으로 선언해야 함 ⇒ 명시하지 않으면 unknown으로 가정됨
//타입 Promise<unknown>
const resolvesUnknown=new Promise((resolve)=>{
setTimeout(()=>resolve("A"),1000);
})
//타입: Promise<string>
const resolveString=new Promise<string>((resolve)=>{
setTimeout(()=>resolve("A"),1000);
})
//타입 Promise<string>
const textEventaully=new Promise<string>((resolve)=>{
setTimeout(()=>resolve("Done!"),1000);
})
//타입: Promise<number>
const lengthEventaully=textEventaully.then((text)=>text.length);
- .then()메서드는 반환되는 Promise의 resolve된 값을 나타내는 새로운 타입 매개변수를 받는다. (즉, text.length(number이기 때문에 ⇒ lengthEventually는 Promise<number> 타입을 가지게 된다)
10.7.2 async 함수
- JS에서 async 키워드를 사용해 선언한 모든 함수는 Promise를 반환함
- thenable(.then() 메서드가 있는 객체)가 아닌 경우 Promise.resolve가 호출된 것처럼 Promise로 Wrapping 된다!
//(text:string)=>Promise<string>
async function lengthAfterSecond(text:string){
await new Promise((resolve)=>setTimeout(resolve,1000));
return text.length;
}
//타입:(text:string)=>Promise<number>
async function lengthImmediately(text:string){
return text.length;
}
- .then()처럼 반환값의 타입이 Promise 제네릭 타입으로 지정됨을 확인할 수 있음
async function givesPromiseForString():Promise<string>{
return "Done!";
}
//Error:the return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
async function giveString(): string{
return "Done";
}
10.8 제네릭 올바르게 사용하기
- 모범사례: 필요할 때만 사용해라 남발 금지! ⇒ (범용 모듈일 경우 많이 사용하는 경우도 있음)
10.8.1 제네릭 황금률
- 함수 타입 매개변수의 필요성을 판단하는 기준: 타입 매개변수가 최소한 두 번 이상 사용되는가?
//input 타입 매개변수를 정확히 한 번만 사용함
function logInput<Input extends string>(input:Input){
console.log("HI",input);
}
- 더 많은 매개변수 반환X ⇒ Input 타입 매개변수 선언은 굳이? 불필요함!!!
function logInput(input:string){
console.log("HI",input);
}
- 위와 같이 작성하는 것이 차라리 better (추가적인 정보는 “이펙티브 타입스크립트”에 나와 있음)
10.8.2 제네릭 명명 규칙
- 타입 매개변수에 대한 표준 명명 규칙⇒ 첫 번째 타입 인수로 T를 사용함! ⇒ 후속은 U,V형식으로 진행한다.
- 타입 인수의 용도에 따라 관련된 정보가 있는 경우 첫 글자를 따서 사용하기도 함(ex:상태 관리 라이브러리를 S로)
- 하지만 단일 문자를 사용할 때 의미가 명확하지 않을 경우 타입이 사용되는 용도를 가르키는 설명적인 제네릭 타입 이름을 사용하는 것이 좋음!
function labelBox<Label,Value>(label:Label,value:Value){};