Javascript 作为对象键访问时未推断类型

Javascript 作为对象键访问时未推断类型,javascript,typescript,Javascript,Typescript,给出以下示例: 类型字典={ [键:字符串]:字符串|未定义 } 函数(名称:字符串){ 返回“Hello”+name+“!”; } 函数callgreetror(键:string,d:Dictionary){ 如果(d类型[键]!=='未定义'){ return greet(d[key])//TS错误:“string | undefined”类型的参数不可分配给“string”类型的参数。“undefined”类型不可分配给“string”类型。(2345) } 返回键+'不在字典中'; }

给出以下示例:

类型字典={
[键:字符串]:字符串|未定义
}
函数(名称:字符串){
返回“Hello”+name+“!”;
}
函数callgreetror(键:string,d:Dictionary){
如果(d类型[键]!=='未定义'){
return greet(d[key])//TS错误:“string | undefined”类型的参数不可分配给“string”类型的参数。“undefined”类型不可分配给“string”类型。(2345)
}
返回键+'不在字典中';
}
函数callGreetNoError(键:string,d:Dictionary){
const storedVal=d[键];
如果(storedVal的类型!==“未定义”){
return greet(storedVal)//当值存储在外部变量中时,错误消失。
}
返回键+'不在字典中';
}
我试图理解为什么在
callgreetror
上,
if
块中的
d[key]
类型没有被推断为
字符串
,即使我明确告诉TS它不是
未定义的


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

尝试这样做:

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

这样,您就可以确保
不是未定义的,而不仅仅是
d[key]
。如果语法让您感到困惑,您可以阅读更多关于可选链接的内容。

只需创建一个临时变量,这样您就不会每次单独访问该值。对于所有类型脚本,该值可能在检查和使用之间发生了更改:

function callGreetError(key: string, d: Dictionary) {
  const temp = d[key];
  if (temp !== undefined) {
    return greet(temp)
  }
  return key + ' is not in dictionary';
}
在这种情况下,两个检查都会检查
typeof temp!==“未定义”
temp!==未定义的
可以工作

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

const testDict: Dictionary = {};

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

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

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

添加一个to d[key]告诉TypeScript在这种情况下d[key]不会未定义,尽管通常可以。这就是我们所知道的

一个新的!post fix表达式运算符可用于断言其 操作数在类型为 核查人员无法得出那个事实的结论。具体来说,是行动 x!生成x类型的值,并排除null和undefined。 与形式x和x as T的类型断言类似! 只需在发出的声明中删除非null断言运算符 JavaScript代码


基本上TS不会缩小像
key
这样的属性访问类型

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)
为了允许适当的变量收缩,TS必须清楚地知道您所指的属性:使用点符号
d.foo
或使用括号符号和类似
d[“foo”]
的文字进行属性访问

使用
const storedVal=d[key]
的“技巧”有效,因为TS推断
storedVal
的变量类型为
string |未定义
。由于控制流分析通常基于变量,编译器现在可以更轻松地缩小
storedVal
,并检查
未定义的


您是否尝试过
d[key]!==未定义
而不是?don;t检查类型,检查值,它应该工作是的,相同的问题。。检查一个值而不是一个类型并不能修复它。这是否回答了您的问题@KrzysztofKrzeszewski-我不知道它是如何做到的..有趣的是,我认为这是我一直在寻找的答案-然而,这显然是一个同步场景。。不能这么说吗?@itaydafna我编辑了我的答案,加入了一个示例typescript警告你about@KrzysztofKrzeszewski这是一个合理的想法,TS可以警告检查和使用之间
d[key]
的潜在值变化。但这是因为编译器无法结合计算属性
(组成一个答案)缩小属性访问类型。我批准@ford04的答案,因为它完美地回答了我的问题,并解释了这个答案中的缺陷。
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)