티스토리 뷰
제네릭
- 제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다.
- C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는 데 사용된다.
- TS에서 제네릭은 클래스, 인터페이스, 함수에서 쓸 수 있음(제네릭 함수, 제네릭 인터페이스, 제네릭 클래스)
제네릭은 타입에 대한 함수라고 생각하면 된다는 ..제로초님 (선생님의 머리를 갖고싶어요)
function add<T>(x: T, y: T): T { return x + y }
add<number>(1, 2);
add(1, 2);
add<string>('1', '2');
add('1', '2');
add(1, '2');
제네릭 선언 위치 기억하기
function a<T>() {}
class B<T>() {}
interface C<T> {}
type D<T> = {};
const e = <T>() => {};
제네릭 기본값, extends
//제네릭으로 매개변수를 받는 것을 제한할 수 있음(extends 키워드를 통해)
//제네릭을 여러개 동시에 만들면서 각각 다른 제한을 줄 수 있음
function add<T extends number, K extends string>(x: T, y: K): T {
return x + y;
}
add(1, 2);
add("1", "2");
// <T extends {...}> // 특정 객체
// <T extends any[]> // 모든 배열
// <T extends keyof any> // string | number | symbol
- 제네릭이란 함수를 선언할 때말고 함수를 실행할 때 타입이 정해진다.
- 아래의 예시처럼 제네릭을 동시에 만들면서 각각 다른 제한(extends를 통해)을 줄 수 있음
- 매개변수 제한이라는 것의 의미는 아래 예시에서 return x + y;에는 문법 오류가 뜨기 때문임 하지만 매개변수는 제한할 수 있기때문임
함수를 제한하는 제네릭(주로 콜백함수)
- 모든 함수를 제한할 수도 있는데 이럴 땐 any를 써도 된다. 제한이 없는 경우가 많지는 않아서 잘 안쓸 뿐
//콜백함수 형태제한
function add<T extends (a: string) => number>(x: T): T {
return x;
}
add((a) => +a);
// <T extends (...args: any) => any> // 모든 함수
클래스 자체를 넘기고 싶을 때 쓰는 제네릭
- 클래스의 생성자만 뽑고 싶을 때는 이 제네릭을 쓴다
function add<T extends abstract new (...args: any) => any>(x: T): T {
return x;
}
//클래스 A 자체가 타입이고 내부에 constructor가 있기 때문에
class A {
constructor() {}
}
add(A); //new A() 는 에러
// <T extends abstract new (...args: any) => any> // 생성자 타입(클래스 내부에 있는 constructor의 타입)
매개변수 기본값(ES2015) 사용 시
- a: number라고 직접 명시해주지 않아도 추론이 가능하면 a = 3 이렇게만 써도 됨
const a = (a: number = 3, b: number = 5) => {
return 3;
};
//기본값은 참조형도 가능하다. 객체
const b = (c: { childNum: number } = { childNum: 3 }) => {
return c.childNum;
};
제네릭도 위처럼 기본값을 넣어줄 수 있음
- 리액트에서 JSX를 사용시, 화살표함수 제네릭을 사용할 때 에러가 나는 경우가 있는데 제네릭에 기본값(= unknown)을 넣어주면 에러가 사라진다. (extends 혹은 = unknown으로 기본값 넣기)
//TS가 기본값을 추론하지 못할 때 제네릭에 기본값을 넣어준다
//리액트 JSX를 사용할 때,
// const add = <T extends unknown> extends 사용
// const add = <T,> 콤마 찍는 것으로도 해결이되지만 명시적이지 않아서 쓰는건 권고되지 않음
// 제네릭에 기본값 할당하기
const add = <T = unknown>(x: T, y: T) => ({
x,
y,
});
const result = add(1, 2);
제네릭 <T>는 인터페이스 명 바로 뒤, 클래스명, 타입 뒤에 붙일 수 있다. 선언 시 이름뒤에 항상 제네릭이 같이 온다. 제네릭은 js변환 시 당연히 사라진다.
lib.es5.d.ts 를 통해 공부하기
//선언 시에는 T자리에 뭐가 올 지 모른다. (제네릭이니까)
//제네릭을 사용할 때 타입이 정해진다.
{
interface Array<T> {
forEach(
callbackfn: (value: T, index: number, array: T[]) => void,
thisArg?: any
): void;
}
//사용할 때 T(제네릭)을 number로 지정하면 위에 선언된 T자리가 number로 된다고 생각하면 된다.
//아래처럼 Array<number>로 직접 명시하는 경우는 ts가 제대로 추론하지 못할 때이다.
//실행 시 제네릭의 자리에 넣어주는 값을 '타입 파라미터'라고 한다.
//<number>add(1,2); 는 제네릭이 아니라 강제타입지정이다(as 로 바꾸는). 유의
const a: Array<number> = [1, 2, 3];
a.forEach((value) => {
console.log(value);
});
[true, false, true].forEach((value) => {
console.log(value);
});
//string | number | boolean
["123", 123, true].forEach((value) => {
console.log(value);
});
}
{
interface Array<T> {
map<U>(
callbackfn: (value: T, index: number, array: T[]) => U,
thisArg?: any
): U[];
}
//map 분석
const strings = [1, 2, 3].map((elem) => elem.toString()); // ["1","2","3"]
}
{
interface Array<T> {
//필터가 제대로 타입 추론을 못할 경우
filter(
predicate: (value: T, index: number, array: T[]) => unknown,
thisArg?: any
): T[];
filter<S extends T>(
//T는 number
predicate: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): S[];
}
//value % 2 가 unknown일지 value is S일지 골라야한다.
//홀수값만 뽑아내는 식이기 때문에 value is S이다.
//S는 number
//타입이 문자열인 애들만 뽑아내고 싶은 것인데 결과를 보면
//string | number []로 잘못추론하고있다. unknown보단 S로 한번 더 추론하고 있는 타입정의를 가져와서 재정의
const predicate = (value: string | number): value is string =>
typeof value === "string";
// is는 커스텀 타입가드이며 형식조건자이다.
//위의 식을 재정의해서 다시 filteredResult 의 타입 추론을 보면 string[] 으로 잘 나온다.
const filteredResult = ["1", 2, "3", 4, "5"].filter(predicate);
}
value is S 에서 is 는 타입가드에서 나온 키워드, 형식조건자라고도 불린다.
is (타입가드)
문법: parameterName is Type
typeof 같은 걸로 타입을 따져서 분기 처리하는 역할을 TS에서 is으로 쓴다.
function isString(test: any): test is string{
return typeof test === "string";
}
function example(foo: any){
if(isString(foo)){
console.log("it is a string" + foo);
console.log(foo.length); // string function
}
}
example("hello world");
커스텀 타입 가드에서 많이 사용된다.
interface Cat { meow: number }
interface Dog { bow: number }
function catOrDog(a: Cat | Dog): a is Dog {
if ((a as Cat).meow) { return false }
return true;
}
const cat: Cat | Dog = { meow: 3 }
if (catOrDog(cat)) {
console.log(cat.meow);
}
if ('meow' in cat) {
console.log(cat.meow);
}
as(타입 단언, 강제 형변환?)
개발자가 타입을 지정하지 않아도 ts 컴파일러가 추론이 간으한 타입 추론 기능이 TS에 존재한다.
이럴 때 as를 쓴다.
타입 단언은 2종류
- <Fish>pet //런타임과 컴파일 단계에서 모두 돌아가고
- (pet as Fish) // 컴파일 때만 돌아간다.
❌주의사항: 리액트로 개발시 꺽쇠로 타입캐스팅하는 것은 tsx 태그 문법과 비슷하기 때문에 as를 권장한다.
'Frontend > TypeScript' 카테고리의 다른 글
[TypeScript] Utility Types - Partial/ Omit / Pick / Exclude / Extract (0) | 2022.12.30 |
---|---|
[TypeScript] 타입스크립트 오버로딩, 에러핸들링 (0) | 2022.12.30 |
[TypeScript] 빈객체, 객체 타입, 클래스 추가 개념 (0) | 2022.12.26 |
[TypeScript] 타입 가드(좁히기), 커스텀 타입 가드 (0) | 2022.12.24 |
[TypeScript] create-react-app 으로 만든 리액트 앱 TS로 변환 세팅 (0) | 2022.12.06 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 항해99추천비추천
- 프리렌더링확인법
- 원티드 프리온보딩 FE 챌린지
- aspect-ratio
- getStaticPaths
- 틸드와 캐럿
- 형제 요소 선택자
- is()
- 부트캠프항해
- 원티드 3월 프론트엔드 챌린지
- && 셸 명령어
- text input pattern
- nvm경로 오류
- Prittier
- getServerSideProps
- 타입스크립트 장점
- ~ ^
- 항해99프론트후기
- D 플래그
- float 레이아웃
- 프리온보딩 프론트엔드 챌린지 3월
- fs모듈 넥스트
- 원티드 FE 프리온보딩 챌린지
- grid flex
- 항해99프론트
- tilde caret
- 타입스크립트 DT
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- nvm 설치순서
- reactAPI
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함