[TS] TypeScript 다시 공부하기 1
약 3개월 전 TypeScript를 공부해 놓고 계속 JavaScript만 썼더니 거의 다 잊어버렸다,,, ^^ 그래서 스터디 준비할 겸 다시 꼼꼼히 공부해 보자
참고 링크 - 타입스크립트 핸드북
Fundamentals
1. 기본 타입
- Boolean, Number, String, Object, Array, Tuple, Enum, Any, Void, Null, Undefined, Never
- Type Annotation:
:
를 이용하여 타입을 명시하는 방법
(1) String
let text: string = 'hello';
(2) Number
let num: number = 17;
(3) Boolean
let isDone: boolean = false;
(4) Object
let obj:
(5) Array
let arr: number[] = [1, 2, 3];
let arr: Array<number> = [1, 2, 3]; // 제네릭 사용
(6) Tuple
:
를 이용하여 타입을 명시하는 방법let text: string = 'hello';
let num: number = 17;
let isDone: boolean = false;
let obj:
let arr: number[] = [1, 2, 3];
let arr: Array<number> = [1, 2, 3]; // 제네릭 사용
배열의 길이가 고정되고 요소의 타입이 지정되어 있는 배열
let arr: [string, number] = ['hello', 10];
(7) Enum
특정 상수 값들의 집합
enum Avengers {Capt, IronMan, Thor}
let hero: Avengers = Avengers.Capt;
아래처럼 enum의 인덱스를 마음대로 변경 가능
enum Avengers {Capt, IronMan, Thor}
let hero: Avengers = Avengers[2]; // Capt
let hero: Avengers = Avengers[4]; // Thor
(8) Any
let arr: any = ['a', 1, true];
(9) Void
변수에는 undefined
와 null
만 할당
함수에는 반환값 설정 불가
let unuseful: void = undefined;
function notuse(): void {
console.log('sth');
}
(10) Never
함수 끝까지 실행되지 않는다는 의미
function neverEnd(): never {
while (true) {
}
}
2. 함수
함수를 사용할 때에는 크게 3가지에 대한 타입을 정의해야 한다.
- 매개변수 타입
- 반환 타입
- 구조 타입
(1) 함수의 기본적인 타입 선언
매개변수와 반환값에 타입 추가하기
함수 반환값에 타입을 정하지 않을 때에는 void
사용
function sum(a: number, b: any): number {
return a + b;
}
(2) 함수의 인자
정의된 매개변수 값과 개수만큼만 인자를 받는다.
만약 정의된 매개변수 개수만큼 넘기고 싶지 않다면 ?
를 이용한다.
function sum(a: number, b?: number): number {
return a + b;
}
sum(10); // okay
sum(10, 20, 30); // error: too many parameters
3. Interface
아래 범주에 대한 약속을 정의
- 객체의 속성과 속성 타입
- 함수의 파라미터
- 함수의 파라미터, 반환타입
- 배열과 객체 접근법
- 클래스
(1) readonly
속성
인터페이스로 객체를 처음 생성할 때에만 값을 할당하고 그 이후에는 변경 불가
interface CraftBeer {
readonly brand: string;
}
let arr: ReadonlyArray<number> = [1, 2, 3]; // 읽기 전용 배열
(2) 객체 선언과 타입 체킹
interface로 선언한 객체의 key 값이 다를 경우 에러 발생
위 같은 에러를 무시하고 싶다면... as
interface CraftBeer {
brand?: string;
}
function brewBeer(beer: Craftbeer){
}
let myBeer = {brandon: 'what'};
brewBeer(myBeer as CraftBeer);
(3) 함수 타입
interface login {
(username: string, password: string): boolean;
}
let loginUser: login;
loginUser = function(id: string, pw: string){
return true;
}
4. Enums
숫자형 이넘과 문자형 이넘이 존재
그런데 이넘에 대해 더 찾아보다가 이런 글을 발견했다. 아래 글에서는 enum
보다는 Union Types
를 사용할 것을 권장한다.
TypeScript enum을 사용하지 않는 게 좋은 이유를 Tree-shaking 관점에서 소개합니다.
(1) 숫자형 이넘
enum Direction {
Up = 1,
Down,
Left,
Right
}
위처럼 초깃값을 주면 차례대로 1씩 부여된다.
초깃값이 없으면 0부터 1씩 증가한다.
(2) 숫자형 이넘 사용하기
enum Response {
No = 0,
Yes = 1
}
function respond(recipient: string, message: Response): void {
}
respond("Me", Response.Yes);
(3) 문자형 이넘
문자형 이넘은 이넘 값 전부 특정 문자 또는 다른 이넘 값으로 초기화해야 한다.
숫자형 이넘과 다르게 auto-incrementing이 없다.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
(4) 복합 이넘 (Heterogeneous Enums)
이넘에 문자와 숫자를 혼합하여 사용할 수는 있지만 권고하진 않는다.
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}
5. 연산자를 이용한 타입 정의
(1) Union Type
JavaScript의 OR 연산자(||
)와 같은 의미의 타입
function logText(text: string | number){
// ...
}
(2) Intersection Type
여러 타입을 모두 만족하는 하나의 타입
interface Person {
name: string;
age: number;
}
interface Developer {
name: string;
skill: number;
}
type Capt = Person & Developer;
위처럼 하면 Capt
의 타입은 아래와같이 된다.
{
name: string;
age: number;
skill: number;
}
(3) Union Type을 쓸 때 주의할 점
interface Person {
name: string;
age: number;
}
interface Developer {
name: string;
skill: number;
}
function introduce(someone: Person | Developer) {
someone.name; // Ok
someone.age; // Type error
someone.skill; // Type error
}
위와 같은 에러가 발생하는 이유는...
introduce()
함수를 호출하는 시점에 Person
타입이 올지 Developer
타입이 올지 알 수 없기 때문에 어느 타입이 오든 오류가 나지 않는 방향으로 타입을 추론한다.
const capt: Person = {name: 'capt', age: 100};
introduce(capt); // someone.skill 속성 접근 시 에러
const tony: Developer = {name: 'tony', skill: 100};
introduce(tony); // someone.age 속성 접근 시 에러
즉 Person
과 Developer
두 타입에 공통적으로 들어 있는 속성인 name
만 접근 가능한 것이다!
6. Generic
(1) Generic의 사전적 정의
재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 특히 여러 가지 타입에서 동작하는 컴포넌트를 생성하는 데 사용된다.
(2) Generic의 한 줄 정의와 예시
Generic은 타입을 함수의 파라미터처럼 사용하는 것을 의미한다.
function getText<T>(text: T): T {
return text;
}
위 형식은 제네릭 기본 문법이 적용된 형태이고, 이 함수를 호출할 때에 타입을 넘겨 줄 수 있다.
getText<string>('hi');
getText<number>(10);
getText<boolean>(true);
(3) Generic을 사용하는 이유
function logText<T>(text: T): T {
return text;
}
이렇게 함수를 선언하면 아래와같이 함수를 호출할 수 있다.
// 1
const text = logText<string>("Hello Generic");
// 2
const text = logText("Hello Generic");
보통 두 번째 방법이 가독성이 좋아서 더 많이 사용된다. 하지만 복잡한 코드에서 두 번째 방법으로 타입 추정이 되지 않는다면 첫 번째 방법을 사용하자.
(4) Generic 타입 변수
만약 함수의 인자로 받은 값의 length
를 확인하려 한다면 다음과 같은 에러가 뜬다.
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
따라서 이런 경우에는 아래와같이 제네릭에 타입을 부여할 수 있다.
function logText<T>(text: T[]): T[] {
console.log(text.length); // 제네릭 타입이 배열이라 length를 허용
return text;
}
or
function logText<T>(Text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
이런 식으로 제네릭을 사용하면 유연하게 함수의 타입을 정의할 수 있다.
(5) Generic 타입
function logText<T>(text: T): T {
return text;
}
let str: <T>(text: T) => T = logText; // 1
let str: {<T>(text: T): T} = logText; //2
위 코드에서 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;
이런 식으로 제네릭 인터페이스뿐만 아니라 클래스도 생성할 수 있지만, 이넘과 네임스페이스는 제네릭으로 생성할 수 없다.
(6) Generic 제약 조건
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
이때 에러가 발생한느데, 타입을 지정하지 않고도 length
속성 정도는 허용하려면 아래와같이 작성하면 된다.
Interface LengthWise {
length: number;
}
function logText<T extends LengthWise>(text: T): T {
console.log(text.length);
return text;
}
위와같이 작성하면 length
에 대해 동작하는 인자만 넘겨 받을 수 있게 된다.
또한 두 객체를 비교할 때에도 제네릭 제약 조건을 사용할 수 있다.
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"); // Ok
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.
위 코드에서 <O extends keyof T>
부분이 첫 번째 인자로 받은 객체에 없는 속성을 접근할 수 없게끔 제한하는 코드다.
7. Type Inference
타입 추론: 타입스크립트가 코드를 해석해 나가는 동작
타입 체크는 갑스이 형태에 기반하여 이루어져야 한다.
Duck Typing: 객체의 변수 및 메서드의 집합이 객체의 타입을 결정 (동적 타이핑의 한 종류)
Structural Subtyping: 객체의 실제 구조나 정의에 따라 타입을 결정
8. Type Compatibility
(1) 구조적 타이핑 예시
코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단
interface Avengers {
name: string;
}
let hero: Avengers;
let capt = {name: "Capt", location: "Seoul"};
hero = capt;
capt
가 hero
타입에 호환될 수 있는 이유는 capt
의 속성 중에 name
이 있기 때문이다. 함수를 호출할 때에도 마찬가지이다.
function assemble(a: Avengers) {
console.log("어벤저스", a.name);
}
assemble(capt);
capt
변수에 location
속성도 있기 때문에 assemble
함수의 호출 인자로 넘길 수 있다.
(2) Soundness
TypeScript는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다.
(3) Enum 타입 호환 주의 사항
Enum 타입은 number 타입과 호환되지만, Enum 타입끼리는 호환되지 않는다.
enum Status {Ready, Waiting};
enum Color {Red, Blue, Green};
let status = Status.Ready;
status = Color.Green; // Error
(4) Generics
제네릭은 제네릭 타입 간 호환 여부를 판단할 때 타입 인자 <T>
가 속성에 할당되었는지를 기준으로 한다.
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // Ok, because y matches structure of x
위 인터페이스는 속성(멤버 변수)이 없기 때문에 x
와 y
는 같은 타입으로 간주된다.
하지만 아래와 같이 속성이 있어서 제네릭의 타입 인자가 속성에 할당되면 x
와 y
는 다른 타입으로 간주된다.
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error, because x and y are not compatible
9. Type Aliases
타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다.
// string 타입을 사용할 때
const name: string = 'capt';
// type aliases를 사용할 때
type MyName = string;
const name: MyName = 'capt';
type Developer = {
name: string;
skill: string;
}
type User<T> = {
name: T
}
(1) 타입 별칭의 특징
새로운 타입값을 생성하는 게 아니라 정의한 타입에 대해 쉽게 참고할 수 있도록 이름을 부여하는 것이다.
(2) type vs. interface
이 둘의 가장 큰 차이점은 확장 가능 여부이다.
type
은 확장이 불가능하고, interface
는 확장 가능하다. 따라서 가능한 interface
사용을 추천한다.
어우 힘들어
타입스크립트 핸드북
Author And Source
이 문제에 관하여([TS] TypeScript 다시 공부하기 1), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@corinthionia/TS-TypeScript-다시-공부하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)