简体   繁体   中英

Behavior of Generics in the TypeScript

I want to study TS a bit, and I am following the official page .

I am on the Generics chapter

From the guide:

function identity(arg: any): any {
    return arg;
}

They state the following:

While using any is certainly generic in that it will cause the function to accept any and all types for the type of arg, we actually are losing the information about what that type was when the function returns. If we passed in a number, the only information we have is that any type could be returned.

Now lets observe this piece of code:

let whatIsMyType = identity(666);
typeof whatIsMyType; // number

I have not lost type information. What kind of loss they refer to?

Reading a bit further, guide suggests to use T type, as in:

function identity<T>(arg: T): T {
    return arg;
}

With explanation:

We've now added a type variable T to the identity function. This T allows us to capture the type the user provides (eg number), so that we can use that information later. Here, we use T again as the return type. On inspection, we can now see the same type is used for the argument and the return type. This allows us to traffic that type information in one side of the function and out the other.

And lastly: function in TS (with any type of signature)

function identity(arg: any): any {
    return arg;
}

and the function with generic type T:

function identity<T>(arg: T): T {
return arg;

}

once compiled to plain JS, they look both identical:

function identity(arg) {
    return arg;
}

Now, I understand the benefit of early warning, I also understand the any signature. What I do not understand why saying I lost the type, and I should use "T" signature instead.

Which is more complicated, and is basically identical.

Now lets observe this piece of code: let whatIsMyType = identity(666); typeof whatIsMyType; // number I have not lost type information. What kind of loss they refer to?

It's about the difference between static type information and runtime type information. TypeScript is all about static type information — the information available to the TypeScript compiler to help you keep your program code correct. But typeof (in that context¹) is a runtime operation telling you what kind of value is stored in the variable whatIsMyType at runtime as of when you use typeof on it.

If you paste that code into the playground and hover your mouse over the whatIsMyType variable, you'll see that it has the type any , which means you can happily assign "foo" to it after the call to identity :

function identity(arg: any): any {
    return arg;
}
let whatIsMyType = identity(666);
console.log(whatIsMyType);
whatIsMyType = "foo";          // No error here
console.log(whatIsMyType);

The variable is, for all intents and purposes, loosely typed like JavaScript variables are.

TypeScript's goal, its raison d'être, is static type checking. any is effectively an escape hatch for those situations (and they do exist, but are rare) where static type checking isn't useful (or even possible because of third-party integrations).

once compiled to plain JS, they look both identical:

That's right. The static type information that TypeScript uses is not part of the final output. It's a compile-time thing, not a runtime thing. But notice how using the generic changes things — if you run this code in the playground :

function identity<T>(arg: T): T {
    return arg;
}
let whatIsMyType = identity(666);
console.log(whatIsMyType);
whatIsMyType = "foo";       // <== Error: Type '"foo"' is not assignable to type 'number'.
console.log(whatIsMyType);

...you can see that assigning "foo" to whatIsMyType is now a compile-time error.


¹ ".. typeof (in that context)..." Somewhat confusingly, there is also a static typeof keyword in TypeScript that you can use where a type is expected, which is different from JavaScript's runtime typeof keyword (used in your example). In TypeScript, there are places a type is expected (a type context ), such as after the : in a variable declaration:

let a: type_expected_here;

...and other places a value is expected (a value context ), such as the right-hand side of an assignment:

a = value_expected_here;

If you use typeof in a type context , it's TypeScript's typeof operator. For instance, if you have this declaration:

let foo: number = 42;

...you can use typeof bar to declare a variable whose type is the same as foo 's, like this :

let a: typeof foo;

Since that usage of typeof is where a type is expected, a will have the type number because that's foo 's type. If you change foo 's declaration so it's a string instead, a 's type will change to follow suit.

In contrast:

let a = typeof foo;

There, typeof is in a value context , so it's JavaScript's runtime typeof keyword, and a will get the value (not type) "number" (an in that specific example, a 's type will be inferred as string , since that's what JavaScript's typeof 's result always is).

That seems very confusing at first, but gets clearer with time.

Sadly, TypeScript's typeof isn't well-described by the documentation. :-|


Note that all of the above is from a TypeScript perspective. Whether you find static type checking a help or a hindrance is up to you. :-) But I'm assuming a TypeScript perspective for the purposes of this answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM