【问题标题】:TypeScript filter out nulls from an arrayTypeScript 从数组中过滤掉空值
【发布时间】:2017-08-24 10:15:37
【问题描述】:

TypeScript,--strictNullChecks 模式。

假设我有一个可空字符串数组(string | null)[]。什么是single-expression 方法来删​​除所有空值,以使结果具有string[] 类型?

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray: string[] = ???;

Array.filter 在这里不起作用:

// Type '(string | null)[]' is not assignable to type 'string[]'
array.filter(x => x != null);

数组推导可以工作,但 TypeScript 不支持。

实际上,该问题可以概括为通过从联合中删除具有一种特定类型的条目来过滤任何联合类型的数组的问题。但让我们关注具有 null 和可能未定义的联合,因为这些是最常见的用例。

【问题讨论】:

  • 遍历数组有什么问题,检查他的索引是否为空,如果不将它们添加到过滤后的数组中?
  • Iteration+if+insertion 是我现在所做的。我觉得它太罗嗦了。
  • 在操场上,它与您发布的array.filter 配合得很好。它甚至不需要: string[],这就足够了:const filterdArray = array.filter(x => x != null); 并且编译器推断filterdArraystring[] 类型。你用的是什么版本的打字稿?
  • 在操场上选择 Options 并检查 strictNullChecks

标签: typescript null


【解决方案1】:

您可以在.filter 中使用type predicate 函数来避免选择退出严格类型检查:

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
    return value !== null && value !== undefined;
}

const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
const filteredArray: string[] = array.filter(notEmpty);

您也可以使用array.reduce&lt;string[]&gt;(...)

2021 年更新:更严格的谓词

虽然此解决方案适用于大多数情况,但您可以在谓词中获得更严格的类型检查。如前所述,函数notEmpty 实际上并不能保证它在编译时正确识别值是null 还是undefined。例如,尝试将其返回语句缩短为return value !== null;,您将不会看到编译器错误,即使该函数将在undefined 上错误地返回true

缓解这种情况的一种方法是首先使用控制流块来约束类型,然后使用虚拟变量来让编译器检查一些内容。在下面的示例中,编译器能够推断出value 参数在进行赋值时不能是nullundefined。但是,如果您从 if 条件中删除 || value === undefined,您将看到一个编译器错误,通知您上面示例中的错误。

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
  if (value === null || value === undefined) return false;
  const testDummy: TValue = value;
  return true;
}

请注意:在某些情况下,这种方法仍然会让您失败。请务必注意与contravariance 相关的问题。

【讨论】:

  • 这是 Type Guards 最有用的用法,也是最优雅的解决方案!
  • 我认为这是不对的 value !== null &amp;&amp; value !== undefined 不会返回字符串,它会返回 true 或 false。另外,传递一个 null 或 undefined in 会给你一个 null 或 undefined 的值,所以它并没有真正的约束。问题是这个函数并不是真正做过滤的东西,所以它不能真正做出保证。
  • notEmpty 是否保证它将类型从string|null 限制为string?并不真地。使用类型谓词得到的保证是,如果它有错误的类型,您将无法在过滤器中使用它,这仍然很有用,因为您可以轻松地通过几个单元测试来填补空白.对定义进行单元测试,使用被类型系统覆盖。
  • @Bijou 我不明白,它确实将我的类型限制为TValue
  • @S.TarıkÇetin 考虑这样一个事实:如果 notEmpty 函数的返回值减少到 return value !== null;,您将不会收到编译器错误。
【解决方案2】:

类似于@bijou-trouvaille 的回答,您只需将&lt;arg&gt; is &lt;Type&gt; 声明为过滤器函数的输出:

array.filter((x): x is MyType => x !== null);

