ค้นหาเว็บไซต์

ไทป์สคริปต์ - การประกาศเนมสเปซ


ในเอกสาร TypeScript, namespaces ถูกเรียกว่าเป็น internal TypeScript modules และในการใช้งานเป็นการอ้างอิงภายในไฟล์แต่ละไฟล์ เราต้องเพิ่ม ref header ไปยัง namespace เช่น: /// <reference path="Validation.ts" /> ตามที่ระบุในเอกสาร อย่างไรก็ตาม เรายังสามารถ export และประกาศ namespace ได้ด้วย: export declare namespace MyNameSpace แล้วจึง import ไปยัง entity อื่น ๆ มีเหตุผลใดบ้างที่เราได้รับคำแนะนำให้ใช้ header ref tag แทนการ export ของการประกาศ namespace

หลังจากได้ยินคำตอบแรกเกี่ยวกับลำดับของการคอมไพล์และรันไทม์ ฉันคิดว่าบางทีอาจจะเป็นการดีกว่าถ้าให้ตัวอย่าง นี่คืแอปทักทายง่ายๆ ที่ประกอบด้วยไฟล์หลักสามไฟล์ที่มีการประกาศเนมสเปซ (ซึ่งฉันสงสัยว่าเราจำเป็นต้องนำเข้ามันด้วยซ้ำเมื่อเราต้องการใช้ในขอบเขตแอปพลิเคชันของเรา)

ชื่อไฟล์: GreetingApp.ts

export declare namespace GreetingApp {}

ชื่อไฟล์: Hello.ts

namespace GreetingApp {
    export class Hello {
        constructor(private name: string) {
            this.name = name.toLowerCase().charAt(0).toUpperCase() + name.substr(1).toLowerCase();
        }

        public toString(): string {
            return "Hello! " + this.name;
        }
    }
}

ชื่อไฟล์: Bye.ts

namespace GreetingApp {
    export class Bye {
        constructor(private name: string) {
            this.name = name.toLowerCase().charAt(0).toUpperCase() + name.substr(1).toLowerCase();
        }

        public toString(): string {
            return "Goodbye! " + this.name;
        }
    }
}

ชื่อไฟล์: UseCase.ts (ไฟล์ทดสอบเพื่อ import และสร้างวัตถุ)

import GreetingApp from './GreetingApp';

console.log(new GreetingApp.Hello('adam').toString());
console.log(new GreetingApp.Bye('adam').toString());

คำตอบ #1

ถ้าคุณไม่ใช้การส่งออก/นำเข้า ไฟล์ทั้งหมดของคุณจะทำงานในสโคประดับโลก และถ้าไฟล์หลายไฟล์ประกาศเนมสเปซที่มีชื่อเดียวกัน (ตามตัวอย่างที่คุณลิงก์มา) เนมสเปซจะถูกรวมเข้าด้วยกัน วิธีการนี้สามารถสะดวกมาก; มันถูกใช้ในซอร์สโค้ดของคอมไพเลอร์ TypeScript เอง รวมถึงในหนึ่งในโปรเจ็กต์ของฉันด้วย (ทีม TypeScript อ้างว่าโมดูลภายนอกดีกว่าเนมสเปซ แต่ฉันจะเชื่อพวกเขาเมื่อพวกเขาย้ายคอมไพเลอร์จากเนมสเปซไปสู่โมดูลภายนอก!) โปรดทราบว่า /// <reference path="..."> มีสองวัตถุประสงค์: โหลดการประกาศจากไฟล์เป้าหมายในเวลาคอมไพล์และทำให้แน่ใจว่าไฟล์เป้าหมายถูกโหลดก่อนไฟล์ปัจจุบันในเวลารันไทม์ คุณจะสามารถละเว้น /// <reference path="..."> ได้ถ้าคุณตรงตามทั้งสองเงื่อนไขต่อไปนี้: คุณใช้ไฟล์ tsconfig.json เพื่อให้แน่ใจว่าทุกไฟล์ของคุณถูกโหลดในเวลาคอมไพล์ และลำดับการโหลดในเวลา runtime ไม่สำคัญ หนึ่งในเหตุผลที่พบบ่อยที่สุดที่ทำให้ลำดับการโหลดในเวลา runtime สำคัญคือถ้าคุณนิยามคลาสหนึ่งและจากนั้นขยายมันในไฟล์ที่ต่างออกไป

ถ้าคุณส่งออกและนำเข้าชื่อเนมสเปซ นั่นหมายความว่าคุณกำลังใช้โมดูลภายนอก และถ้าคุณประกาศชื่อเนมสเปซสองชื่อเดียวกันในโมดูลภายนอกที่ต่างกัน พวกมันจะไม่ถูกรวมกัน โมดูลภายนอกหนึ่งสามารถมีการขยายโมดูลที่เพิ่ม การประกาศ ให้กับเนมสเปซในโมดูลภายนอกอีกหนึ่งโมดูลได้ แต่ไม่สามารถเพิ่มโค้ดในเวลารันได้ ดังนั้นนี่จึงไม่สะดวกเหมือนกับการรวมเนมสเปซในไฟล์ที่รันในสโคปแบบทั่วโลก

