TypeScript 기본 타입: 객체와 함수까지 strict로 익히기

2026.09.05·수정 2026.09.16·약 16분·작성: 해비·블로그 소개

이 글에서 완성할 것

터미널에서 TypeScript 파일을 검사하고 실행하면서 기본 타입, 배열, 객체, 함수, 선택 속성, readonly를 한 프로그램에 연결합니다. 컴파일 오류와 런타임 결과가 서로 다른 단계라는 점도 직접 확인합니다.

실습 경로: 현재 ZIP전체 코드. 먼저 설명에서 지정한 파일만 읽고, 설정·테스트 파일은 필요한 때 확인하세요.

먼저 읽기: JavaScript 함수와 객체, import export로 모듈 나누기 · TypeScript란 무엇인가: JavaScript와 차이 정리

목표·선수 지식: JavaScript 함수·배열·객체를 알고 있고, 앞선 TypeScript 입문 글에서 Node.js·npm 확인, TypeScript 로컬 설치, 첫 .ts 파일의 컴파일·실행까지 완료했다는 전제로 진행합니다. 본문의 짧은 문법 블록은 서로 독립된 부분 예제입니다. 같은 파일에 모두 합치면 중복 선언이 생길 수 있습니다.

첫 글의 TypeScript 환경에서 strict 실습 준비하기

이 글에서는 프로젝트 생성과 TypeScript 설치를 다시 반복하지 않습니다. 아직 환경을 만들지 않았다면 먼저 TypeScript란 무엇인가: JavaScript와 차이 정리에서 Node.js·npm 확인부터 TypeScript 설치, 첫 파일 실행까지 완료해 주세요. 이미 준비했다면 현재 프로젝트 터미널에서 아래 명령으로 사용할 TypeScript 버전만 확인하면 됩니다.

npx tsc --version

TypeScript는 JavaScript에 타입 검사를 더하지만 브라우저나 Node.js가 타입 표기를 그대로 실행하는 것은 아닙니다. 먼저 tsc가 소스의 타입을 검사하고 JavaScript로 변환하며, 그다음 Node.js가 변환된 JavaScript를 실행합니다. 이 글의 검증 환경은 TypeScript 7.0.2와 Node.js이며, 프로젝트마다 버전이 달라질 수 있으므로 위 명령으로 실제 버전을 확인해 주세요.

tsconfig.json에서 strict: true, target: ES2022, module: CommonJS, outDir: dist를 사용합니다. 정상 파일은 npx tsc로 컴파일한 뒤 node dist/basics.js로 실행합니다. 타입 검사를 통과했다는 사실은 프로그램의 업무 규칙까지 옳다는 보장은 아니지만, 값의 모양이 어긋나는 실수를 실행 전에 잡는 강한 첫 관문이 됩니다. tsconfig.json 전체 코드

설정의 역할도 구분해 두면 오류를 덜 헤맵니다. strict는 느슨한 추측을 줄여 잠재적인 undefined 접근과 잘못된 함수 호출을 더 일찍 알립니다. target은 생성할 자바스크립트 문법의 기준이며, module은 파일 간 코드를 묶는 방식을 고릅니다. outDir은 원본과 결과물을 나누는 폴더입니다. 처음에는 설정을 많이 추가하기보다 네 항목으로 검사와 실행의 흐름을 먼저 익히는 편이 좋습니다.

기본 타입과 배열로 값의 범위를 적기

string, number, boolean은 자주 쓰는 원시 타입입니다. 대문자 String처럼 박싱 객체 타입을 쓰지 않고 소문자 타입을 사용합니다. 값에서 타입을 충분히 추론할 수 있을 때는 매번 표기하지 않아도 되지만, 학습 단계에서는 변수의 의도를 읽기 위해 명시적으로 적어 보는 것이 좋습니다. 배열은 같은 원소 타입을 반복해서 담으며 number[] 또는 Array<number>로 표현합니다.

type CourseState = "draft" | "open"은 여러 타입 표현에 이름을 붙이는 type alias입니다. 여기서는 허용할 문자열을 두 값으로 제한하며, 정상 예제의 courseState가 이 별칭을 실제로 사용합니다. interface가 객체 모양을 설명하는 데 알맞다면 type alias는 원시 타입, union, 객체 등 다양한 타입 표현에 이름을 붙일 수 있습니다.

