728x90
타입스크립트의 기본 타입
- 타입은 부모 자식 관계를 이루며 계층을 형성하게 된다.
원시 타입과 리터럴 타입
- 단 하나의 값만 저장할 수 있다.
- :number와 같이 타입을 정하는 부분을 타입 주석(type annotation)이라고 한다.
// number
let num1: number = 123;
let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;
// string
let str1: string = 'hi';
let str2: string = 'hi';
let str3: string = `hi`;
let str4: string = `hi ${num1}`;
// boolean
let bool1: boolean = true;
let bool2: boolean = false;
// null
let null1: null = null;
// undefined
let un1: undefined = undefined;
// literal
let numA: 10 = 10;
- let numA: number = null; 과 같이 null 타입이 아닌 변수에 null을 넣게 되는 경우 strictNullChecks: "false"여야 한다.
- 이 옵션은 null 타입 검사를 엄격하게 하겠다는 말인데, false라면 임의로 위와 같은 경우를 허용하고 true라면 허용치 않는다.
- 기본적으로 true인데, strictNullChecks의 상위 개념이 strict라서, 별도로 지정하지 않으면 strict의 설정값을 따라가게 된다.
- 리터럴은 값을 뜻한다. 따라서 리터럴 타입은 값이 타입이라는 뜻이다.
- 그렇기 때문에 위 경우와 같이 값이 10인 경우 타입이 값 10이기 때문에 다른 값을 할당할 수 없다.
- 이 값은 숫자, 문자열, 불리언 등 다른 원시 타입으로도 만들 수 있다.
배열과 튜플
// 배열
let numArr: number[] = [1, 2, 3];
let strArr: string[] = ['a', 'b', 'c'];
let boolArr: boolean[] = [true, false];
let boolArr2: Array<boolean> = [true, false]; // 제네릭 문법
- let numArr: number[] = [1, 2, 3]처럼 배열 안에 들어갈 요소들의 타입을 먼저 작성하고 []를 붙여준다.
- 또는 Array<원소 타입> = [] 과 같이 제네릭 문법으로 작성할 수도 있다.
let multiArr: (string | number)[] = [1, 'hi'];
let multiArr2: Array<string | number> = [1, 'hi'];
- 배열에 들어가는 요소들의 타입이 다양할 경우 위와 같이 작성해준다.
- |는 유니온 타입이라고 한다.
- 이 경우도 제네릭 문법으로 쓸 수 있다.
- TS는 점진적 타입 시스템으로 사용하기 때문에 타입을 추론해주니 모르겠으면 커서를 올려보자 🥲
// 다차원 배열의 타입
let doubleArr: number[][] = [
[1, 2, 3],
[4, 5],
];
let tripleArr: (number | number[])[][] = [
[1, 2, [3]],
[4, 5, 6],
[7, 8, 9],
];
- n차원 배열은 먼저 요소들의 타입을 적어주고, 괄호를 n번 열어주면 된다.
// 튜플
let tup1: [number, number] = [1, 2];
let tup2: [number, string, boolean] = [1, 'hi', true];
- 튜플은 JS에는 없고 TS에만 있다.
- 튜플은 길이와 타입이 '고정'된 배열이다.
- tup1 = [1, 2, 3] 또는 tup1 = ["1", "2"]와 같이 길이나 요소의 타입이 일치하지 않은 값을 할당하려 하면 에러가 발생한다.
- 튜플도 배열이기 때문에 배열과 관련된 메서드를 사용할 수 있다.
- 다만 tup1에 push, pop을 사용하면 에러가 발생해야 하는데 에러가 발생하지 않고 있다.
- 배열 메서드를 사용할 때는 JS의 배열이라고 생각하기 때문에 튜플의 길이 제한이 발동하지 않는다.
- 따라서 튜플에 배열 메서드를 사용할 때는 각별히 주의해야 한다.
let users: [string, number][] = [
['김씨', 1],
['이씨', 2],
['박씨', 3],
// [4, '최씨']
];
- 위와 같이 주석 처리한 부분은 [string, number]라는 규칙에 어긋난다.
객체
let user: object = {
id: 1,
name: "summermong",
};
user.id; // object 형식에 id 속성이 없다
- object라고 타입을 지정하면 점 표기법으로 속성에 접근할 때 속성을 읽어오지 못한다.
- TS에서 object는 타입은 어떤 객체든 할당할 수 있는 가장 일반적인 객체 타입일 뿐, 객체의 구조에 대한 정보를 제공하지 않는다.
- 즉 단순히 object로 타입을 지정할 경우 object 안의 속성은 이해하지 못한다. 그래서 잘 쓰지 않는다.
let user: {
id: number;
name: string;
} = {
id: 1,
name: 'summermong',
};
- 따라서 이 때는 객체 리터럴 타입을 사용하면 된다.
- 객체 리터럴 타입은 {}를 열고 안에 속성의 이름과 속성의 타입을 지정해주면 된다.
- 이렇게 구조를 기준으로 코드를 작성하는 TS의 특징을 구조적 타입 시스템이라고 부른다. (Property Based System)
- 대부분의 언어가 사용하는 명목적 타입 시스템 (Nominal Type System)과는 대립되는 개념
- 객체의 타입이나 이름을 기준으로 함.
- 객체나 타입의 이름이 동일해야 호환 가능하다고 간주한다.
let user: {
id?: number;
name: string;
} = {
name: 'summermong',
};
- 만약 id 값이 있어도 되고 없어도 되는 값이라면 속성 뒤에 ?를 붙여주면 된다.
- ?를 붙이지 않고 id를 입력하지 않으면 오류가 발생하지만 ?를 붙이면 오류가 발생하지 않는다.
- id의 타입을 number로 지정했기 때문에 만약 id가 들어온다면 무조건 number로 들어와야 한다.
- 이런 속성을 선택적 프로퍼티라고 한다.
let config: {
readonly apiKey: string;
} = {
apiKey: 'my api key',
};
// config.apiKey = 'hacked';
- api key처럼 절대 값이 수정 되어서는 안되는 프로퍼티가 있다면 readonly 키워드를 붙여 읽기 전용 속성으로 만들어준다.
타입 별칭과 인덱스 시그니처
// 타입 별칭
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
let user: User = {
id: 1,
name: '썸머몽',
nickname: 'summermong',
birth: '1997',
bio: 'hi',
location: 'suwon',
};
- 타입을 변수처럼 지정해 코드의 중복을 방지한다.
- User라는 타입 별칭을 똑같이 써주면 된다.
- 만약 속성을 또 추가해야 한다면 User에 추가해 이 별칭을 쓰고 있는 다른 변수의 속성 누락을 방지할 수 있다.
- 타입 별칭은 같은 스코프에서 중복되면 에러가 발생한다.
- TS -> JS로 컴파일 시 타입 별칭으로 만든 타입도 다 제거가 된다.
// 인덱스 시그니처
type countryCodes = {
[key: string]: string;
};
let countryCodes: countryCodes = {
korea: 'ko',
usa: 'us',
uk: 'uk',
};
- 만약 countryCodes 안에 들어가는 프로퍼티가 3개가 아니라 nn개라면 적을 게 너무 많아진다.
- 키와 밸류의 규칙을 기준으로 객체의 타입을 정할 수 있는 문법을 인덱스 시그니처라고 한다.
- [키: 키의 타입]: 밸류의 타입 과 같이 적어주고 해당 타입 별칭을 적어주면 된다.
- 인덱스 시그니처는 지정한 [키: 키의 타입]: 밸류의 타입을 위반하지만 않으면 에러가 발생하지 않는다.
- 따라서 만약 해당 타입 별칭을 사용하는 변수에 {}처럼 빈 객체가 있어도 에러가 발생하지 않는다. (위반할 게 없어서)
- 빈 객체를 방지하기 위해 최소한 입력해야 하는 값을 적어둘 수 있다. (Korea: number;)
- 단, 이때 Korea: "123" 과 같이 지정해 놓으면 인덱스 시그니처에 정해둔 number 타입에 위반되므로 에러가 발생한다.
Enum 타입
- TS에만 있는 타입으로, 여러 가지 값들에 각각 이름을 부여해 열거하여 사용하는 타입을 말한다.
- ADMIN, USER, GUEST에 숫자를 할당하지 않아도 0, 1, 2가 자동으로 할당된다.
- 10번부터 시작하고 싶으면 ADMIN = 10, 만 써도 아래의 인덱스가 알아서 바뀐다. (어떤 부분에 어떤 수로 기준을 정할 수 있음)
- 이러한 형태를 숫자형 enum이라고 한다.
enum Role {
ADMIN = 0,
USER = 1,
GUEST = 2,
}
enum Language {
korean = 'ko',
english = 'en',
}
const user1 = {
name: 'summermong',
role: Role.ADMIN, // 0이 저장됨
language: Language.korean,
};
const user2 = {
name: 'autumnmong',
role: Role.USER,
language: Language.english,
};
const user3 = {
name: 'wintermong',
role: Role.GUEST,
language: Language.korean,
};
- 문자형도 지정할 수 있는데 이러한 형태를 문자형 enum이라고 한다.
- enum은 컴파일을 해도 사라지지 않고 JS의 객체로 컴파일 된다.
var Role;
(function (Role) {
Role[Role["ADMIN"] = 0] = "ADMIN";
Role[Role["USER"] = 1] = "USER";
Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
Language["korean"] = "ko";
Language["english"] = "en";
})(Language || (Language = {}));
const user1 = {
name: 'summermong',
role: Role.ADMIN,
language: Language.korean,
};
const user2 = {
name: 'autumnmong',
role: Role.USER,
language: Language.english,
};
const user3 = {
name: 'wintermong',
role: Role.GUEST,
language: Language.korean,
};
console.log(user1, user2, user3);
export {};
Any 타입과 Unknown 타입
let anyVar: any = 10;
let num: number = 10;
num = anyVar;
- TS에만 있는 타입
- any는 특정 변수의 타입을 프로그래머가 확실히 알지 못할 때 사용한다.
- 처음에 할당하는 값의 타입을 추론하는 TS의 특성 상 any를 입력하지 않고 10을 할당하면 'hi'를 재할당할 수 없지만, any로 타입을 지정한 후에는 문자열로 재할당이 가능하다.
- 모든 타입이 될 수 있기 때문에 어떤 타입에만 있는 메서드든 자유롭게 사용할 수 있다.
- 하지만 이때문에 런타임에서 에러가 발생하는 경우가 생겨 TS의 장점을 잃게 되므로 지양해야 한다.
let unknownVar: unknown;
unknownVar = '';
unknownVar = 1;
- TS에만 있는 타입
- any 타입과 매우 유사하지만 any처럼 대입할 수 없고, 특정 타입의 메서드를 사용할 수 없으며 연산도 할 수 없다.
- 타입을 알 수 없을 때, 조건문에서 타입을 좁히는 (=타입 정제) 때 사용된다.
- 런타임 때 에러를 발생 시키는 any보다는 낫기 때문에 타입을 모른다면 unknown을 쓰는 것을 추천한다.
Void와 Never 타입
function func1(): string {
return 'hello';
}
function func2(): void {
console.log('hi');
}
- Void는 아무 것도 없음을 뜻한다.
- TS는 함수의 반환값에도 타입을 지정할 수 있다. (func1(): string처럼)
- func2처럼 반환값이 없고 콘솔에 출력만 하는 경우 반환값이 없다 === void로 표현할 수 있다.
- let a: void처럼 변수에도 지정할 수 있지만 undefined만 할당할 수 있다. (strictNullChecks가 false면 null도 가능)
- undefined나 null로 표현할 수 있을 것 같은데 굳이 void를 쓰는 이유는 무엇일까?
- undefined로 지정한다면 return; 과 같이 이런 거라도 써줘야 한다.
- null로 지정한다면 return null;처럼 진짜 null을 써줘야 한다.
- return문을 쓰고 싶지 않을 때 void를 쓰는 것
function func3(): never {
while (true) {}
}
function func4(): never {
throw new Error();
}
- never는 존재하지 않는, 불가능한 타입을 뜻한다.
- func3처럼 정상적으로 종료되지 않아 반환값 자체가 있을 수 없는 경우에 never를 쓴다.
- func4처럼 실행하면 에러를 던져주는, 따라서 반환값이 있을 수 없는 경우에도 never를 쓴다.
- let a: never처럼 변수에도 지정할 수 있지만 undefined, null도 할당할 수 없다. (진짜 아무 값 할당도, any 타입 지정도 안됨)
**출처: 한 입 크기로 잘라먹는 타입스크립트 (인프런, 이정환 강사님)
728x90
'❔ TypeScript > 💭 한 입 크기 TypeScript' 카테고리의 다른 글
[TypeScript] 인터페이스 (0) | 2023.12.20 |
---|---|
[TypeScript] 함수 타입 (1) | 2023.12.19 |
[TypeScript] 타입스크립트 이해하기 (1) | 2023.12.18 |
[TypeScript] 타입스크립트 실행 및 컴파일러 옵션 설정 (0) | 2023.09.08 |
[TypeScript] 타입 스크립트의 등장과 동작 원리 (0) | 2023.09.08 |