รอบ 2

ใน UseCase.ts ผมกำลังสันนิษฐานว่าคุณตั้งใจที่จะ import { GreetingApp } from './GreetingApp' เพราะ import GreetingApp from './GreetingApp' ทำให้ผมพบข้อผิดพลาดในการคอมไพล์ (ถ้าเป็นเช่นนั้น กรุณาอัปเดตคำถามด้วย)

ว้าว คุณได้ค้นพบแง่มุมที่บ้าของการระบุชื่อใน TypeScript ที่ผมไม่เคยรู้มาก่อน ในตัวอย่างนี้ Hello.ts และ Bye.ts เป็นไฟล์ระดับโลกและไม่ใช่โมดูลภายนอก (เพราะไม่มีการ import หรือ export ในระดับบนของ ES6) ดังนั้นทั้งสองไฟล์จึงมีส่วนทำงานร่วมกับ namespace GreetingApp ในสโคประดับโลก ซึ่งไม่ได้เกี่ยวข้องกับ namespace GreetingApp ที่ถูก export จาก GreetingApp.ts แต่อย่างใด

แล้วเกิดอะไรขึ้นใน UseCase.ts ? ตามที่ได้กล่าวไว้ที่นี่ สัญลักษณ์ของ TypeScript สามารถมีความหมายหนึ่งหรือมากกว่าต่อไปนี้: ค่าตัวแปร (รวมถึง namespace ของค่าตัวแปร), ชนิดข้อมูล, และ "namespace" (หมายถึง namespace ของชนิดข้อมูล) namespace GreetingApp ใน GreetingApp.ts มีความหมายเพียง namespace เพราะมันไม่มีค่าตัวแปรใด ๆ ในขณะที่ namespace GreetingApp ระดับ global มีทั้งความหมายของค่าตัวแปรและความหมายของ namespace เพราะมันมีคลาส ซึ่งประกอบด้วยทั้งค่าตัวแปร (ฟังก์ชันตัวสร้าง) และชนิดข้อมูล (ชนิดของอินสแตนซ์)

เมื่อมีการอ้างอิงสัญลักษณ์ บริบทของการอ้างอิงจะกำหนดว่าสามความหมายใดถูกใช้: หากคุณใช้ X.Y เป็นค่า จะใช้ความหมายค่าของ X และนำคุณสมบัติ Y ของมันไป ในขณะที่หากคุณใช้ X.Y เป็นประเภท จะใช้ความหมายของ namespace ของ X และความหมายประเภทของ Y ภายใน X นอกจากนี้ การซ้อนของสัญลักษณ์ทำงานแยกตามความหมาย! เมื่อ UseCase.ts นำเข้า GreetingApp จาก ./GreetingApp จะนำเข้าความหมายเดียวเท่านั้นที่ GreetingApp ใน GreetingApp. ts มีความหมายของเนมสเปซ ซึ่งเป็นการบังความหมายของเนมสเปซของ GreetingApp ในระดับโลก ค่าความหมายของ GreetingApp ในระดับโลกยังคงมองเห็นได้และถูกใช้โดยการอ้างอิงถึง GreetingApp.Hello และ GreetingApp.Bye อย่างไรก็ตามคุณจะสังเกตได้ว่าถ้าคุณพยายามประกาศตัวแปรประเภท GreetingApp.Hello มันจะไม่ทำงาน เพราะการอ้างอิงนั้นใช้ความหมายของเนมสเปซของ GreetingApp ซึ่งชี้ไปยังเนมสเปซว่างจาก GreetingApp.ts และไม่ใช่เนมสเปซระดับโลก

ฉันรู้ว่าการวิเคราะห์นี้ค่อนข้างเทคนิค แต่ฉันหวังว่าฉันจะทำให้ชัดเจนแล้วว่าตัวอย่างนี้ไม่ได้ทำงานในแบบที่คุณคาดคิด (และฉันหวังว่าคนถัดไปที่สับสนกับพฤติกรรมการซ่อนเอกสารที่ไม่ค่อยมีการอธิบายของ TypeScript จะเจอกระทู้นี้ในผลการค้นหาเว็บ) การนำเข้าและไฟล์ GreetingApp.ts ทั้งหมดไม่มีประโยชน์ใดๆ ในตัวอย่างนี้ หากคุณลบทั้งสองอย่างออกไป คุณจะได้วิธีการ "ไฟล์ทั่วโลกที่รวม namespaces" สำหรับการจัดโครงสร้างโค้ดของคุณตามที่ฉันอธิบายในคำตอบต้นฉบับ