Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[Typescript] 타입 스크립트 동작 원리, 타입 추론, 제네릭 본문

프론트엔드 부트캠프

[Typescript] 타입 스크립트 동작 원리, 타입 추론, 제네릭

heyday2024 2024. 12. 21. 15:24

타입 스크립트란? 자바 스크립트 + 정적 타입 시스템

즉, 자바 스크립트의 모든 기능을 포함하지만 정적 타입 시스템이 곁들인....

그렇다면 타입 시스템은 무엇일까?

타입 시스템은 프로그래밍 언어에서 값들을 분류하고 각 값이 어떤 타입인지 정의하는 체계임. 이를 위해 타입 시스템은 코드 타입 검사 시기와 방법에 대한 규칙까지 설정함.

  1. null
  2. undefined
  3. boolean
  4. string
  5. number

* 자바스크립트는 동적 타입 언어 ==> 동적 타입 언어(dynamic typed)는 프로그램을 실행한 이후에 변수, 함수의 타입이 결정됨

정적 타입 시스템(static typed)은 타입 시스템 중에 프로그램이 실행되기 전에 모든 변수와 표현식의 타입을 확인하고 고정하는 방식

==> 즉, 정의하자면 동적 타입 언어는 변수의 타입이 런타임에 결정되고 실행 중에 값에 따라 타입이 유동적으로 변함. 그러나 정적 타입은 변수의 타입이 컴파일 타임에 결정되며 컴파일러가 코드의 타입을 검사하여 타입 불일치인 경우 에러 발생함.

 

특징
JavaScript
TypeScript
타입 시스템
동적 타입
정적 타입
컴파일
필요 없음
JavaScript로 컴파일 필요 (프로젝트가 대형화되면 컴파일 시간이 길어짐)
개발 경험
유연하고 빠르게 시작 가능
코드 완성, 타입 검사 등으로 향상된 개발 경험
프로젝트 규모
작은 프로젝트에 적합
대규모 프로젝트에 적합
런타임 오류
런타임에 타입 오류 발견 가능
컴파일 타임에 타입 오류 발견 가능
학습 곡선
낮음
높음
도구 지원
광범위한 도구와 라이브러리 지원
JavaScript 생태계의 모든 도구와 라이브러리 지원
브라우저 지원
모든 브라우저에서 기본적으로 지원
트랜스 파일 된 JavaScript가 모든 브라우저에서 지원

 

==> 그럼 왜 귀찮게 굳이 타입을 미리 정해야하는 정적타입 시스템을 사용하는 걸까???

개발자가 변수 타입에 관한 실수를 했을 떄 정적타입 시스템은 그것을 엄격하게 에러로 나타내줌!! 즉, 정리하자면 개발 과정에서 에러를 잡을 수 있게 도와주기 때문에 프로젝트 대형화, 유지 보수에 용이함.


<타입 스크립트의 동작 원리>

  1. 컴파일 시작: tsc 명령어를 사용해 타입스크립트 컴파일러를 실행합니다. 이때 tsconfig.json 파일을 참고하여 어떤 파일을 컴파일할지, 어떤 옵션을 사용할지 설정합니다.
  2. 파일 로드: 컴파일러가 모든 입력 파일과 import된 파일을 로드합니다.
  3. 코드 분석: 코드를 읽어들여 프로그램 구조를 나타내는 AST(구문 트리)를 만듭니다.
  4. 심볼 테이블 생성: 코드를 분석하여 변수와 함수 등 모든 요소의 관계를 정리한 심볼 테이블을 만듭니다.
  5. 자바스크립트 코드로 변환: AST를 바탕으로 타입스크립트 코드를 자바스크립트 코드로 변환합니다.
  6. 타입 검사: 컴파일러가 코드를 검사하여 타입 오류를 찾습니다. 오류가 없다면 최종 자바스크립트 파일을 생성합니다.

==> 타입스크립트 컴파일러가 코드를 분석하고 분석 오류가 없다면 전부 JAVASCRIPT 코드로 변경함. 드 후에는 전부 JAVASCRIPT 와 동일하게 런타임에 동작함.


<vite 프로젝트 생성>

npm create vite@latest my-react-ts-app -- --template react-ts

