요즘 틈틈히 사이드 프로젝트 코드를 짜고 있는데, 갑자기 타입을 정할 때 있어서 Number와 number의 차이가 헷갈리는 것이었다.
분명 이전에도 이랬어서 정리를 하고 '아, 소문자를 쓰는 게 좋군!' 하고만 넘어갔던 것만 기억에 남아있고, 이유는 생각이 나지 않아서 이전에 정리했던 거를 다시 보게 되었다.
TypeScript
는 MicroSoft
에서 개발하며 유지보수 하는 오픈소스 프로그래밍 언어이다. 기본적으로 JavaScript
의 슈퍼셋인 언어이다.
기존 JavaScript
에서는 Type
미지원으로 인하여 잦은 오타 및 개발자가 parameter
나 Type
을 혼돈하는 경우가 많았다.
이때문에 TypeScript
가 코드 어시스턴트나 Type
을 지원함으로써 개발자의 실수와 Type
오류를 줄였다.
참/거짓(true/false) 를 가질 수 있다.
let isDone: boolean = false;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let color : string = "blue";
let list: number[] = [1,2,3];
let list: Array<number> = [1,2,3];
let x: [string, number] = ["hello", 10];
enum Color {Red=1, Green, Blue}
let c: Color = Color.Green;
애플리케이션을 만들 때 알지 못하는 타입을 표현해야 할 수도 있다. 이때 코드에서 타입검사를 하지 않고 컴파일 때 검사를 하길 원한다면, any
를 사용하면 된다.
let notSure: any = 4;
notSure = "maybe a string instead"; //성공
notSure = false; //성공
any
를 사용하면 기존의 JavaScript
로 작업하는 것과 크게 다르지 않다.
let list: any[] = [1, true, "free"];
이런식으로, 타입 여러개가 섞인 배열을 사용할 때 유용하다.
어떤 타입도 존재할 수 없음을 나타낸다. 보통 함수에서 반환 값이 없을 때 반환 타입을 표현하기 위해 쓰인다.
function warnUser(): void {
console.log("This is my warning message");
}
말그대로 null
과 undefined
기본적으로 null
과 undefined
는 다른 모든 타입의 하위 타입이다. ⇒ null
과 undefined
를 number
같은 타입에 할당할 수 있다.
절대 발생할 수 없는 타입이다.
function create(o: object | null) : void {
...
}
create({prop:0}); //성공
create(null); //성공
create(42); // 오류
함수의 인자로 파라미터를 전달할 때, 파라미터가 object
라면 이런식으로 길어질 수 있다.
function printLabel(labeledObj: { label: string }) {
console.log(labeledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
이를 인터페이스를 이용해, 따로 정의할 수 있는데, 이런식으로 구현하면 코드 안, 그리고 프로젝트 외부에서의 규약을 정의할 수 있는 강력한 방법이 될 수 있다.
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
위와같이 인터페이스를 지정해줄 수 있다.
인터페이스 안의 어떤 프로퍼티는, 어떤 조건에서만 존재하거나 존재하지 않을 수도 있다. 이러한 프로퍼티들을 정의하기 위해 :
앞에 ?
를 붙여준다.
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
선택적 프로퍼티를 사용함으로써, 인터페이스에 속하지 않은 프로퍼티의 사용을 방지할 수 있다.
전체적으로 인터페이스와 같지만, 확장하는 방법이 다르다.
//인터페이스
interface PeopleInterface {
name: string
age: number
}
interface StudentInterface extends PeopleInterface {
school: string
}
//타입
type PeopleType = {
name: string
age: number
}
type StudentType = PeopleType & {
school: string
}
또한 인터페이스는 새로운 속성을 추가하기 위해 다시 같은 이름으로 선언할 수 있으나, 타입은 불가능하다.
interface Window {
title: string
}
interface Window {
ts: TypeScriptAPI
}
// 같은 interface 명으로 Window를 다시 만든다면, 자동으로 확장이 된다.
const src = 'const a = "Hello World"'
window.ts.transpileModule(src, {})
type Window = {
title: string
}
type Window = {
ts: TypeScriptAPI
}
// Error: Duplicate identifier 'Window'.
// 타입은 안된다.
interface
는 객체에서만 가능하지만, type
은 다른 변수도 가능하다.
interface FooInterface {
value: string
}
type FooType = {
value: string
}
type FooOnlyString = string
type FooTypeNumber = number
// 불가능
interface X extends string {}
interface
는 computed value
가 사용 가능하지만, type
은 불가능하다.
type names = 'firstName' | 'lastName'
type NameTypes = {
[key in names]: string
}
const yc: NameTypes = { firstName: 'hi', lastName: 'yc' }
interface NameInterface {
// error
[key in names]: string
}
둘다 유사한 기능을 하기에 무엇을 사용해도 큰 차이는 없는 것 같다. 그냥 프로젝트에 있어서 type
을 쓸지, interface
를 쓸지만 정하면 될 것 같다.
함수 안에 들어가는 각 파라미터와, 함수 자신의 반환될 타입을 정해줄 수 있다.
fucntion add(x, y) {
return x + y;
}
let myAdd = function(x, y) { return x + y };
이것을
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y };
위와같이 바꿀 수 있다.
여기서 함수의 전체 타입또한 작성할 수 있는데,
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; };
이런식으로 지정 가능하다.
// 매개변수 x 와 y는 number 타입을 가집니다
let myAdd: (baseValue: number, increment: number) => number =
function(x, y) { return x + y; };
이런식으로, 함수의 전체 타입과 함수의 파라미터가 같지 않아도 된다.
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // 지금은 바르게 동작
let result2 = buildName("Bob", "Adams", "Sr."); // 오류, 너무 많은 매개변수
let result3 = buildName("Bob", "Adams"); // 정확함
함수에도 이런식으로, 선택적 매개변수를 지정할 수 있다.
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
// employeeName 은 "Joseph Samuel Lucas MacKinzie" 가 될것입니다.
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie")
내가 TypeScript
보다 어떤 값의 타입을 명확하게 알고 있을 때 활용한다.
예를들어 코드상에서 document.getElementById
가 사용되는 경우, TypeScript
는 이때 HTMLElement
중에 “무언가”가 반환된다는 것만을 알 수 있는 반면에 나는 언제나 HTMLCanvasElement
가 반환된다는 사실을 이미 알고 있을 수도 있다.
const myCanvas = document.getElementById("main canvas") as HTMLCanvasElement;
https://baek.dev/til/typescript/effective-typescript/ch02/item9
interface Person {
name: string;
}
const alice: Person = { name: "alice" };
const bob = { name: "bob" } as Person;
alice
는 변수에 타입 선언
을 붙여서 그 값이 선언된 타입임을 명시하는 것이고,
bob
은 기존에 타입스크립트가 추론한 타입이 있더라도 Person 타입으로 간주하는 것이다.
따라서 타입 단언을 사용하면, 타입 체커에게 타입을 강제로 지정했으니, 오류를 무시하라고 하는 것이다. 그렇기 때문에 as
를 사용하면 타입 체크를 할 수 없게 된다.
https://baek.dev/til/typescript/effective-typescript/ch02/item9
string, number, boolean, symbol, bigint
String, Number, Boolean, Symbol, BigInt
JavaScript
는 기본형과 객체를 자유롭게 변환한다.
예시를 들어보자면, string
기본형에는 메소드가 없지만, String
이라는 객체형에는 메소드가 정의되어 있다.
'primitive'.charAt(3);
위 예제에서 charAt
은 string의 메소드가 아니다, 그렇지만 JavaScript
는 기본형과 객체형을 서로 자유롭게 변환하기 때문에 string
기본형에서 charAt
과 같은 메소드를 사용할 때, 기본형을 String
개체로 Wrapping
하고, 메소드를 호출하고, 마지막에 Wrapping
한 객체를 버린다.
타입스크립트는 객체 Wrapper
타입을 지양하고, 대신 기본형 타입을 사용해야한다.
기본형은 객체 타입에 할당될 수 있지만, 객체 타입은 기본형에 할당할 수 없기 때문이다!!
let test1: boolean;
let test2: boolean;
let test3: Boolean;
test1=true;
test2=true;
test3=true;
test1=test2;
test2=test3; // 이거만 오류!!
test3=test2;