TypeScript

예를 들어, 컴퓨터 프로그램은 사람이 만들어서 컴퓨터에게 지시하는 일종의 "레시피"와 같습니다. 그런데 때로는 레시피를 작성할 때 실수를 할 수 있고, 이 실수가 컴퓨터가 이해하지 못하는 경우 문제가 발생할 수 있습니다. 타입스크립트는 이러한 문제를 줄이기 위해 등장한 것입니다.
실수 방지: 사람들이 레시피를 만들 때 오타나 잘못된 재료를 사용하면 요리가 맛없거나 안 되는 경우가 있습니다. 타입스크립트는 프로그래머가 프로그램을 만들 때 오타나 잘못된 부분을 미리 알려주어 실수를 줄여줍니다.
프로그램 이해: 컴퓨터도 레시피를 이해해야 하는데, 때로는 레시피가 너무 복잡하거나 모호하면 컴퓨터가 헷갈릴 수 있습니다. 타입스크립트는 레시피를 좀 더 명확하게 설명하여 컴퓨터가 이해하기 쉽게 도와줍니다.
협업 용이: 여러 명의 요리사가 함께 요리를 할 때, 레시피가 명확하면 협업이 훨씬 수월해집니다. 타입스크립트는 여러 프로그래머가 함께 프로그램을 만들 때 레시피를 더 잘 이해하고 협력할 수 있도록 도와줍니다.
유지 보수: 만든 요리가 맛있을 때 좋지만, 시간이 지나면 재료가 상하고 맛이 바뀔 수 있습니다. 프로그램도 마찬가지로 시간이 지나면 문제가 발생할 수 있습니다. 타입스크립트는 프로그램을 더 오래 유지하고 문제를 찾아 고치는 데 도움이 됩니다.
요약하면, 타입스크립트는 프로그래머가 프로그램을 더 정확하게 만들고, 컴퓨터가 이해하기 쉽도록 도와주며, 협업과 유지 보수를 편리하게 만들어줍니다. 이렇게 함으로써 프로그램을 더 효율적으로 만들고 문제를 예방할 수 있게 됩니다.
number[] === Array<number>
function getFavoriteNumber(): number {
return 26;
}
변수의 타입 표기와 마찬가지로, 반환 타입은 표기하지 않아도 되는 것이 일반적입니다.
왜냐하면 TypeScript가 해당 함수에 들어있는 return 문을 바탕으로 반환 타입을 추론할 것이기 때문입니다. 위 예시에서 사용된 타입 표기는 큰 의미를 갖지 않습니다. 때에 따라 문서화를 목적으로, 또는 코드의 잘못된 수정을 미연에 방지하고자, 혹은 지극히 개인적인 선호에 의하여 명시적인 타입 표기를 수행하는 코드도 존재합니다.
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
객체 타입은 일부 또는 모든 프로퍼티의 타입을 선택적인 타입,
즉 옵셔널로 지정할 수 있습니다.
프로퍼티 이름 뒤에 ?를 붙이면 됩니다
function printCoord(pt: { x: number; y?: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
printCoord({ x: 3 });
유니언 타입은 서로 다른 두 개 이상의 타입들을 사용하여 만드는 것으로,
유니언 타입의 값은 타입 조합에 사용된 타입 중 무엇이든 하나를 타입으로 가질 수 있습니다.
조합에 사용된 각 타입을 유니언 타입의 멤버라고 부릅니다.
밑에 코드에서 number , string 이 유니언 타입의 멤버 입니다.
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
매우매우매우 중요
TypeScript에서 유니언을 다룰 때는 해당 유니언 타입의 모든 멤버에 대하여 유효한 작업일 때에만 허용됩니다. 예를 들어 string | number라는 유니언 타입의 경우,
string 타입에만 유효한 메서드는 사용할 수 없습니다
function printId(id: number | string) {
console.log(id.toUpperCase());
}
Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
따라서 코드상에서 유니온을 좁혀야 합니다.
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
때로는 유니언의 모든 멤버가 무언가 공통점을 가질 수도 있습니다.
예를 들어, 배열과 문자열은 둘 다 slice 메서드를 내장합니다.
유니언의 모든 멤버가 어떤 프로퍼티를 공통으로 가진다면, 좁히기 없이도 프로퍼티를 사용할 수 있게 됩니다.
// 반환 타입은 'number[] | string'으로 추론됩니다
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
유니언은 의미상 합집합을 뜻하는데, 실제로는 유니언 타입이 프로퍼티들의 교집합을 가리키는 것처럼 보여
헷갈리게 느낄 수 있습니다. 이는 지극히 우연이 아닙니다.
유니언이라는 명칭은 타입 이론에서 비롯된 것입니다.number | string유니언 타입은 각각의 타입을
가지는 값들에 대하여 합집합을 취하여 구성됩니다.
두 집합과 각각의 집합에 대한 특성이 주어졌을 때, 두 집합의 유니언에는 각각의 특성들의 교집합만이
적용된다는 점에 유의하시기 바랍니다. 예를 들어, 한 방에는 모자를 쓰고 키가 큰 사람들이 있고
다른 방에는 모자를 쓰고 스페인어를 사용하는 사람들이 있다고 합시다.
이때 두 방을 합친다면, 모든 사람들에 대하여 알 수 있는 사실은 바로 누구나 모자를 쓰고 있다는 것입니다.
지금까지는 객체 타입과 유니언 타입을 사용할 때 직접 해당 타입을 표기하였습니다.
이는 편리하지만, 똑같은 타입을 한 번 이상 재사용하거나 또 다른 이름으로 부르고 싶은 경우도 존재합니다.
타입 별칭은 바로 이런 경우를 위하여 존재하며, 타입을 위한 이름을 제공합니다.
type Point = {
x: number;
y: number;
};
type ID = number | string; // 이렇게 사용할 수도 있다.
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
하지만 여러가지 이유로 interface 가 개발자들에게 흔합니다. 그 이유는 밑에서 차차 이야기 하겠습니다.
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
매우매우 중요
TypeScript는 오직 printCoord에 전달된 값의 구조에만 관심을 가집니다.
즉, 예측된 프로퍼티를 가졌는지 여부만을 따집니다.
이처럼, 타입이 가지는 구조와 능력에만 관심을 가진다는 점은
TypeScript가 구조적 타입 시스템이라고 불리는 이유입니다.
type Person = {
name: string;
age: number;
};
type Employee = {
jobTitle: string;
company: string;
};
type PersonEmployee = Person & Employee;
const employee: PersonEmployee = {
name: "John",
age: 30,
jobTitle: "Engineer",
company: "ABC Inc.",
};
interface Person {
name: string;
age: number;
}
interface Employee {
jobTitle: string;
company: string;
}
interface PersonEmployee extends Person, Employee {}
const employee: PersonEmployee = {
name: "John",
age: 30,
jobTitle: "Engineer",
company: "ABC Inc.",
};
가장 다른 차이점
타입 별칭은 메서드를 직접 정의할 수 없습니다.
타입 별칭은 객체의 구조를 정의하거나 기존 타입을 간략하게 명명하는 데 유용하며,
메서드를 직접 정의하거나 확장할 수 없습니다.
// 타입 별칭
type PersonType = {
name: string;
age: number;
};
// 인터페이스
interface PersonInterface {
name: string;
age: number;
sayHello(): void;
}
// 인터페이스 확장
interface Employee extends PersonInterface {
jobTitle: string;
company: string;
sayHello(): void; // 메서드 재정의
}
매우매우매우 중요
타입 스크립트 공식 문서에서도 일단 인터페이스를 사용하고 인터페이스에서 오류가 나는 경우 타입 별칭을
사용하라고 추천하고 있습니다.
때로는 TypeScript보다 당신이 어떤 값의 타입에 대한 정보를 더 잘 아는 경우도 존재합니다.
예를 들어 코드상에서 document.getElementById가 사용되는 경우,
TypeScript는 이때 HTMLElement 중에 무언가가 반환된다는 것만을 알 수 있는 반면에,
당신은 페이지 상에서 사용되는 ID로는 언제나 HTMLCanvasElement가 반환된다는 사실을
이미 알고 있을 수도 있습니다.
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
string과 number와 같은 일반적인 타입 이외에도,
구체적인 문자열과 숫자 값을 타입 위치에서 지정할 수 있습니다
const a = 'hello';
a = 'hello'; //OK
a = 'hey'; // NO
Type 'hey' is not assingnable to type 'hello'
하지만 리터럴 타입은 단 하나의 값만을 가질 수 있는 변수를 만들어 그다지 쓸모가 없죠!
하지만 리터럴을 유니언과 함께 사용하면, 보다 유용한 개념들을 표현할 수 있게 됩니다.
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left"); // OK
printText("G'day, mate", "centre"); // No
Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
undefined 를 if 문으로 설정하지 않는다면 오류가 발생합니다.
하지만 이렇게 만들면 코드 가독성이 안좋아 지기 때문에 단언 연산자를 쓰는 것을 추천합니다.
function doSomething(x: string | undefined) {
if (x === undefined) {
// 아무 것도 하지 않는다
} else {
console.log("Hello, " + x.toUpperCase());
}
}
function liveDangerously(x?: number | undefined) {
console.log(x!.toFixed());
}
// 버튼 클릭시의 event
e:React.MouseEvent<HTMLButtonElement>
만약 이벤트 함수 자체를 넘겨 준다면
interface InputData {
username: string;
handleChange: (e:React.MouseEvent<HTMLButtonElement>) => void;
}
const User:React.FC<InputData> = ({username, InputData}) => {
return <input type="text" placeholder={username} onChange={InputData} />
}