빠른 설정
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true
}
}| 상황 | 먼저 볼 기준 |
|---|---|
| optional 필드가 많다 | 없음과 undefined를 구분해야 하는지 확인 |
| patch DTO를 설계한다 | 누락과 명시적 초기화 의미를 분리 |
"key" in obj 검사를 쓴다 | undefined 값이 들어온 경우까지 확인 |
| 기존 코드 오류가 많다 | public 타입부터 단계 적용 |
무엇이 바뀌나
property?: T는 "값이 없을 수 있다"는 뜻입니다. exactOptionalPropertyTypes를 켜면 이 의미를 더 엄격하게 적용해서, optional property에 undefined를 명시적으로 대입하는 것을 별도 계약 없이는 허용하지 않습니다.
interface UserSettings {
theme?: "dark" | "light";
}
const settings: UserSettings = {};
settings.theme = "dark";옵션이 켜진 상태에서 settings.theme = undefined를 허용하려면 타입에 undefined를 직접 포함해야 합니다. 즉 "필드가 없음"과 "필드는 있지만 값이 undefined"를 의도적으로 구분하게 됩니다.
interface UserSettings {
theme?: "dark" | "light" | undefined;
}
const settings: UserSettings = {};
settings.theme = undefined;없음과 undefined
JavaScript에서는 필드가 아예 없는 객체와 필드가 있는데 값이 undefined인 객체가 다르게 관찰될 수 있습니다. 예를 들어 "theme" in settings는 키 존재 여부를 봅니다.
const a: { theme?: string } = {};
const b: { theme?: string | undefined } = { theme: undefined };
console.log("theme" in a); // false
console.log("theme" in b); // true이 차이는 patch API, form dirty state, 설정 병합처럼 "보내지 않음"과 "비움"이 다른 의미를 갖는 코드에서 중요합니다.
interface UpdateUserInput {
displayName?: string;
}
// displayName이 없으면 변경하지 않음
// displayName이 있으면 그 값으로 변경적용하면 좋은 곳
서버 API의 update 입력 타입처럼 누락과 명시 값을 구분해야 하는 곳에 적합합니다. 필드를 빼면 유지하고, 값을 보내면 변경하는 계약에서는 optional property의 의미가 정확해야 합니다.
interface PatchProfile {
nickname?: string;
avatarUrl?: string | null;
}설정 객체에서도 유용합니다. 기본값을 상속할지, 사용자가 일부러 비웠는지를 구분해야 한다면 undefined를 임의로 넣는 패턴을 줄이고 delete, null, 명시 union 중 하나를 선택해야 합니다.
const next = { ...current };
delete next.theme;필드가 없다는 상태만 필요하면 property?: T를 사용합니다. 필드가 존재하면서 undefined 값도 의미가 있다면 property?: T | undefined처럼 명시합니다. 비어 있음을 값으로 표현하려면 null이 더 읽기 쉬운 경우도 많습니다.
interface SearchFilter {
keyword?: string;
selectedId?: string | null;
}optional property를 "느슨한 값"으로 쓰기 시작하면 런타임 분기가 흐려집니다. 저장소에 들어가는 데이터, API wire format, form state는 특히 없음과 빈 값을 먼저 정의하는 편이 안전합니다.
주의할 점
exactOptionalPropertyTypes를 켜면 기존 코드에서 obj.key = undefined 패턴이 오류가 될 수 있습니다.
의도가 키 제거라면 delete obj.key, 값 초기화라면 타입에 undefined 또는 null을 명시하십시오.
외부 라이브러리 타입과 맞물릴 때도 차이가 드러날 수 있습니다. 라이브러리가 optional property를 어떤 의미로 설계했는지 확인하고, 로컬 타입에서 무리하게 undefined를 주입하기보다 adapter 경계에서 변환하는 편이 낫습니다.
참고 링크
1 sources