|Getting Started With TypeScript|
|Written by Mike James|
|Thursday, 12 April 2018|
Page 2 of 3
The TypeScript class construct is much what you would expect. You can define a class:
and the body of the class can contain functions, variables and objects. A constructor of the same name as the class is declared using:
A class can inherit from another class, or as we shall see later from an interface using the extends or implements keyword. To call the base class constructor you use the super keyword.
Members of the class can be declared as static and these belong to the constructor function and so appear to belong to the class rather than the instance. You have to use the this keyword when working with class or instance members.
You can override inherited methods and properties by simply redefining them.
Instance members can be declared as public or private with the default being public.
The Type System
They resort to duck typing. Forget the ideas of a type hierarchy based on inheritance. All that matters is that an object has the properties and methods you want to use.
Interestingly this is the approach that TypeScript takes, but it is heavily disguised as static hierarchical typing.
In TypeScript a type is simply a specification for a set of methods and properties an object has. It can be taken as a statement of what it is safe to use and what the object is expected to support.
The type system starts with any, which you can consider to be the top of the type hierarchy, but it is best thought of as a statement that nothing is known about the type. In other words, you can use any method or property in any way that you like and no compiler errors will be generated. Clearly if we are going to rely on type checking we need to use any as little as possible.
To allow functions to act as procedures i.e. to not return a result, we also have the special type void which means the absence of a value.
Whenever possible the system will infer a type for a variable. For example,
allows the system to infer that i is of type Number. After this assigning say a string generates a compile time error.
Type inference is impressive but it can be a problem.
Perhaps the message is that you shouldn't write code like this, but you also need to be aware that type inference is lexical and not semantic.
You can explicitly type a variable using a type annotation. For example
sets i to be of type number and initializes it to one.
Clearly using explicit type annotation is a better idea than relying on type inference.
You can also form arrays of types using type[ ] - for example number[ ] is a numeric array.
Functions are just objects you can call but in TypeScript you can specify the functions signature - the types of the parameters and return value.
Notice that the return value is included in the signature - this is unusual.
Functions can also have required and optional parameters, marked by a trailing ? within the signature. You can also include a rest parameter which accepts any additional parameters - it has to be of type any
defines a function with a signature number,string return string. The function
accepts no parameters and returns no result.
When you call a function the parameter types have to match the signature.
Notice that if you don't specify a signature then one is inferred and type checking is performed using it.
You can also use function overloading i.e. same function with different signatures. In this case you can call the function without error if the call matches any of the signatures you specify.
The type of the function doesn't include the actual implementation using any.
Arrow Function Expressions
is equivalent to
There are other variations on this form.
Once we move beyond primitive types we need to look at the interface.
An interface is a set of type definitions e.g. member definitions without implementations.
defines an interface with a public variable i a number and a method myFunction with the specified signature.
Interfaces can extend existing interfaces by using the extends keyword. As interfaces can extend multiple interfaces, there are some rules about what happens when the something with the same name is inherited.
If you define a class so that it implements an interface, this is a promise that the class supports all the methods and properties of the interface. You can then use the interface type to define variables and function signatures etc and only objects that implement the interface can be used.
you can now write:
If myClass didn't implement myInterface then there would be a compile time error.
You can think of declaring a variable as an interface type as a promise that it will support all of the method and properties of the interface.
Now we come to some subtle points.
To be of the same type as an interface all that an object has to satisfy is having the required set of members of the correct type and signature. That is even if an object doesn't implement an interface type inference can deduce that it is of that type. What this means is that types work at compile time much like they do at run-time.
There is even notion of a type hierarchy in TypeScript but it is a very lose and ad-hoc one. A type is a subtype of another type if it contains at least all of the properties and methods of the supertype.
For example if you try:
then the inferred type of the object literal is i:number and you will see an error because myInterface also needs myFunction(x:number) :void;
You can make the assignment work by adding the missing method:
but you can also add additional methods and properties without triggering a compile time error
You can see that an interface is a promise to implement at least all of the variables and functions it defines.
|Last Updated ( Thursday, 12 April 2018 )|