블로그 목록으로
React

[TypeScript] 01 - TypeScript의 기본 - (1부)

요즈음 JavaScript는 TypeScript로 대체되어 가고 있는 추세입니다.TypeScript는 뭐길래 JavaScript를 대체하고 있을까요?React를 마스터하기 앞서 TypeScript에 대해서 배워봅시다!! 본 수업은 JavaScript에 어느 정도 지식이 있음을 바탕으로…

  • #react
[TypeScript] 01 - TypeScript의 기본 - (1부) 대표 이미지

요즈음 JavaScript는 TypeScript로 대체되어 가고 있는 추세입니다.

TypeScript는 뭐길래 JavaScript를 대체하고 있을까요?

React를 마스터하기 앞서 TypeScript에 대해서 배워봅시다!!

본 수업은 JavaScript에 어느 정도 지식이 있음을 바탕으로 진행합니다.

TypeScript란?

마이크로소프트는, JavaScript의 Superset(기존 언어에, 새로운 기능과 문법을 추가하여, 보완하거나 향상함) 언어인, TypeScript를 공개했습니다. 기존, JavaScript 코드를 그대로 사용할 수 있어서, 기존 JavaScript 개발자들이 매우 좋아했습니다.

TypeScript 활용시 장점
1. 안정성 보장
2. 개발 생산성 향상
3. 협업에 유리
4. JavaScript에 점진적으로 적용 가능

 대부분의 웹 개발자 채용 공고를 보면, 개발자들과 효율적인 협업을 위해 JavaScript 보다 TypeScript를 기본적으로 선호하는 회사가 많습니다.

TypeScript를 잘 배워두면, React Native를 활용하여 iOS 및 Android 앱을 개발할 수 있으며, Nest.js 또는 Node.js로 서버를 구축할 수 있고, Electron을 이용해 데스크톱 애플리케이션도 만들 수 있습니다.

반환값에 타입을 붙이면 그것이 TypeScript!

기본적으로 변수 이름 바로 뒤에 콜론과 함께 타입을 표기합니다.

const 변수 : 변수의 예상되는 반환값 : '변수';

string

- 문자열을 표현하는 타입.