【讨论】:

  • 又短又甜
  • 有吸引力。但这不是类型安全的。这和使用“as”一样糟糕。如果您这样写,Typescript 不会抱怨:const realArr: number[] = arr.filter((x): x is number =&gt; x === undefined); 实际上返回一个未定义数组。
  • @VivekMaharajh 这是一个很好的观点,感谢您指出。
  • @VivekMaharajh user-defined type guards 永远不会像您期望的那样“类型安全”:const isString = (x: number | string): x is string =&gt; true; 非常好,即使它会报告 true 的数字。如果你搞砸了你的类型保护,你的类型系统就会出错。对于这个答案,对于已接受的答案同样如此。你真的有一种“类型-更安全”的方式来解决手头的问题吗?
  • 我预计很多人会阅读这篇文章而没有意识到它包含一个未经检查的类型断言。这些人最终可能会复制/粘贴它,而不是编写不需要任何类型断言的更冗长的替代方案 ```` const removeNulls = (arr: (string | null)[]): string[] => { const输出:字符串[] = []; for (arr 的常量值) { if (value !== null) { output.push(value); } } 返回输出; }; ````
【解决方案3】:

还有一个更好的衡量标准,因为人们经常忘记flatMap,它可以一次性处理filtermap(这也不需要对string[] 进行任何转换):

// (string | null)[]
const arr = ["a", null, "b", "c"];
// string[]
const stringsOnly = arr.flatMap(f => f ? [f] : []);

