Typescript将动态变量分配给Map对象

时间:2019-06-13 22:56:50

标签: typescript

我正在解决这个问题(来自Exercism

给出一个短语,计算该短语中每个单词的出现次数。

例如,输入“ olly olly in free”

olly: 2
in: 1
come: 1
free: 1

我很确定我在理论上已经找到了答案,但是我在类型方面遇到了麻烦。分割字符串后,我运行一个reduce函数,返回一个Map<string, number>,如下所示:

    return string.split(' ').reduce((acc: Map<string, number>, word): Map<string, number> => {
      if (acc.has(word)) {
        console.log("working")
        // I've tried this
        return acc[word] + 1
        // And this
        return acc.set(word, acc.get(word) + 1)
      } else {
        return acc.set(word, 1)
      }
    }, new Map<string, number>())

在进行调整时遇到了一些问题

1)acc没有“具有”方法(当我将acc键入any而不是Map时)

2)acc.get(word)可能是未定义的,因此不起作用(我检查了一下,但是Typescript没有看到?)

当然,无论如何我都会犯错,但有什么办法可以使我的工作取得成功吗?

1 个答案:

答案 0 :(得分:0)

JavaScript中的Map,因此TypeScript不会将其键/值对作为属性直接存储在对象上,因此,除非将acc[word]替换为Map,否则{[k: string]: number | undefined}表示法将不起作用。像Map这样的字典对象类型。这并不是一个坏方法,但是由于某种原因,您大概想使用Map而不是普通对象,所以我们会坚持下去。


TypeScript无法理解has(x)实例的true方法返回get(x)的意思是对undefined的后续调用不会是has(x)。代表这样的约束也没有好办法。您可以扩展existing typings for Map,以使Map实例或x参数的return acc.set(word, acc.get(word) + 1)方法充当type guard function,这可能工作,具体取决于您在做什么,但这非常复杂,而且非常脆弱。

如果我想在JavaScript中将acc.get(word)行保持原样,我想在acc.has(word)的结果上使用non-null assertion operator !。在这里,您知道检查acc.get(word)意味着number将是number | undefined而不是if (acc.has(word)) { return acc.set(word, acc.get(word)! + 1); // no error now } ,但是编译器并不知道这一点。而且,只要您确定编译器不知道某些内容,就可以使用type assertion。这是它的外观:

acc.has(word)

那太好了。全部完成!


不过,类型断言仍然可以隐藏潜在的错误……通常更可取的是,使用编译器实际上可以将其验证作为类型安全的代码,而不是我们必须进行断言的代码 em>是安全的。那么,如何使编译器帮助我们而不是反过来呢?

好吧,我们使用acc.get(word)作为缩小acc.get(word)结果的一种方法。但是,如果我们只保存has()的结果并直接检查呢?然后,我们可能会完全忘记const cnt = acc.get(word); if (typeof cnt !== 'undefined') { return acc.set(word, cnt + 1); // no error here } else { return acc.set(word, 1); } 方法。像这样:

cnt

我们已明确检查undefined是否为cnt,普通control flow analysis知道number是{{1}内的true if语句的}子句。

好的,现在我们都完成了!


嗯,但是typeof cnt === "undefined"是罗word的;我们只需要检查cnt是否为truthy,是否适用于number以外的所有0值即可。而且由于无论如何我们都将undefined的情况视为0,因此这样做并没有什么害处:

const cnt = acc.get(word);
if (cnt) {
  return acc.set(word, cnt + 1);
} else {
  return acc.set(word, 1);
}

等等,一旦我们愿意检查真实性,就可以使用logical "or" operator来确保cnt总是 一个number:< / p>

const cnt = acc.get(word) || 0; // always a number
if (cnt) {
  return acc.set(word, cnt + 1);
} else {
  return acc.set(word, 1);
}

但是现在我们知道cnt的唯一虚假值为0,因此我们可以始终执行cnt + 1

const cnt = acc.get(word) || 0;
return acc.set(word, cnt + 1);

现在cnt仅使用一次,因此不值得将其作为单独的变量:

return acc.set(word, (acc.get(word) || 0) + 1);

哦,其中只有一个return语句的箭头功能可以更改为使用concise body的箭头功能:

function countStuff(string: string) {
  return string
    .split(" ")
    .reduce(
      (acc: Map<string, number>, word): Map<string, number> =>
        acc.set(word, (acc.get(word) || 0) + 1),
      new Map<string, number>()
    );
}

Link to code

好的,现在我们都完成了!或至少是。希望能有所帮助。祝好运!