【问题标题】:Typescript array find possibly undefind打字稿数组发现可能未定义
【发布时间】:2019-07-11 07:15:35
【问题描述】:

我有一个如下所示的数据集合:

interface Item {
  name: "one" | "two";
  data: string;
}

const namedItems: Item[] = [
  {
    name: "one",
    data: "some data one",
  },
  {
    name: "two",
    data: "some data two",
  },
];

每个项目都有一个名称,值可以是“一”或“二”。

然后在此运行一个数组查找:

const getData = (query: "one" | "two") =>
  namedItems.find((item): boolean => query === item.name).data;

引发打字稿错误“对象可能是'未定义'”。这似乎是因为 find 可能找不到某些东西,但在我的示例中,您只能查找总是返回结果的“一个”或“两个”。

我如何让 typescript 知道它总是会在查找中返回结果?

【问题讨论】:

  • (这很难看,但应该可以)尝试投射它?
  • find 可能无法成功找到任何内容,在这种情况下,.data 将无效。如果您将结果分配给类型化变量,那么它可能应该假设它始终有效
  • 或者更好的是,做一些防御性编程并处理未定义的情况:)
  • 编译器无法轻松地为您验证这一点,因为find() 返回可能为空的结果。但是如果你确定它不能为空,你可以通过non-null assertion operator...namedItems.find(predicate)!.data
  • 你确定集合总是包含可能的值吗?容器是否总是正好包含两个元素,或者它可以更少或更多?如果总是有两个元素,你知道它们都有唯一的名称吗?

标签: typescript


【解决方案1】:

选项 1: 最简单的 hack 是使用 non-null assertion operator
(但作为 @Emanuel Lindström mentioned,它是最懒惰、最丑陋和最容易出错的 hack )

const getData = (query: "one" | "two") =>
  namedItems.find((item): boolean => query === item.name)!.data;
                                                         ^

选项 2:从 Typescript 3.7 开始,这可以通过 assertion function 轻松实现:
(无需任何黑客、转换或解决方法) p>

const getData = (query: 'one' | 'two'): string => {
  const item = namedItems.find(({ name }) => query === name);
  assert(item); // (A)

  return item.data; // (B)
}

A 行中的断言函数assert() 影响了B 行中item 的静态类型为Item

在 Node.js 中,assert() 函数可通过 the built-in module 获得:

import assert from 'assert';

在浏览器环境中,需要自己声明。
例如,在项目的实用程序/文件夹中:

function assert(value: unknown): asserts value {}

