타입스크립트

정민·2023년 4월 21일
1

요즘 틈틈히 사이드 프로젝트 코드를 짜고 있는데, 갑자기 타입을 정할 때 있어서 Number와 number의 차이가 헷갈리는 것이었다.
분명 이전에도 이랬어서 정리를 하고 '아, 소문자를 쓰는 게 좋군!' 하고만 넘어갔던 것만 기억에 남아있고, 이유는 생각이 나지 않아서 이전에 정리했던 거를 다시 보게 되었다.

개념

TypeScriptMicroSoft 에서 개발하며 유지보수 하는 오픈소스 프로그래밍 언어이다. 기본적으로 JavaScript 의 슈퍼셋인 언어이다.

기존 JavaScript 에서는 Type 미지원으로 인하여 잦은 오타 및 개발자가 parameterType 을 혼돈하는 경우가 많았다.

이때문에 TypeScript 가 코드 어시스턴트나 Type 을 지원함으로써 개발자의 실수와 Type 오류를 줄였다.

기본 타입

boolean

참/거짓(true/false) 를 가질 수 있다.

let isDone: boolean = false;

number

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

string

let color : string = "blue";

array

let list: number[] = [1,2,3];
let list: Array<number> = [1,2,3];

tuple

let x: [string, number] = ["hello", 10];

enum

enum Color {Red=1, Green, Blue}
let c: Color = Color.Green;

any

애플리케이션을 만들 때 알지 못하는 타입을 표현해야 할 수도 있다. 이때 코드에서 타입검사를 하지 않고 컴파일 때 검사를 하길 원한다면, any 를 사용하면 된다.

let notSure: any = 4;
notSure = "maybe a string instead"; //성공
notSure = false; //성공

any 를 사용하면 기존의 JavaScript 로 작업하는 것과 크게 다르지 않다.

let list: any[] = [1, true, "free"];

이런식으로, 타입 여러개가 섞인 배열을 사용할 때 유용하다.

void

어떤 타입도 존재할 수 없음을 나타낸다. 보통 함수에서 반환 값이 없을 때 반환 타입을 표현하기 위해 쓰인다.

function warnUser(): void {
	console.log("This is my warning message");
}

null & undefined

말그대로 nullundefined

기본적으로 nullundefined 는 다른 모든 타입의 하위 타입이다. ⇒ nullundefinednumber 같은 타입에 할당할 수 있다.

never

절대 발생할 수 없는 타입이다.

object

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'.
// 타입은 안된다.

인터페이스 vs 타입

interface 는 객체에서만 가능하지만, type 은 다른 변수도 가능하다.

interface FooInterface {
  value: string
}

type FooType = {
  value: string
}

type FooOnlyString = string
type FooTypeNumber = number

// 불가능
interface X extends string {}

interfacecomputed 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")

as (Type Assertion)

내가 TypeScript 보다 어떤 값의 타입을 명확하게 알고 있을 때 활용한다.

예를들어 코드상에서 document.getElementById 가 사용되는 경우, TypeScript 는 이때 HTMLElement 중에 “무언가”가 반환된다는 것만을 알 수 있는 반면에 나는 언제나 HTMLCanvasElement 가 반환된다는 사실을 이미 알고 있을 수도 있다.

const myCanvas = document.getElementById("main canvas") as HTMLCanvasElement;

as 사용을 지양해야하는 이유

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;
profile
괴발개발~

0개의 댓글