作为对象键访问时无法推断类型

时间:2020-02-03 08:13:53

标签: javascript typescript

给出以下示例:

type Dictionary = {
  [key: string] : string | undefined
}

function greet(name: string) {
  return 'Hello ' + name + '!';
}

function callGreetError(key: string, d: Dictionary) {
  if (typeof d[key] !== 'undefined') {
    return greet(d[key]) // TS Error: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.Type 'undefined' is not assignable to type 'string'.(2345)
  }
  return key + ' is not in dictionary';
}

function callGreetNoError(key: string, d: Dictionary) {
  const storedVal = d[key];
  if (typeof storedVal !== 'undefined') {
    return greet(storedVal) // Error goes away when value is stored in an external var.
  }
  return key + ' is not in dictionary';
}

我试图理解为什么callGreetErrord[key]类型的if不能被推断为string,即使我明确告诉TS不是undefined

为什么将d[key]的值存储在storedVal的外部变量callGreetNoError上可以解决此错误。

4 个答案:

答案 0 :(得分:1)

只需创建一个临时变量,这样就不必每次都分别访问该值。对于所有打字稿,都知道该值可能在检查和使用之间发生了变化:

function callGreetError(key: string, d: Dictionary) {
  const temp = d[key];
  if (temp !== undefined) {
    return greet(temp)
  }
  return key + ' is not in dictionary';
}

在这种情况下,检查typeof temp !== "undefined"temp !== undefined都可以

显示问题的最简单示例是手动将get运算符分配给字典键。在下面的代码示例中,您可以看到key的值确实始终是一个字符串,满足类型要求,但是每次访问它都会改变

const testDict: Dictionary = {};

Object.defineProperty(testDict, "key", {
  get() { return Math.random().toString() }
});

console.log(testDict.key);
console.log(testDict.key);

因此与第一次访问检查类型并与第二次访问一起使用是不相关的

答案 1 :(得分:1)

在return语句中的d [key]上添加可以告诉TypeScript在这种情况下d [key]不会是不确定的,尽管通常是可以的。这就是所谓的non-null assertion operator

function callGreetError(key: string, d: Dictionary) {
  if (typeof d[key] !== 'undefined') {
    return greet(d[key]!) 
  }
  return key + ' is not in dictionary';
}

一个新的!后缀表达式运算符可用于断言其 在以下类型的上下文中,操作数是非null和undefined的: 检查者无法得出结论。具体来说,操作 X!产生x类型的值,其中排除了null和undefined。 类似于形式为x和x的类型断言,即T! 非null断言运算符被简单地删除在发出 JavaScript代码。

答案 2 :(得分:1)

基本上,TS不会像key那样缩小computed property names的属性访问类型。

const d: Dictionary = {...};
const key = "foo";
const fooProp = d[key];

// using .length as string type check
d["foo"] !== undefined && d["foo"].length; // ✔
fooProp !== undefined && fooProp.length; // ✔
d[key] !== undefined && d[key].length; // error, possibly undefined

不是 ,因为TS会进行一些可变性检查,并警告d[key]值可能在检查和使用之间发生了变化。例如,以下代码对于编译器而言是完美的,但可能会在运行时抛出:

const testDict: Dictionary = {
  get foo() { return Math.random() > 0.5 ? "hey" : undefined }
};

function callGreetError(d: Dictionary) {
  // compiles fine, but throws error at run-time from time to time
  if (d["foo"] !== undefined) d["foo"].length
}
callGreetError(testDict)

要允许使用control flow适当缩小变量,TS必须清楚地知道您的意思是什么:通过使用点表示法d.foo或使用括号表示法和诸如d["foo"]的文字进行属性访问。

使用const storedVal = d[key]的“技巧”起作用,因为TS推断storedVal的变量类型为string | undefined。由于控制流分析通常基于变量,因此编译器现在可以更轻松地通过检查storedVal来缩小undefined的范围。

Playground

答案 3 :(得分:-1)

尝试这样做:

function callGreetError(key: string, d: Dictionary) {
  return d?.[key]
    ? greet(d[key])
    : key + ' is not in dictionary';
}

通过这种方式,您可以确保key不仅是d[key]都没有定义。如果语法使您感到困惑,则可以阅读有关可选链接here的更多信息。