Why does this give a Typescript warning?

限于喜欢 提交于 2021-01-29 13:51:10

问题


Why does the second call to needsString(var1) in this .tsx component give a warning, but not the first or third calls? And if I change the code to use var2 instead of var1 (which I think have exactly the same types), I don't get a warning. What gives?

import { useParams } from "react-router-dom";

const DeleteTeams2 = ({ var2 } : { var2: string | undefined})=> {
    // use var here so that var1 and var2 get exactly the same types
    var { var1 } = useParams();

    function needsString(str : string) : string {
        return "hello";
    }

    // I can tell typescript that var1
    // definitely is non-null with an if statement
    if (! var1)
        throw new Error("bad, bad varN");

    // This call is warning-free
    needsString(var1);

    // But JSX doesn't know that var1 is non-null for some reason...
    return (
        <div
            onClick={() => {
                // I get a warning from this!
                needsString(var1);
            }}
        >
            { needsString(var1) }
        </div>
    )
}

When I move the mouse over the definitions of both var1 and var2, they both seem to have the same type: var varN: string | undefined.

Due to the if statement, I expect var1 to behave as a string after the if statement. And indeed it does for var2, but not for var1.

But I get a warning from the first call to needsString(var1):

TypeScript error in /path/to/foobar.tsx(146,29):
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.  TS2345

    144 |             onClick={() => {
    145 |                 // I get a warning from this!
  > 146 |                 needsString(var1);
        |                             ^
    147 |             }}
    148 |         >
    149 |             { needsString(var1) }

Edit: Removed reference to Non-null assertion operator as I was not using a Non-null assertion operator as several comments have pointed out.


回答1:


The issue is that the code here:

onClick={() => {
  // I get a warning from this!
  needsString(var1);
}}

will not be executed at the same time as this code:

if (! var1)
  throw new Error("bad, bad varN");

// This call is warning-free
needsString(var1);

Moreover, var1 is a mutable reference, so from TypeScript's perspective it might change between checking if it's empty you actually using it. For example consider this sample code:

//it's populated
var myVar = "hello";

//verify it's populated
if(!myVar) throw Error("WRONG!");

//absolutely positively `myVar` is populated

//set this to execute later
setTimeout(() => console.log("myVar", myVar));

//change it
myVar = undefined;

So, TypeScript can only determine that var1 might be reassigned. The useParams() function returns an object typed as

{ [K in keyof Params]?: string | undefined; }

which means that the possible type for var1 is just string | undefined. If the value changes (TypeScript has no way to determine if it does or not), then it can see that undefined is not allowed.

Use const

You can simple declare it as const { var1 } (Playground Link) which makes its type string only as it assures the compiler you'd not change it between declaration and usage.

Type assertion

Alternatively, explicitly set the type to only string: var { var1 } = useParams() as { var1: string }; Playground Link. The compiler would accept that maybe you've reassigned it but it would at least know that the only possible thing you would change the variable to is another string. Which satisfies the type used later and would thus work. However, that requires a type assertion and as always, it relies on you being sure this would be the case, otherwise you might get a runtime error that the TypeScript compiler cannot see (because you've "blinded" it, intentionally)

const only after a check

If you feel uneasy setting var1 to a constant for whatever reason - either you change it or anything else, then you can just do your normal check for var1 and then set a new constant:

if (! var1)
  throw new Error("bad, bad varN");

const checkedVar1 = var1;

/* ... */

onClick={() => {
  // I get a warning from this!
  needsString(checkedVar1);
}}

Playground Link

This still works and will satisfy the TypeScript compiler that checkedVar1 (I assume you'd name it something more sensible) is not changed between definitely being being assigned something that's definitely a string and when it's actually used.



来源:https://stackoverflow.com/questions/60815482/why-does-this-give-a-typescript-warning

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!