【讨论】:

  • 这应该是最佳答案。实际上,我什至会将其更改为 f =&gt; !!f ? [f] : [] 以简化。
  • 值得注意的是,flatMap 是在 ECMA-262(又名 ECMAScript 2021)中定义的。对某些人来说,这可能是一个障碍。
  • @AlexKlaus,这是一个 TypeScript 问题,ECMAScript 2021 与它有什么关系?您可以将 TypeScript 转换为许多目标。
  • 它可能会影响tsconfig.jsoncompilerOptions,尤其是“lib”部分(example
  • 很好的答案,总是忘记 flatMap
【解决方案4】:

您可以将filter 结果转换为您想要的类型:

const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
const filterdArray = array.filter(x => x != null) as string[];

这适用于您提到的更一般的用例,例如:

const array2: (string | number)[] = ["str1", 1, "str2", 2];
const onlyStrings = array2.filter(x => typeof x === "string") as string[];
const onlyNumbers = array2.filter(x => typeof x === "number") as number[];

(code in playground)

【讨论】:

    【解决方案5】:

    一个班轮:

    const filteredArray: string[] = array.filter((s): s is string => Boolean(s));
    

    TypeScript playground

    诀窍是传递type predicate:s is string 语法)。

    This answer 表示Array.filter 要求用户提供类型谓词。

    【讨论】:

    • !!s (bang-bang) 可以用来代替Boolean(s)
    • @AlexPo 不太清楚,所以我建议不要这样做
    【解决方案6】:

    为了避免每个人都不得不一遍又一遍地编写相同的类型保护辅助函数,我将名为 isPresentisDefinedisFilled 的函数捆绑到一个辅助库中:https://www.npmjs.com/package/ts-is-present

    目前的类型定义是:

    export declare function isPresent<T>(t: T | undefined | null): t is T;
    export declare function isDefined<T>(t: T | undefined): t is T;
    export declare function isFilled<T>(t: T | null): t is T;
    

    你可以这样使用:

    import { isDefined } from 'ts-is-present';
    
    type TestData = {
      data: string;
    };
    
    const results: Array<TestData | undefined> = [
      { data: 'hello' },
      undefined,
      { data: 'world' }
    ];
    
    const definedResults: Array<TestData> = results.filter(isDefined);
    
    console.log(definedResults);
    

    当 Typescript 在中捆绑此功能时,我将删除该包。但是,现在,享受吧。

    【讨论】:

      【解决方案7】:

      如果你已经使用 Lodash,你可以使用compact。 或者,如果你更喜欢 Ramda,ramda-adjunct 也有 compact 函数。

      两者都有类型,因此您的 tsc 会很高兴并得到正确的类型。

      来自 Lodash d.ts 文件:

      /**
       * Creates an array with all falsey values removed. The values false, null, 0, "", undefined, and NaN are
       * falsey.
       *
       * @param array The array to compact.
       * @return Returns the new array of filtered values.
       */
      compact<T>(array: List<T | null | undefined | false | "" | 0> | null | undefined): T[];
      

      【讨论】:

        【解决方案8】:

        这是一个比@bijou-trouvaille 接受的答案更简洁的解决方案

        const notEmpty = <T>(value: T): value is NonNullable<typeof value> => !!value 
        
        const array: (string | null | undefined)[] = ['foo', 'bar', null, 'zoo', undefined];
        
        const filteredArray: string[] = array.filter(notEmpty);
        console.log(filteredArray)
        [LOG]: ["foo", "bar", "zoo"]
        

        【讨论】:

          【解决方案9】:

          我相信除了类型检查只是使过滤后的类型与返回类型没有什么不同之外,一切都很好。

          const array: (string | null)[] = ["foo", "bar", null, "zoo", null];
          const filterdArray: string[] = array.filter(f => f !== undefined && f !== null) as any;
          console.log(filterdArray);
          

          【讨论】:

          • 你是对的,临时类型选择退出将起作用。更严格的解决方案可能吗?
          • 这是我的第一直觉——但打字稿不允许这样做。但是,由于过滤数组的类型为 string[],因此它与 IMO 一样严格。
          【解决方案10】:

          简单地使用

          array.filter(Boolean);
          
          

          这适用于所有真值。

          不幸的是,这不提供类型推断,找到了这个解决方案 here

          
          type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T; //from lodash 
          
          function truthy<T>(value: T): value is Truthy<T> {
              return Boolean(value);  //  or !!value
          }
          
          const arr =["hello","felow","developer","",null,undefined];
          
          const truthyArr = arr.filter(truthy);
          
          // the type of truthyArr will be string[]
          
          

          【讨论】:

          • 它不支持 TS 中的类型(出于某种不幸的原因)
          • @DmitriPisarev 如果你想输入 infer,你可以使用类似 `` type Truthy = T extends false | '' | 0 |空 |不明确的 ?从不:T; function truthy(value: T): value is Truthy { return Boolean(value); } const truthyArr = arr.filter(truthy); ```
          【解决方案11】:

          我认为这将是一种简单的方法,代码更简洁

          const array: (string | null)[] = ['foo', 'bar', null, 'zoo', null];
          const filteredArray: string[] = array.filter(a => !!a);
          

          【讨论】:

          • 这个解决方案不是类型安全的 - 它不能在 strictNullChecks 打开时编译。
          • 还要注意 empty string '' is considered falsy 并因此在过滤过程中被删除
          【解决方案12】:

          如果您使用过滤器检查 null 与其他条件,则可以使用此方法,希望这对正在寻找object array 解决方案的人有所帮助

          array.filter(x => x != null);
          array.filter(x => (x != null) && (x.name == 'Tom'));
          

          【讨论】:

            【解决方案13】:

            TypeScript 有一些实用程序可以推断数组的类型并从中排除 null 值:

            const arrayWithNulls = ["foo", "bar", null, "zoo", null]
            
            type ArrayWithoutNulls = NonNullable<typeof arrayWithNulls[number]>[]
            
            const arrayWithoutNulls = arrayWithNulls.filter(x => x != null) as ArrayWithoutNulls
            

            比在新阵列上手动投射 as string[] 更长但更安全。

            一步一步:

            1. 从原始数组中获取类型:
            typeof arrayWithNulls[number] // => string | null
            
            1. 排除null 值:
            NonNullable<typeof arrayWithNulls[number]> // => string
            
            1. 将其设为数组:
            NonNullable<typeof arrayWithNulls[number]>[] // => string[]
            

            链接:

            【讨论】:

              【解决方案14】:

              您也可以使用双重否定 (!!) 过滤掉所有 falsy 值:

              • 未定义
              • 空字符串“”('')
              • 数字0
              • 数字 NaN 例如,
                  array?.filter(item => !!item).map((item: any)
              

              【讨论】:

                【解决方案15】:

                使用reduce

                一些答案​​建议reduce,方法如下:

                const languages = ["fr", "en", undefined, null, "", "de"]
                
                // the one I prefer:
                languages.reduce<string[]>((previous, current) => current ? [...previous, current] : previous, [])
                
                // or
                languages.reduce((previous, current) => current ? [...previous, current] : previous, Array<string>())
                
                // or
                const reducer = (previous: string[], current: string | undefined | null) => current ? [...previous, current] : previous
                languages.reduce(reducer, [])
                

                结果:["fr", "en", "de"]

                TS 游乐场here.

                【讨论】:

                  【解决方案16】:
                  const filterdArray = array.filter(f => !!f) as string[];
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-08-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-12-30
                    • 1970-01-01
                    相关资源
                    最近更新 更多