onebite TS - 2강. 타입스크립트의 기본

박하늘·2025년 9월 24일

| 타입 스크립트의 기본 타입

1️⃣ 원시타입과 리터럴 타입

▪️ number

타입 주석(Type Annotation)은 변수, 함수, 객체 등의 데이터 타입을 명시적으로 지정하는 프로그래밍 문법으로, 특히 TypeScript와 같은 언어에서 사용됩니다.
변수 선언 시 콜론(:) 뒤에 데이터 타입을 붙여서 표시하며 (예: let 변수명: 타입;), 이를 통해 코드의 명확성을 높이고 잠재적인 오류를 줄일 수 있습니다.

let num = 10;
// 이렇게 선언할 경우 초기값(10)을 보고 ts 가 `num = number 타입`으로 타입 추론을 함.


let num1: number = 123;
// 변수의 이름 뒤에 콜론 뒤에 타입명(number)을 정의하는 것을 타입주석 이라고 한다.

let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;

[ 타입에러 예시 ]

let num1: number = 123;

num1 = 'hello'; // ❌
// 맨 위에 number라는 타입을 명시해주었기 때문에 hello 와 같은 string 타입 선언 불가능
// Type 'string' is not assignable to type 'number'.

num1.toUpperCase(); // ❌ 
// 맨 위에 number라는 타입을 명시해주었기 때문에 string 타입만 사용 가능한 함수 사용 불가능
// Property 'toUpperCase' does not exist on type 'number'.

num1.toFixed() // ⭕️

▪️ string

let str1: string = "hello";
let str2: string = 'hello';
let str3: string = `hello`;
let str4: string = `hello ${str1}`;
let str5: string = `hello ${num1}`;

[ 타입에러 예시 ]

let str1: string = "hello";

str1 = 123; // ❌
// 맨 위에 이미 string 이라는 타입을 명시해주었기 때문에 number 타입 선언 불가능
// Type 'number' is not assignable to type 'string'.

str1.toFixed() // ❌
// 맨 위에 이미 string 이라는 타입을 명시해주었기 때문에 number 타입만 사용 가능한 함수 사용 불가능
// Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?

str1.toUpperCase() // ⭕️

▪️ boolean

let bool1 : boolean = true;
let bool2 : boolean = false;

▪️ null

let null1: null = null;

[ 타입에러 예시 ]

let numA: number = null

위처럼 확정된 값이 없어 임시로 null 값을 넣는 경우 자바스크립트는 가능하지만, 타입스크립트는 불가능.
하지만, 📁 tsconfig.json 수정 하면 가능하다.

"strict": true 의 하위옵션 "strictNullChecks": false 추가 해주면 된다.
즉, strict 가 true 이면 자동으로 strictNullChecks 해당 옵션도 true가 되므로 수동으로 수정해주어야 한다.

▪️ undefined

let unde1: undefined = undefined;

▪️ literal

프로그래밍 언어에서 변수에 대입하거나 연산에 사용하는, 변하지 않는 실제 값 그 자체를 의미

let numA: 10 = 10;
let strA: "hello" = "hello";
let boolA: true = true;
let boolB: false = false;

2️⃣ 배열과 튜플

▪️ 배열

[] , <> 이 두 개의 기호로 표현 가능

✴︎ 기본

let numArr: number[] = [1, 2, 3]
let srtArr: string[] = ['hello', 'im', 'neul']

let boolArr: Array<boolean> = [true, false, true];
// <> 안에 타입 넣는 걸 '제네릭 타입' 이라고 한다

✴︎ 배열에 들어가는 요소들의 타입이 다양할 경우

let multiArr = [1, "hello"];

let multiArr1: (number | string)[] = [1, "hello"];

✴︎ 다차원 배열의 타입을 정의할 경우

let doubleArr : number[][] = [
  [1, 2, 3], 
  [4, 5],
];

▪️ 튜플

자바스크립트에는 없고 타입스크립트에만 존재. 길이와 타입이 고정된 배열.

let tup1: [number, number] = [1, 2];

