JavaScript 和 Typescript 中模块导入的区别?

Difference between module imports in JavaScript and Typescript?

我正在尝试 Shadow DOM / 自定义元素,我认为我发现了 JS 和 TS 处理模块化导入的方式之间的一个怪癖,或者我做错了什么?

我的主要 JS 文件如下所示...

// import classes (and register custom elements)
import { Game } from './engine/game.js';
import { MainMenuState } from './menu/main-menu-state.js';

// get the game custom element from the DOM and create the main menu state
const oGame = document.querySelector('test-game');
const oState = document.createElement('main-menu-state');

// push the state onto the game
oGame.setState(oState);

模块看起来像这样...

export class Game extends HTMLElement {

    constructor() {
        super();
        this._shadowRoot = this.attachShadow({
            mode: 'open'
        });
    }

    setState(oState) { ... }

    pushState(oState) { ... }

    popState() { ... }

}

if(!customElements.get('test-game')) {
    customElements.define('test-game', Game);
}

正如您在模块底部看到的,我正在使用游戏 class 定义自定义 test-game 元素。 main-menu-state 元素以类似的方式定义。

这工作正常,代码按照我的预期执行

一旦我将这段代码转换成 Typescript,我 运行 就成了一个问题。

主 TS 文件如下所示...

import { StateInterface } from './engine/state.interface.js';

import { Game } from './engine/game.js';
import { MainMenuState } from './menu/main-menu-state.js';

const oGame: Game = document.querySelector('test-game') as Game;
const oState: StateInterface = document.createElement('main-menu-state') as MainMenuState;

oGame.setState(oState);

TS 模块看起来很眼熟...

export class Game extends HTMLElement implements GameInterface {

    public constructor() {
        super();

        this._shadowRoot = this.attachShadow({
            mode: 'open'
        });
    }

    public setState(oState: StateInterface): void { ... }

    public pushState(oState: StateInterface): void { ... }

    public popState(): void { ... }

}

if(!customElements.get('test-game')) {
    customElements.define('test-game', Game);
}

但是这次浏览器控制台给我以下错误...

Uncaught TypeError: oGame.setState is not a function at main.ts:9

我的 tsconfig 文件设置为使用 ES6 模块解析...

{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true,
        "alwaysStrict": true,
        "noUnusedLocals": true,
        "outDir": "./public/js",
        "rootDir": "./public/ts"
    }
}

我只是不明白为什么这两段代码之间会有差异,除非 TS 编译器对模块导出做一些不同的事情


编辑

好的,看来这是由 TS 编译器引起的奇怪问题。 简单地类型检查对象似乎理顺它...

import { StateInterface } from './engine/state.interface.js';

import { Game } from './engine/game.js';
import { MainMenuState } from './menu/main-menu-state.js';

const oGame: Game = document.querySelector('test-game') as Game;
const oState: StateInterface = document.createElement('main-menu-state') as MainMenuState;

if(oGame instanceof Game && oState instanceof MainMenuState) {
    oGame.setState(oState);
}

在 TypeScript 中,当您导入某些内容但仅将其用作类型时,TS 在编译您的代码时会忽略导入。在这种情况下,导入消失意味着元素未注册,因此当您尝试查询自定义元素时,这些方法会丢失。在这里对值进行类型检查的原因是因为导入的 类 实际上被用作 instanceof 的值,因此导入保留。

Here's an issue about it, 也有一些解决方案。对于需要触发的副作用,入口 JS 文件顶部的 import './engine/game.js' 将确保它始终运行并注册元素。