Is there a way to write a Typescript definition for an ES6 mix-in?
I've this pattern in library.js, and I'd like to create the library.d.ts
// declaration in `library.js`
class Super extends Simple {
constructor() {}
static Compose(Base = Super) {
return class extends Base {
// ...
}
}
}
// usage in `client.js`
class MyClass extends Super.Compose() {}
let myInstance = new MyClass();
class MyOtherClass extends Super.Compose(AnotherClass) {}
No, Typescript type system is not expressive enough for that - see the discussion in https://github.com/Microsoft/TypeScript/issues/7225 and https://github.com/Microsoft/TypeScript/issues/4890.
The idiomatic 'type of classes' in typescript is written as
interface Constructor<T> {
new (...args): T;
}
So one way to write declaration for Compose is
export declare class Simple {}
export declare class Super extends Simple {
static Compose<T>(Base?: Constructor<T>): Constructor<T & {/*mixed-in declarations*/}>
}
That is, Compose return type is declared to be a constructor for intersection type - a type which must have all the properties of parameter (Base) together with all the properties of the mixin.
You can use that declaration (assuming it's in the library.d.ts file) like this
import {Super} from './library'
let MyComposed = Super.Compose(Super)
let myInstance = new MyComposed
The minor inconvenience is that you always have to provide argument for Super.Compose() because type inference does not work without knowing the value for default parameter, and you can't provide value for default parameter in the declaration file.
But the big problem is that you can't really use the result of Compose as a class:
class MyClass extends Super.Compose(Super) {}
does not compile due to the issues mentioned above:
error TS2509: Base constructor return type 'Super & {}' is not a class or interface type.
Related
I have a superclass property with a generic type, e.g.:
class Super {
static get foo(): Record<string, any> {
return ...
}
}
I thought I could override the type like this:
class Sub extends Super {
static foo: SubType;
}
However, this sets Sub.foo to undefined. I want Sub.foo to maintain the same value, but have a new type. Is this possible?
The type for Sub.foo is different because Super.foo depends on other static properties of the subclass.
I think implementing an interface should work, but it'll be good to know if there's another way.
Use declare to avoid compiling the variable into the actual Javascript:
class Sub extends Super {
declare static foo: SubType;
}
Not to be confused with inheriting from multiple classes.
Suppose there's a class, Player, with its own methods and properties:
class Player {
//...
}
Next, suppose there's a class which extends from Player, with its own special properties
class SpecificPlayer extends Player {
constructor() {
super();
this.foo = "bar";
}
}
Now suppose there's another class, Core, with a property of type Player[] (types are defined through JSDoc-style comments in VSCode), and a method which returns a portion of that array.
class Core {
constructor(players) {
/** #type {Player[]} */
this.players = players;
}
getSomePlayers() {
return this.players.filter(...);
}
}
Finally, let there be a class which extends from Core, with a constructor and method as follows:
class SpecificClass extends Core {
/** #param {SpecifcPlayer[]} players */
constructor(players) {
super(players);
}
doThing() {
console.log(this.players[0].foo);
this.getSomePlayers().forEach(p => console.log(p.foo));
}
}
Now, if the following main body code is ran:
const SpecificInstance = new SpecificClass([new SpecificPlayer(), new SpecificPlayer()]);
SpecificInstance.doThing();
Everything is fine and dandy -- "bar" is printed successfully for all log statements.
However, when writing the code (specifically the body of doThing), VSCode Intellisense would only recognize this.players as a type of Player, meaning it wouldn't recognize the property foo. How would I go about writing the classes such that the VSCode autocomplete/Intellisense recognizes the correct type of this.players, and each method in Core which uses this.players also returns the correct type of SpecificPlayer? Realistically, there are several classes extending from Core, some with their own special variation on Player. As such, the types returned should be that which was provided originally to the constructor.
(Or is there a better way to organize/format my code?)
I'm looking for a way to require a class to own some static methods without having to implement them by myself (like an interface can define normal methods). Since interfaces do not support static methods, the following code does not work.
interface MyInterface {
static fromJSON(json: string): MyInterface
toJSON(): object
}
Abstract classes are not the thing I want, because they do not require the developer to write the method himself, but I have to implement it.
Is there something similar to this, without having to write lots of custom logic?
Using the interface from above, the following implementation should not be accepted:
class MyClass implements MyInterface {
// Missing static method "fromJSON"
toJSON() {
return {}
}
}
Neither should this one:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return {}
}
}
But this one should be accepted:
class MyClass implements MyInterface {
static fromJSON(json: string) {
return new MyClass()
}
toJSON() {
return {}
}
}
There really isn't much support in TypeScript for constraining the static side of a class. It's a missing feature; see microsoft/TypeScript#14600 for an overall feature request, as well as microsoft/TypeScript#33892 for just the "support static implements on classes" part, and microsoft/TypeScript#34516 for just the "support abstract static class members" part.
One big blocker for something like static members of an interface of the form you've shown is that it's hard for the type system to make sense of it in a way that will actually do what you want. There's a longstanding open issue, microsoft/TypeScript#3841, asking that the constructor property of a class should be strongly typed. Currently it only has the type Function:
class Foo {
instanceProp: string = "i"
static staticProp: string = "s"
}
const foo = new Foo();
foo.constructor.staticProp; // error!
// -----------> ~~~~~~~~~~
// Property 'staticProp' does not exist on type 'Function'
There are some sticky reasons for why this cannot be easily done, spelled out in the issue, but essentially the problem is that subclass constructors are not required to be true subtypes of parent class constructors:
class Bar extends Foo {
subInstanceProp: string;
constructor(subInstanceProp: string) {
super();
this.subInstanceProp = subInstanceProp;
}
}
const bar = new Bar("hello");
Here, the Bar constructor is of type new (subInstanceProp: string) => Bar, which is not assignable to the type of the Foo constructor, which is new () => Foo. By extends, bar should be assignable to Foo. But if bar.constructor is not assignable to Foo['constructor'], everything breaks.
There might be ways around that, but nothing has been implemented so far.
All this means that there's no way to look at an object of type MyInterface and be sure that the thing that constructed it has a fromJSON method. So having static inside interface definitions doesn't really behave in any useful way.
The requests in microsoft/TypeScript#33892 and microsoft/TypeScript#34516, don't have this problem. If you could write this:
class MyClass implements MyInterface static implements MyInterfaceConstructor {
// not valid TS, sorry ------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
or this:
abstract class MyAbstractClass {
abstract toJSON(): string;
abstract static fromJSON(json: string): MyAbstractClass
// ------> ~~~~~~
// not valid TS, sorry
}
you'd have a way to do this. Alas, neither of those features have been implemented as of TS4.1, so the only way to proceed is with workarounds.
Let's take the MyInterface and MyInterfaceConstructor interfaces I wrote above and see what we can do with them. Right now we can only constrain the instance side via implements MyInterface:
class MyClass implements MyInterface {
toJSON() { return "" };
static fromJSON(json: string) { return new MyClass() };
}
We can't write static implements MyInterfaceConstructor. But we can make a no-op helper function called staticImplements and call it:
function staticImplements<T>(ctor: T) { }
staticImplements<MyInterfaceConstructor>(MyClass); // okay
The fact that this compiles with no error is your guarantee that MyClass's static side is acceptable. At runtime this is a no-op, but at compile time this is valuable information. Let's see what happens when we do it wrong:
class MyClassBad implements MyInterface {
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassBad); // error!
// ------------------------------------> ~~~~~~~~~~
// Property 'fromJSON' is missing in type 'typeof MyClassBad'
// but required in type 'MyInterfaceConstructor'.
class MyClassAlsoBad implements MyInterface {
static fromJSON(json: string) {
return 123 // Wrong type
}
toJSON() {
return ""
}
}
staticImplements<MyInterfaceConstructor>(MyClassAlsoBad); // error!
// ------------------------------------> ~~~~~~~~~~~~~~
// The types returned by 'fromJSON(...)' are incompatible between these types.
function validMyClass(ctor: MyInterfaceConstructor) { }
Those are the errors you were looking for. Yes, the static constraint and the errors are not located exactly where you want them in the code, but at least you can express this. It's a workaround.
There are other versions of this workaround, possibly using decorators (which are sort of deprecated or on hold until decorator support in JS is finalized), but this is the basic idea: try to assign the constructor type to the "static part" of your interface and see if anything fails.
Playground link to code
How do I import function from a typescript interface?
In the example below, how to call foo from another file?
namesapce a{
export interface b{
foo():number;
}
class c implements b{
public foo():number{...}
}
}
I tried with import = and import require and import {} from as but nothing works.
I get not a module and imported type used as a value but from the solutions I see it seems like I need to add an export or change code that I cannot change.
You cannot use import or export here. The instance method foo belongs to the class c, which is a member of the namespace a inside the global script scope (so the file is not a module).
Also c as containing class is not exported, so foo is not public as well. The only namespace member available is interface b, which just contains type declarations for c and no implementation (b will be erased at runtime).
In general, a namespace a inside the script scope can be used like this (no import):
a.c; // implicit global
window.a.c; // (explicit window global)
globalThis.a.c // environment independent global (e.g. for node and browser)
Assuming, you want to use namespaces and change the export of c, foo can be invoked with following code snippet - paste that into a file without import/export at top-level:
namespace a {
export interface b {
foo(): number;
}
export class c implements b {
public foo(): number {
return 42;
}
}
}
const cInstance = new a.c()
cInstance.foo()
Even though Namespaces are simply named JavaScript objects in the global namespace. from typescript documentation you can revise your code and use it as follows (let's say this code exists in namespace-a.ts file)
Example 1:
export namespace a {
export interface b {
foo(): number;
}
export class c implements b {
public foo(): number {
console.log('calling foo from class c');
return 123;
}
}
}
and in another file you can import it like this
import * as namespaceA from '../location-to-file/namespace-a';
and in your code you can use it like following:
let testVar = new namespaceA.a.c();
testVar.foo();
NOTE: The above is considered a bad practice if you read this from the documentation.
To reiterate why you shouldn’t try to namespace your module contents, the general idea of namespacing is to provide logical grouping of constructs and to prevent name collisions. Because the module file itself is already a logical grouping, and its top-level name is defined by the code that imports it, it’s unnecessary to use an additional module layer for exported objects.
You should just revise your code as follows. In your file fileBC.ts:
Example 2:
export interface b {
foo(): number;
}
export class c implements b {
public foo(): number {
console.log('calling foo from class c');
return 1;
}
}
and in another file use it as follows:
import * as fileBC from '../location-to-file/fileBC';
let testVar = new fileBC.c();
testVar.foo();
P.S.: Sorry for the bad names I use for files and variables but you can get the point. I hate my variable naming. I hope it helped.
I am using flow-runtime plugin for babel to generate dynamically typechecked javascript code. The following is the workflow I am using
write static javascript code (with flow annotations)
compile this code using babel to convert flow annotations to typechecked code
run this compiled code in node.js
The following workflow gives me an ability to write typescript type code, but with type checking only where I want.
So, now that we understand what I am doing, let me explain what I am trying to achieve
I basically need to build a class called Interface, which will do exactly what it sounds like. This class will be extended by classes that are supposed to be interfaces, and then extended by other classes. Something like this :
class Interface() {
constructor() {
...
}
// interface superclass, supposed to be extended by all interfaces
// this class will provide all the utility methods required
// by an interface, such as validating the implementation of the
// interface, ...
validateInterfaceImplementation() {
...
}
}
// interface definition
class FooInterface extends Interface {
constructor() {
super();
...
}
}
// actual class, that will implement the "FooInterface" interface
class Foo extends FooInterface {
constructor() {
super();
...
}
}
Now, I want to enforce strict implementation of the FooInterface. That means that I want a way to define all the methods that the FooInterface interface expects to be implemented, and validation that all these methods have been implemented by the Foo class.
What I have tried looks something like this
// interface.js
// #flow-runtime
class Interface<T> {
constructor(t: T) {
(this: T); // let flow validate that the interface is implemented
}
}
// FooInterface.js
// #flow-runtime
type foInterface = {
bar(param: string): number;
}
class FooInterface extends Interface<fooInterface> {
constructor() {
super(fooInterface);
}
}
// Foo.js
// #flow-runtime
class Foo extends FooInterface {
}
new Foo(); // should throw an error, because the interface is not implemented
// (the function bar is not defined)
I am facing multiple problems with this approach
I am not sure how to implement the generic class Interface<T>. In think my implementation is incorrect, and the compiled babel code also throws an error, but I can't figure out how to do this.
I am not even sure whether this method will work or not, or whether this is the best way to approach this problem.
Any help would be welcome. Thanks in advance :)
As of flow-runtime 0.5.0 you can use Flow's implements keyword combined with Flow interfaces. I think this will give you what you want without having to create the concrete classes at all:
// #flow
// #flow-runtime
interface IPoint<T> {
x: T;
y: T;
}
interface IJSON {
toJSON (): any;
}
class Point implements IPoint<number>, IJSON {
x: number = 123;
y: number = 456;
toJSON () {
return {x: this.x, y: this.y};
}
}