【讨论】:

    【解决方案2】:

    我如何让 typescript 知道它总是会在 找到了吗?

    最简单、最懒惰、最丑和最容易出错的解决方案是使用非空断言运算符:

    namedItems.find((item): boolean => query === item.name)!.data;

    这是一种告诉 typescript 值将始终被定义的方法,但话又说回来,如果要覆盖它,为什么还要使用 typescript?您应该防止“真正的”可能的未定义值,因为代码将来可能会更改并且可能会发生错误。因此,请使用之前推荐的任何解决方案,或我的解决方案:

    const getData = (query: "one" | "two") =>
      (namedItems.find((item) => query === item.name) ?? {name: "undefined", data: "undefined"}).data;
    

    nullish 合并运算符 (??) 是一个逻辑运算符, 当其左侧操作数为时,返回其右侧操作数 null 或未定义,否则返回其左侧操作数。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

    如果你想要一个默认值,你可以在??-操作符的右边添加任何你想要的值。如果没有,你可以做一个假检查:if(!getData())

    【讨论】:

      【解决方案3】:

      如果你绝对确定你总能得到匹配,那么你可以告诉 TS:

      const getData = (query: "one" | "two") => (namedItems.find((item) => query === item.name) as Item).data;

      【讨论】:

      • 谢谢。为我工作
      【解决方案4】:

      最简洁的代码大概是这样的: 使用可选的链接运算符 (?.) 并键入 Item|any

      const getData = (query: "one" | "two"):Item|any => namedItems.find((item:Item): boolean => query === item.name)?.data;
      

      【讨论】:

      • 这仍然不能保证getData() 函数的输出是Item 类型。由于可选的链接运算符,它可以是未定义的。
      【解决方案5】:

      将 find 的结果设置为 any|Item 类型的变量,允许它接受空结果:

      const getData = (query: "one" | "two") => {
        let item:any|Item = namedItems.find((item:Item): boolean => query === item.name);
        return item.data;
      };
      

      See CodePen

      【讨论】:

        【解决方案6】:

        说明

        您遇到此问题的原因是 Array.prototype.find 的类型签名:

        find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T | undefined;
        

        如您所见,它总是返回T | undefined。这很有意义,因为定义为 Item[] 的集合可以包含任意数量的项目 - 包括 0。在您的情况下,集合是完整的,但在类型级别上它与 [{ name: "one", data: "some data one" }] 甚至 @987654326 没有什么不同@。

        为了以类型安全的方式访问item.data,TypeScript 将要求您仔细检查是否确实找到了结果。

        const lookup = namedItems.find(item => item.name === 'one');
        
        if (lookup === undefined) {
          throw new TypeError('The value was promised to always be there!');
        }
        
        console.log(lookup.data);
        

        解决方案

        由于从长远来看这会变得很麻烦,您可能会发现为这种情况创建一个辅助函数很有用。

        function ensure<T>(argument: T | undefined | null, message: string = 'This value was promised to be there.'): T {
          if (argument === undefined || argument === null) {
            throw new TypeError(message);
          }
        
          return argument;
        }
        

        用法:

        const getData = (query: "one" | "two") =>
          ensure(namedItems.find(item => query === item.name)).data
        

        【讨论】:

        • 我希望我能给这 10 票。很好的答案和实际使用它的非常有用的方法。简直完美。
        • 很好的解释
        • @KarolMajewski 你在生产中使用过这个ensure 吗?我想知道由于这实际上并不能确保任何价值,您仍然会遇到需要处理的运行时错误。打字稿在这里大喊,因为代码可能会爆炸。使用“确保”,即使 TS 会保持沉默,它仍然会爆炸。
        • 我确实在它所针对的场景中使用它,即断言我知道事实存在但编译器不存在的东西的存在。如果允许缺少某个元素,则必须适当处理这种情况。
        • @KarolMajewski 好的,所以只是名称令人困惑:ensure 函数听起来 ensures 你得到了一些东西(比如如果参数不存在,它会提供一些后备值)
        【解决方案7】:

        Array.find() 可能不会成功,可能会返回 undefined

        由于 Typescript 不知道您的 namedItems 数组在运行时不为空(在这种情况下保证失败),因此您无法对它做任何事情。

        但是,您可以使用不同的方法仅在找到项目时提取data 字段,例如您可以将结果包装在一个数组中,然后映射 if 并提取它:

        const getData = (query: "one" | "two") =>
          [namedItems.find((item): boolean => query === item.name)]
            .map(x => x && x.data).shift();
        

        const namedItems = [
          {
            name: "one",
            data: "some data one",
          },
          {
            name: "two",
            data: "some data two",
          },
        ];
        
        const getData = (items, query) =>
          [items.find(item => query === item.name)]
            .map(x => x && x.data).shift();
        
        console.log(getData(namedItems, 'one'));
        console.log(getData(namedItems, 'two'));
        console.log(getData([], 'one'));

        【讨论】:

        • shift() 也返回 undefined 所以这行不通。
        【解决方案8】:

        使用过滤器代替查找:

        V1:

        const results: string[] = namedItems.filter((item: Item) => 
            item.name === "one" | item.name ===  "two")
            .map((item:Item) => item.data)
        

        V2:

        const results: string[] = namedItems.filter((item: Item) => 
            ["one","two"].indexOf(item.name) !== -1)
            .map((item:Item) => item.data)
        

        不确定我是否理解您是否只想要一个结果... 在这种情况下

        const results: string[] = namedItems.filter(
            (item: Item, index) => ["one", "two"].indexOf(item.name) !== -1 && index === 0)
            .map((item:Item) => item.data)
        

        【讨论】:

          猜你喜欢
          • 2019-07-30
          • 2021-12-21
          • 2019-12-24
          • 2019-09-15
          • 1970-01-01
          • 1970-01-01
          • 2021-02-12
          • 2021-07-12
          相关资源
          最近更新 更多