[ 타입에러 예시 ]

let tup1: [number, number] = [1, 2];

tup1 = [1, 2, 3] // ❌
// 길이를 2개로 설정했으므로 그 이상 number 타입이 들어갈 수 없음

tup1 = ['1', '2'] // ❌
// 타입을 number로 지정했으므로 string 타입 불가

let tup2: [number, string, boolean] = [true, "hello", 1]; // ❌
// 길이나 배열 순서가 달라도 오류

튜플은 별도로 존재하는 자료형이 아닌 사실은 그냥 배열임. 위 예시 ts 파일을 컴파일하여 js 로 보면 그냥 배열 형태로 나옴.
원래는 배열 메서드 사용 시 길이나 타입상 작동하지 않아야 하지만 따로 오류가 발생하진 않음

tup1.push(1);
// 튜플 길이제한으로 인해 하나가 더 추가되면 원래는 안 되지만 오류가 발생하지 않음

tup1.push(1);

tup1.pop();
// ⭐️ 즉, 배열 메서드 사용 시 튜플의 길이제한이 발동하지 않음

✴︎ 튜플 사용 좋은 예시

const users = [
  ["이정환", 1],
  ["이아무개", 2],
  ["김아무개", 3],
  ["박아무개", 4],
];

// 여기서 한 명을 추가하고 싶었지만 아래처럼 이상하게 추가할 수도 있음.

const users1 = [
  ["이정환", 1],
  ["이아무개", 2],
  ["김아무개", 3],
  ["박아무개", 4],
  [5, "조아무개"],
];
// 잘못된 타입(순서)으로 추가함.

// ⭐️ 튜플 적용
const users2: [string, number][] = [
  ["이정환", 1],
  ["이아무개", 2],
  ["김아무개", 3],
  ["박아무개", 4],
  // [5, "조아무개"], // ❌ 오류 발생
];

3️⃣ 객체

▪️ object

let user: object = {
  id: 1,
  name: "이정환",
};

✴︎ object의 한계

  • object로 지정 시 아래처럼 객체의 프로퍼티에 접근 시 오류 발생
user.id // ❌ 
// Property 'id' does not exist on type 'object'.
// 특정 키 값에 대해 .으로 접근 불가
// object는 객체이긴 하지만 난 그 이상은 몰라 라고 하는 것

▪️ 객체 리터럴 타입

let user1: {
  id: number;
  name: string;
} = {
  id: 1,
  name: "이정환",
};

user1.id;
// 객체 리터럴 타입 사용 시 점(.)표기법으로 프로퍼티 사용 가능

👩🏻‍💻 구조적 타입 시스템 vs 명목적 타입 시스템

  1. 구조적 타입 시스템 (Structural Type System) - TS
    • 기준: 객체의 형태(프로퍼티 구조) 를 보고 타입을 판단합니다.
    • 즉, 이름이 같지 않아도, 프로퍼티의 구조가 같으면 같은 타입으로 간주합니다.
// TS 예시: 구조적 타입
type Person = { name: string; age: number };
type User = { name: string; age: number };

const p: Person = { name: "Lee", age: 20 };
const u: User = p; // ✅ 구조가 같으므로 할당 가능
여기서 Person과 User는 이름은 다르지만 구조가 동일하기 때문에 호환됩니다.
→ 이걸 property based typing이라고 표현합니다.
  1. 명목적 타입 시스템 (Nominal Type System) - C/Java
    • 기준: 타입의 이름(선언) 자체를 기준으로 합니다.
    • 구조가 같더라도 타입 이름이 다르면 별개의 타입으로 취급합니다.
    • Java, C, C++ 같은 전통적인 정적 타입 언어들이 이 방식 사용
// Java 예시: 명목적 타입
class Person {
    String name;
    int age;
}

class User {
    String name;
    int age;
}

Person p = new Person();
User u = new User();
u = p; // ❌ 타입 이름이 달라서 불가능
→ Person과 User는 필드 구조가 같아도 이름이 다르면 다른 타입입니다.