- 작은따옴표('), 큰따옴표("), 또는 백틱(`)을 사용할 수 있습니다.

const otter : string = '수달';
 
let text : string = "Hello, TypeScript";
let template : string = `안녕하세요, ${otter}`

number

- 정수와 소수를 포함한 모든 숫자를 표현합니다.

- 10진수, 16진수, 2진수, 8진수를 사용할 수 있습니다.

const age: number = 26;
 
let intNum: number = 42;
let floatNum: number = 3.14;
let hexNum: number = 0xff; // 16진수
let binNum: number = 0b1010; // 2진수
let octNum: number = 0o52; // 8진수

boolean

- true 또는 false 값을 가질 수 있습니다.

const isMac: boolean = true;
const isGram: boolean = false;

null

- 값이 없음을 의미하는 타입입니다.

- null은 보통 명시적으로 값이 없음을 나타낼 때 사용됩니다.

const isNull: null = null;

undefined

- 변수가 초기화되지 않았거나, 존재하지 않는 속성을 참조할 때 나타내는 값입니다.

const isUndefined: undefined = undefined;

여기서 null 과 undefined의 차이를 잠깐 짚고 넘어가자면.

- null : 의도적으로 값이 없음. 초기화되지 않은 객체를 표시할 때 주로 사용됨

개발자가 의도적으로 값이 없음을 표현하고자 할 때 사용됩니다.

예를 들어, 데이터베이스에서 정보를 찾을 수 없거나 아직 초기화되지 않은 객체를 표현할 때 사용됩니다.

- undefined : 정의되지 않음. 값이 설정되지 않은 변수의 초기값.

undefined는 TypeScript에서 자동으로 할당되기 때문에, 주로 초기화되지 않은 상태나 값이 없는 것을 표현할 때 사용됩니다.

symbol

- 항상 고유한 값

같은 symbol을 생성하더라도 서로 다른 값으로 취급됩니다.

따라서 객체의 프로퍼티 키로 사용할 경우, 다른 프로퍼티와 충돌할 위험이 없습니다.

- 변경 불가능 (Immutable)

한 번 생성된 symbol은 변경할 수 없습니다.

- 객체의 숨겨진 속성으로 활용 가능

일반적인 객체 키(문자열)와 달리, symbol을 키로 사용하면 Object.keys()나 for...in 반복문에 노출되지 않습니다.

즉 은닉화된 프로퍼티를 만들 때 유용합니다.

const isSymbol: symbol = Symbol('symbol');

bigint

- 매우 큰 정수를 다룰 때 사용합니다.

- n을 숫자 뒤에 붙이면 bigint 타입이 됩니다.

let bigNumber: bigint = 900930992547140991n;
let anotherBig: bigint = BigInt(12345678901234567890);

object

- 객체를 표현하는 타입입니다.

- 객체는 키-값 쌍을 가지며, 속성을 정의할 수 있습니다.

const yaho: object = { yaho: 'yaho' };
 
let engName: { firstName: string; lastName: string } = {
  firstName: "Kim",
  lastName: "Otter"
};

함수에서의 TypeScript

parameter (매개 변수) 타입은, 매개변수 바로 뒤에 표기하고, 반환 값의 타입은, 파라미터 뒤에 콜론과 함께 예상되는 반환값의 타입을 명시해줍니다. 

함수 선언식

function minus(x: number, y: number): number {
	return x - y;
}

TypeScript의 함수 선언식은 function 키워드를 사용하여 정의하며, 코드의 어느 위치에서든 호출할 수 있는 호이스팅 기능을 제공합니다. 또한, 함수 선언식은 함수 오버로딩을 지원하여 같은 이름의 함수를 여러 형태로 정의할 수 있습니다. 함수 선언식의 this 바인딩은 호출될 때 결정되며, 호출하는 객체에 따라 다르게 동작합니다.

**호이스팅 (Hoisting)
**함수 선언은 코드가 실행되기 전에 메모리에 올라가기 때문에, 함수 선언 전에 호출해도 문제가 없음.

화살표 함수

const getFullname = (firstName: string, lastName: string): string => {
    return firstName + lastName;
};
 
const fullName = getFullname('김', '수달');
console.log(fullName); // "김수달"

화살표 함수는 => 문법을 사용하여 정의하며, 선언 전에 호출할 수 없는 특징을 가집니다. 화살표 함수는 TypeScript에서 함수 오버로딩을 지원하지 않으며 this 바인딩이 선언될 때의 문맥으로 고정되는 렉시컬 바인딩 방식을 사용합니다. 이로 인해 클래스 내부에서의 콜백 함수로 사용될 때 유용합니다.

오버로딩(overloading) :
오버로딩은 하나의 함수 이름으로 다양한 매개변수 타입 또는 개수를 허용하는 기능을 말합니다.

**동적 바인딩 (Dynamic Binding) - 함수 선언식의 this
**- this는 호출될 때 결정됩니다.
- 함수가 어디서 선언되었는지와는 상관없이, 호출하는 객체에 따라 this가 바뀝니다.

**렉시컬 바인딩 (Lexical Binding) - 화살표 함수의 this
**- this는 함수가 선언될 때의 환경을 기억합니다.

- 선언된 위치의 this 값이 사용되며, 호출 방식에 영향을 받지 않습니다.

리터럴 타입

리터럴 타입(Literal Types)은 TypeScript에서 특정한 값 그 자체만을 허용하는 타입을 정의할 수 있는 기능입니다.

일반적으로 우리가 사용하는 문자열 타입, 숫자 타입, 불리언 타입은, 그 타입에 해당하는 모든 값을 포함합니다.

하지만 리터럴 타입을 사용하면 특정한 값 하나만 허용할 수 있습니다.

리터럴 타입을 사용하면 코드의 안정성을 높이고,, 예기치 않은 값을 할당하는 오류를 방지할 수 있습니다.

리터럴 타입의 기본 개념

리터럴 타입은 값의 "리터럴"을 타입으로 사용하는 방식입니다. 즉, 특정한 값만을 허용하는 타입을 정의할 수 있습니다. 예를 들어, 변수에 문자열 리터럴 타입을 정의하면 그 변수는 해당 문자열만 가질 수 있게 됩니다.

예시

const name: "Otter" = "Otter";

위 코드에서 변수 name은 문자열 리터럴 타입 "Otter"를 가지고 있습니다. 따라서 이 변수는 "Otter"라는 값만 가질 수 있으며, 다른 문자열을 대입하면 TypeScript에서 오류를 발생시킵니다.

잘못된 예시

const name: "Otter" = "sea otter";

이 코드는 변수 name이 특정한 값 "Otter"만 가질 수 있도록 정의했기 때문에, "sea otter"를 대입하려고 하면 타입 불일치 오류가 발생합니다.

객체 리터럴 타입

객체 리터럴 타입 (Object Literal Types)은 TypeScript에서 특정 구조와 값을 가진 객체만을 허용하도록 제한하는 타입입니다. 이는 객체가 가질 수 있는 프로퍼티의 이름과 해당 프로퍼티의 값의 타입을 명확하게 정의함으로써, 예상치 못한 값이 객체에 포함되는 것을 방지합니다.

예시

const person: { name: string; age: number } = {
    name: "Otter",
    age: 25
};

위 코드에서 person은 객체 리터럴 타입 { name : string; age : number } 을 가지고 있습니다.

이 타입은 name 프로퍼티가 문자열이고, age 프로퍼티가 숫자여야 함을 의미합니다.


객체 리터럴 타입은 왜 사용하나요??

객체 리터럴 타입은 객체의 구조를 엄격하게 제한하여 코드의 안정성을 높여줍니다.

특히 복잡한 구조를 가지는 객체를 다룰 때, 미리 정의된 프로퍼티 외의 값이 객체에 포함되는 것을 방지할 수 있습니다.

예시 : 불필요한 프로퍼티 방지

const person: { name: string; age: number } = {
    name: "Otter",
    age: 25,
    job: "Helldiver"
};

위 예시에서는 정의하지 않은 프로퍼티인 job을 추가하려고 했기 때문에, TypeScript에서 오류를 발생 시킵니다.

만약 추가적인 프로퍼티를 허용하려면??

객체에 추가적인 프로퍼티를 허용하려면 인덱스 시그니처 (Index Signature)를 사용할 수 있습니다.

인덱스 스그니처를 사용하면 객체의 프로퍼티 이름과 값의 타입에 대해 유연성을 부여할 수 있습니다.

const person: { name: string; age: number; [key: string]: any } = {
    name: "Otter",
    age: 25,
    job: "Helldiver"
};

위 코드에서는 인덱스 시그니처를 사용하여 추가적인 프로퍼티등을 허용하고, 그 값은 any로 정의헀습니다.

이 방식으로 객체의 특정 프로퍼티 외에도 임의의 키-값 쌍을 허용할 수 있습니다.

선택적 프로퍼티

TypeScript 에서는 객체 리터럴 타입의 프로퍼티 중 일부를 선택적으로 만들 수 있습니다.

선택적 프로퍼티는 있어도 되고, 없어도 되는 프로퍼티 입니다.

이를 위해 프로퍼티 이름 뒤에 ?를 붙여 사용합니다.

예시 : 선택적 프로퍼티

const person: { name: string; age?: number } = {
    name: "Otter"
};

이 경우 age는 선택적 프로퍼티이므로, 객체에서 해당 프로퍼티가 없더라도 오류가 발생하지 않습니다. age가 없어도 문제없고, 있으면 숫자여야 합니다.

자바스크립트의 객체는 const 변수여도 수정이 가능하다.

자바스크립트의 객체는 const 변수라도, 수정이 가능하므로, 타입스크립트는 수정 가능성이 있다고, 판단하여 타입을 넓게 추론합니다.

만약 지정한 값 이외에 다른 값을 들어가지 않게 하고 싶고, 수정 가능성이 없는 것이 확실하다면 as const라는 특별한 접미사를 붙이면 됩니다.

const Otter = { name: 'Otter' } as const;

객체 리터럴 타입과 읽기 전용 (Readonly) 프로퍼티

객체 리터럴 타입에서 특정 프로퍼티가 읽기 전용이어야 한다면, readonly 키워드를 사용할 수 있습니다.

읽기 전용으로 정의된 프로퍼티는 객체가 생성된 후 값을 변경할 수 없습니다.

예시 : 읽기 전용 프로퍼티

const person: { readonly name: string; age: number } = {
    name: "Otter",
    age: 25
};
 
person.name = "John";  // 오류: 'name'은 읽기 전용이므로 값을 변경할 수 없습니다.

위 코드는 name을 readonly로 선언했기 때문에, 객체가 생성된 후에 값을 변경하려고 하면 오류가 발생합니다.

배열

배열 타입은 아래와 같이 2가지 방식으로 정의할 수 있습니다.

const stringArray: string[] = ['테르미니드', '오토마톤', '일루미닛'];
const stringArray2: Array = ['테르미니드', '오토마톤', '일루미닛'];
 
stringArray.push(14); // 오류: Argument of type 'number' is not assignable to parameter of type 'string'.

push를 활용해서 현재 문자열로 선언된 배열에 숫자를 넣는 것은, 타입이 호환되지 않으므로 에러가 발생합니다.

배열의 문제점

const array = [1, 2, 3];
 
array[3].toFixed(2); // ❓ (존재하지 않는 요소)

toFiexd 는  숫자를, 소수점 이하 자리수를 정확하게 갖는 문자열 표현으로 반환하는 메소드입니다.

현재 배열의 3번 인덱스에 값이 존재하지 않고, 위의 코드를 실행한다면 에러가 발생할 것입니다.

하지만 타입스크립트는 array가 이미 number[] 숫자 배열이기 때문에, array[3] 또한 숫자로 추론이 됩니다.

따라서 타입스크립트에서 에러를 잡을 수 없습니다.

이러한 문제는 튜플로 해결이 가능합니다.

튜플 Tuple

각 요소 자리에 타입이 고정되어 있는 배열을 특별하게 튜플이라 부릅니다.

const tuple: [string, boolean, number] = ['수달', true, 25];

아래와 같이, 여러가지 값을 대입해보면 아래와 같은 에러를 볼 수 있습니다.

const tuple: [string, boolean, number] = ['수달', true, 25];
 
// 문자열은 대입이 된다.
tuple[0] = '고구마'; 
 
// 문자열이 아닌 boolean이 들어갔으므로 에러가 발생한다.
tuple[0] = false;
 
// 배열의 3번 인덱스 자리는, 아무것도 들어갈 수 없으므로 에러가 발생한다.
tuple[3] = true;

아까 배열인 경우 발생한 문제점도 해결할 수 있음을 알 수 있다. 실행 이전 단계에서, 에러를 확인할 수 있습니다.

const array: [number, number, number] = [1, 2, 3];
 
array[3].toFixed(2); // 튜플로, 선언했기에 이제 에러가 발생함을 알 수 있다.

튜플 타입의 문제점

튜플 타입에 경우, push, pop, unshift, shift 메서드와 같은 배열에 요소를 추가하거나 제거하는 것은 막지 않습니다.

const array: [number, string, boolean] = [1, '야호', false];
 
array.push(4);
array.push(false);
array.push('수달');
array.pop();
array.unshift();
array.shift();

이를 막기 위해서 우리는 위에서 배운 readonly를 통해 해결할 수 있습니다.

const array: readonly [number, string, boolean] = [1, '야호', false];
 
// 에러 발생 Property 'push' does not exist on type 'readonly [number, string, boolean]'.(2339)
array.push(4);
array.push(false);
array.push('수달');
array.pop();
array.unshift();
array.shift();

유니언 타입 (|) 

TypeScript의 유니언 타입은 둘 이사으이 타입을 허용하여 변수가 여러 타입 중 하나를 가질 수 있게 합니다.

이는 여러가지 값을 처리할 때 타입을 유엲게 지정할 수 있어, 다양한 상황에서 유용하게 사용됩니다.

유니언 타입의 기본 개념

유니언 타입은 파이프(|) 기호를 사용해서 두 개 이상의 타입을 결합합니다.

이를 통해 변수나 함수의 인자, 반환 값 등이 여러 타입 중 하나를 가질 수 있도록 지정할 수 있습니다.

유니온 타입의 기본 예시

let value: string | number;
 
value = "Hello";  // 정상
value = 123;      // 정상
value = true;     // 오류: 'boolean' 타입은 허용되지 않습니다.

위 예시에서 value는 string 또는 number 타입을 가질 수 있으며, 문자열이나 숫자 값을 대입할 수 있습니다. 그러나 boolean 타입의 값을 대입하려고 하면 오류가 발생합니다.

유니언 타입의 활용

유니언 타입은 주로 함수의 인자나 반환 타입을 지정할 때 많이 사용됩니다. 예를 들어, 특정 함수가 문자열 또는 숫자를 인자로 받아야하거나, 문자열 또는 숫자를 반환할 수 있는 겨웅에 유니언 타입을 지정할 수 있습니다.

함수에서 유니언 타입 사용

function printValue(value: string | number) {
    console.log(value);
}
 
printValue("Hello");  // 출력: Hello
printValue(123);      // 출력: 123

유니언 타입과 조건부 로직

유니언 타입을 사용할 때는 **타입 좁히기 (Type Narrowing)**로 각 타입별로 분기 처리할 수 있습니다.

TypeScript는 유니언 타입을 사용할 때, 런타임에서 특정 타입으로 좁혀 처리할 수 있도록 다양한 방식을 제공합니다.

타입 좁히기 (Type Narrowing)

유니언 타입을 사용할 때 조건문을 통해 타입을 좁히는 방법입니다.

typeof 또는 instanceof를 사용하여 변수의 실제 타입을 검사하고, 그에 맞게 처리할 수 있습니다.

function process(value: string | number) {
    if (typeof value === "string") {
        console.log(`문자열 처리: ${value.toUpperCase()}`);
    } else {
        console.log(`숫자 처리: ${value.toFixed(2)}`);
    }
}
 
process("Hello");  // 출력: 문자열 처리: HELLO
process(123);      // 출력: 숫자 처리: 123.00

이 코드에서는 typeof를 사용하여 value의 타입 검사를 한 후, 각각 문자열과 숫자에 맞는 로직을 처리합니다.

유니언 타입과 배열

유니언 타입은 배열에도 적용될 수 있습니다. 배열에 여러 타입의 값을 허용해야 할 때, 유니언 타입을 사용하여 정의할 수 있습니다.

let mixedArray: (string | number)[] = ["Hello", 123, "World", 456];

위 예시는 string과 number 타입을 모두 허용하는 배열을 선언한 것입니다. 배열에 문자열과 숫자가 섞여있을 수 있으며, 각각의 값이 해당 타입에 맞는지 체크할 수 있습니다.

유니언 타입과 리터럴 타입의 결합

유니언 타입은 리터럴 타입과 결합하여 특정 값들만 허용하는 타입을 정의할 때도 유용합니다. 예를 들어, 함수의 인자로 특정한 값들만 허용하고 싶을 때 리터럴과 유니언 타입을 결합할 수 있습니다.

function move(direction: "left" | "right" | "up" | "down") {
    console.log(`You moved: ${direction}`);
}
 
move("left");  // 정상
move("right"); // 정상
move("forward"); // 오류: '"forward"'는 'Direction'에 할당할 수 없습니다.

위 코드에서는 direction 타입을 "left" | "right" | "up" | "down" 으로 정의하여, 특정 문자열 값들만 허용하도록 제한했습니다. 유니언 타입을 사용함으로써 허용가능한 값들을 제한할 수 있어, 잘못된 값을 입력하는 실수를 방지 할 수 있습니다.

타입 스크립트에만 존재하는 타입

Any

- 모든 타입을 허용하며 타입 안정성을 완전히 무시하는 타입.

- 타입 체크를 하지 않기 때문에 모든 값으로 할당 및 변환이 가능.

- 타입스크립트의 엄격한 타입 검사 기능을 무력화시키기 때문에 가능한 사용을 지양하는 것이 좋음.

unknown

- 모든 타입을 허용하지만, 사용 시 타입 검사를 요구하는 안전한 타입.

- 값의 타입을 알 수 없을 때 사용하며, 안전하게 타입을 확인한 후에 사용할 수 있음.

- 타입을 미리 확정할 수 없는 경우에 any 대신 사용하는 것이 좋음.

void

- 함수가 값을 반환하지 않을 때 사용되는 타입.

- 반환값이 없는 함수의 반환 타입을 명시할 때 주로 사용.

- 타입으로서의 의미가 없으므로 변수 타입으로는 거의 사용되지 않음.

never

- 절대 발생하지 않는 값의 타입을 나타냄.

- 예외를 발생시키거나 영원히 실행되는 함수의 반환 타입으로 사용.

- 다른 타입으로 할당할 수 있지만, 다른 타입이 never로 할당되는 것은 불가능.

- 비정상적인 코드 경로를 명확히 표현하기 위해 사용.

2부에서 계속됩니다!