티스토리 뷰
타입스크립트 공부를 다시 시작하며 기본기를 다지기 위해 문서를 통해 다시 공부를 시작하였습니다.
공부는 타입스크립트 문서를 통해서 동일한 순으로 진행하였습니다.
따라서 주관적인 생각이 주입이 되었을 수 있습니다. 정확한 내용은 아래 레퍼런스 문서를 보고 판단하시길 바랍니다.
레퍼런스 : https://www.typescriptlang.org/ko/docs/handbook/2/basic-types.html
레퍼런스 : https://www.typescriptlang.org/ko/docs/handbook/2/everyday-types.html
정적 타입 검사
타입스크립트는 정적인 타입이 선언됨으로써 값들의 형태를 추론할 수 있고, 이 때문에 프로그램이 제대로 동작하지 않을 수 있는 요소들을 알려줍니다.
const notFunctionIsString = "this is string!!!!";
notFunctionIsString();
// This expression is not callable. Type 'String' has no call signatures.
실행 실패에 예외를 두지 않는 타입스크립트
자바스크립트는 다음과 같은 객체 내부의 프로퍼티에 접근했을때 없는 프로퍼티면 undefined를 반환합니다.
// app.js
const user = {
name: "Jone",
id: 10,
};
console.log(user.test);
// undefined
하지만 다음과 같이 타입스크립트에서는 유효한 코드여도 타입스크립트의 정적 타입 시스템에서 오류라고 판단하면 경고를 합니다.
const user = {
name: "Jone",
id: 10,
};
console.log(user.test);
// any
// Property 'test' does not exist on type '{ name: string; id: number; }'
오타도 잡아주는 타입스크립트
타입스크립트는 다음과 같이 오타를 냈을때 사용하려 했던 구문을 알려주기도 합니다.
const number = 109;
// 다음과 같이 오타를 잡아줍니다.
number.toSrting();
// Property 'toSrting' does not exist on type '109'. Did you mean 'toString'?
number.toString();
의도와 다르게 호출되지 않은 함수를 잡아주는 타입스크립트
function getRandomNum() {
return Math.random < 0.5;
// Operator '<' cannot be applied to types '() => number' and 'number'.
}
위와 같이 타입스크립트는 더 많은 버그를 잡아주어 프로그램에서 오류가 발생되는것을 방지해주는 역할도 합니다.
모든 타입들
[ 자바스크립트의 원시타입 ]
흔히들 사용되는 원시 타입만 다루겠습니다.
자바스크립트의 원시 타입은 일반적으로 string, number, boolean 값이 있습니다.
해당 타입은 typeof 연산자를 사용하여 타입을 얻을 수 있는 이름과 동일합니다.
- string : 문자열
- number : 숫자 ( int와 float과 같은 것은 존재하지 않습니다. )
- boolean : true or false
[ any ]
any 타입이 표기되면 해당 값은 임의의 값을 할당할 수 있으며, 암묵적으로 어떠한 타입이 올 수 있다고 명시합니다.
let anyType: any = { any: "any" };
anyType = "any";
anyType = 10;
[ 변수에 대한 타입 표기 ]
변수에서는 다음과 같이 변수 이름 다음에 작성합니다.
let string: string = "number";
let number: number = 10;
string = 10;
// Type 'number' is not assignable to type 'string'.
number = "number";
// Type 'string' is not assignable to type 'number'.
하지만 대부분 변수에서는 타입을 작성하지 않아도 됩니다. 타입스크립트는 변수 초기화 단계가 이루어질 때 변수의 타입을 자동으로 추론합니다.
let string = "string";
// type : string
let number = 10;
// type : number
[ 함수 ]
매개변수 타입표기
매개변수는 다음과 같이 매개변수 이후에 타입을 표기합니다.
function getName(name: string) {
// ...
return `My name is ${name}`;
}
getName("Jone");
getName(10);
// Argument of type 'number' is not assignable to parameter of type 'string'.
- 추후에 다룰 declare을 통한 타입 표기
declare function getName(name: string): void;
getName(10);
// Argument of type 'number' is not assignable to parameter of type 'string'.
반환 타입 표기
반환 타입은 매개변수 목록의 뒤에 표기하게 됩니다.
function getMyName(): string {
return `My name is Jone`;
}
getMyName();
// string
변수에서 타입 표기를 했던것과 마찬가지로 타입스크립트는 return 문을 통해 반환될 값의 타입을 추론하여 일반적으로는 타입을 적지 않아도 됩니다.
function getMyName() {
return `My name is Jone`;
}
getMyName();
// string
익명 함수에서의 문맥적 매개변수 타입 추론
익명 함수에서도 문맥적으로 해당 함수가 호출될지 추론해서 타입이 지정됩니다.
names.forEach(function (s) {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
객체 타입
다음과 같이 매개변수에서의 객체는 객체에 담길 값의 프로퍼티와 타입으로 지정합니다.
function debugPoints(point: { x: number; y: number }) {
console.log(`points: x:${point.x} y:${point.y}`);
}
debugPoints({ x: 10, y: 10 });
옵셔널 프로퍼티
객체에서 선택적으로 값을 받고 싶을때는 프로퍼티 이름 뒤에 ?를 붙이면 됩니다.
function debugPoints(optionalPoints: { x: number; y?: number }) {
console.log(`points: x:${optionalPoints.x} y:${optionalPoints.y}`);
}
debugPoints({ x: 10 });
// points: x:10 y:undefined
자바스크립트는 존재하지 않는 프로퍼티를 읽을 때 런타임 오류가 발생하지 않고 undefined가 발생합니다.
따라서 옵셔널 프로퍼티를 사용하게되면 프로그램에서 오류가 발생되는 것을 방지하기 위해서는 해당 값이 있는지 여부를 판별하는 코드가 필요합니다.
function debugName(userName: { first: string; last?: string }) {
// 해당 프로퍼티의 unefined 여부를 판별합니다.
if (userName.last !== undefined) {
console.log(userName.last.toUpperCase());
}
// 최신 자바스크립트 스펙의 문법을 사용하면 다음과 같이 ?를 통해 옵셔널 체이닝을 할 수 있습니다.
console.log(userName.last?.toUpperCase());
}
debugName({ first: "Jone" });
[ 유니언 타입 ]
타입스크립트를 통해서 다양한 연산자를 사용해서 새로운 타입을 만들 수 있습니다.
유니언 타입은 서로 다른 두개 이상의 타입을 사용하고자 할 때 사용됩니다.
사용 방법은 다음과 같이 타입 사이에 | 연산자 를 사용하여 표기합니다.
이렇게 조합에 사용된 각 타입을 유니언 타입의 멤버 라고 부릅니다.
ex) debugId 의 매개변수 id의 유니언 타입의 멤버는 무엇이 있나요? => number 과 string 있습니다.
function debugId(id: number | string) {
console.log(`id is:${id}`);
}
debugId(10);
debugId("10");
유니언 타입 사용 대처 : typeof
다음과 같이 유니언 타입과 같이 여러 멤버 타입을 받는 함수 내부에서는 다음과 같이 대처를 합니다.
예를 들어 number에서는 toUpperCase와 같은 메서드를 사용할 수 없으므로 각 상황에 맞는 대처를 typeof 를 통해 코드를 작성합니다.
if 문에서 string인 타입만 typeof 로 검사하고 나머지 number의 타입인 경우에서는 else 문으로 처리가 가능한 이유는 id는 number과 string 두 타입만을 받기 때문입니다.
function debugId(id: number | string) {
if (typeof id === "string") {
// ....
// id 타입이 string 인 값을 처리합니다.
} else {
// ...
// number 타입의 값을 처리합니다.
}
}
debugId(10);
debugId("10");
배열에서의 대처 : Array.isArray()
배열은 typeof 를 통해 타입을 걸러내지 않고 Array.isArray()라는 Array의 메서드를 사용합니다.
function debugUsers(id: number | number[]) {
console.log(`id is:${id}`);
if (Array.isArray(id)) {
// ....
// id 타입이 배열일 때 처리합니다.
} else {
// ...
// number 타입의 값을 처리합니다.
}
}
debugUsers(10);
공통적으로 처리가 가능한 메서드를 사용할 수 있을 때
항상 typeof 나 Array.isArray()를 통해서 타입을 처리하지 않아도 됩니다.
값에서 공통적으로 사용될 수 있는 메서드를 사용할 때 별도의 처리가 필요 없습니다.
function getFirstThree(value: number[] | string) {
// slice는 배열과 문자열에서 사용할 수 있는 메서드입니다.
return value.slice(0, 3);
}
getFirstThree([10, 20, 30]);
getFirstThree("123");
타입 별칭
지금까지의 타입들을 한 번 이상 재사용을 하거나 명시적으로 이름을 부여해 타입을 사용하고 싶을 때 타입 별칭을 사용합니다. 타입 별칭을 사용하면 새로운 타입이 생성되는 것이 아닌 그저 별칭으로 작동합니다.
// before
function notUseAliasDebugPoint(point: { x: number; y: number }) {
return console.log(`point : ${(point.x, point.y)}`);
}
notUseAliasDebugPoint({ x: 10, y: 10 });
// after
// 타입 별칭 사용
type Point = {
x: number;
y: number;
};
function debugPoint(point: Point) {
return console.log(`point : ${(point.x, point.y)}`);
}
debugPoint({ x: 10, y: 10 });
유니언 타입을 타입 별칭과 함께
type ID = number | string;
[ 인터페이스 ]
인터페이스를 선언하면 객체 타입을 만들 수 있습니다.
타입 별칭과 마찬가지로 인터페이스도 별칭에 지나지 않습니다.
interface Point {
x: number;
y: number;
}
function debugPoint(point: Point) {
return console.log(`point : ${(point.x, point.y)}`);
}
debugPoint({ x: 10, y: 10 });
타입 별칭과 인터페이스의 차이점
두 타입 선언법은 매우 유사하지만 큰 차이점은 타입은 새 프로퍼티를 추가하도록 개방될 수 없고, 인터페이스는 항상 확장 될 수 있다는 점입니다.
<확장하기>
- 인터페이스 확장하기
extends 구문을 통해 확장합니다. 2개 이상의 인터페이스를 확장하고 싶다면 extends 이후 , 를 통해 확장합니다.
// 인터페이스 확장하기
interface User {
name: string;
age: number;
}
interface SpecialUser extends User {
authority: number;
}
//... 더 많은 인터페이스를 확장하고 싶다면 extends 구문 다음에 , 를 통해 확장합니다.
interface Somthing {
somthing: boolean;
}
interface SpecialUser extends User, Somthing {
authority: number;
}
- 교집합으로 타입 확장하기
& 연산자를 통해 확장하며 2개 이상 확장한다면 확장할 때 &를 연달아 작성해줍니다.
// 교집합으로 타입 확장하기
type User = {
name: string;
age: number;
};
type SpecialUser = User & {
authority: number;
};
//... 더 많은 타입을 확장하기 위해서는 다음과 같이 작성합니다
type Somthing = {
somthing: boolean;
};
type SpecialUser = User &
Somthing & {
authority: number;
};
<필드 추가시>
- 기존의 인터페이스에 필드를 추가하기
// 기존의 인터페이스에 필드를 추가하기
interface User {
title: string;
}
interface User {
id: number;
}
const user: User = { title: "test", id: 10 };
- 타입으로 선언시 생성 뒤에는 달라질 수 없음
// 기존의 인터페이스에 필드를 추가하기
type User = {
title: string;
};
type User = {
id: number;
};
const user: User = { title: "test", id: 10 };
// Error : Identifier 'User' has already been declared
[ 타입 단언 ]
타입스크립트보다 코드 작성자가 타입에 대한 정보를 더 잘 아는 경우도 있어 명확하게 어떤 값이 반환 될 지 알고 있는 상황에서 타입을 구체적으로 명시를 할 수 있는 타입 단언을 사용할 수 있습니다.
- as 타입 단언 (.tsx, .ts 파일에서 사용가능)
as 연산자를 통해서 반환 될 명확한 타입을 명시해줍니다.
const canvasElement = document.getElementById('canvas_view') as HTMLCanvasElement;
- <>꺾쇠를 통한 타입 단언 (.tsx 파일에서 사용불가능, .ts 파일에서 사용가능)
export const canvasElement = <HTMLCanvasElement>document.getElementById("canvas_view");
※ 위와 같이 타입 단언은 명확하게 타입을 알 때에 사용해야합니다. 타입스크립트 문서에서 설명하길 타입 단언은 컴파일 시간에 제거되어, 타입 단언에 관련된 검사는 런타임 중에 이루어지지 않아 틀렸더라도 예외가 발생되거나 null 이 생성되지 않습니다.
- 불가능한 강제 변환 방지
당연하게도 아래와 같이 초기화 된 값의 타입이 string인데 타입 단언을 통해 number 타입을 지정해주게 되면 경고를 표출합니다.
const x = "hello" as number;
// Conversion of type 'string' to type 'number' may be a mistake
// because neither type sufficiently overlaps with the other.
// If this was intentional, convert the expression to 'unknown' first.
- 두 번의 단언으로 타입 변환
아래와 같이 불가피하게 강제 변환이 허용되지 않아 어려움을 겪을 수 있습니다. 그럴 땐 두 번의 단언을 통해 타입을 변환할 수 있습니다.
declare const IDontKnow: any;
type IKnow = { a: 1; b: 2; c: 3; d: 4 };
//...
const whatsThis = IDontKnow as any as IKnow;
// any 또는 unknown 타입으로 바꾸고 변환해줍니다.
// const whatsThis = IDontKnow as unknown as IKnow;
[ 리터럴 타입 ]
일반적인 string, number 과 같은 원시적인 타입 이외에 구체적인 값을 지정할 수 있습니다.
자바스크립트에서 변수 선언시 var 과 let으로 선언하면 이후에 값을 변경이 가능하고 const로 선언하게 되면 이후 값을 변경 할 수 없는 이 규칙이 타입스크립트가 리터럴 값을 위한 타입을 생성하는 방식에 반영이 됩니다.
let으로 선언하고 문자열을 초기값으로 할당하면 선언된 변수는 자동적으로 string 타입이 지정됩니다.
반대로 const 로 선언된 변수는 초기값 자체가 리터럴 타입으로 지정됩니다.
// 선언과 동시에 자동으로 string 타입 지정됨
let canChangeString = "hello";
canChangeString = "wow";
// canChangeString type : string
// 선언과 동시에 cannotchangevalue 문자열 값이 리터럴 타입으로 지정됨
const canNotChangeString = "cannotchangevalue";
canNotChangeString = "wow";
// const canNotChangeString: "cannotchangevalue"
유니언 타입과 함께 쓰는 리터럴
리터럴 타입을 하나의 타입만을 지정하는 상수와 같이 사용하게 되면 그다지 쓸모가 없습니다.
여러 타입을 함께 쓸 수 있는 유니언 타입과 함께 사용해서 특정 값을 받을 수 있도록 사용하면 유용합니다.
- 그다지 쓸모 없는 하나의 타입만을 허용하는 리터럴 타입
let value: "Hi!!" = "Hi!!";
value = "Hi!!";
value = "Hmm.....";
// Type '"Hmm....."' is not assignable to type '"Hi!!"'.
- 유니언 타입과 함께 쓰는 리터럴 타입
리터럴 타입은 다음과 같이 유니언 타입을 지정하면 그 값은 지정된 타입의 값만 사용할 수 있게 됩니다.
아래의 예시에서 소개된 string 이외의 number, 타입 별칭, 인터페이스 등 타입으로 지정할 수 있는 타입을 사용 가능합니다.
function textAlignment(text: String, textAlign: "left" | "right" | "center") {
// ...
}
textAlignment("this is text", "left");
- boolean 타입!!
사실 boolean 타입은 true | false 유니언 타입의 별칭으로써 리터럴 타입입니다.
function boolTest(boolValue: true | false) {
// boolValue type = boolean
}
// ...
function boolTest(boolValue: boolean) {
// boolValue type = boolean
}
boolTest(true);
리터럴 추론
타입스크립트에서 변수에 객체에 값을 초기화 하여 사용하면 해당 객체의 프로퍼티의 값은 언제나 바뀔 수 있다고 가정합니다.
const obj = { value: 0 };
// 프로퍼티의 값은 언제나 바뀔 수 있다고 가정하여 타입스크립트에서 오류라고 판단하지 않습니다.
// 여기서 프로퍼티 value의 타입은 number로 지정됩니다.
obj.value = 1;
위와 같은 상황은 문자열에서도 이루어집니다.
아래에서 request 과정에서 사용되는 메서드 GET, POST 와 같은 유니언 타입과 함께 리터럴 타입으로 지정된 매개변수 값이 있고 객체를 통해 값을 인자로 넘기면 같은 문자열이여도 타입스크립트는 오류라고 판단합니다.
const requestUser = {
url: "https://getuser",
method: "GET",
};
function getUser(url: string, method: "GET" | "POST" | "PUT" | "DELETE") {
// ....
}
getUser(requestUser.url, requestUser.method);
// Argument of type 'string' is not assignable to parameter of type
// '"GET" | "POST" | "PUT" | "DELETE"'.
위와 같은 상황을 해결할 수 있는 방법은 두 가지가 있습니다.
1. 타입 단언을 통해 타입을 지정해주기
const requestUser = {
url: "https://getuser",
method: "GET" as "GET",
};
function getUser(url: string, method: "GET" | "POST" | "PUT" | "DELETE") {
// ....
}
getUser(requestUser.url, requestUser.method);
const requestUser = {
url: "https://getuser",
method: "GET",
};
function getUser(url: string, method: "GET" | "POST" | "PUT" | "DELETE") {
// ....
}
getUser(requestUser.url, requestUser.method as "GET");
2. 타입 단언에서 const 사용하기
타입 단언에서 as const 라는 구문을 사용할 수 있는 이는 const 로 선언한 변수와 유사하게 동작하고 리터럴 타입의 값이 되도록 보장합니다.
const requestUser = {
url: "https://getuser",
method: "GET",
} as const;
function getUser(url: string, method: "GET" | "POST" | "PUT" | "DELETE") {
// ....
}
getUser(requestUser.url, requestUser.method);
[ null 과 undefined ]
자바스크립트에서 초기화 되지 않은 빈 값을 의미하는 null 과 undefined 은 타입스크립트에서도 동일한 이름의 타입이 있습니다. 각 타입의 동작 방식은 strictNullChecks 옵션에 따라서 달라집니다.
* strictNullChecks 옵션은 일반적으로 tsconfig.json 파일에서 설정하면 됩니다.
// tsconfig.json
{
"compilerOptions": {
// ...
"strictNullChecks": false // or true
},
}
strictNullChecks : false (또는 설정되지 않았을 때)
해당 옵션이 설정되지 않았다면 null 또는 undefined 값이 할당이 되더라도 접근이 가능합니다.
따라서 예기치 못한 상황에서 제대로 null 과 undefined 를 체크하지 못해서 프로그램에서 오류가 발생될 수 있습니다.
타입스크립트 문서에서는 별다른 이유가 없다면 strictNullChecks 옵션을 설정하는 것을 권장하고 있습니다.
// strictNullChecks:false 또는 설정 안했을 때
function testFunction(value: string | undefined) {
if (value === undefined) {
console.log("undefined");
} else {
console.log(value.toUpperCase());
}
}
// 오류가 발생하지 않습니다.
testFunction(null);
strictNullChecks : true (설정되었을 때)
다음과 같이 옵션을 설정하면 지정되지 않은 null 타입에서 오류가 발생합니다. 따라서 예기치 못하게 전달되는 인자의 값이 null 일때 별도 처리를 할 수 있습니다.
// strictNullChecks:true
function testFunction(value: string | undefined) {
if (value === undefined) {
console.log("undefined");
} else {
console.log(value.toUpperCase());
}
}
testFunction(null);
// Argument of type 'null' is not assignable to parameter of type 'string | undefined'.
다음은 null 의 경우 별도 처리 방법입니다.
function testFunction(value: string | undefined | null) {
if (value === undefined) {
console.log("undefined");
} else if (value === null) {
console.log("null");
} else {
console.log(value.toUpperCase());
}
}
testFunction(null);
null 아님 단언 연산자 (접미사 !)
null 또는 undefined 가 아니라고 타입을 단언해주는 연산자가 있습니다. null 과 undefined 가 절대적으로 아닌 표현식 뒤에 !를 붙이면 됩니다.
function thisNeverNullOrUndefined(value?: number | undefined) {
console.log(value!.toFixed());
}
위의 코드에서 undefined 값으로 인자를 넘기면 런타임에서 오류가 발생하게 됩니다. 따라서 반드시 확실히 null 또는 undefined 가 아닐 때만 사용해야 합니다.
function thisNeverNullOrUndefined(value?: number | undefined) {
console.log(value!.toFixed());
}
thisNeverNullOrUndefined(undefined);
// intro.tsx:6 Uncaught TypeError: Cannot read properties of undefined (reading 'toFixed') at thisNeverNullOrUndefined
'Typescript' 카테고리의 다른 글
[ Typescript ] Object 타입스크립트에서의 객체 타입 (0) | 2022.09.09 |
---|---|
[ Typescript ] function 타입스크립트에서의 함수 (0) | 2022.09.07 |
[ Typescript ] Narrowing 좁히기 (0) | 2022.09.05 |
[Typescript] 타입스크립트 핸드북 시작하기 (0) | 2022.09.03 |