✴︎ Optional property

?으로 표현

let user2: {
  id?: number; // 선택적 프로퍼티가 된 id
  name: string;
} = {
  id: 1,
  name: "이정환",
};

user = {
  name: "홍길동",
};
// ? 를 통해 선택적(Optional) 프로퍼티 를 설정하면 id 값은 꼭 포함되지 않아도 됨

✴︎ readonly

말 그대로 읽기만 가능. 수정 불가.

let user3: {
  id?: number;
  readonly name: string; // name은 이제 Readonly 프로퍼티가 되었음
} = {
  id: 1,
  name: "이정환",
};

user3.name = "dskfd"; // ❌
// readonly 가 있으면 같은 타입일 경우 변경이 가능하지만, 설정해주면 고유의 값으로 변해서 수정 불가

4️⃣ 타입 별칭과 인덱스 시그니처

▪️ 타입 별칭

타입 별칭 또한 타입 관련 문법이기 때문에 컴파일 결과 사라집니다.

let userex: {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
} = {
  id: 1,
  name: "이정환",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

별도의 타입 별칭 없이 이렇게 만들었다고 가정했을 때, 같은 타입으로 다른 한 유저를 추가하면 이렇게 긴 객체타입을 다시 지정해주어야 함.

...

✴︎ 타입 별칭 설정

type User = {
  id: number;
  name: string;
  nickname: string;
  birth: string;
  bio: string;
  location: string;
};
// ⭐️ User 라는 타입 별칭 추가 설정

let user: User = {
  id: 1,
  name: "이정환",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

let user2: User = {
  id: 2,
  name: "홍길동",
  nickname: "winterlood",
  birth: "1997.01.07",
  bio: "안녕하세요",
  location: "부천시",
};

type User = {} // ❌
// 당연히 같은 스코프 내에 같은 타입 별칭이 있으면 안 됨

function func() {
    type User = {};
}
// 이렇게 함수 스코프 안에 넣어주면 다른 스코프로 인식

▪️ 인덱스 시그니처

인덱스 시그니처는 “모든 키에 적용되는 규칙”을 정의합니다.

type CountryCodes1 = {
  Korea: string;
  UnitedState: string;
  UnitedKingdom: string;
  // (... 약 100개의 국가)
  Brazil : string
};

let countryCodes1: CountryCodes1 = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

➡️ 이런 식으로 생성 했을 때 국가가 추가되면 타입도 계속 추가 해야 하는 번거로움 발생

type CountryCodes = {
  [key: string]: string;
};

let countryCodes: CountryCodes = {
  Korea: "ko",
  UnitedState: "us",
  UnitedKingdom: "uk",
  // (... 약 100개의 국가)
  Brazil : 'bz'
};

⭐️ 이렇게 해주면 CountryCodes의 키값과 value 값의 타입에 맞춰 추가하면 오류 없음

✴︎ 추가예제

type CountryNumberCodes = {
  [key: string]: number;
};

let contryNumberCodes: CountryNumberCodes = {
    korea: 401
}

// 이렇게 하면 되고 인덱스의 경우 규칙을 위반하지 않으면 오류 발생하지 않음

let contryNumberCodes1: CountryNumberCodes = {

}
// 빈 객체는 위반할 객체도 없기 때문에 오류 발생하지 않음



type CountryNumberCodes2 = {
  [key: string]: number;
  Korea: number;
};
// 하지만 꼭 들어가야 하는 키 값(korea)이 있을 경우 아래처럼 설정 후 빈배열을 넣으면 오류 발생

let contryNumberCodes2: CountryNumberCodes2 = {} // ❌
// 이는 빈객체일 경우 오류 Korea 라는 키 값 무조건 존재

type CountryNumberCodes4 = {
  [key: string]: number;
  Korea: string; // ❌ 
};
// [key: string]: number; 이 구문은 “모든 string 키는 number 값을 가져야 한다”라는 규칙을 선언하는 것


5️⃣ Enum 타입

여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입

const exuser1 = {
  name: "이정환",
  role: 0 //관리자
};

const exuser2 = {
  name: "홍길동",
  role: 1, // 회원
};

const exuser3 = {
  name: "아무개",
  role: 2, // 게스트
};

이렇게 설정하면 나중에 각 번호의 의미가 뭔지 까먹을 수 있으므로 따로 지정

✴︎ 숫자 enum

enum Role {
  ADMIN = 0, // 첫번째 아무 숫자도 배정하지 않으면 자동으로 0 배정
  USER = 1, // 위에 0번 혹시 10번과 같이 숫자 지정 해주면 아래 번호는 순차적으로 자동 배정 
  GUEST = 2,
}

const user1 = {
  name: "이정환",
  role: Role.ADMIN, //관리자
};

const user2 = {
  name: "홍길동",
  role: Role.USER, // 회원
};

const user3 = {
  name: "아무개",
  role: Role.GUEST, // 게스트
};

✴︎ 문자열 enum

enum Role1 {
  ADMIN,
  USER,
  GUEST,
}

enum Language {
  korean = "ko",
  english = "en",
}

const user4 = {
  name: "이정환",
  role: Role.ADMIN, // 0
  language: Language.korean,// "ko"
};

enum은 컴파일될 때 다른 타입들 처럼 사라지지 않고 자바스크립트 객체로 변환됩니다.
따라서 우리가 위에서 했던 것 처럼 으로 사용할 수 있는 것 입니다.

📁 컴파일 후 js 파일

var Role;
(function (Role) {
    Role[Role["ADMIN"] = 0] = "ADMIN";
    Role[Role["USER"] = 1] = "USER";
   Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
    Language["korean"] = "ko";
    Language["english"] = "en";
    Language["japanese"] = "jp";
})(Language || (Language = {}));
const user1 = {

6️⃣ Any 와 Unknown 타입

▪️ any

• 모든 타입을 허용하는 타입입니다.
• 어떤 값이든 any 변수에 대입할 수 있고, any 변수를 다른 타입 변수에 대입하는 것도 허용됩니다.
• 즉, 타입 검사를 우회합니다.
let anyVar: any = 10;

let num1: number = 10;
num1 = anyVar; // ✅ 허용됨

✴︎ any의 문제점

• 타입 안정성이 깨집니다.
• anyVar는 string, boolean, object, function 등 무엇이든 대입 가능
anyVar = "hello";
anyVar = true;
anyVar = {};
anyVar = () => {};

따라서 개발자가 의도하지 않은 타입이 들어올 수 있습니다.

✴︎ 런타임 에러 가능성

• any 타입에서는 에디터(컴파일 타임)에서 오류가 발생하지 않지만, 런타임 시점에 실제 타입과 맞지 않으면 에러가 발생합니다.
anyVar.toUpperCase(); // 런타임: anyVar가 string이 아닐 경우 에러
anyVar.toFixed();     // 런타임: anyVar가 number가 아닐 경우 에러
anyVar.a;             // 런타임: undefined이거나 없는 속성 접근 시 에러

→ 컴파일러가 잡아주지 않기 때문에 런타임에서야 문제가 드러남

✴︎ 결론

  • any는 타입 시스템의 장점을 무력화시키므로 사용을 최소화해야 합니다.
  • any는 모든 타입 검사를 건너뛰기 때문에, 코드 작성은 편리하지만 런타임 오류를 초래할 수 있어 사용을 지양해야 한다.

▪️ unknown

  • any와 비슷하게 모든 타입의 값을 대입할 수 있는 상위 타입
  • 하지만 any와 달리 다른 타입 변수에 바로 대입불가
    → 즉, 타입 안전성을 보장
let num: number = 10;


let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};

num = unknownVar; // ❌ 
// 타입이 확인되지 않았으므로 대입 불가

✴︎ 타입 정제(Type Narrowing) 필요

• unknown 값을 사용하려면 타입을 먼저 확
• TypeScript는 typeof, instanceof, 사용자 정의 타입 가드 등을 통해 타입을 정제할 수 있습니다.
if (typeof unknownVar === "number") {
	// 이 조건이 참이된다면 unknownVar는 number 타입으로 볼 수 있음
  unknownVar * 2;
}

✴︎ any와의 차이

any: 모든 타입을 대입·할당 가능, 타입 검사 자체를 생략 → 런타임 오류 위험 ↑
unknown: 모든 타입을 대입 가능하지만, 사용 시 반드시 타입 검증 필요 → 타입 안전성 ↑
  • 어떤 변수가 어떤 값이 들어올지 확실하지 않은 경우에는 any보다 unknown을 사용하는 것이 안전합니다.
  • unknown은 “타입은 모르지만, 안전하게 다루겠다”라는 의미를 가집니다.

7️⃣ void 타입과 never 타입

▪️ void

아무것도 반환하지 않는 함수의 반환 타입

  • 실제로는 undefined만 허용 (strictNullChecks: false이면 null도 허용)
function func1(): void {
  console.log("hello"); // 출력만 하고 반환 없음
}

let a: void;
a = undefined; // ✅
// a = 1;      // ❌
// a = "hi";   // ❌

✴︎ 주의점

• void 반환 함수에서는 return; 또는 return undefined;는 가능하지만, return null;은 오류가 됩니다.
function func2(): void {
  return;            // ✅
  // return undefined; ✅
  // return null;      ❌
}

▪️ never

• 절대 도달할 수 없는 타입(불가능한 타입)을 의미합니다.
• 즉, 아무 값도 반환할 수 없는 함수에 사용합니다.

▪️ 대표적인 경우
  1. 무한 루프 → 함수가 끝나지 않음
  2. 에러 발생 → 함수가 정상적으로 종료되지 않음

✴︎ 대표 경우 예시

function func3(): never {
  while (true) {} // 무한 루프 → 반환 불가능
}

function func4(): never {
  throw new Error("Error!"); // 예외 던짐 → 정상적으로 반환 불가
}

✴︎ 변수에 never 타입을 지정하면?

• 어떤 값도 담을 수 없습니다. (any, null, undefined 포함 불가)
• 즉, 비어 있는 공집합 타입입니다.
let n: never;
// n = 1;         ❌
// n = null;      ❌
// n = undefined; ❌
// n = anyVar;    ❌

▪️ void 와 never 차이

구분voidnever
의미값이 없음값이 존재할 수 없음(불가능)
함수 반환반환 값이 없는 함수 (console.log)끝나지 않거나, 항상 에러를 던지는 함수
변수 저장undefined만 저장 가능 (null은 옵션)어떤 값도 저장 불가
예시function log(): void {}function fail(): never { throw new Error(); }
  • void : 반환 값이 “없음” (실제로는 undefined)
  • never : 반환 자체가 “불가능” (무한 루프, 에러 등)

⛓️‍💥 추가 문제

1. TypeScript에서 객체의 타입을 단순히 object로 지정하는 것만으로는 불충분한 주된 이유는 무엇일까요?

A. object 타입은 원시 타입만 저장한다

B. 특정 속성 접근 시 타입 안전성이 어렵다

C. object 타입은 컴파일 후 제거된다

D. 선택적 속성 등을 정의할 수 없다

해설

object 타입은 객체라는 것만 알 수 있어 속성 이름이나 타입 정보가 없어요. . 으로 특정 속성에 접근하면 오류가 납니다.



2. TypeScript에서 any 타입과 unknown 타입 값이 다르게 다루어지는 가장 중요한 차이점은 무엇일까요?

A. any만 모든 타입 값을 저장한다

B. unknown은 타입 가드 없이 속성 접근이 자유롭다

C. any는 바로 할당되지만 unknown은 확인이 필요하다

D. unknown은 컴파일 후 자바스크립트에 남는다

해설

any는 타입 검사를 비활성화하지만, unknown은 더 안전해서 다른 타입에 할당하거나 사용하려면 명시적인 타입 확인 과정이 필요해요.

0개의 댓글