10단원 '모듈 · 선언 파일 · 설정'의 다섯 레슨에서 여덟 문제를 내고, 앞 단원에서 두 문제를 섞었습니다. 끝의 만들어 보기 둘은 직접 코드를 채우고 짜 보는 문제입니다. 이 단원을 처음 본다면 진단 문항인 문제 1 · 2 · 4 · 6 · 7부터 풀어 보세요. 막히는 문항에 적힌 레슨만 골라 읽어도 됩니다.
문제 1
(진단 · 모듈과 타입 내보내기) menu.ts에 export interface Menu만 들어 있을 때, main.ts에서 type 없이 import { Menu }로 가져오면 검사와 실행 때 각각 어떻게 될까요?
- ① 타입 검사와 Node 실행 모두 문제없이 통과한다
- ② 타입 검사 때는
TS1484로 막히고, Node로 실행하면menu.ts에서Menu가 지워져SyntaxError로 멈춘다 - ③ 타입 검사 때는 통과하고, 실행 때 빈 객체
{}가 넘어온다
힌트
menu.ts의 interface Menu가 타입 지우기 단계 뒤에 남는지 생각해 보세요.
정답
정답: ②
interface Menu는 타입 지우기 단계에서 사라지므로 실행 시점의 menu.ts에는 Menu가 없습니다(타입이 실행 때 남는다는 생각). 따라서 import type { Menu }로 가져와야 하며, type을 빼면 검사기는 TS1484로 막고 Node는 SyntaxError로 멈춥니다.
다시 볼 레슨: 모듈과 타입 내보내기
문제 2
(진단 · 선언 파일과 @types) 과목 기본 tsconfig.json("types": [])에서 다음 코드를 타입 검사하면 어떤 결과가 나올까요?
console.log(process.version);
- ① Node 내장 기능이므로 검사 오류
0으로 통과한다 - ②
process의 선언을 찾지 못해TS2591오류가 난다 (@types/node설치와"types": ["node"]설정 필요) - ③ 문법 오류(
TS1294)가 난다
힌트
"types": [] 설정일 때 검사기가 외부 선언 패키지를 자동으로 읽는지 떠올려 보세요.
정답
정답: ②
main.ts(1,13): error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.TypeScript 7.0과 과목 tsconfig.json은 "types": []가 기본이므로, @types/node를 설치하고 "types": ["node"]를 적어야 Node 전용 전역 이름인 process의 타입을 읽습니다.
다시 볼 레슨: 선언 파일과 @types
문제 3
(선언 파일과 @types) 다음 코드를 타입 검사할 때와 Node로 실행할 때의 결과로 맞는 것은?
declare const bonus: number;
console.log(bonus + 100);
- ① 타입 검사는 통과하지만, 실행 때는
bonus값이 실제로 없어ReferenceError로 멈춘다 - ② 타입 검사와 실행 모두 통과하여
100이 출력된다 - ③ 1번 줄에서 타입 검사 오류가 난다
힌트
declare 줄이 타입 지우기 뒤에 무엇으로 바뀌는지 생각해 보세요.
정답
정답: ①
main.ts:2console.log(bonus + 100); ^ ReferenceError: bonus is not defined at main.ts:2:13declare는 검사기에게 "실행 환경에 이 값이 있다"고 믿게만 할 뿐(검사 규칙 6), 실제 실행 값을 만들어 주지 않습니다(타입이 실행 때 남는다는 생각).
다시 볼 레슨: 선언 파일과 @types
문제 4
(진단 · DOM 요소의 타입) 다음 브라우저 코드를 타입 검사하면 3번 줄에서 왜 막힐까요?
const input = document
.querySelector('#qty');
console.log(input.value);
- ①
input이null일 수 있다는 오류(TS18047) 하나만 난다 - ②
input이null일 수 있고(TS18047),Element에는value속성이 없다(TS2339)는 두 오류가 함께 난다 - ③ 오류가 나지 않는다
힌트
document.querySelector가 돌려주는 타입(Element | null)의 두 갈래를 살펴보세요.
정답
정답: ②
main.ts(3,13): error TS18047: 'input' is possibly 'null'.main.ts(3,19): error TS2339: Property 'value' does not exist on type 'Element'.document.querySelector의 반환 타입은 Element | null이므로, 요소를 못 찾았을 때의 null 가능성과 일반 Element에 value가 없다는 점 두 가지를 instanceof HTMLInputElement로 함께 좁혀야 합니다.
다시 볼 레슨: DOM 요소의 타입
문제 5
(DOM 요소의 타입) 다음 코드에서 9번 줄이 타입 검사에서 막히는 까닭은 무엇일까요?
const input = document
.querySelector('#qty');
if (
input instanceof
HTMLInputElement
) {
const total =
input.value * 4000;
console.log(total);
}
- ①
input이 여전히null일 수 있기 때문이다 - ②
HTMLInputElement의value타입은 언제나string이라 산술 곱셈(*) 왼쪽 자리에 바로 쓸 수 없기 때문이다 —TS2362 - ③
HTMLInputElement에는value속성이 없기 때문이다
힌트
HTMLInputElement로 좁힌 뒤 input.value의 타입이 number인지 string인지 떠올려 보세요.
정답
정답: ②
main.ts(9,5): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.input.value는 언제나 string이므로 수 계산에 쓰려면 Number(input.value) * 4000으로 변환해야 합니다.
다시 볼 레슨: DOM 요소의 타입
문제 6
(진단 · tsconfig.json 읽기) 과목 tsconfig.json의 옵션과 그 역할이 잘못 짝지어진 것은?
- ①
"noEmit": true—tsc가.js파일을 만들지 않고 타입 검사만 하게 한다 - ②
"erasableSyntaxOnly": true—enum이나 매개변수 속성처럼 타입만 지워서는 실행할 수 없는 문법을TS1294로 막는다 - ③
"verbatimModuleSyntax": true— 모든 파일을 자동으로.js로 변환해 저장한다
힌트
verbatimModuleSyntax가 import type과 어떤 관련이 있는지 떠올려 보세요.
정답
정답: ③
"verbatimModuleSyntax": true는 타입을 가져올 때 반드시 import type을 쓰도록 강제해(TS1484), 지울 줄과 남길 줄을 명확히 구분하게 하는 옵션입니다.
다시 볼 레슨: tsconfig.json 읽기
문제 7
(진단 · strict가 켜는 것들) 과목 기본 설정("strict": true)에서 다음 코드를 검사하고 실행한 결과로 맞는 것은?
function half(n) {
return n / 2;
}
console.log(half(10));
- ① 검사 때는
noImplicitAny때문에TS7006으로 막히지만, Node 실행 결과는5가 찍힌다 - ② 검사 때도 막히고 Node 실행도 멈춘다
- ③
"strict": true라도n / 2를 보고n을number로 추론해 검사를 통과한다
힌트
매개변수 n에 타입 표기가 없을 때 검사기와 Node가 각각 어떻게 동작하는지 비교해 보세요.
정답
정답: ①
main.ts(1,15): error TS7006: Parameter 'n' implicitly has an 'any' type.5"strict": true가 켜는 noImplicitAny는 타입을 안 적은 매개변수 n을 TS7006으로 막습니다. 하지만 옵션은 검사만 바꿀 뿐 Node 실행 결과(5)는 바꾸지 않습니다(타입이 실행 때 남는다는 생각).
다시 볼 레슨: strict가 켜는 것들
문제 8
(strict가 켜는 것들) tsconfig.json에 noUncheckedIndexedAccess를 true로 추가로 켰을 때 달라지는 점은 무엇일까요?
- ① 배열
const a = ["커피", "라테"]에서 꺼낸a[2]의 지금 타입이string에서string | undefined로 바뀌어, 확인 없이a[2].length를 읽으면TS18048로 막힌다 - ② 배열에
push를 부를 수 없게 된다 - ③ Node가 배열 범위를 벗어날 때 실행을 자동으로 멈춘다
힌트
배열 인덱스로 꺼낸 값의 타입에 무엇이 추가되는지 생각해 보세요.
정답
정답: ①
noUncheckedIndexedAccess를 켜면 배열이나 인덱스 시그니처에서 인덱스로 꺼낸 값의 타입에 | undefined가 더해져, undefined 거르기 없이 쓰는 코드를 TS18048로 잡아 줍니다.
다시 볼 레슨: strict가 켜는 것들
문제 9
(복습 · interface를 지키는 클래스) 다음 코드를 타입 검사하면 어떻게 될까요?
interface Priced {
calc(qty: number): number;
}
class Menu implements Priced {
calc(qty) {
return qty * 4500;
}
}
- ①
Priced인터페이스에qty: number가 적혀 있으므로 통과한다 - ② 6번 줄
calc(qty)에서 매개변수 타입을 안 적어TS7006으로 막힌다 - ③
Menu에 생성자가 없어서 막힌다
힌트
implements가 클래스 메서드의 매개변수 타입까지 추론해 주는지 떠올려 보세요.
정답
정답: ②
main.ts(6,8): error TS7006: Parameter 'qty' implicitly has an 'any' type.implements는 클래스가 인터페이스의 모양을 지켰는지 대조만 할 뿐, 메서드 매개변수 타입을 인터페이스에서 가져와 추론해 주지 않습니다(implements가 매개변수 타입을 추론한다는 생각).
다시 볼 레슨: interface를 지키는 클래스
문제 10
(복습 · Promise와 async의 타입) 다음 코드에서 5번 줄이 타입 검사에서 막히는 까닭은 무엇일까요?
let note: string | null = '쿠폰';
if (note !== null) {
setTimeout(() => {
console.log(note.length);
}, 10);
note = null;
}
- ①
let으로 선언한 변수는if조건문 안에서도 처음부터 좁혀지지 않고 선언 타입 그대로 남기 때문이다 - ② 콜백은 언제 불릴지 몰라서, 콜백을 만든 뒤 다시 대입되는
let이름은 콜백 안에서 선언 타입으로 돌아가기 때문이다 - ③ 검사기가
10ms 뒤 실행 시점을 미리 따라가 보고 그때note의 값이 이미null로 바뀐 것을 확인했기 때문이다
힌트
setTimeout에 넘긴 콜백이 언제 실행되는지 생각해 보세요.
힌트 둘
콜백을 만든 4번 줄 뒤(7번 줄)에 note를 다시 대입하는 줄이 있는지 함께 살펴보세요.
정답
정답: ② — 콜백을 만든 뒤 다시 대입되는 let 이름은 콜백 안에서 좁힘이 풀린다
main.ts(5,17): error TS18047: 'note' is possibly 'null'.콜백은 언제 불릴지 몰라서, 콜백을 만든 뒤 7번 줄에서 다시 대입되는 note는 콜백 안에서 좁힘을 풀고 선언 타입 string | null로 봅니다(검사 규칙 5). 7번 줄에 글자를 넣어도 같습니다. const saved = note;에 옮겨 담으면 해결됩니다.
- ①
let변수도if (note !== null)바로 아래에서는string으로 좁혀집니다. 콜백 안에서 풀릴 뿐입니다. - ③ 검사기는 타이머 시간(
10ms)이나 실행 순서를 따라가지 않고, 콜백 뒤에 재대입 줄이 있는지만 봅니다(검사기가 실행을 따라간다는 생각). - 흔한 오답 하나는 3번 줄의 좁힘이 콜백 안까지 그대로 유지된다고 보는 것입니다(좁힘이 끝까지 간다는 생각).
다시 볼 레슨: Promise와 async의 타입
문제 11 (만들어 보기)
(DOM 요소의 타입) #coupon 요소를 찾아 instanceof HTMLInputElement로 좁힌 뒤, 입력 칸의 .value 문자열을 출력하는 브라우저 코드를 검사 오류 0으로 완성해 보세요.
(가) 뼈대 채우기 — ___ 자리에 알맞은 클래스 이름과 속성을 채워 보세요.
// const el = document
// .querySelector('#coupon');
// if (
// el instanceof
// ___
// ) {
// console.log(el.___);
// }
(나) 백지에서 짜기 — #memo 요소를 찾아 HTMLInputElement로 좁힌 뒤 el.value를 출력하는 코드를 작성해 보세요(검사 오류 0).
힌트
<input> 태그에 대응하는 DOM 클래스 이름은 HTMLInputElement입니다.
정답
const el = document
.querySelector('#memo');
if (
el instanceof
HTMLInputElement
) {
console.log(el.value);
}el instanceof HTMLInputElement로 좁히면 null이 걸러지고 el.value가 string으로 정해져 검사 오류 0으로 통과합니다.
다시 볼 레슨: DOM 요소의 타입
문제 12 (만들어 보기)
(strict가 켜는 것들) 문자열 배열 words: string[]과 인덱스 idx: number를 받아, 그 칸의 문자열이 있으면 글자 수(length)를 돌려주고 없으면(undefined) 0을 돌려주는 함수 lenAt을 작성해 보세요(noUncheckedIndexedAccess를 켜도 통과하도록 undefined를 거릅니다).
(가) 뼈대 채우기 — ___ 자리에 알맞은 타입과 조건을 채워 보세요.
// function lenAt(
// words: ___,
// idx: ___,
// ): number {
// const w = words[idx];
// if (w !== ___) {
// return w.length;
// }
// return 0;
// }
(나) 백지에서 짜기 — lenAt(["커피", "녹차"], 1)과 lenAt(["커피", "녹차"], 5)의 결과를 차례로 출력해 보세요(2와 0이 찍혀야 합니다).
힌트
인덱스로 꺼낸 값 w가 undefined가 아닐 때만 w.length를 돌려줍니다.
정답
function lenAt(
words: string[],
idx: number,
): number {
const w = words[idx];
if (w !== undefined) {
return w.length;
}
return 0;
}
const list = ['커피', '녹차'];
console.log(lenAt(list, 1));
console.log(lenAt(list, 5));2
0if (w !== undefined)로 먼저 확인하면 noUncheckedIndexedAccess가 켜진 환경에서도 검사 오류 0이고, 범위를 벗어난 인덱스 5에서도 실행 중 TypeError 없이 안전하게 0을 돌려줍니다.
다시 볼 레슨: strict가 켜는 것들