Demystifying Exports in TypeScript: Default vs. Named Approaches
Here's a breakdown to understand them better:
Use cases:
- Default export:
- You have a single function or class that's the core functionality of the module.
- You want to give the imported value a custom name in the other file.
- Named exports:
- You need to export multiple functions, classes, or variables from the same module.
- You want to import specific functionalities with clear names.
Example (using basic functions):
defaultExport.ts:
export default function greet(name: string) {
return `Hello, ${name}!`;
}
namedExport.ts:
export function greet(name: string) {
return `Hello, ${name}!`;
}
export function sayGoodbye(name: string) {
return `Goodbye, ${name}!`;
}
Importing and using (import.ts):
// Default import (can use any name)
import welcomeMessage from './defaultExport.ts';
console.log(welcomeMessage('Alice')); // Output: Hello, Alice!
// Named imports (use specific names)
import { greet, sayGoodbye } from './namedExport.ts';
console.log(greet('Bob')); // Output: Hello, Bob!
console.log(sayGoodbye('Charlie')); // Output: Goodbye, Charlie!
Choosing between them:
- Use default export for single core functionalities.
- Use named exports for multiple functionalities or better code organization.
Additional points:
- TypeScript builds on top of JavaScript (ECMAScript-6 adds these export features).
- There are other export options (e.g.,
export * from ...
), but these are less common.
// Function with type annotations for clarity
export default function greet(name: string): string {
return `Hello, ${name}!`;
}
// Function with type annotations for clarity
export function greet(name: string): string {
return `Hello, ${name}!`;
}
// Another function with type annotations
export function sayGoodbye(name: string): string {
return `Goodbye, ${name}!`;
}
import.ts:
// Default import with a descriptive name
import welcomeMessage from './defaultExport.ts';
console.log(welcomeMessage('Alice')); // Output: Hello, Alice!
// Named imports using destructuring for cleaner syntax
import { greet, sayGoodbye } from './namedExport.ts';
console.log(greet('Bob')); // Output: Hello, Bob!
console.log(sayGoodbye('Charlie')); // Output: Goodbye, Charlie!
These improvements:
- Add type annotations to function parameters and return values for better readability and type safety in TypeScript.
- Use a more descriptive name (
welcomeMessage
) for the default import inimport.ts
. - Use destructuring syntax (
import { greet, sayGoodbye } from ...
) inimport.ts
for a more concise way to import named exports.
export *
:
This syntax exports all entities (functions, classes, variables) declared in a module at once. It's generally discouraged for several reasons:
- Namespace pollution: It clutters the importing module's namespace, making it harder to avoid naming conflicts with other imports.
- Less maintainability: It becomes difficult to track which specific functionalities are being used from the exported module.
- Barrel files:
These are empty files that act as a central point to re-export functionalities from other modules within a directory. This can be useful for:
- Organizing multiple exports: Create a central file (e.g.,
index.ts
) that re-exports specific named exports from other files in the directory. This keeps the main module file cleaner and simplifies importing everything from that directory.
Here's an example of a barrel file:
index.ts (barrel file):
export * from './file1';
export * from './file2'; // Re-exports from other files
import * as utils from './index'; // Import everything from the directory
console.log(utils.functionFromfile1()); // Access functions from re-exported modules
Remember:
- Use
export *
sparingly due to potential namespace pollution. - Barrel files can improve organization but can become cumbersome for very large directories.
- Default and named exports are generally preferred for clarity and maintainability.
javascript typescript ecmascript-6