在 TypeScript 中指定 Threejs Object3D.userData 的类型

Ged*_*sFX 7 extend user-data three.js typescript .d.ts

在 Three.js 中,类上有一个Object3D名为 userData 的字段,它在类型声明文件中node_modules/three/src/core/Object3D.d.ts定义为

/**
 * Base class for scene graph objects
 */
export class Object3D extends EventDispatcher {

[...]

    /**
     * An object that can be used to store custom data about the Object3d. It should not hold references to functions as these will not be cloned.
     * @default {}
     */
    userData: { [key: string]: any };

[...]

}
Run Code Online (Sandbox Code Playgroud)

我想对 userData 进行更强的类型化,因此我创建了一个模块类型声明src/typings/three.d.ts

declare module 'three' {
  export class Object3D {
    userData: MyType1 | MyType2;
  }
}

type MyType1 = {
  type: 'type1';
  radius: number;
};
type MyType2 = {
  type: 'type2';
  name: string;
};
Run Code Online (Sandbox Code Playgroud)

虽然它确实覆盖了该userData属性,但它不是合并,而是覆盖了three模块中的所有类型声明,使得更改的破坏性大于有用性(请注意缺少其他属性)。 vscode 中缺少类型声明

有没有办法以 userData覆盖而不覆盖整个模块的方式合并类型声明?

And*_*ier 4

用打字稿术语来说,您要做的是“声明合并”与“模块增强”相结合(请参阅文档)。到目前为止,您的示例存在一些问题,其中大多数是可以修复的,其中之一则不可修复(即您遇到了语言的限制)。

我将首先解释这些问题和(部分)解决方案,然后提出一种完全可行的替代方法。

  1. 当您想通过模块扩充来扩充类定义时,扩充必须采用未导出接口的形式,而不是导出的类定义。所以export class Object3D你必须写成interface Object3D. (我知道,这不是最直观的事情 - 但文档指出“TypeScript 中并非允许所有合并。目前,类不能与其他类或变量合并。”)
  2. 您必须与您尝试增强的类的签名完全匹配,包括通用参数和extends语句。所以而不是...
    interface Object3D {
    
    Run Code Online (Sandbox Code Playgroud) ……你需要写……
    interface Object3D<E extends BaseEvent> extends EventDispatcher<E> {
    
    Run Code Online (Sandbox Code Playgroud)(向之前的答案 致敬,指出了这个记录不足的“陷阱”)
  3. 根据特定的编写方式@types/three,它不仅仅是一个顶层带有“export class Object3D...”类型语句的大文件。相反,根文件中@types/three有这样的语句:
    export * from './src/Three';
    
    Run Code Online (Sandbox Code Playgroud) 反过来,src/Three(除其他外)有这样的声明:
    export * from './core/Object3D';
    
    Run Code Online (Sandbox Code Playgroud) 最后src/core/Object3D有核心类定义:
    export class Object3D<E extends BaseEvent = Event> extends EventDispatcher<E> {
    
    Run Code Online (Sandbox Code Playgroud) 我通过实验发现(尽管我找不到明确的解释),这些中间export *语句妨碍了模块增强代码实际上与它试图影响的事物相匹配(例如Object3D中的类定义@types/three/src/core/Object3D) 。为了解决这个问题,您的declare module语句必须专门针对该文件,而不是它的重新导出版本。

所以把所有这些放在一起,这(几乎)会起作用:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

最后一个问题是语言的限制 - 声明合并无法更改原始类定义上现有属性的类型(即使新类型在技术上是原始类型的子类型)。它只能向类添加新属性。

一种可行的替代方法

您可以创建一个扩展原始类的新类声明,并覆盖其中的类型,而不是使用声明合并:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

查看此codesandbox 以获取工作示例