예제의 scores는 숫자 배열이므로 문자열을 넣으면 컴파일 오류입니다. 이 제약은 실행 중 배열을 감시하는 장치가 아닙니다. 타입 표기는 컴파일 뒤 사라집니다. 외부 JSON처럼 런타임에 들어오는 값은 다음 단계에서 별도 검증이 필요합니다.

타입 추론과 타입 표시는 경쟁하는 방식이 아닙니다. 지역 변수의 오른쪽 값이 분명하면 추론에 맡겨 소음을 줄일 수 있고, 함수의 매개변수나 공유 객체처럼 경계가 되는 곳에는 타입을 적어 의도를 드러낼 수 있습니다. 예를 들어 숫자 세 개로 시작한 배열은 숫자 배열로 추론됩니다. 반면 비어 있는 배열이나 여러 파일이 함께 사용하는 데이터는 타입을 명시하면 나중에 코드를 읽는 사람이 허용된 값을 더 빠르게 이해합니다.

나무 블록과 모양에 맞는 구멍으로 표현한 타입 검사
나무 블록과 모양에 맞는 구멍으로 표현한 타입 검사입니다. 실제 동작은 예제 코드에서 확인합니다.

interface로 객체 모양과 변경 규칙 정하기

interface Student는 학생 객체가 가져야 할 속성의 이름과 타입을 선언합니다. name: string은 필수이고 nickname?: string은 없어도 되는 선택 속성입니다. 선택 속성을 바로 문자열처럼 쓰면 undefined일 수 있으므로, 예제는 null 병합 연산자 ??로 닉네임이 없을 때 이름을 사용합니다.

readonly id는 객체를 만든 뒤 해당 속성을 다시 대입하지 못하게 합니다. 다만 깊은 불변성을 뜻하지 않습니다. 또한 scores: number[] 자체는 수정 가능한 배열입니다. 함수에서 점수 배열을 읽기만 한다면 매개변수를 readonly number[]로 받아 함수 안의 push 같은 변경을 막을 수 있습니다. 이렇게 실제 의도를 좁혀 쓰면 호출하는 쪽의 수정 가능한 배열도 안전하게 읽기 용도로 전달할 수 있습니다.

선택 속성은 단순히 입력을 편하게 만드는 표기가 아닙니다. 값이 없는 상태도 모델의 일부라고 선언하는 것입니다. 닉네임이 필수가 아니라면 모든 학생 데이터에 빈 문자열을 억지로 넣기보다 속성을 생략하고 사용하는 지점에서 대체값을 정하는 편이 의미가 분명합니다. 반대로 반드시 있어야 할 식별자에 물음표를 붙이면 이후 코드마다 불필요한 확인이 늘어납니다. 실제 데이터 규칙에 맞춰 필수와 선택을 나누는 일이 객체 타입 설계의 중심입니다.

함수 매개변수와 반환 타입을 계약처럼 읽기

함수 타입은 입력과 출력의 관계를 보여 줍니다. average(scores: readonly number[]): number는 숫자 배열을 읽고 숫자 하나를 돌려준다는 뜻입니다. introduce(student: Student): string은 학생 객체를 받아 문장을 반환합니다. 반환 타입을 적으면 함수 구현이 실수로 다른 종류의 값을 돌려줄 때 그 자리에서 오류를 확인할 수 있습니다.

전체 basics.ts는 아래 완성 코드에서 확인하고 복사하세요. basics.ts 전체 코드

예상 출력

TypeScript 기초: 4개, 상태=open
민준님의 평균은 90점입니다.

객체 리터럴에는 필수 속성을 모두 넣었고 선택 속성인 nickname은 생략했습니다. 따라서 표시 이름은 name으로 좁혀집니다. 평균 함수는 전달받은 배열을 바꾸지 않고 계산만 합니다. 작은 예제지만 값 선언, 객체 모양, 함수 입력과 출력의 경계가 한 흐름으로 이어집니다.

함수 호출을 볼 때는 먼저 인수가 매개변수 타입에 맞는지 확인하고, 다음으로 반환값이 사용되는 자리의 타입에 맞는지 확인해 보세요. 이 두 방향을 습관처럼 읽으면 긴 오류 메시지도 단순해집니다. 배열을 문자열 자리에 전달했는지, 문자열 반환 함수를 숫자 계산에 썼는지처럼 경계 하나씩 좁혀 볼 수 있습니다. 편집기의 빨간 밑줄만 고치기보다 터미널의 전체 오류를 읽으면 자동화된 빌드에서 무엇이 실패하는지도 함께 이해할 수 있습니다.

