ChatGPT解决这个技术问题 Extra ChatGPT

What does the `is` keyword do in typescript?

I came across some code that looks like this:

export function foo(arg: string): arg is MyType {
    return ...
}

I haven't been able to search for is in either the docs or google, it's a pretty common word and shows up on basically every page.

What does the keyword do in that context?


M
Md_Zubair Ahmed

See the reference for user-defined type guard functions for more information.

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

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length); // string function
    }
}
example("hello world");

Using the type predicate test is string in the above format (instead of just using boolean for the return type), after isString() is called, if the function returns true, TypeScript will narrow the type to string in any block guarded by a call to the function. The compiler will think that foo is string in the below-guarded block (and ONLY in the below-guarded block)

{
    console.log("it is a string" + foo);
    console.log(foo.length); // string function
}

A type predicate is just used in compile time. The resulting .js file (runtime) will have no difference because it does not consider the TYPE.

I will illustrate the differences in below four examples.

E.g 1: the above example code will not have a compile error nor a runtime error.

E.g 2: the below example code will have a compile error (as well as a runtime error) because TypeScript has narrowed the type to string and checked that toExponential does not belong to string method.

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length);
        console.log(foo.toExponential(2));
    }
}

E.g. 3: the below example code does not have a compile error but will have a runtime error because TypeScript will ONLY narrow the type to string in the block guarded but not after, therefore foo.toExponential will not create compile error (TypeScript does not think it is a string type). However, in runtime, string does not have the toExponential method, so it will have runtime error.

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length);
    }
    console.log(foo.toExponential(2));
}

E.g. 4: if we don’t use test is string (type predicate), TypeScript will not narrow the type in the block guarded and the below example code will not have compile error but it will have runtime error.

function isString(test: any): boolean{
    return typeof test === "string";
}
function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length);
        console.log(foo.toExponential(2));
    }
}

The conclusion is that test is string (type predicate) is used in compile-time to tell the developers the code will have a chance to have a runtime error. For javascript, the developers will not KNOW the error in compile time. This is the advantage of using TypeScript.


B
Bruno Grieder

The only use I know is the one of your example: specifying a "type predicate" (arg is MyType) in an user defined Type Guard

See User Defined Type Guards in this reference

Here is another reference


I saw this in the docs too, what a bizarre design decision, this case can totally be handled by returning a type boolean right?
@benjaminz This might warrant it's own question on SO but I can show you a quick example of how they are different. The is keyword is actually casting the type and can catch type errors later in the code. See this example for more info.
@benjaminz I don't see how it could be handled by a boolean. Typescript needs to know that the function into which you pass an object is functioning like a type guard. If it just returns type true or false, how can Typescript know that it's indeed a type guard and not just some arbitrary function that returns true if the object is truthy. How will it know to narrow the object's type? Second, how can it know what type to narrow the object's type to? What if the parameter is one of three types? It needs to know that true corresponds to a specific type.
Okay maybe returning boolean won't work, but why not implement 'is' operator? Instead of writing function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; }; if (isFish(pet)){fish.swim()} we could just write if (pet is Fish){fish.swim()}.
On the other side, type guards are bad from the standpoint of OOP. So, maybe this boilerplate is a scarecrow for those who want to do crazy typecasts. If you want to check a variable type, probably you should revise your architecture. If you still want type-checking, maybe you deserve suffering:)