类型错误:类扩展值未定义的函数或为空。

210

在尝试创建这些实体时,我遇到了以下错误。

TypeError: Class extends value undefined is not a function or null

我猜这与循环依赖有关,但是当使用表继承和一对多关系时,应该如何避免它?

它在 BaseComic_1.BaseComic 的以下javascript代码中报错:

let Variant = class Variant extends BaseComic_1.BaseComic {

这是完整的文件。

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const typeorm_1 = require("typeorm");
const Comic_1 = require("./Comic");
const BaseComic_1 = require("./BaseComic");
let Variant = class Variant extends BaseComic_1.BaseComic {
};
__decorate([
    typeorm_1.ManyToOne(type => Comic_1.Comic, comic => comic.variants),
    __metadata("design:type", Comic_1.Comic)
], Variant.prototype, "comic", void 0);
Variant = __decorate([
    typeorm_1.ClassEntityChild()
], Variant);
exports.Variant = Variant;
//# sourceMappingURL=Variant.js.map

import {Entity, Column, PrimaryGeneratedColumn, OneToMany} from "typeorm";
import {Comic} from "./Comic";

@Entity()
export class Series {

    @PrimaryGeneratedColumn()
    public id: number;

    @Column("text", {
        length: 30
    })
    public copyright: string;

    @Column("text", {
        length: 100
    })
    public attributionText: string;

    @Column("text", {
        length: 150
    })
    public attributionHTML: string;

    @Column("text", {
        length: 50
    })
    public etag: string;

    @Column("text", {
        length: 200
    })
    public title: string;

    @Column("text")
    public description: string;

    @Column("number", {
        length: 4
    })
    public startYear: number;

    @Column("number", {
        length: 4
    })
    public endYear: number;

    @Column("text", {
        length: 20
    })
    public rating: string;

    @Column("text", {
        length: 20
    })
    public type: string;

    @Column("text")
    public thumbnail: string;

    @OneToMany(type => Comic, comic => comic.series)
    public comics: Array<Comic>;
}

import {Entity, TableInheritance, PrimaryGeneratedColumn, Column, ManyToOne, DiscriminatorColumn} from "typeorm";
import {Series} from "./Series";

@Entity()
@TableInheritance("class-table")
@DiscriminatorColumn({ name: "type", type: "string"})
export class BaseComic {

    @PrimaryGeneratedColumn()
    public id: number;

    @Column("text", {
        length: 30
    })
    public copyright: string;

    @Column("text", {
        length: 100
    })
    public attributionText: string;

    @Column("text", {
        length: 150
    })
    public attributionHTML: string;

    @Column("text", {
        length: 50
    })
    public etag: string;

    @Column("text", {
        length: 200
    })
    public title: string;

    @Column("int")
    public issue: number;

    @Column("text")
    public variantDescription: string;

    @Column("boolean")
    public variant: boolean;

    @Column("text")
    public description: string;

    @Column("int")
    public pageCount: number;

    @Column("date")
    public onSaleDate: Date;

    @Column("date")
    public unlimitedDate: Date;

    @Column("text")
    public thumbnail: string;

    @ManyToOne(type => Series, series => series.comics)
    public series: Series;
}

import {OneToMany, ClassEntityChild} from "typeorm";
import {Variant} from "./Variant";
import {BaseComic} from "./BaseComic";

@ClassEntityChild()
export class Comic extends BaseComic {

    @OneToMany(type => Variant, variant => variant.comic)
    public variants: Variant[];
}

import {ManyToOne, ClassEntityChild} from "typeorm";
import {Comic} from "./Comic";
import {BaseComic} from "./BaseComic";

@ClassEntityChild()
export class Variant extends BaseComic {

    @ManyToOne(type => Comic, comic => comic.variants)
    public comic: Comic;
}
22个回答

275

我曾遇到相同的问题。事实证明,我在循环导入类,这显然是一种限制。(请参见这些GitHub问题:#20361#4149#10712

请注意,循环引用似乎也受文件之间的限制,而不仅仅是类型。

请看此回答


3
我在使用browserify时遇到了循环导入的问题。这个问题非常棘手,有时候修改文件、注释掉一些代码再重新运行可以解决,但是并不总是有效。 - user1978019
7
请注意,似乎循环引用是发生在文件之间,而不仅仅是类型之间。因此,即使您的类型引用没有出现循环,根据您的类型所在的.ts文件,仍可能会遇到这个问题。 - barfuin
2
@JoshuaKing,你能否提供一个关于TypeScript中虚拟导入类“限制”的资源链接? - Bruno Bieri
6
根据我的经验,导入的顺序也很重要。我有一个用户实体和另外2个实体,它们拥有相同的抽象类。这另外两个实体也导入了用户实体,只要抽象类是最后一个被导入的(通常在用户实体之后),一切都很好。但是,一旦我改变了导入的顺序,应用程序就会崩溃。 - Tobias Stangl
1
@BrunoBieri 这不是 TypeScript 的限制。这是一个运行时错误 - 模块解析器无法在循环导入中导航。我发现了一个好的资源在 You Don't Know JS 中。 - gombosg
显示剩余4条评论

60

正如Thomas Jensen在上面的评论中所指出的,循环引用不仅可以发生在类型中,还可以发生在文件中。当我从同一个文件中导出基本类型和派生类型时,我也遇到了这个问题。例如:

// index.ts
export { BaseClass } from "./base";
export { DerivedClass } from "./derived";
这是一个容易掉进去的陷阱。在这里发布,希望能为他人节省调试时间。

3
那么,当您有这样的索引文件时,最好的解决方法是什么?您应该把基础文件放在哪里?或者说索引文件有点反模式吗? - Jonathan
1
我不认为index.ts文件是一种反模式,虽然有些人持不同意见。我认为它们是向模块提供有用公共接口的好方法。从记忆中,我最终通过重构来解决了这个问题,根本不需要导出基类。不幸的是,我无法给你比这更好的答案。 - ajxs
30
在关于循环引用的文章中,“pitfall to fall into”这个词组有一种奇妙的诗意... :) - Nathan Beach
1
@NathanBeach,这就是为什么它被称为重言式的原因。 :) - Ndianabasi
1
这是我的问题,我必须将这两个类放在不同的文件中。 - Shonubi Korede
显示剩余3条评论

52

循环依赖关系可能很难识别。Michael Weststrate提出了一种模式来解决它们,这是一篇有趣的阅读

自动检测循环依赖关系。

除了使用可扩展性的模式外,您还可以使用一个非常有用的工具来帮助您轻松地识别循环依赖关系,那就是Madge

Madge可以运行在.ts.js文件上。我发现在两个目录中都运行它很有用,因为它们可能由于编译过程而产生不同的结果。

对于Typescript .ts文件:

madge --circular --extensions ts <directory_path>

对于 JavaScript 的 .js 文件:

madge --circular <directory_path>

谢谢你分享这个。奇怪的是,我得到了“未发现循环依赖项!”的提示,但问题仍然存在。虽然它没有解决我的问题,但如果不是因为这个,我可能会花费很长时间寻找循环依赖项。 - K-Dawg
5
我想为 dpdm 代替 Madge 提供赞美之词。在我的代码中,Madge 找到了一个微不足道的循环依赖,并没有帮助我;相反,dpdm 发现了27个实质性的循环路径。推荐使用。 - Josh Hansen
@JoshHansen 如果给定一个目录,dpdm 搜索是否递归进行?我非常确定 madge 并不会递归搜索,从 github 上来看它似乎被放弃了,这两个原因足以选择 dpdm,但后者并不报告它分析了多少文件,所以很难判断它是否忽略了任何子目录。 - theberzi

9

我来这里是因为在使用jest执行代码时,它抛出了这个错误。这是因为在为jest.config.js编写moduleNameMapper时,对象中元素的顺序是至关重要的。

有一个帮助程序可以从ts-config.json导入模块名称:

// jest.config.js
const { pathsToModuleNameMapper } = require('ts-jest/utils');
// In the following statement, replace `./tsconfig` with the path to your `tsconfig` file
// which contains the path mapping (ie the `compilerOptions.paths` option):
const { compilerOptions } = require('./tsconfig');

module.exports = {
  // [...]
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths /*, { prefix: '<rootDir>/' } */ )
};

本文摘自 ts-jest 官方文档


是的,当我没有正确地在 Jest 中模拟东西时,我看到了这个错误 - 具体来说,当通过 __mocks__ 模拟整个模块时,需要模拟所有从那里使用的符号(或使用 requireActual 等取消模拟)。 - Reed Sandberg

5

我最近遇到了一个问题,真的很奇怪。我使用以下命令执行项目:

node --require ts-node/register path/to/index.ts

即使我按照被采纳的答案建议删掉了循环引用,但还是无法成功运行并出现以上错误。

然而,如果我运行 tsc 将其编译后再执行,即使在加上 --require ts-node/register... 参数也可以正常运行。

希望这能对有需要的人有所帮助。


3

对我来说,我想要一个基础实体,它可以为所有子实体执行常见任务,例如使用@BeforeInsert等方法。因此,我的结构是这样的:

entity/
-- index.ts
-- base.entity.ts
-- foo/
-- --foo.entity.ts

index.ts 文件中。
export { default as MyBaseEntity } from './base.entity'; // <-- This was the problem
export { default as FooEntity } from './foo/foo.entity';

base.entity.ts

import {
  BaseEntity, BeforeInsert,
  CreateDateColumn,
  ObjectIdColumn,
  UpdateDateColumn
} from 'typeorm';
import { v4 as uuidv4 } from 'uuid';

export class MyBaseEntity extends BaseEntity {
  @ObjectIdColumn()
  id!: string;

  @CreateDateColumn({name: 'created_at'})
  createdAt: Date;

  @UpdateDateColumn({name: 'updated_at'})
  updatedAt: Date;

  @BeforeInsert()
  beforeInsert() {
    this.id = uuidv4();
  }
}

以及我的foo.entity.ts

import {
  BaseEntity, BeforeInsert,
  CreateDateColumn,
  ObjectIdColumn,
  UpdateDateColumn
} from 'typeorm';
import {Injectable} from '@nestjs/common';
import { MyBaseEntity } from './../base.entity';

@Injectable()
@Entity('foo')
export class FooEntity extends MyBaseEntity {}

循环文件依赖来自于index.ts的导出。 我只需要移除base.entity的导出即可。


1

对于我来说,这不是循环依赖。这是用于Angular Web应用的。

我有一个抽象类:

export abstract class BaseService {
  ...
}

我试图扩展它以实现一个Angular服务。

@Injectable({
  providedIn: 'root'
})
export class MyExtendedService extends BaseService {

但是,在实现这个扩展的时候,我一直收到错误提示 TypeError: Class extends value undefined is not a function or null,并且出现在我的app.module.ts中。

经过多次尝试,我发现我还需要将@Injectable添加到基类中。所以,我更新了我的基类:

@Injectable()
export abstract class BaseService {
  ...
}

然后,一切都正常运行了。

1
我遇到了相同的错误。
原因是我的index.vue单文件组件导入了包含通用逻辑的index.ts文件。
// index.vue

import Impl from './index'

文件结构如下:

/my-component
  index.vue
  index.ts

据我理解,index.vue 在自我引入。因此,我只需要将index.ts 重命名为common.ts即可。

1
在 Next.js 13 中,对我来说,通过将 Node 版本升级到 16 版本解决了该问题。

0

我在使用ES6的基类和继承(extends关键字)时遇到了相同的错误(例如...export class Arrow extends Pointer {})。

在我阅读有关将"export"关键字替换为"module.exports"的内容之前,没有任何建议能帮助我解决我的问题...我检查了我的基类并意识到我忘记在我的类签名之前包括"export"关键字...

class Pointer {}

成为

export class Pointer {}

只要做那一个改变,就消除了我的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接