빈 종이를 살피는 돋보기와 나무 문
빈 종이를 살피는 돋보기와 나무 문입니다. 실제 동작은 예제 코드에서 확인합니다.

컴파일 오류와 런타임 결과를 분리해 확인하기

아래 파일은 학습을 위한 의도적 오류이며 정상 예제와 분리합니다. 첫 오류는 readonly 속성을 다시 대입했고, 둘째 오류는 숫자 배열에 문자열을 넣었습니다. 오류 전용 설정으로 단일 파일만 검사하려고 npx tsc -p tsconfig.error.json를 실행합니다. 오류의 파일명, 줄과 열, 오류 코드, 기대 타입과 실제 타입을 차례로 읽으면 수정 지점을 찾기 쉽습니다. tsconfig.error.json 전체 코드

전체 compile-error.ts는 아래 완성 코드에서 확인하고 복사하세요. compile-error.ts 전체 코드

예상 컴파일 오류: TS2540은 읽기 전용 id에 대입할 수 없다고 알리고, TS2345는 string 인수를 number 매개변수에 전달할 수 없다고 알립니다. 이는 의도한 실패입니다. 반대로 정상 파일이 컴파일된 뒤의 출력은 런타임 결과입니다. 타입 오류가 없더라도 0으로 나누기나 잘못된 외부 데이터처럼 실행 중 문제가 생길 수 있으므로 두 검증을 같은 것으로 여기지 않아야 합니다.

오류를 고칠 때 타입을 강제로 덮어쓰는 표기부터 찾지 마세요. 식별자는 생성 뒤 바꾸지 않는다는 모델이 맞다면 재대입 코드를 삭제하고, 새 학생이 필요하다면 새 객체를 만듭니다. 점수는 숫자여야 하므로 문자열을 숫자로 바꾸되, 외부 입력이라면 변환 결과가 유효한지도 검사해야 합니다. 컴파일러를 조용하게 만드는 것보다 데이터 규칙과 구현을 일치시키는 것이 수정의 목표입니다. 정상 예제와 실패 예제를 파일로 나누면 학습용 오류 때문에 실행 흐름 전체가 막히지 않고 두 결과를 독립적으로 반복할 수 있습니다.

직접 확인하는 경계와 재현 방법

빈 점수 배열의 평균은 이 실습에서 정의하지 않습니다. 원래 0 / 0으로 NaN이 나오던 경로를 명시적 오류로 바꾸었습니다. number 타입에는 NaN도 포함되므로 타입 검사만으로 수학적 유효성까지 보장되지 않습니다. 닉네임이 빈 문자열이면 ??가 이름으로 바꾸지 않는 것도 별도로 확인합니다.

아래 파일들은 한 프로젝트의 전체 코드입니다. 프로젝트 루트 기준 경로로 저장합니다. 시작·완성 내용이 같은 탭을 중복하지 않고 완성형으로 제공합니다.

현재 실습 ZIP

현재 본문과 같은 실습 파일 내려받기

basics.ts

const courseName: string = "TypeScript 기초";
const lessonCount: number = 4;
type CourseState = "draft" | "open";
const courseState: CourseState = "open";

interface Student {
  readonly id: number;
  name: string;
  nickname?: string;
  scores: number[];
}

function average(scores: readonly number[]): number {
  if (scores.length === 0) throw new Error("점수가 하나 이상 필요합니다.");
  return scores.reduce((sum, score) => sum + score, 0) / scores.length;
}

function introduce(student: Student): string {
  const displayName = student.nickname ?? student.name;
  return `${displayName}님의 평균은 ${average(student.scores)}점입니다.`;
}

const student: Student = {
  id: 1,
  name: "민준",
  scores: [80, 90, 100],
};

console.log(`${courseName}: ${lessonCount}개, 상태=${courseState}`);
console.log(introduce(student));

if (average([80, 90, 100]) !== 90) throw new Error("평균");
if (
  !introduce({ id: 2, name: "이름", nickname: "", scores: [100] }).startsWith(
    "님의",
  )
)
  throw new Error("빈 닉네임");
let rejected = false;
try {
  average([]);
} catch {
  rejected = true;
}
if (!rejected) throw new Error("빈 배열을 거절해야 합니다.");
console.log("PASS average and empty input checks");

