Namespace definition and usage
Definition of Namespaces
Namespaces in TypeScript are a way to organize code to avoid naming conflicts in the global scope. Namespaces encapsulate related code under a specific name, forming an independent block of code. Namespaces are defined using the namespace
keyword:
namespace MyNamespace {
export interface Person {
name: string;
age: number;
}
export function greet(person: Person) {
return `Hello, ${person.name}!`;
}
}
In this example, MyNamespace
contains a Person
interface and a greet
function. Note that the export
keyword is required; otherwise, these members will not be visible outside the namespace.
Namespaces can be nested:
namespace Outer {
export namespace Inner {
export const message = "Hello from inner namespace";
}
}
Using Namespaces
To access members within a namespace, use the fully qualified name:
const person: MyNamespace.Person = {
name: "Alice",
age: 30
};
console.log(MyNamespace.greet(person)); // Output: Hello, Alice!
For nested namespaces:
console.log(Outer.Inner.message); // Output: Hello from inner namespace
You can use import
to create an alias for a namespace to simplify the code:
import NS = MyNamespace;
const person2: NS.Person = { name: "Bob", age: 25 };
Differences Between Namespaces and Modules
Namespaces and modules are both ways to organize code, but they have key differences:
-
Scope:
- Namespaces operate in the global scope.
- Modules have their own scope.
-
Dependencies:
- Namespaces declare dependencies using the
/// <reference>
directive. - Modules use
import/export
syntax.
- Namespaces declare dependencies using the
-
Loading Mechanism:
- Namespaces are typically used in client-side code and loaded via
<script>
tags. - Modules use module loaders (e.g., CommonJS, AMD, etc.).
- Namespaces are typically used in client-side code and loaded via
Example of a namespace file structure:
project/
├── app.ts
├── utilities.ts
utilities.ts:
namespace Utilities {
export function formatDate(date: Date) {
return date.toISOString();
}
}
app.ts:
/// <reference path="utilities.ts" />
const today = new Date();
console.log(Utilities.formatDate(today));
Merging Namespaces
TypeScript allows merging namespaces with the same name, which is useful for extending third-party libraries:
namespace MyLib {
export function func1() { /*...*/ }
}
// Later in another file
namespace MyLib {
export function func2() { /*...*/ }
}
// Now MyLib contains both func1 and func2
Interfaces can also be merged with namespaces:
interface Person {
name: string;
}
namespace Person {
export function create(name: string): Person {
return { name };
}
}
const p = Person.create("Charlie");
The Role of Namespaces in Modern TypeScript
With the widespread adoption of ES6 modules, the use of namespaces has declined, but they still hold value in certain scenarios:
- Global Library Declarations: Adding type definitions for third-party libraries.
- Legacy Code Migration: Gradually migrating old code to a module system.
- Complex Type Organization: Organizing type definitions in large projects.
Example of a global library declaration:
declare namespace Chart {
interface Options {
responsive: boolean;
}
function create(config: Options): void;
}
Combining Namespaces and Modules
Namespaces can also be used within module files to organize internal structures:
// shapes.ts
export namespace Shapes {
export class Circle { /*...*/ }
export class Square { /*...*/ }
}
// Usage
import { Shapes } from "./shapes";
const circle = new Shapes.Circle();
This pattern is useful for grouping related classes, but a purely modular approach is often preferred:
// More modular alternative
export class Circle { /*...*/ }
export class Square { /*...*/ }
// Usage
import { Circle, Square } from "./shapes";
Compilation Output of Namespaces
Understanding how namespaces are compiled into JavaScript helps clarify their workings. The MyNamespace
example above compiles to:
var MyNamespace;
(function (MyNamespace) {
function greet(person) {
return "Hello, " + person.name + "!";
}
MyNamespace.greet = greet;
})(MyNamespace || (MyNamespace = {}));
This Immediately Invoked Function Expression (IIFE) pattern creates a closure, ensuring variables within the namespace do not pollute the global scope.
Best Practices for Namespaces
- Avoid Overuse: Prefer ES6 modules in modular projects.
- Meaningful Naming: Use descriptive, unlikely-to-conflict names.
- Moderate Nesting: Avoid excessive nesting levels.
- Combine with Modules: Use namespaces within modules to organize complex logic.
- Consistency: Stick to either namespaces or modules within a project to avoid confusion.
Example of organizing types in a large project:
namespace Data {
export interface User {
id: number;
name: string;
}
export namespace API {
export interface Response {
success: boolean;
data: any;
}
}
}
// Usage
function handleResponse(res: Data.API.Response) {
if (res.success) {
const user: Data.User = res.data;
// ...
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn