简体   繁体   中英

How does Typescript "+" operator work with generics and functions?

I have a function that takes some value, and executes + operator over it and a 2 value:

function myFunction(input: number): number {
    return input + 2;
}

If I pass a number, it will add this number to 2 :

const result = myFunction(2);
console.log('Result: ', result);
// Result: 2

If I pass a string, it will concatenate this string to 2 :

const result = myFunction('2');
console.log('Result: ', result);
// Result: "22"

Everything good so far. Now I want to use generics in order to capture the type:

function myFunction<T>(input: T): T {
    return input + 2;
}

And if I want to use it capturing the implicit type of the parameter I can do:

const result = myFunction(2);
console.log('Result: ', result);
// Error: `(parameter) input: T. Operator '+' cannot be applied to types 'T' and '2'.`

As you see TypeScript returns an error about the type and the + operator, and I can't understand why. The same if I set the type explicitly:

const result = myFunction<number>(2);
console.log('Result: ', result);
// Error: `(parameter) input: T. Operator '+' cannot be applied to types 'T' and '2'.`

I cant understand why it returns an error with + . Any help will be welcome!

In general, TypeScript + operator is more restrictive than JavaScript + operator. Latter and can do implicit conversions between types and is more tolerant in terms of its operands.

Interaction with Generics and functions

Let's take your function example. Given myFunction down under, you get the error as T can be literally anything (see TypeScript + operator section down under for compatible types).

function myFunction<T>(input: T): T {
  // Operator '+' cannot be applied to types 'T' and '2'.  
  return input + 2; 
}

TypeScript also requires, that you narrow down an union type like string | number string | number via control flow analysis.

declare const t: string | number;
t + 3; // Operator '+' cannot be applied to types 'string | number' and '3'.

// this works!
if (typeof t === "string") {
  const res = t + 3; // const res: string
} else {
  const res = t + 3; // const res: number
}

Unfortunately, type narrowing does not work so well with generics extending a union type yet:

function myFunction<T extends string | number>(input: T): string | number {
  if (typeof input === "string") {
    return input + 3;
  } else {
    // TypeScript could not narrow here to number, we have to cast.
    const inputNumber = input as number;
    return inputNumber + 3;
  }
}

So that would be the final version and answer to your question, I guess. As enhancement, a neat thing is to actually return a conditional type. So when we put in string, we want string back. Analogues number -> number . See this Playground example.


Possible combinations for TypeScript + operator

Type matrix for operands (empty spaces means compile error; eg between "Other" and "Boolean" type):

+----------+---------+----------+---------+---------+--------+
|          |  Any    | Boolean  | Number  | String  | Other  |
+----------+---------+----------+---------+---------+--------+
| Any      | Any     | Any      | Any     | String  | Any    |
| Boolean  | Any     |          |         | String  |        |
| Number   | Any     |          | Number  | String  |        |
| String   | String  | String   | String  | String  | String |
| Other    | Any     |          |         | String  |        |
+----------+---------+----------+---------+---------+--------+

Excerpt from the specs:

The binary + operator requires both operands to be of the Number primitive type or an enum type, or at least one of the operands to be of type Any or the String primitive type. Operands of an enum type are treated as having the primitive type Number. If one operand is the null or undefined value, it is treated as having the type of the other operand. If both operands are of the Number primitive type, the result is of the Number primitive type. If one or both operands are of the String primitive type, the result is of the String primitive type. Otherwise, the result is of type Any.

Some of the phrases seem a bit outdated. Numeric enums resolve to numbers, but string enums are treated as strings. With null or undefined and number you get a compile error regardless strict settings or not, with string you do a concatenation. Playground


Background info: Javascript + operator

The addition operator produces the sum of numeric operands or string concatenation.

Some constellations, you can only "do" in JavaScript:

true + 1 // 2
false + false // 0
1 + undefined // NaN
1 + null // 1; typeof(1+ null) === "number";   // :)
new Date() + new Date() // toString() is invoked for both Date objects implicitly

Playground

Hope, this wasn't too much text for the scope of your question!

Cheers

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