- yarn 쓸 거면 앞에 yarn으로만 바꾸기

tsconfig.json 파일

<리액트 + 타입스크립트>

import React from 'react';

// 이렇게 타입을 선언해줄 수 있습니다.
type HelloProps = {
    name: string;
}

const Hello = ({ name } : HelloProps) => {
    return <h1>Hello, {name}!</h1>;
};

export default Hello;

- 이런 식으로 type 설정 가능

https://www.typescriptlang.org/docs/

 

The starting point for learning TypeScript

Find TypeScript starter projects: from Angular to React or Node.js and CLIs.

www.typescriptlang.org

 

https://basarat.gitbook.io/typescript

 

README | TypeScript Deep Dive

Last updated 8 months ago

basarat.gitbook.io

 

==> 쉽게 생각하면 타입 스크립트는 자바스크립트랑 똑같이 쓰는데 그냥 추가적으로 타입을 미리 작성해주면 됨!!
==> 그리고 나중에 컴파일 타임 이후에도 결국 100% 자바스크립트로 변경되어 동작됨.


타입을 어떻게 선언하는데??

const a = "Hello World";

console.log(typeof a); // string
console.log(a.length); // 11
console.log(a.toUpperCase()); // HELLO WORLD
console.log(a.slice(0, 5)); // Hello

여기서 a는 toUpperCase()라는 메서드를 가지고 있는데 그 메서드는 모든 문자를 대문자로 바꿔주는 역할을 하고, 또 a는 length라는 프로퍼티를 가지고 있는데 그 프로퍼티는 문자열의 길이를 나타내고... 라고 속성을 길게 설명해주는 대신 이미 정의된 타입으로 간단하게 a는 string 타입이야. 라고 말하면 간단해짐.
==> 즉, 이렇게 같은 속성을 가지는 것들을 하나의 이름으로 정의하기 위해 타입을 사용함.

type Profile = {
    id: string;
    name: string;
    age: number;
    isMarried: boolean;
}

- 이런식으로 하나의 타입(이미 정해진 원시타입) 뿐만 아니라 우리가 직접 어떤 객체의 내부에 타입을 각각 지정해줄 수도 있음.

function add(a: number, b: number): number {
  return a + b;
}

const sum: number = add(1, 2);
console.log(sum); // 3

- 함수 선언할 때에도 각각의 인자를 위한 그리고 반환값을 위한 타입을 지정해줘야함


<타입 종류>

TypeScript 주요 타입

1. 기본 타입

  • 불린 (boolean)
  • let isDone: boolean = false;
  • 숫자 (number)
  • let integer: number = 6; let float: number = 3.14;
  • 문자열 (string)
  • let myColor: string = `My color is ${red}`;

2. 배열 (Array)

  • 타입 지정:
  • let fruits: string[] = ["Apple", "Banana"]; let numbers: Array<number> = [1, 2, 3];

3. 인터페이스 (interface)

  • 객체 구조 정의:
  • interface IUser { name: string; age: number; } let user: IUser = { name: "Neo", age: 10 };

4. 타입 별칭 (type)

  • 복잡한 타입 표현:
  • type User = { name: string; age: number }; let user: User = { name: "Neo", age: 10 };

5. Null과 Undefined

  • 명시적 선언:
  • let stringOrNull: string | null = null; let numberOrUndefined: string | undefined = undefined;

6. 유니온 타입 (Union)

  • OR 연산처럼:
  • let union: string | number; union = "Hello"; union = 123;

7. 인터섹션 타입 (Intersection)

  • AND 연산처럼:
  • interface User { name: string; age: number; } interface Validation { isValid: boolean; } let user: User & Validation = { name: "Alice", age: 25, isValid: true };

8. 튜플 (Tuple)

  • 고정된 길이 배열:
  • let tuple: [string, number] = ["Alice", 25];

9. 열거형 (enum)

  • 고정된 값 목록:
  • enum Color { Red = "red", Green = "green", Blue = "blue", } let myColor: Color = Color.Red;

10. 알 수 없는 타입 (unknown)

  • 안전한 any 대체:
  • let u: unknown = 123; let test: number = u as number;

