일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 타입
- 타입스크립트
- react
- 리액트
- svgr
- Docker
- test
- Primitive
- jest
- error
- nextjs
- 아키텍처
- next.js
- SVG
- DAILY
- Unit Test
- 프론트엔드
- docker-compose
- 자바스크립트
- Solid
- Study
- DFS
- Component
- 다이나믹프로그래밍
- typescript
- javascript
- type
- BFS
- 알고리즘
- 백준
- Today
- Total
`프론트엔드 개발자` 개형이의 벽돌집
타입스크립트 (2) - 타입의 사용. 어떻게 사용할 수 있을까? 본문
회사 스프린트 일정으로 인해 정신이 없어서 업로드가 늦어져서 아쉽지만,
타입스크립트에서 타입을 어떻게 사용하는지 기록해두고자 한다.
원시 타입 (Primitive type)
기본적으로 알고 있는 타입의 종류이다.
- string
- number
- boolean
- etc..
let str: string = "string";
let num: number = 100;
let bool: boolean = true;
변수 선언 시 타입을 명시해줄 수 있다. (위의 경우는 굳이 명시를 안해줘도 알아서 타입을 추론한다)
배열
배열의 타입은 ~~[] 혹은 Array<~~>의 형태로 지정 가능하다.
let num_arr: number[] = []
let num_arr1: Array<number> = []
// -> number[] or Array<number>
또다른 방법은 Type, Interface를 이용하여 배열 타입의 alias를 지정할 수 있다.
// type
type PersonList = string[];
// interface
interface PersonList {
[index: number]: string;
}
함수
매개변수의 타입, 반환 값의 타입을 지정할 수 있다.
function getFavoriteNumber(x: number): number {
return x;
}
또다른 방법은 Type, Interface를 이용하여 함수 타입의 alias를 지정할 수 있다.
// type
type EatType = (food: string) => void;
// interface
interface EatType {
(food: string): void;
}
객체
객체 타입의 경우 , 혹은 ; 로 구분이 가능하여 지정할 수 있다.
-> ({ x: number; y: number } or { x: number, y: number } )
function printCoord(pt: { x: number; y: number })
{
//~~~
}
Type & Interface
위에 예시를 봐도 알 수 있지만, 타입의 별칭으로 Type과 Interface를 사용할 수 있다.
type Point = {
x: number;
y: number;
}
interface Point {
x: number;
y: number;
}
❗Type vs Interface
사용법은 대부분 동일하다. 단, 타입은 생성된 뒤 달라질 수 없지만 인터페이스는 병합하여 확장이 가능하다.
일반 확장 예시)
// Interface는 extends를 통해 확장을 통한 새로운 타입 설정 가능
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
// Type은 교집합을 통한 확장을 통해 새로운 타입 설정 가능
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
병합 확장 예시)
// Interface의 경우 동일한 이름으로 타입 선언시 자동 병합
interface Window {
title: string
}
interface Window {
title2: string
}
// Type은 위와 같이 사용할 경우 에러가 발생
❓ Type과 Interface를 잘 사용하는 법은?
- Interface는 객체의 모양을 선언하는 데만 사용한다. 따라서 원시 타입 별칭(Primitive type alias)는 Type만 가능하다는 점을 참고하자.
- 타입스크립트 공식 핸드북 에서는 개인적 선호에 따라 자유롭게 선택해서 사용할 수 있지만 잘 모르겠을 경우에는 interface를 사용해보고 문제가 발생했을 때 type을 사용하라고 나와있다.
유니언 (Union)
| 를 사용하여 여러 개의 타입을 사용 가능하게 할 수 있다.
function printId(id: number | string) {
// 매개변수 id는 number 혹은 string 타입을 가질 수 있다.
}
단, 유니언을 다룰 때는 해당 유니언 타입의 모든 멤버에 대하여 유효한 작업일 때에만 허용된다.
function printId(id: number | string) {
console.log(id.toUpperCase()); // 에러 발생 (string 타입에만 유효한 메소드이기 때문)
// typeof 등으로 분기하여 사용 가능함
if (typeof id === "string") {
id.toUpperCase() // 정상
}
}
-> 위의 예시는 Type Guard를 사용한 것인데 이는 추후 포스팅에 기록할 것이다.
읽기 전용 프로퍼티 (readonly)
readonly를 사용하면 객체가 처음 생성될 때만 수정이 가능하고 이후에는 수정이 불가능하다.
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = {x: 10, y: 20};
p1.x = 5; // 에러 발생
❓ const랑 readonly의 차이는?
→ 변수에는 const를 사용하고 프로퍼티에는 readonly를 사용합니다.
리터럴 타입 (Literal Type)
구체적인 문자나 숫자를 타입으로 지정할 수 있다.
function printText (str: string, alignment: "left" | "right" | "center")
{
// alignment에 "left" | "right" | "center" 제외한 값이 들어오면 에러 표시
}
제네릭 (Generic)
Generic은 타입에 변수를 제공하는 방법이다.
자바의 Generic, C++의 Template 개념과 동일하다고 보면 될 듯 하다.
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
// 출력 타입은 'string'입니다.
let output = identity("myString");
// 출력 타입은 'string'입니다. ===> Generic도 타입 인수를 추론
→ 위 예제에서 보면 유저가 주는 타입으로 함수 내에서 사용되는 것을 알 수 있다.
type Item<T> = {
id: T extends string | number ? T : never;
};
// 위에 extends는 상속의 의미로 생각하기 보다는
// 'T가 string이거나 number라면'이라고 생각하면 된다.
const item: Item<boolean> = {
id: true // 타입 'boolean'은 never 타입에 할당될 수 없다고 나옴
}
→ 위 예제에서 보면 Generic은 conditional type으로 조건을 줄 수도 있다.
Any & Unknown
unknown은 무조건 타입을 좁혀서 사용해야 하는 의무가 있지만, any는 타입을 좁혀서 사용하지 않아도 허용된다는 차이점이 있다.
- Any 예시
function func(a: any): number | string | void {
a.toString(); // any 타입이므로 문제가 발생하지는 않는다
if (typeof a === 'number') {
return a * 10;
} else if (typeof a === 'string') {
return `Hello ${a}`
}
}
- Unknown 예시
function funcUnknown(a: unknown): number | string | void {
a.toString(); // unknown 타입이므로 문제가 발생
// 아래 코드에 대해서는 문제 발생 안함
// any 대신 unknown을 사용할 경우 안정성 높일 수 있음
if (typeof a === 'number') {
return a * 10;
} else if (typeof a === 'string') {
return `Hello ${a}`
}
}
Obtional Type
옵셔널 타입을 사용하면 값을 필수적으로 넣지 않아도 사용이 가능하다.
type Result1<T> = {
data?: T; // 값이 없어도 에러 발생 안함
error?: Error; // 값이 없어도 에러 발생 안함
loading: boolean;
};
❓ 다음과 같은 경우에서는 Optional type을 사용했을 때 문제가 생긴다.
// r1의 data가 있으면 error는 null이고 loading은 false로 만들고 싶음
type Result1<T> = {
data?: T;
error?: Error;
loading: boolean;
};
declare function getResult1(): Result1<string>;
const r1 = getResult1();
r1.data; // 타입은? string | undefined
r1.error; // 타입은? Error | undefined
r1.loading; // 타입은? boolean
if (r1.data) {
r1.error; // Error | undefined --> data의 값이 있지만 에러가 null이 아닐 수도 있음
r1.loading; // boolean
}
-> 위 예시에서 보여지는 문제는 Optional type을 사용하는 대신 유니온 타입을 사용할 경우 해결할 수 있는데,
유니온 타입과 Type Guard 방식을 활용하여 해결한 예시는 다음 코드에서 확인할 수 있다.
type Result2<T> =
| { type: 'pending'; loading: true }
| { type: 'success'; data: T; loading: false }
| { type: 'fail'; error: Error; loading: false };
declare function getResult2(): Result2<string>;
const r2 = getResult2();
if (r2.type === 'success') {
r2; // { type: 'success'; data: string; loading: false; }
}
if (r2.type === 'pending') {
r2; // { type: 'pending'; loading: true; }
}
if (r2.type === 'fail') {
r2; // { type: 'fail'; error: Error; loading: false; }
}
// 목표했던 사항: r2의 data가 있으면 error는 null이고 loading은 false로 만드는 것을
// 안정성 있게 작성할 수 있습니다.
-> literal type으로 분기를 시켜 data가 string 타입일 때는 에러 값을 받지 않게 할 수 있는 것을 확인했다.
서브 타입
좁은 타입에서 넓은 타입으로의 대입이 가능하다. 반대일 경우는 에러가 발생하게 된다.
let sub1: 1 = 1;
let sup1: number = sub1;
// -> 좁은 타입에서 넓은 타입으로의 대입은 가능 (리터럴 1은 number의 서브타입)
sub1 = sup1;
// -> 에러. sup1은 number, sub1은 리터럴타입이므로 넓은 타입에서 좁은 타입으로의 대입인 상황
let sub2: number[] = [1];
let sup2: object = sub2;
// -> 배열은 object에 포함이므로 가능 (배열은 object의 서브타입)
sub2 = sup2; // 에러
let sub3: [number, number] = [1, 2];
let sup3: number[] = sub3;
// -> 튜플은 배열의 포함 (튜플은 배열의 서브타입)
sub3 = sup3; // 에러
let sub4: { a: string, b: number} = { a: "a", b: 1}
let sup4: { a: string | number, b: number} = sub4
// Object의 경우 각각의 프로퍼티가 대응하는 프로퍼티가 같거나 서브타입이어야 대입 가능,
// Array도 마찬가지로 동작
sub4 = sup4;; // 에러
타입스크립트 서론에 대한 포스팅을 올린 후 해당 포스팅을 올리기까지 시간이 꽤 걸려서 아쉽다.
하지만 영원히 방치하진 않았다는 사실에 우선 만족...
이번 포스팅에 타입의 사용에 대해 기록했다면 다음 포스팅에는 타입의 추론에 대해 기록해보고자 한다.
꾸준한 개발자가 되자!! 😎
'타입스크립트' 카테고리의 다른 글
타입스크립트 (5) - tsconfig란? what is tsconfig in TypeScript? (0) | 2022.06.14 |
---|---|
타입스크립트 (4) - 타입 가드란? what is Type Guard in TypeScript? (0) | 2022.05.24 |
타입스크립트 (3) - 타입의 추론. 타입은 어떻게 추론될까? (0) | 2022.05.13 |
타입스크립트 (1) - 타입스크립트를 왜 사용하는가? why typescript? (0) | 2022.01.12 |