Interfaces
serve as a valuable tool to outline the requirements that implementers must adhere to. Here is an example of an interface
based on your specified criteria:
interface HelloPrinter {
printHello(name: string): string
}
When you provide this interface to someone for implementation, they can create a class that conforms to it. If they attempt to write:
class HelloPrinterImpl implements HelloPrinter {
}
TypeScript will rightly flag an error:
Property 'printHello' is missing in type 'HelloPrinterImpl' but required in type 'HelloPrinter'.
13 class HelloPrinterImpl implements HelloPrinter {}
At this stage, they can proceed to create their implementation:
class HelloPrinterImpl implements HelloPrinter {
printHello(name: string): string {
const hello = "Hello, I am ";
return hello.concat(name);
}
}
If desired, these interfaces can be stored in a separate file, such as an interfaces.ts
file.
One potential use case is in crafting APIs where developers must adhere to your specifications. By defining functions that expect these interfaces as parameters, implementers can supply any implementations that fulfill your requirements. Here's how it could work:
function bodyOfWork(printer: HelloPrinter) {
// Execute some operations here
printer.printHello("someone who completes tasks effectively.")
}
bodyOfWork(new HelloPrinterImpl()) // => Hello, I am someone who gets work done.
This is just one approach. You can also achieve similar outcomes using types. For instance, by creating a function type that takes a string argument and returns a string, then requiring it as a parameter, you restrict implementers to passing functions that align with this specification:
type helloPrinter = (name: string) => string
function bodyOfWorkWithTypes(print: helloPrinter) {
// Perform some work here
print("someone who gets work done.")
}
bodyOfWorkWithTypes(message => "Hello! I am ".concat(message))
// => Hello! I am someone who gets work done.
You can export this type and place it in a separate file:
export type helloPrinter = (name: string) => string
There are various ways to enforce adherence to requirements in TypeScript, showcasing its flexibility. We hope this information proves to be beneficial.
Sidenote: Employing .d.ts
In TypeScript versions 2.x and above, utilizing .d.ts
files is unnecessary. These files are designed for JavaScript libraries needing to expose TypeScript bindings. The previous practice of using /// reference
to load .d.ts
files where type definitions were unavailable is no longer required.
However, when strictFunctionTypes
is enabled, the following code snippet does not compile:
def.d.ts:1:18 - error TS2394: This overload signature is not compatible with its implementation signature.
1 declare function printhello(name: string): string;
To prevent complications, it's advisable to avoid this pattern altogether. Deleting the .d.ts
file would produce the appropriate error for your scenario:
index.ts:1:36 - error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
1 function printhello(name: string): string {
~~~~~~
Subsequently, the function can be corrected with suitable types:
function printHello(name: string): string {
const hello = "Hello, I am ";
return hello.concat(name);
}
Given TypeScript's adeptness at inferring function return types, you can further streamline the function:
function printHello(name: string) {
const hello = "Hello, I am ";
return hello.concat(name);
}
For scenarios necessitating exported types, one can enable this setting at the compiler level:
{
"compilerOptions": {
"strictFunctionTypes": true,
"declaration": true
}
}
This configuration will generate .d.ts
files automatically.