If you come from chapter
one and
two, you’re now motivated to start using TypeScript, so,
let’s get started! 🚀
Types? Typing? What is it?
In simple words, types (in web languages):
- helps the compilers to
- optimise the storage of the values in memory — at runtime
- optimise code (mainly for transpilers, ex: Dart) — at compile time
- helps developers to know how to use a value (methods, structure, etc)
JavaScript — The dynamic weakly typed
A JavaScript variable can contain any type of data,
JavaScript is dynamically typed, this means that the type of a variable is defined by the type of its data, at runtime.
1let a = 1; // a is a number
2a = 'ok'; // a is now a string
Though, since it’s weakly typed, you can do “what ever you want” in your expression.
This means that you don’t need to inform the compiler how to convert your values, example :
1let a = 1;
2let b = "2";
3let c = a + b; // "12" -> c is a string
4c = a \* b; // 2 -> c is a number
This might look much simpler or cool than a * (int) b,
however this weak typing can introduce some weird type conversion.
1const sum = (a, b) => a + b;
2sum(1, "2") // "12" -> ??!?!?!
That’s why sometimes you need to “force value type” to avoid runtime unexpected behaviours by using parseInt() or the + (unary operator) .
1const sum = (a, b) => parseInt(a, 10) + parseInt(b, 10);
2sum(1, "2") // 3
3
4// Or
5const sum = (a, b) => (+a) + (+b);
Then, how to evaluate a variable type in JavaScript ?
typeof is a built-in operator that returns the type of a value by looking in the “type flag” (
see MDN article)
With normal typing, suitability is assumed to be determined by an object’s type only. In duck typing, an object’s suitability is determined by the presence of certain methods and properties (with appropriate meaning), rather than the actual type of the object.
TypeScript —Static Structural typing (with erasure)
TypeScript, as a layer over JavaScript, add an optional static typing system.
As we’re going to see later, TypeScript bring a lot of more complete type system but some theoretical things must be covered.
Structural Typing
Like “duck typing”, TypeScript will look the shape/structure of a type, instead of the type itself.
This is different from C# or Java, and it bring the same flexibility as JavaScript.
1type Person = { name: string; };
2type Animal = { name: string; kind: string; };
3const isPerson(p: Person): p is Person => !!p.name;
4
5let a: Animal = { name: 'flocon', kind: 'cat' };
6isPerson(a) // true is TypeScript, impossible in C#/Java
7
Type erasure
Type erasure means that all types and annotations are removed at transpile time.
Since JavaScript is still dynamically typed, leaving some comments about types in the code would be pointless for V8 or other engine that have their owns optimisation systems.
Write our first types with scalars
The term “scalar” comes from
linear algebra, where it is used to differentiate a single number from a vector or matrix.
The meaning in computing is similar.
It distinguishes a single value like an integer or float from a data structure like an array.⠀
Use types in TypeScript
It’s really simple, just use a : suffix to any variable, class property declaration or interface property as below:
1const myVar**: string** = 'hello';
2
3type myObjectType = { a**: string**; b**: number**; }
4
5class MyClass {
6 myProp**: string**;
7 /\* ... \*/
8}
Please notice the space after each `:` , it’s important to avoid confusion with object property declaration.
The Scalar types
TypeScript define the same scalar types as JavaScript, except it adds a enum , any and never ones:
The never type represents the type of values that never occur.
Specifically, never is the return type for functions that never return and never is the type of variables under type guards that are never true.
🚨Beware, this is incorrect: let a: never = undefined; never shows the absence of type, you can see it as the opposite of any .
The variable can contains any type of data, like in JavaScript by default.
1let a: any = “hello world”; // valid
2let a: any = 1; // valid
3let a: any = undefined; // valid
1let a: string = “hello world”
- array (not a scalar though.)
1let a: string[] = [“a”, “b”, “c”]
2
3// Or tuple :
4let a: [string, number] = [“1”, 2]
- object (not a scalar though.)
1let a: object = {} // correct
2
3let a: object = { a: 1 }
4// not correct, we'll see this in next section with "interface"
1let a: boolean = true; // correct
1enum Color {Red, Green, Blue}
2let c: Color = Color.Green; // (value in JS is a int)
1let a: null = null; // correct
2let a: null = undefined; // incorrect, it should have a null value.
1let a: undefined;
2let a?: string; // undefined | string
⚠️ Notice: By default, all types includes null and undefined , as says the documentation:
The type checker previously considered null and undefined assignable to anything. Effectively, null and undefined were valid values of every type and it wasn’t possible to specifically exclude them (and therefore not possible to detect erroneous use of them).
--strictNullChecks switches to a new strict null checking mode.
In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any
So, let a: string = undefined;
is correct without the _--strictNullChecks_ compiler option.
🚨 Beware : do not use String or Object types, those are primitive class types.
Rich types: interface and type
The interface keyword
The scalar types are essential but not sufficient for “real world” usage.
We need to describe complex data structure as types like objects, classes, etc
TypeScript helps us to describe theses types in a “structural way” — as seen in the first paragraph — with the interface keyword.
Interface is used to describe objects, functions or classes.
Describe a User object
1interface User {
2 name: string;
3 birthday?: string; // optional property
4}
As seen in the previous paragraph, the birthday property type is string | undefined .
Since in JavaScript a object property with undefined value is considered missing, having a nullable type property mean a optional object property.
Describe a Function
1interface MyAddFunction {
2 (a: number, b: number): number
3}
4
5const add: MyAddFunction = (a, b) => a + b;
Here, the root () describe a callable object, in another way, a function.
We’ll see in the next paragraph how to type a function.
Describe an Cat type by re-using Animal type.
Since interfaces are named, you can extends and “re-open” them.
Example:
1interface Animal {
2 name: string;
3}
4
5interface Cat extends Animal {
6 mustache\_len: number;
7}
8
9// ---------------------
10
11interface A { a: string; }
12interface A { b: string; }
13
14// is the same as
15
16interface A {
17 a: string;
18 b: string;
19}
The type keyword
The type keyword is used to create “type aliases”, in other words :
- renaming a existing type:
1type MyString = string;
Useful for documentation purposes.
- compose existing types : union types, augmenting, …
1type PossibleValues = "open" | "close";
2const a: PossibleValues = "open";
3
4interface Car {
5 speed: string;
6}
7
8type CompetitionCar = Car & { competitor\_id: string };
We will see more useful example is future articles about advanced types.
What the difference between interface and type?
Given the following, example, we could say that interface and type are the same:
1interface A {
2 a: string;
3}
4
5type B = { a: string; }
But as explains the official doc and the stack-overflow answer in source, there is some fundamental differences:
- interface can be edited, they are open; type are closed.
- type can compose existing types with union, pick etc
To conclude, we can say that:
- type is especially useful to build intermediate type composed of others
- interface is useful for build fundamental and re-usable types
Function types, arguments, return type
A
function signature (or
type signature, or
method signature) defines input and output of
functions or
methods.
A signature can include:
-
parameters and their
types- a return value and type
-
exceptions that might be thrown or passed back
- information about the availability of the method in an
object-oriented program (such as the keywords
public,
static, or
prototype).
Let’s type some functions
1// inline typing
2const capitalize = (str: **string**): **string** => {
3 return str.charAt(0).toUpperCase() + str.slice(1);
4}
5
6// types with interfaces
7interface CapitalizeFunction {
8 (str: string) => string;
9}
10
11const capitalize: **CapitalizeFunction** = str => {
12 return str.charAt(0).toUpperCase() + str.slice(1);
13}
14
15// function as property in object
16interface **ReactComponentProps** {
17 onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
18}
19
20class MyComponent extends React.Component<**ReactComponentProps**\> { }
As you can see, a function type is always using () , only return type change depending on the context:
- : for “inline return type — to avoid conflict with arrow function definition
- => for “interface function type”
ℹ️ Please note that “interface function type” is not usable for function declared function — only for “anonymous functions” aka “arrow functions”.
Object as arguments
Let’s consider this example
1interface MyFunctionArgs {
2 name: string;
3 security\_code: string;
4 id?: number;
5}
6
7function isCompleteUser({ name, id = 1 }: MyFunctionArgs): boolean {
8 return !!name;
9}
10
11isCompleteUser({ name: 'a', security\_code: 'b' });
Here, arguments destructuring is clear, the function only pick name property.
This notation is particularly useful for compact function signature and optional and named arguments.
I personally find this syntax useful for “data validation” functions.
Conclusion
In this chapter we saw:
- That TypeScript bring clearer variable typing with static typing.
- How the TypeScript typing system behave in a structural and static way.
- That TypeScript bring all fundamental types and even more with nifty ones like never or any .