In typescript jargon, what you're trying to do is "declaration merging" combined with "module augmentation" (see docs). There are a few problems with your sample so far, most of which is fixable, one of which is not (i.e. you've hit a limitation of the language).
I'm going to first explain these problems and (partial) solutions, and then suggest an alternative approach that will work completely.
- When you want to augment a class definition through module augmentation, your augmentation must be in the form of an un-exported interface, not an exported class definition. So instead of
export class Object3D
you must write interface Object3D
. (Not the most intuitive thing, I know - but the docs state "Not all merges are allowed in TypeScript. Currently, classes can not merge with other classes or with variables.")
- You must match the signature of the class you are trying to augment exactly, including generic parameters and
extends
statements. So instead of...
interface Object3D {
...you'd need to write...
interface Object3D<E extends BaseEvent> extends EventDispatcher<E> {
(hat tip to this previous answer for pointing out this poorly-documented "gotcha")
- The particular way that
@types/three
is written, it's not just one big file with "export class Object3D..." type statements at the top-level. Instead, the root file of @types/three
has this statement:
export * from './src/Three';
In turn, src/Three
had (among other things) this statement:
export * from './core/Object3D';
And the src/core/Object3D
finally has the core class definition:
export class Object3D<E extends BaseEvent = Event> extends EventDispatcher<E> {
I've found through experimentation (although I can't find a clear explanation as to why) that these intermediate export *
statements get in the way of your module augmentation code actually matching up with the thing it's trying to affect (e.g. the Object3D
class definition in @types/three/src/core/Object3D
). To get around this, your declare module
statement must target that file specifically, not the re-exported version of it.
So putting all this together, this will (almost) work:
import { BaseEvent, EventDispatcher } from "three";
declare module "three/src/core/Object3D" {
interface Object3D<E extends BaseEvent> extends EventDispatcher<E> {
// This works fine...
somethingElse: string;
// However, this does not (see below)
// Typescript will throw this error:
// Subsequent property declarations must have the same type. Property 'userData' must be of type '{ [key: string]: any; }'
userData: MyType1 | MyType2;
}
}
This final problem is a limitation of the language - declaration merging cannot change the type of an existing property on the original class definition (even if the new type is technically a sub-type of the original one). It can only add new properties to the class.
An Alternative Approach That Does Work
Instead of using declaration merging, you can make a new class declaration that extends the original, and override the type there:
declare module "three" {
import { BaseEvent } from "three/src/core/EventDispatcher";
import { Object3D as Object3DOriginal } from "three/src/core/Object3D";
export * from "three/src/Three";
export class Object3D<E extends BaseEvent = Event> extends Object3DOriginal<
E
> {
userData: MyType1 | MyType2;
}
}
Check out this codesandbox for a working example.
getObjectByName
still returns the original Object3D type, not the overridden type. See codesandbox.io/s/… – Ballon