본문 바로가기
개발 공부 일지/Library

[Three.js] userData 대신 클래스 상속하기

by yelimu 2026. 1. 30.

 

- 구현할 기능

클릭 이벤트를 통해📍Pin mesh 를 화면에 추가하고, 이를 클릭했을때는 Ray caster로 intersect된 node 중 pin mesh를 찾아서 selected pin 설정하기

 

- 초기 구현 방식

userData.pinId를 추가해 핀 여부를 판단

intersect 결과에서 userData 기반으로 selected pin 결정

 

- 문제점

userData를 추가한 사람 외에는 어떤 속성값이, 어떤 목적으로 들어가있는지 알 수 없음 : 협업 시 추적성과 가독성 저하

객체의 역할이 코드에 드러나지 않음 

타입 시스템으로 추적하기 어려움

 

- 설계 변경 

PinMesh extends THREE.Mesh {
    id : string;
    
    constructor(id:string){
        super();
        this.id = id;
	}
}

 

// findIntersectNode
const intersects = raycaster.intersectObjects(scene.children, true);

for (const hit of intersects) {
  let obj: THREE.Object3D | null = hit.object;

  while (obj) {
    if (obj instanceof PinMesh) {
      // 선택된 핀
      selectPin(obj.pinId);
      break;
    }
    obj = obj.parent;
  }
}

 

- 효과 

intersect 반환하는 객체 중에서 pinMesh 클래스의 인스턴스로 확인 가능하므로 가독성과 유지보수성이 향상된다. 

객체가 pinMesh인지 여부를 속성 값 검사가 아니라 타입으로 판별할 수 있다. 

도메인 개념이 클래스 자체로 표현되어 객체 정체성이 명확해진다.

핀 관련 책임(id)이 객체 내부에 캡슐화된다.

userData에 의존하지 않아 암묵적인 규칙이나 매직 키를 줄일 수 있다. 


그렇다면 userData는 언제 사용하면 좋을까? 왜 Three.js에서는 userData를 제공할까?

  • 엔진이 알 필요 없는 정보를 객체에 붙이기 위해서입니다.
    three.js는 렌더링 엔진이고, 앱의 도메인 의미까지 책임지지 않습니다.
  • 빠른 프로토타이핑과 실험을 위해서입니다.
    간단한 플래그, 임시 상태, 디버깅용 메타데이터를 붙이기 좋습니다.
  • 외부 데이터 매핑을 위해서입니다.
    glTF 로딩 시 모델에 포함된 커스텀 정보, 서버에서 내려온 ID 등을 연결할 수 있습니다.
  • 직렬화 / 로더 친화성 때문입니다.
    userData는 glTF export/import 과정에서도 보존되도록 설계되어 있습니다.

userData는 아래와 같은 특성이 있다. 

 

  • 어떤 키가 있는지 타입으로 드러나지 않는다
  • 누가 언제 어떤 값을 넣었는지 추적하기 어렵다
  • 문자열 키 기반이라 오타·충돌에 취약하다
  • 객체의 “정체성”이 코드가 아니라 관례에 의존하게 된다

임의 데이터 저장소라고 생각하면 되겠다. 

 

 

An object that can be used to store custom data about the 3D object. It should not hold references to functions as these will not be cloned.

심지어 공식문서에 따르면, userData는 cloned되지 않는다고 한다. 도메인 개념인 mesh의 정보를 저장하는 용도로는 좋지 않은 선택이었던 것이다.