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 | 31 |
Tags
- Promise
- debounce
- ios
- Throttle
- hydration mismatch
- RN새로운아키텍쳐
- react-native-image-picker
- react-native
- animation
- react
- React-Native업데이트
- named type
- promise.all
- javascript
- RN업데이트
- rn
- CS
- react-native-permissions
- await
- private-access-to-photos
- motion.div
- RN아키텍쳐
- Swift
- no-permission-handler-detected
- async
- react animation
- react native
- 비동기
- Hash-table
- axios
Archives
- Today
- Total
하루살이 개발일지
[TypeScript] 제네릭(Generics)에 대하여 본문
제네릭(Generics)이란?
- 제네릭 : 타입을 마치 함수의 파라미터처럼 사용하는 것
- 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용
제네릭의 예시
function getText(text) {
return text;
}
getText('hi'); // 'hi'
getText(10); // 10
getText(true); // true
- text라는 인자에는 string, number, boolean 등 어떤 값이 들어가더라도 그대로 반환됨
function getText<T>(text: T): T {
return text;
}
getText<string>('hi');
getText<number>(10);
getText<boolean>(true);
- 제네릭 기본 문법이 적용된 형태
- 이제 함수를 호출할 때 함수 안에서 사용할 타입을 넘겨줄 수 있음
getText<string>('hi') 호출 시 제네릭 동작 과정
function getText<T>(text: T): T {
return text;
}
getText<string>('hi');
이렇게 호출하면,
function getText<string>(text: T): T {
return text;
}
- 먼저 이런 과정을 거침
- 함수에서 제네릭 타입이 string이 되는 이유 :
- getText() 함수를 호출할 때 제네릭 값(=함수에서 사용할 타입)으로 string을 넘겨줬기 때문
function getText<string>(text: string): string {
return text;
}
- 그래서 결과적으로 이렇게 타입을 정의한 것과 같아짐
- 이 함수는 입력 값의 타입이 string 이면서 반환값 타입도 string이어야 함
제네릭을 사용하는 이유
function logText(text: string): string {
return text;
}
- 인자를 하나 넘겨 받아 반환해주는 함수
- 여기서 이 함수의 인자와 반환값은 모두 string으로 지정되어 있지만 만약 여러 가지 타입을 허용하고 싶으면 아래와 같이 any를 사용할 수 있음
function logText(text: any): any {
return text;
}
- 하지만 함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수 없음
- any는 타입 검사를 하지 않기 때문
이러한 문제점을 해결할 수 있는게 제네릭
function logText<T>(text: T): T {
return text;
}
- 함수 이름 바로 뒤 <T>
- 함수의 인자와 반환 값에 모두 T라는 타입을 추가
- 이렇게 되면 함수를 호출할 때 넘긴 타입에 대해 타입스크립트가 추정할 수 있게 됨
- 즉, 함수의 입력 값에 대한 타입과 출력 값에 대한 타입이 동일한지 검증할 수 있음
이렇게 선언한 함수는 아래와 같이 2가지 방법으로 호출할 수 있음
// #1
const text = logText<string>("Hello Generic");
// #2
const text = logText("Hello Generic");
- 보통 두 번째 방법이 코드도 더 짧고 가독성이 좋아 흔하게 사용
- 하지만, 만약 복잡한 코드에서 두 번째 코드로 타입 추정이 되지 않는다면 첫 번째 방법 사용하면 됨
제네릭 타입 변수
function logText<T>(text: T): T {
return text;
}
- 이와 같이 제네릭을 사용하기 시작하면 컴파일러에서 인자에 타입을 넣어달라는 경고를 보게 됨
- 만약 여기서 함수의 인자로 받은 값의 length를 확인하고 싶다면 어떻게 해야 할까?
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
- 위 코드는 에러 발생
- 왜냐하면 모든 T타입이 .length 속성을 가지는 것은 아니기 때문
- 현재, 함수의 인자와 반환 값 타입에 마치 any를 지정한 것과 같이 동작
- 그래서 당연히 인자에 number 타입 등을 넘길 수 있음
- 이러한 특성 때문에 현재 인자인 text에 문자열이나 배열이 들어올 것이라고 하더라도 아직은 컴파일러 입장에서 .length 를 허용할 수 없음
- 왜냐하면 number가 들어왔을 때는 .length 코드가 유효하지 않기 때문
function logText<T>(text: T[]): T[] {
console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
return text;
}
logText([1, 2, 3]); // 여기서 T는 number
logText(['a', 'b', 'c']); // 여기서 T는 string
- 그래서 이렇게 제네릭에 타입을 줄 수 있음
- 위 코드가 기존 제네릭 코드와 다른 점은 인자의 T[] 부분
- logText는 매개변수 text를 받음
- text의 타입은 T타입의 배열
- T는 logText를 호출할 때 결정됨
- 즉 logText함수는 어떤 타입의 배열이든 받을 수 있고, 동일한 타입의 배열을 반환하도록 보장됨
아니면 이렇게 좀 더 명시적으로 제네릭 타입을 선언할 수 있음
function logText<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
- T[]와 Array<T>는 TypeScript에서 배열을 표현하는 두 가지 방법
- 즉 이거랑 저거랑 완전히 동일한 코드이고 표현의 차이일 뿐
제네릭 타입
제네릭 인터페이스
function logText<T>(text: T): T {
return text;
}
// #1
let str: <T>(text: T) => T = logText;
// #2
let str: {<T>(text: T): T} = logText;
- 1번 코드와 2번 코드는 같은 의미
interface GenericLogTextFn {
<T>(text: T): T;
}
function logText<T>(text: T): T {
return text;
}
let myString: GenericLogTextFn = logText;
// **만약, 인터페이스에 인자 타입을 강조하고 싶다면**
interface GenericLogTextFn<T> {
(text: T): T;
}
function logText<T>(text: T): T {
return text;
}
let myString: GenericLogTextFn<string> = logText;
- GenericLogTextFn : 제네릭 함수를 나타내는 인터페이스
- T라는 제네릭 타입을 가진 함수를 정의
- 이 함수는 T타입의 매개변수를 받아 T타입의 값을 반환
- logText : T라는 제네릭 타입을 가진 함수
- T타입의 매개변수 text를 받아 text를 그대로 반환
- 이는 GenericLogTextFn 인터페이스와 일치
- myString 변수 : logText 함수가 할당됨
- myString의 타입은 GenericLogTextFn
- 즉, myString은 T타입의 매개변수를 받아 T타입의 값을 반환하는 함수
- 이렇게 인터페이스를 사용하면, 특정 형태를 가진 함수를 나타내는 타입을 쉽게 정의
- 여기서는 GenericLogTextFn 인터페이스를 사용해 T타입의 매개변수를 받아 T타입의 값을 반환하는 함수의 타입을 정의함
- 이렇게 인터페이스를 사용하면 코드가 읽기 쉽고 유지보수 편해짐
- 이와 같은 방식으로 제네릭 인터페이스 뿐만 아니라 클래스도 생성 가능
제네릭 클래스
class GenericMath<T> {
pi: T;
sum: (x: T, y: T) => T;
}
let math = new GenericMath<number>();
- 제네릭 클래스를 선언 시 클래스 이름 오른쪽에 <T>를 붙여줌
- 그리고 해당 클래스로 인스턴스를 생성할 때 타입에 어떤 값이 들어갈 지 지정하면 됨
제네릭 제약 조건
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
- 인자의 타입에 선언한 T는 어떤 타입인지 구체적으로 정의하지 않았음 → length 코드에서 오류
- 이 코드에서 length 속성 정도는 허용하려면 아래와 같음
interface LengthWise {
length: number;
}
function logText<T extends LengthWise>(text: T): T {
console.log(text.length);
return text;
}
logText(3); // Error: Argument of type '3' is not assignable to parameter of type 'LengthWise'.
logText({ length: 3 }); // No error
- extends 키워드
- 제네릭 타입 변수가 특정 조건을 만족하도록 제한할 때 사용
- <T extends LengthWise> : T타입이 LengthWise 인터페이스를 충족해야 한다는 것을 의미
- LengthWise 인터페이스
- length라는 number 타입의 속성을 가지는 객체를 나타냄
- logText 함수
- T라는 제네릭 타입을 가짐
- T는 LengthWise 인터페이스를 충족하는 타입이어야 함
- 즉, T는 length 속성을 가진 객체 타입이어야 함
- T라는 제네릭 타입을 가짐
- 함수 내부에서 text.length를 출력할 때 T extends LengthWise로 인해 TypeScript가 text가 반드시 length 속성을 가질 것이라 가정하고, text.length에 접근하는 것이 안전하다고 판단
- 이와 같이 extends를 사용하여 제네릭 타입에 제약조건을 두면, 제네릭 타입이 어떤 속성이나 메소드를 가질 것이라고 가정하고 코드를 작성할 수 있음
객체의 속성을 제약하는 방법 : keyof
두 객체를 비교할 때도 제네릭 제약 조건을 사용할 수 있음
function getProperty<T, O extends keyof T>(obj: T, key: O) {
return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.
- keyof 연산자
- TypeScript에서 제공하는 인덱스 타입 쿼리 연산자
- 어떤 타입의 키를 나타내는 타입을 생성
- 예를 들어, { a: 1, b: 2, c: 3 } 타입의 keyof 결과는 "a" | "b" | "c"
- O extends keyof T
- O가 T의 키로서 존재해야 한다는 제약
- 이런 제약 때문에, getProperty 함수에는 실제 객체의 속성 이름만을 인수로 전달할 수 있음
- getProperty(obj, "a"); // okay
- a는 obj의 키 중 하나이므로, 이 호출은 허용
- obj["a"]를 반환
- getProperty(obj, "z");
- "z"는 obj의 어떠한 키와도 일치하지 않음
- 따라서 O extends keyof T 제약조건에 위배
- TypeScript 컴파일러는 이 호출에 대해 오류를 발생
- 이런 방식으로 TypeScript의 제네릭과 keyof 연산자, 그리고 extends 키워드를 사용하면, 객체의 키를 안전하게 다룰 수 있음
(출처 : https://joshua1988.github.io/ts/guide/generics.html#제네릭-클래스)
'웹개발 > TypeScript' 카테고리의 다른 글
[TypeScript] type과 interface의 차이점 (7) | 2023.07.13 |
---|---|
TypeScript로 Create React App (CRA) 프로젝트를 생성 (0) | 2023.06.23 |