שגיאת זמן ריצה - הבדל בין ייבוא מודולים ב-JavaScript וב-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);
}

כפי שאתה יכול לראות בתחתית המודול, אני מגדיר את האלמנט המותאם אישית test-game באמצעות מחלקת 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);
}

אבל הפעם קונסול הדפדפן נותן לי את השגיאה הבאה...

שגיאת TypeError לא נתפסה: oGame.setState אינה פונקציה ב-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 עושה משהו שונה עם ה-exports של המודול

ערוך

בסדר, אז נראה שזה משהו מוזר שנגרם על ידי מהדר 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);
}

תשובה #1

ב-TypeScript, כאשר אתה מייבא משהו אך משתמש בו רק כסוג, TypeScript משאירה את הייבוא מחוץ לקוד כאשר היא מקמפל את הקוד שלך. במקרה הזה, העובדה שהייבוא נעלם משמעותה שהאלמנטים לא נרשמים, ולכן השיטות חסרות כאשר אתה מנסה לשאול אלמנט מותאם אישית. הסיבה לכך שבדיקת סוג הערכים עבדה כאן היא שהמחלקות המיובאות אכן משמשות כערכים עבור instanceof, ולכן הייבוא נשאר.

הנה בעיה בנושא הזה, עם כמה פתרונות בתוכה גם כן. עבור תופעות לוואי שצריכות להתרחש, import './engine/game.js' בראש קובץ ה-JS הראשי שלך יוודא שזה תמיד ירוץ וירשום את האלמנט.