11. 모든 타입(any)

let any: any = 123;
any = 'play game';
any = {};
any = null;

const arr: any[] = [1, true, 'typescript'];

12. void

function coding(msg: string): void {
  console.log(`Happy ${msg}`);
}

13. 함수(Function)

let myFunc: (arg1: number, arg2: number) => number;
myFunc = function(x, y) {
  return x + y;
}
myFunc(1, 2); // 3

let noneFunc: () => void;
noneFunc = function () {
  console.log('hihi');
};

14. 읽기 전용 : readonly

let arrA: readonly number[] = [1, 2, 3, 4];
let arrB: ReadonlyArray<number> = [2, 4, 6, 8];

15. 객체 : object

let obj: object = {};
let arr: object = [];
let func: object = function () {};
let date: object = new Date();

interface Users {
  name: string,
  age: number
}

let userA: Users = {
  name: 'juyoung',
  age: 27
};

16. Never

function error(message: string): never {
  throw new Error(message);
}

const never: [] = [];
never.push(3); // Error

타입 관련 심화 내용

Type Alias vs Interface

  • 인터페이스==> 인터페이스는 확장이 가능함. 다른 인터페이스를 상속하거나 동일한 이름으로 다시 열어 새로운 속성 추가 가능.
  • interface Hello { name: string; } interface Hello { age: number; // ✅ 확장 가능 }
  • 타입 별칭==> 한번 정의 되면 다시 열어 새로운 속성 추가 불가능.
  • type Hello = { name: string }; type Hello = { age: number }; // ❌ 불가능
  • 복잡한 타입 표현(Complex Type Expressions):
    • 인터페이스(interface): 객체 형태의 타입을 정의하는 데에 주로 사용됩니다.
    • 타입 별칭(type alias): 객체 형태 뿐만 아니라, 유니온 타입, 튜플, 매핑된 타입 등 복잡한 타입 표현에 유리합니다.
      • type A = B | C처럼 더 다양한 타입을 정의할 수 있습니다.

구조적 타입 시스템

  • 타입이 아니라 구조가 중요: 두 개체가 동일한 구조를 가지면 동일한 타입으로 간주
  • interface Person { name: string; age: number; } let p1: Person = { name: "Alice", age: 25 }; let p2 = { name: "Bob", age: 30 }; p1 = p2; // 성공 (구조가 같기 때문)

타입어노테이션과 추론

**- Type annotation : 사용하려고 하는 변수, 함수 옆에 : 기호와 함께 선언 가능한 타입을 넣어주면 끝!

let name: string = 'Owen';
let age: number = 30;
let isMarried: boolean = false;

// built in object
let now: Date = new Date();

// array
let animals: string[] = ['cat', 'dog', 'cow'];

// object literal
let point: {x: number, y: number} = {
    x: 10,
    y: 20
}

// function
const logNumber: (i: number) => void = (i: number) => {
    console.log(i);
}

let haveNothing: null = null;
let nothing: undefined = undefined;

타입 어노테이션은 어떤 값이 어떤 타입을 참조하고 있는지 개발자가 직접 타입을 작성해서 타입스크립트에게 알려줌

타입 추론

===> 타입 추론은 TypeScript의 중요한 기능 중 하나로, 개발자가 타입을 명시적으로 지정(타입 어노테이션)하지 않아도 타입 안전성을 유지할 수 있게 도와줍니다.

기본 타입 추론

변수의 타입 추론

let num = 10; // number로 추론
let str = "Hello"; // string으로 추론
let bool = true; // boolean으로 추론

배열의 타입 추론

let numbers = [1, 2, 3, 4, 5]; // number[]로 추론
let strings = ["a", "b", "c"]; // string[]로 추론

객체 리터럴의 타입 추론

let person = {
    name: "Alice",
    age: 25
}; // { name: string; age: number }로 추론

person.name = "Bob"; // 정상
person.age = 30; // 정상
person.age = "thirty"; // 오류: 'string' 형식은 'number' 형식에 할당할 수 없습니다.

함수 반환 타입 추론

function add(a: number, b: number) {
    return a + b; // number로 추론
}

const result = add(5, 3); // result는 number로 추론

<컨텍스트를 통한 타입 추론>

TypeScript는 문맥을 통해 변수나 함수의 타입을 추론 가능!!

  • 함수 매개변수와 반환 타입
  • let add = (a: number, b: number) => a + b; // (a: number, b: number) => number로 추론
  • 콜백 함수의 타입 추론
  • let numbers = [1, 2, 3, 4, 5]; numbers.forEach(num => { console.log(num); // num은 number로 추론 });

TypeScript의 타입 추론은 코드의 문맥을 통해 타입을 자동으로 결정함. 이를 통해 개발자는 명시적인 타입 선언 없이도 타입 안전성을 유지할 수 있음.

타입 추론은 변수, 함수, 객체, 배열 등 다양한 상황에서 동작하고, 제네릭 타입 또한 추론 가능함. 타입 추론을 이해하고 활용하면 코드의 가독성과 유지 보수성을 높일 수 있음.

==> 근데 이렇게 타입스크립트는 자동으로 타입 추론이 이뤄지는데 왜 타입 어노테이션 해야함??

암묵적 타입 추론에 의존하게 되면 타입스크립트의 특징인 안전한 코드 작성에서 멀어질 수 있는 위험이 있기 때문에 처음 타입을 작성할때, 꼼꼼히 타입 어노테이션을 해주는 것을 습관화 하는 것이 좋음.

<이럴 때 특히 타입 어노테이션 필요>

  • 1. 함수가 any 타입을 리턴하고 이 값을 명확하게 해야 할 때
  • 2. 어떤 변수를 선언한 이후에 다른 라인에서 초기화를 할 때
  • 3. 추론할 수 없는 타입을 변수가 가지게 하려 할 때
// 1.

const json = '{"a": 1, "b": 2}';
const object = JSON.parse(json); // any 타입으로 인식 -> 타입 어노테이션 필요
console.log(object); // {a: 1, b: 2}
object.asdadsfgsdasdasdasd // 타입스크립트는 object를 any 타입으로 인식해서 이러한 에러를 찾지 못함

// 2.

const colors = ['red', 'blue', 'green'];
let foundColor; // 암묵적으로 any 타입을 가짐 -> 타입 어노테이션 필요

for (let i = 0; i < colors.length; i++) {
    if (colors[i] === 'blue') foundColor = true;
}

// 3.
let numbers = [-10, -5, 10];
let numberAboveZero = false; // boolean 타입으로 타입 추론 -> 타입 어노테이션 필요 boolean | number

for (let i = 0; i < colors.length; i++) {
    if (numbers[i] > 0) numberAboveZero = numbers[i]; // error
}

제네릭

제네릭 : 타입을 마치 클래스나 함수 등에서 파라미터처럼 사용하는 것

  • Generic

제네릭이 왜 필요할까요??

function printStrings(arr: string[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
}

function printNumbers(arr: number[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
}

==> 위 함수들은 같은 역할을 하는데 타입이 달라서 굳이 두번 만들어줌. 근데 이런 식으로 함수를 만들면 모든 타입들마다 굳이 같은 역할의 함수를 정의해야하는 문제가 생김. 이때는 그냥 제네릭을 사용해서 타입을 변수처럼 사용하면됨!!!

<useState 에서 제네릭 사용하기>

React Hooks 에서도 Generic을 사용할 수 있음.

import { useState } from "react";

function App() {
  const [counter, setCounter] = useState<number>(1);
  const increment = () => {
    setCounter((prev) => prev++);
  };
  return <div onClick={increment}>{counter}</div>;
}

export default App;

==> 이렇게 useState 뒤에 <>와 number 타입을 넣어주면 counter 와 setCounter 에 각각 number, number 를 인자로 받는 함수의 타입이 할당됨.

하지만 우리가 제네릭을 사용하지 않고 초깃값을 number로 설정해도 같은 타입이 들어감.

import { useState } from "react";

function App() {
  const [counter, setCounter] = useState(1);
  const increment = () => {
    setCounter((prev) => prev++);
  };
  return <div onClick={increment}>{counter}</div>;
}

export default App;

그 이유는 제네릭도 다른 타입들 처럼 타입을 추론하기 때문!!!