Posts in current category

May 07, 2021 21:00 TypeScript

# Cross Types (Intersections)

A cross-type is a combination of multiple types into one type. T his allows us to overlay the existing multiple types into a type that contains all the types of features we need. F or example, ``` Person & Serializable & Loggable ``` both ``` Person ``` and ``` Serializable ``` and ``` Loggable ``` This means that this type of object has all three types of members at the same time.

Most of us see the use of crossover types in mixins or other places that are not suitable for typical object-oriented models. ( There are a lot of occasions when this happens in JavaScript!) Here's a simple example of how to create a blend:

``````function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}

class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
``````

# The union type

Union types are associated with cross-types, but they are used completely differently. O ccasionally you encounter this situation where a code base wants to pass in ``` number ``` ``` string ``` or string. For example, the following function:

``````/**
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
}
throw new Error(`Expected string or number, got '\${padding}'.`);
}

padLeft("Hello world", 4); // returns "    Hello world"
``````

There is a problem with ``` padLeft ``` and the type of ``` padding ``` parameter is specified ``` any ``` This means that we can pass in a parameter that is ``` number ``` ``` string ``` type, but TypeScript is not wrong.

``````let indentedString = padLeft("Hello world", true); // 编译阶段通过，运行时报错
``````

In traditional object-oriented languages, we might abstract these two types into hierarchical types. T his is obviously very clear, but there is also over-design. O ne of the benefits of the original ``` padLeft ``` version is that it allows us to pass in the original type. T his is simple and convenient to use. This new approach doesn't apply if we just want to use functions that already exist.

Instead ``` any ``` we can use the union type as a parameter for ``` padding ```

``````/**
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
// ...
}

let indentedString = padLeft("Hello world", true); // errors during compilation
``````

A union type represents a value that can be one of several types. W e use a vertical bar ``` | ``` Separates each type, so ``` number | string | boolean ``` that can be ``` number ``` ``` string ``` or ``` boolean ```

If a value is a union type, we can only access members that are common in all types of this union type.

``````interface Bird {
fly();
layEggs();
}

interface Fish {
swim();
layEggs();
}

function getSmallPet(): Fish | Bird {
// ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
``````

The type of union here may be a bit complicated, but it's easy to get used to it. I f the type of a value is ``` A | B ``` what we can be sure of is that it ``` A ``` that are common in A and ``` B ``` I n this example, ``` Bird ``` has a ``` fly ``` member. W e can't be ``` Bird | Fish ``` of type Fish has ``` fly ``` method. If the variable is a ``` Fish ``` runtime, calling ``` pet.fly() ``` is an error.

# Type protection and differentiation of types

Union types are ideal for situations where there are different types of values that can be received. W hat do we do when we want to know for sure if we get ``` Fish ``` T he common way to distinguish between two possible values in JavaScript is to check for their existence. As mentioned earlier, we can only access members that are common in all types of federation types.

``````let pet = getSmallPet();

// 每一个成员访问都会报错
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
``````

For this code to work, we'll use type assertions:

``````let pet = getSmallPet();

if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
``````

## User-defined type protection

You can note that we used type assertions multiple times. If only we had checked the type once, we would have been able to figure out the ``` pet ``` in each branch that followed.

The type protection mechanism in TypeScript makes it a reality. T ype protection is an expression that checks at runtime to make sure that the type is in a scope. To define a type protection, we simply define a function whose return value is a type assertion:

``````function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
``````

In this example, ``` pet is Fish ``` type predicate. The predicate ``` parameterName is Type ``` and the ``` parameterName ``` must be a parameter name from the current function signature.

Whenever isFish is called ``` isFish ``` TypeScript reduces the variable to that specific type, as long as it is compatible with the original type of the variable.

``````// 'swim' 和 'fly' 调用都没有问题了

if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
``````

Note that TypeScript not only knows ``` Fish ``` ``` if ``` pet is ``` else ``` a Fish type ``` Fish ``` in the ``` pet ``` ``` Bird ```

## ``` typeof ``` type protection

Now let's go back and see how the ``` padLeft ``` federation type. We can write using type assertions like this:

``````function isNumber(x: any): x is number {
return typeof x === "number";
}

function isString(x: any): x is string {
return typeof x === "string";
}

return Array(padding + 1).join(" ") + value;
}
}
throw new Error(`Expected string or number, got '\${padding}'.`);
}
``````

However, it is painful to have to define a function to determine whether the type is the original type. F ortunately, we ``` typeof x === "number" ``` into a function now, because TypeScript can recognize it as a type protection. That means we can check the type directly in the code.

``````function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
}
throw new Error(`Expected string or number, got '\${padding}'.`);
}
``````

There are ``` typeof ``` forms of type protection that can be identified: ``` typeof v === "typename" ``` and ``` typeof v !== "typename" ``` must ``` "number" ``` ``` "string" ``` ``` "boolean" ``` ``` "symbol" ``` ``` "typename" ``` But TypeScript doesn't prevent you from comparing with other strings, and languages don't recognize those expressions as type protection.

## ``` instanceof ``` type protection

If you've ``` typeof ``` type protection and are familiar with ``` instanceof ``` you might have guessed what this section says.

``` instanceof ``` protection is a way to refine types by constructors. For example, let's take a look at an example of a previous string fill:

``````interface Padder {
}

constructor(private numSpaces: number) { }
return Array(this.numSpaces + 1).join(" ");
}
}

constructor(private value: string) { }
return this.value;
}
}

return Math.random() < 0.5 ?
}

}
}
``````

``` instanceof ``` requirement of instanceof is a constructor that TypeScript will refine to:

1. The type of ``` prototype ``` of this constructor, if its type ``` any ```
2. Construct the union of the types returned by the signature

In this order.

# The type alias

A type alias gives a new name to a type. Type aliasses are sometimes similar to interfaces, but can work on raw values, union types, yuans, and any other type you need to write by hand.

``````type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
``````

The alias does not create a new type - it creates a new name to reference that type. Aliasing the original type is usually of little use, although it can be used as a form of documentation.

Like interfaces, type aliases can also be generic - we can add type parameters and pass in to the right side of the alias declaration:

``````type Container<T> = { value: T };
``````

We can also use type alias to refer to ourselves in a property:

``````type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
``````

However, the type alias does not appear on the right side of the notable statement:

``````type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
name: string;
}

var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
``````

However, the type alias cannot appear anywhere to the right of the declaration.

``````type Yikes = Array<Yikes>; // error
``````

## Interface vs. type alias

As we mentioned, type aliases can be like interfaces;

First, the interface creates a new name that can be used anywhere else. T ype aliases do not create new names -- for example, error messages do not use aliases. In the following sample code, hover over ``` interfaced ``` showing that it ``` aliased ``` it shows the literal type of object. ``` Interface ```

``````type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
``````

Another important difference is that type alias cannot be ``` extends ``` ``` implements ``` (you cannot ``` extends ``` ``` implements ``` yourself). Because objects in the software should be open to extensions, but for modifications that are closed, you should try to use interfaces instead of type aliases.

On the other hand, if you can't describe a type through an interface and you need to use a union type or a group type, you typically use a type alias.

# String literal type

String literal types allow you to specify the fixed values that strings must have. I n practice, string literal types work well with union types, type protection, and type aliases. By combining these attributes, you can implement strings of similar enumeration types.

``````type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
``````

You can only select one of the three allowed characters as an argument pass, and passing in other values results in an error.

``````Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"'
``````

String literal types can also be used to distinguish function overloads:

``````function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
function createElement(tagName: string): Element {
// ... code goes here ...
}
``````

# Identifiable Union

You can combine string literal types, union types, type protection, and type aliases to create an advanced pattern called identifiable federation, also known as tag federation or algegegelia data types. I dentifiable union is useful in functional programming. S ome languages automatically identify federations for you, while TypeScript is based on existing JavaScript patterns. It has four elements:

1. Has a common string literal attribute - a recognizable feature.
2. A type alias contains those types of union-union.
3. Type protection on this property.
``````interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
}
``````

First we declare the interface that will be federationd. E ach interface has ``` kind ``` property but has a different literal type of character. ``` kind ``` property is called a recognizable feature or label. O ther properties are specific to each interface. N ote that there is currently no connection between the interfaces. Let's put them together:

``````type Shape = Square | Rectangle | Circle;
``````

Now let's use identifiable union:

``````function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
``````

## Integrity checks

When not all identifiable union changes are covered, we want the compiler to notify us. For example, if we add ``` Triangle ``` ``` Shape ``` we also need to update ``` area ``` :

``````type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
// should error here - we didn't handle case "triangle"
}
``````

There are two ways to do this. The first ``` --strictNullChecks ``` and specify a return value type:

``````function area(s: Shape): number { // error: returns number | undefined
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
``````

Because ``` switch ``` does not cover all cases, TypeScript believes that this function sometimes ``` undefined ``` I f you explicitly specify a return value type of ``` number ``` you will see an error because the type of return value ``` number | undefined ``` However, there are some subtleties to ``` --strictNullChecks ``` support the old code well.

The second method uses the ``` never ``` which the compiler uses for integrity checks:

``````function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
default: return assertNever(s); // error here if there are missing cases
}
}
``````

Here, ``` assertNever ``` ``` s ``` the ``` never ``` -- that is, the type that remains after all possible cases have been removed. I f you forget a case, ``` s ``` have a catch-up type, so you'll get an error. This approach requires you to define an additional function.

# Polymorphic ``` this ``` type

The polymorphic ``` this ``` type represents a sub-type that contains a class or interface. T his is called F-bounded polymorphism. I t can easily be used to perform inheritance between coherent interfaces, for example. In the calculator's example, the this type is returned ``` this ``` operation:

``````class BasicCalculator {
public constructor(protected value: number = 0) { }
public currentValue(): number {
return this.value;
}
this.value += operand;
return this;
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}

let v = new BasicCalculator(2)
.multiply(5)
.currentValue();
``````

Since this class uses ``` this ``` you can inherit it, and the new class can use the previous method directly without any changes.

``````class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) {
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...
}

let v = new ScientificCalculator(2)
.multiply(5)
.sin()
Without ``` this ``` ``` ScientificCalculator ``` be able to inherit ``` BasicCalculator ``` while maintaining interface consistency. ``` multiply ``` return ``` BasicCalculator ``` which does not have ``` sin ``` method. However, ``` this ``` type, ``` multiply ``` ``` this ``` and here ``` ScientificCalculator ```
{"ft_lang_label":"__label__en","ft_lang_prob":0.6751239,"math_prob":0.9129143,"size":15592,"snap":"2021-31-2021-39","text_gpt3_token_len":3826,"char_repetition_ratio":0.12695663,"word_repetition_ratio":0.13058686,"special_character_ratio":0.25532323,"punctuation_ratio":0.1691678,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9625765,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2021-07-25T22:52:40Z\",\"WARC-Record-ID\":\"<urn:uuid:efc7bf9c-86a3-40ab-8776-92c07b9a9671>\",\"Content-Length\":\"70859\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:7c2b23c2-863b-4e0c-bc9d-9493603a726c>\",\"WARC-Concurrent-To\":\"<urn:uuid:46f2ba97-7a02-4352-8c23-669a3cb2b8c8>\",\"WARC-IP-Address\":\"172.67.223.124\",\"WARC-Target-URI\":\"https://codingwithfun.com/p/typescript-advanced-type/\",\"WARC-Payload-Digest\":\"sha1:LN5KJLGHCZLKNIRD7MB6H6ODMDFBOBH4\",\"WARC-Block-Digest\":\"sha1:TVE3WRNBQBHI5PVZKCTXEYA65YL6GZRV\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2021/CC-MAIN-2021-31/CC-MAIN-2021-31_segments_1627046151866.98_warc_CC-MAIN-20210725205752-20210725235752-00262.warc.gz\"}"}