Studio Developer Manual / Version 2406.0
Table Of ContentsTypeScript has a notion of interfaces, but uses different semantics than other statically typed languages like Java or ActionScript.
In TypeScript, a class "automatically" implements an interface when it defines the same member signatures
(duck typing). You can, however, use the keyword implements
to explicitly
state that your class intends to implement some interface. A TypeScript interface defines a so-called
ambient type, that is a type that is only relevant for the compiler/type checker,
but not at runtime. Consequently, there is no built-in way to do an instance-of check with an interface.
To simulate this, you have to provide a custom function that tests whether a given object is of the
interface type (type guard).
Studio used to be implemented in ActionScript, where it can be checked at run-time whether
an object is an instance of a given interface, using the ActionScript built-in operator is
.
This means that in ActionScript, interfaces do have some run-time
representation.
When converting code from ActionScript to TypeScript, we wanted to keep the ActionScript interface semantics,
so we had to find some way to represent interfaces and the is
operator in TypeScript.
Since Jangaroo ActionScript was compiled to JavaScript using the Ext class system, too, there already was
a solution at run-time. Interfaces are represented as "empty" Ext classes, that is, classes that have no
members, but an identity.
When a class A implements an interface I, in Ext, the class corresponding to I is
mixed into A. The is
check is implemented by looking up the
mixins
hierarchy of the object's class.
We use a similar approach in TypeScript. A "runtime interface" is represented as a
completely abstract class, that is, an abstract class that only has abstract members.
At runtime, again, only an empty class with an identity remains. When implementing an interface, this abstract
class is implemented and mixed in.
TypeScript allows to "implement a class", because a class actually defines two entities: a value
(the "class object" that exists at runtime) and a type (only relevant for the type checker / compiler).
If you use a class in an implements
clause, only its type is used.
The mixin aspect is represented in TypeScript by calling the Jangaroo runtime
function mixin(Clazz, Interface1, ..., InterfaceN)
after the class declaration.
The following example illustrates how "runtime interfaces" are specified in TypeScript and how they translate to Ext JS.
TypeScript | Ext JS |
---|---|
abstract class IFoo extends Base { abstract foo: string; abstract get bar(): number; abstract set bar(value: number); abstract isAFoo(obj: any): boolean; } class Foo extends Base implements IFoo { foo: string; #bar: number; get bar(): number { return this.#bar; } set bar(value: number) { this.#bar = value; } isAFoo(obj: any): boolean { return is(obj, IFoo); } } mixin(Foo, IFoo); |
Ext.define("IFoo", { extend: "Ext.Base" }); Ext.define("Foo", { extend: "Ext.Base", mixins: ["IFoo"], requires: ["IFoo"], foo: undefined, bar$fPTk: undefined, __accessors__: { bar: { get: function () { return this.bar$fPTk; }, set: function (value) { this.bar$fPTk = value; } } }, isAFoo: function (obj) { return is(obj, IFoo); } }); |
Table 5.2. Runtime Interfaces in TypeScript and Ext JS
The utility functions is
and mixin
are imported from @jangaroo/runtime
.
Section 5.2.3, “Imports and Exports” explains how importing and exporting works
in Ext TS.
The main takeaways here are that runtime interfaces are represented by abstract classes in TypeScript and
by empty classes in Ext JS, implementing a runtime interface means mixing-in that class, and Ext JS's
mixins
class definition property is represented in TypeScript by calling the utility function
mixin
with the class that implements the runtime interface as the first argument,
and the runtime interface itself as the second argument.