首页 > 解决方案 > 如何键入此合并功能?

问题描述

我对 TypeScripts 或其他类型语言中标准类型以外的任何东西都比较陌生,我正试图找出一种输入这个函数的好方法。我想摆脱参数和返回类型的任何类型。

该函数返回第一个不为 null、未定义或 NaN 的值。

/** returns the first not-null value excluding NaN */
const coalesce = (...args: any): any => {
  for (let i = 0; i < args.length; i++) {
    // args[i] === args[i] is to avoid NaN, because NaN !== NaN
    if (args[i] != null && args[i] === args[i]) {
      return args[i];
    }
  }
  return null;
};

这是你将如何使用它:

test('coalesce returns the first value that is not null, undefined, NaN', () => {
  expect(coalesce(null, undefined, NaN, 'maybe')).toBe('maybe');
}); // -> passes

标签: typescript

解决方案


最简单的版本是捕获我们在一个类型中传递的参数类型的类型,作为所有参数类型的联合返回。

我们可以在剩余参数中使用元组来将所有参数的类型捕获为元组类型,并通过类型查询 来获取元组中所有类型的联合:

const coalesce = <T extends any[]>(...args: T): T[number] => {
    for (let i = 0; i < args.length; i++) {
        // args[i] === args[i] is to avoid NaN, because NaN !== NaN
        if (args[i] != null && args[i] === args[i]) {
            return args[i];
        }
    }
    return null;
};


// o is  string | number | null | undefined under strict null checks
// T is [null, undefined, number, string] so T[number] is string | number | null | undefined
let o = coalesce(null, undefined, NaN, 'maybe')  

现在这个函数的重点是删除 null 和 undefines(还有NaNs ,但它们没有在类型系统中建模,因此与这些无关)。当前签名在类型中留下了nullundefined。我们可以添加一些映射类型和条件类型来移除nulland undefinedif 任何一个参数不能是nullor undefined(好像一个参数不是null或者undefined它会被返回)

type ExcludeNullIfAnyNotNullHelper<T> = {
    [P in keyof T]-?: (null extends T[P] ? never : null) |
        (undefined extends T[P] ? never : undefined) 
}[keyof T]
type ExcludeNullIfAnyNotNull<T extends any[]> = Exclude<T[number], ExcludeNullIfAnyNotNullHelper<T>>

type t1 = ExcludeNullIfAnyNotNull<[null, undefined, number, string]> // number | string
type t2 = ExcludeNullIfAnyNotNull<[null | string, string]> // string
type t3 = ExcludeNullIfAnyNotNull<[null | undefined | string, string]> // string
type t4 = ExcludeNullIfAnyNotNull<[null | undefined | string, string | null]> // string | null
type t5 = ExcludeNullIfAnyNotNull<[null | undefined | string, string | undefined]> // string | undefined
type t6 = ExcludeNullIfAnyNotNull<[null| undefined | string, string | undefined, number]> // string | number

function coalesce<T extends any[]>(...args: T): ExcludeNullIfAnyNotNull<T>
function coalesce<T extends any[]>(...args: T): T[number] {
    for (let i = 0; i < args.length; i++) {
        // args[i] === args[i] is to avoid NaN, because NaN !== NaN
        if (args[i] != null && args[i] === args[i]) {
            return args[i];
        }
    }
    return null;
};


// o is  string | number 
let o = coalesce(null, undefined, NaN, 'maybe')

推荐阅读