728x90
제네릭이란 무엇인가?
제네릭 타입은 타입에 유연성을 제공하여 컴포넌트 등에서 재사용을 가능케 해주는 타입이다.
타입의 유연성이란 :string :number 등과 같이 고정된 타입이 아닌 사용에 따라 여러 타입을 사용하게 해준다는 것
이는 any 타입과 매우 흡사하지만 차이점이 있다.
제네릭은 타입 정보가 동적으로 결정되는 타입이다.
제네릭 타입은 다양한 타입을 받을 수 있다는 유연성이란 점에서 any 타입과 흡사하지만 타입의 정보가 동적으로 결정된다는 차이가 있다.
any 타입과 제너릭
any 타입 사용 코드
다음 any type을 사용한 코드이다.
// identityy의 인자 타입은 any
// 이 함수에 반환되는 타입과 any
function identity(arg: any): any {
return arg;
}
const example1 = identity(1) // 1을 반환
console.log(example1)
const example2 = example1.split('')
- 함수 identity는 any 타입으로 인자를 받아 그 인자를 반환하는 함수
- idetity(1)의 결과 1을 example1 이라는 변수에 할당할때
- example1 값의 타입은 any이다.
- 따라서 함수 호출의 결과의 실제 타입은 숫자(1)이지만 문자열 메서드인 split()를 사용할 수 있는
오류가 발생한다.
// identity의 인자 타입 any
// 이 함수가 반환하는 타입과 number
function identity(arg: any):number {
return arg;
}
const example1 = identity(1) // 1을 반환
console.log(example1);
const example2 = example1.split('')
- 위 코드를 수정하면 identity의 반환값을 number로 설정하여 문자열 메서드를 사용치 못하게 해야 한다.
generic 사용 코드
다음은 generic을 사용한 코드이다.
// 제네릭 사용
function identity2<T>(arg: T): T {
return arg;
}
const gExample1 = identity2(1)
console.log(gExample1)
// 문자열 메서드 사용 오류
gExample1.split('');
- any 타입이 아닌 제너릭('< T >')을 사용하였다.
- identity2 함수의 반환 타입도 T로 설정하였다.
- 이 결과 숫자(1)을 넣었을때 그 인자 타입이 T로 설정된다.
- 따라서 문자열 메서드인 split() 사용에 오류를 감지한다.
// 제네릭 사용
function identity2<T>(arg: T): T {
return arg;
}
const gExample1 = identity2(1)
console.log(gExample1)
// 문자열 메서드 사용 오류
gExample1.split('');
const gExample2 = identity2('gExample')
console.log(gExample2)
gExample2.split(' ')
- 제너릭은 any 타입과 마찬가지로 다양한 유형을 받을 수 있다.
- identity2()의 인자로 이번엔 문자열('gExample')을 전달하였다 (유연성)
- 전달한 문자열에 따라서 제네릭 T의 유형이 문자열로 설정된다.
- 따라서 문자열 메서드인 split() 사용에 문제가 없다.
제너릭 타입 정보가 동적으로 결정된다는 것은 이런 뜻이다.
위와 같이 함수가 호출될때 (동적) 타입이 유연하게 결정된다.
any를 쓰는 것은 함수의 arg가 어떤 타입이든 받을 수 있다는 점에서 제네릭이지만, 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 됩니다. 대신에 우리는 무엇이 반환되는지 표시하기 위해 인수의 타입을 캡처할 방법이 필요합니다.
인수와 반환 타입이 같은 타입을 사용하여, 타입 정보를 함수의 한쪽에서 다른 한쪽으로 운반할 수 있게끔 합니다.
출처 : https://typescript-kr.github.io/pages/generics.html
제너릭과 함수
함수에서 제너릭 정의
제너릭은 <> 기호를 이용해 정의하며, 이름은 자유롭게 지정이 가능하다.
function 함수 이름 <제너릭 이름> (인수: 제너릭 이름) : 제너릭 이름 {}
제너릭으로 재사용 가능한 함수 만들기
- 위 코드는 숫자형 배열과 문자열 배열을 만드는 함수이다.
- 다른 타입의 배열을 만들기 위해 두 개의 함수의 코드를 작성했지만 중복된 코드가 많아 보인다.
- 함수를 선언할때 T는 함수 호출시 입력이 되기 때문에 아직 어떤 타입인지 결정되지 않았다
- 호출될때의 T를 함수 내부에서도 사용한다(arr: T[] = [])
- 호출시 타입 T의 정보를 전달한다(arr1,arr2)
- 작성한 코드에서, 함수의 첫번째 매개변수를 알면 타입T도 알 수 있기 때문에 호출시 타입 T의 정보를 명시적으로
전달하지 않아도 된다(arr3, arr4)
제너릭 사용 함수 2
- 제너릭은 동적으로 타입이 결정된다.
- 위 코드에서 작성한 함수는 호출되지 않은 선언만 된 상태라 제너릭(T)의 타입을 알 수 없고 해당 타입에서 length 속성을 읽을 수 없다는 오류가 발생한다.
- 읽을 수 없기 때문에 element.length의 값이 무엇인지 알 수 없다. 따라서 숫자와 비교연산 (===, > , <)을 할 수 없다.
- 이 경우 interface를 설정해 제너릭이 interface를 가져오면 된다.
- length의 값이 숫자라는 것을 알게되어 오류가 발생하지 않는다.
제너릭 제약 조건
제너릭 제약 조건은 제너릭(T)의 타입을 제한한다.
이러한 제약 조건은 extends 키워드로 지정한다.
제너릭 제약 조건 만들기
function: 함수 이름 < 제너릭 이름 extneds 제한 조건 > (인수 : 제너릭 이름) : 제너릭 이름
- 위 코드에서 제너릭 T에 제약 조건을 걸었다.
- 제약 조건은 제너릭 T는 숫자 또는 문자여야 한다는 것.
- 따라서 객체 형태가 전달되는 arr5, 불리언 형태가 전달되는 arr6에서 오류가 발생
keyof 제약 조건
제너릭을 사용하는 객체의 key 값에 조건을 걸 수 있다.
예를 들어 객체 안에 있는 key의 값만을 받고 싶을 때 다음과 같은 코드를 작성할 수 있다. (keyof 제약)
- 제너릭 T는 객체 타입으로 제한하였다.
- 제너릭 UI는 T의 키로만 제한(U extneds keyof T)
- 위와 같이, keyof 로 제너릭 T의 키로만 제한하여서, extractAndCover의 두번째 인자로 'name'이 들어가면 오류
발생 - 첫번째 인자로 들어가는 객체 {} 에는 'name'이라는 key가 없기 떄문
- 첫번째 인자에 들어가는 객체에 key로 name을 주었기에 두번째 인자 'name'을 전달하여도 오류가 발생하지 않음
이를 통해 존재하지 않는 속성에 접근하는 것을 막을 수 있다.
제너릭과 클래스
클래스에서 제너릭 정의
Class 클래스 이름 <제너릭 이름> {}
- DataStorage 클래스를 제너릭 타입(T)와 함께 사용
- 해당 클래스는 인스턴스를 만들때 유연하게 타입을 받는다.
- newDataStorage()에 제너릭 타입을 설정해 문자형 또는 숫자형으로 인스탄스의 타입을 설정
- textStorage는 문자형을 받기 떄문에 addItem(숫자)에서 오류가 발생하며 같은 이유로
numberStorage.addItem(문자형)에도 오류발생
참고
'프론트 엔드 > Typescript' 카테고리의 다른 글
[TypeScript] Typescript에서 Enum이란 ? (0) | 2021.10.09 |
---|