compile-error.ts

interface Student {
  readonly id: number;
  name: string;
  scores: number[];
}

const student: Student = { id: 1, name: "민준", scores: [80, 90] };
student.id = 2;
student.scores.push("100");

tsconfig.json

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "CommonJS",
    "outDir": "dist",
    "noEmitOnError": true,
    "types": []
  },
  "files": ["basics.ts"]
}

tsconfig.error.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true
  },
  "files": ["compile-error.ts"]
}

package.json

{
  "private": true,
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "start": "node dist/basics.js",
    "check:errors": "tsc -p tsconfig.error.json"
  },
  "devDependencies": {
    "typescript": "7.0.2"
  }
}
npm install
npm run build
npm start
npm run check:errors

실제 확인 결과:

TypeScript 기초: 4개, 상태=open
민준님의 평균은 90점입니다.
PASS average and empty input checks

npm run check:errors는 의도적으로 실패해야 합니다. 기대 오류는 TS2540·TS2345이며 정상 빌드 실패와 구분합니다.

검증 환경은 Node.js v24.19.0, TypeScript 7.0.2입니다. 설치되어 있던 의존성으로 타입 검사와 위 실행을 확인했습니다. 새 환경의 npm install, 실제 서버 응답, 브라우저 클릭·모바일 화면은 이 검증에 포함하지 않습니다.

공식 출처 확인일: 2026-09-12. everyday-types.html · objects.html

위 완성형 뷰어와 같은 코드이며 README와 검증 기록을 포함합니다.

완료 기준과 다음 연습

  • 앞선 입문 글에서 준비한 TypeScript 환경을 그대로 사용하고 현재 버전을 확인했습니다.
  • 원시 타입, 배열, 객체의 필수·선택 속성을 직접 선언했습니다.
  • readonly의 얕은 변경 제한과 readonly 배열 매개변수를 구분했습니다.
  • 함수의 매개변수와 반환 타입을 읽고 예상 출력을 확인했습니다.
  • 의도적 오류 파일에서 TS2540과 TS2345를 확인하고 런타임 출력과 구분했습니다.

마지막으로 학생 한 명을 더 만들고 nickname을 넣어 출력이 바뀌는지 확인해 보세요. 이어 점수 배열에 문자열을 넣었다가 컴파일러 메시지를 읽고 숫자로 고치면 됩니다. 각 변경 뒤에는 먼저 컴파일 성공 여부를 보고, 성공했다면 생성된 자바스크립트를 실행해 출력까지 비교하세요. 이 짧은 반복은 타입 단계와 실행 단계를 자연스럽게 분리하는 연습입니다. 오류가 난 줄만 보지 말고 어떤 값이 어느 경계를 넘었는지 말로 설명해 보면 같은 종류의 실수를 다른 코드에서도 찾기 쉬워집니다. 이 과정을 마치면 타입 문법을 외우는 데서 끝나지 않고, 작은 경계를 선언하고 오류를 읽어 수정하는 TypeScript의 기본 작업 흐름을 갖추게 됩니다.

이 글이 도움이 되었나요?

조회 중

TypeScript 학습 순서

필수 11개 · 전체 11개

읽음 기록 관리

전체 과정 목차 (11개)
  1. 필수 학습 · TypeScript란 무엇인가: JavaScript와 차이 정리
  2. 필수 학습 · TypeScript 기본 타입: 객체와 함수까지 strict로 익히기 현재 글
  3. 필수 학습 · TypeScript 타입 좁히기: union null unknown 안전 검증
  4. 필수 길잡이 · TypeScript 실무 로드맵
  5. 필수 학습 · TypeScript as const 사용법: union 타입을 자동 생성하는 기준
  6. 필수 학습 · React TypeScript 기본 구조: tsx 파일과 props 타입 기준
  7. 필수 학습 · React TypeScript optional props 기본값 처리: undefined·null 차이
  8. 필수 학습 · TypeScript children 타입: ReactNode와 PropsWithChildren 차이
  9. 필수 학습 · TypeScript 제네릭 쉽게 이해하기: props와 API 응답 타입으로 감 잡기
  10. 필수 학습 · TypeScript 고급 상태 모델링: 판별 유니온과 never 타입 테스트
  11. 필수 학습 · TypeScript satisfies 사용법: 타입 지정·as const와 비교하기

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기