[JavaScript] 객체 유효성 검사로 Proxy 이해하기
객체 유효성 검사로 Proxy 이해하기
JavaScript로 개발할 때 객체 데이터의 무결성을 지키는 것은 중요하다. 예를 들어 사용자 정보 객체에서 나이는 양수여야 하고, 이름은 빈 문자열이 아니어야 하는 규칙이 있을 수 있다. 이런 유효성 검사 로직을 깔끔하게 구현하는 방법이 필요하다.
이 글에서는 JavaScript의 Proxy
객체를 사용하여 객체 유효성 검사를 구현하는 방법을 소개하고자 한다. Proxy
를 이용하면 객체 접근을 중간에서 제어하여 유효성 검사 로직을 더 체계적이고 우아하게 작성할 수 있다.
1. JavaScript Proxy란 무엇인가?
Proxy
객체는 다른 객체(대상 target)를 감싸는 래퍼(wrapper)이다. 이 래퍼를 통해 대상 객체에 대한 기본적인 동작(속성 접근, 할당, 함수 호출 등)을 가로채고(intercept) 개발자가 정의한 방식으로 동작을 재정의할 수 있다. 즉, 객체와 상호작용하는 모든 과정을 중간에서 대리인(proxy)이 먼저 확인하고 처리하도록 만드는 것이다.
2. Proxy와 유효성 검사: set
핸들러 활용
객체의 속성에 특정 값을 할당하는 시점에 그 값이 유효한지 검사하고 싶을 때, Proxy
의 set
핸들러(트랩)가 매우 유용하다. set
핸들러는 객체의 속성에 값을 할당하려고 시도할 때 자동으로 호출되는 함수이다.
set
핸들러는 다음 네 개의 인자를 받는다.
target
: 원본 대상 객체이다.property
: 값을 할당하려는 속성의 이름이다.value
: 할당하려는 새로운 값이다.receiver
: 최초 작업이 수행된 객체 (대개 프록시 객체 자신)이다.
set
핸들러는 반드시 불리언 값을 반환해야 한다. true
를 반환하면 할당이 성공적으로 처리되었음을 나타낸다. false
를 반환하면 할당이 실패했음을 나타내며, 엄격 모드(strict mode)에서는 TypeError
가 발생한다. 또는, 유효성 검사에 실패했을 때 직접 에러를 발생시킬 수도 있다.
3. 예제: 사용자 정보 유효성 검사 (switch 사용)
이제 사용자 정보 객체(name
, age
)에 대해 Proxy
를 이용한 유효성 검사를 구현한다. name
은 비어 있지 않은 문자열이어야 하고, age
는 0 이상 150 미만의 정수여야 한다는 규칙을 switch
문을 사용하여 적용해 본다.
// 1. 유효성 검사 핸들러 정의이다.
const userValidator = {
set(target, property, value) {
// console.log는 설명을 위해 유지한다. 실제 코드에서는 필요에 따라 제거하거나 로깅 라이브러리를 사용한다.
console.log(`[Proxy] 속성 '${property}'에 값 '${value}' 할당 시도.`);
// 속성 이름(property)에 따라 유효성 검사를 분기한다.
switch (property) {
case 'name':
if (typeof value !== 'string' || value.trim() === '') {
// 유효하지 않으면 에러를 발생시킨다.
throw new Error('유효성 오류: 이름(name)은 비어 있지 않은 문자열이어야 한다.');
}
console.log(`[Proxy] 이름(name) 유효성 검사 통과.`);
break; // switch 문을 탈출한다.
case 'age':
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value >= 150) {
// 유효하지 않으면 에러를 발생시킨다.
throw new Error('유효성 오류: 나이(age)는 0 이상 150 미만의 정수여야 한다.');
}
console.log(`[Proxy] 나이(age) 유효성 검사 통과.`);
break; // switch 문을 탈출한다.
default:
// 'name', 'age' 외 다른 속성은 별도 검사 없이 통과시킨다.
console.log(`[Proxy] '${property}' 속성은 별도 유효성 검사 없다.`);
break;
}
// 모든 유효성 검사를 통과했거나 해당 없으면 실제 값 할당을 한다.
// Reflect.set을 사용하는 것이 권장된다.
console.log(`[Proxy] '${property}' 속성에 값 할당 완료.`);
return Reflect.set(target, property, value); // 성공 시 true를 반환한다.
}
};
// 2. 원본 데이터 객체이다.
const user = {
name: '김개발',
age: 28
};
// 3. Proxy 객체 생성이다.
const validatedUser = new Proxy(user, userValidator);
// 4. Proxy를 통한 사용 테스트이다.
console.log("--- 유효한 할당 테스트 ---");
try {
validatedUser.name = '이코드'; // 성공이다.
validatedUser.age = 35; // 성공이다.
validatedUser.email = 'lee.code@example.com'; // 'email'은 검사 규칙 없으므로 성공이다.
console.log("현재 validatedUser:", validatedUser);
console.log("현재 원본 user:", user); // 원본 객체도 변경된다.
} catch (e) {
console.error(e.message);
}
console.log("\n--- 유효하지 않은 할당 테스트 ---");
try {
console.log("\n테스트: 빈 이름 할당 시도.");
validatedUser.name = ' '; // 공백 문자열 -> 에러 발생이다.
} catch (e) {
console.error(e.message);
}
try {
console.log("\n테스트: 음수 나이 할당 시도.");
validatedUser.age = -10; // 음수 -> 에러 발생이다.
} catch (e) {
console.error(e.message);
}
try {
console.log("\n테스트: 너무 큰 나이 할당 시도.");
validatedUser.age = 150; // 150 이상 -> 에러 발생이다.
} catch (e) {
console.error(e.message);
}
console.log("\n--- 에러 발생 후 객체 상태 확인 ---");
// 에러가 발생한 할당은 적용되지 않는다.
console.log("최종 validatedUser:", validatedUser);
4. 코드 해설
userValidator
객체: 이것은Proxy
의 핸들러 객체이다. 핵심 로직은set
메서드 안에 있다.set
메서드: 속성 할당 시도가 있을 때마다 호출된다.switch (property)
: 할당하려는 속성 이름(property
)에 따라 검사 로직을 분기한다.case 'name'
,case 'age'
: 각 속성에 맞는 유효성 검사를 수행한다. 조건에 맞지 않으면throw new Error()
를 통해 즉시 에러를 발생시키고 실행을 중단한다.default
:name
이나age
가 아닌 다른 속성이 들어오면 별다른 검사 없이 통과시킨다.Reflect.set(target, property, value)
: 모든 검사를 통과하면Reflect.set
을 사용하여 원본 객체(target
)에 안전하게 값을 할당한다. 이 메서드는 할당 성공 여부를 불리언 값으로 반환하며,Proxy
의set
핸들러에서 이 반환 값을 그대로 사용하는 것이 좋다.
user
객체: 유효성 검사의 대상이 되는 평범한 JavaScript 객체이다.validatedUser
:new Proxy(user, userValidator)
를 통해 생성된 프록시 객체이다. 앞으로validatedUser
를 통해user
객체에 접근하면userValidator
의 규칙이 적용된다.- 테스트 코드:
validatedUser
에 유효한 값과 유효하지 않은 값을 할당해보며 프록시가 잘 작동하는지, 에러 처리가 올바른지 확인한다. 유효하지 않은 값 할당 시try...catch
를 사용하여 에러 메시지를 출력한다.
5. 결론
일반적인 데이터 구조 및 타입 검증에는 스키마 라이브러리(Zod 등)가 더 보편적으로 사용된다. Proxy
는 런타임 중 객체와의 상호작용 시점에 유효성 규칙을 동적으로 적용해야 하는 특정 요구사항이 있을 때 강력한 도구가 된다. Proxy
를 사용하면 객체의 속성 할당과 같은 기본적인 동작에 개입하여 유효성 검사 로직을 객체 자체의 코드와 분리하여 더 깔끔하고 중앙 집중적으로 관리할 수 있다. 이는 코드의 유지보수성을 높이고 데이터의 무결성을 보장하는 데 큰 도움이 된다.
Proxy
에는 set
외에도 get
(속성 읽기), has
(in 연산자), deleteProperty
(속성 삭제) 등 다양한 동작을 가로챌 수 있는 핸들러들이 있다. 필요에 따라 다른 핸들러들도 활용하여 더욱 강력한 객체 제어를 구현해 보기 바란다.