在 TypeScript 条件类型中,如何测试 function/generic 参数的存在?

In TypeScript conditional types, how can I test for the presence of a function/generic parameter?

我正在尝试在 TypeScript 中编写一个类似于以下 JavaScript 示例的简单函数(我的实际用例很复杂,因此我已将问题简化为更简单的用例):

function resolve(value, defaultValue=null) {
  if (value !== undefined && value !== null) {
    return value;
  } else {
    return defaultValue;
  }
}

我有一个函数可以将可为 null 的变量解析为其实际值。如果参数为 null 或未定义,则默认值为 returned。默认值的默认值为空。这种功能的动机是值得商榷的,所以我们假设这个功能有助于执行编码标准或其他东西。

让我们观察语义:

function resolve(value, defaultValue=null) {
    if (value !== undefined && value !== null) {
        return value;
    } else {
        return defaultValue;
    }
}

let a = resolve(undefined); // null
let b = resolve(2); // 2
let c = resolve(13, 0); // 13
let d = resolve(null, ''); // ''

let myVar = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)

let e = resolve(myVar); // Could be typeof myVar or null
let f = resolve(myVar, 0); // Could be typeof myVar or number (cannot be null)
let g = resolve(myVar, ''); // Could be typeof myVar or string (cannot be null)

如果您传入空值,您将获得默认值。如果不指定默认值,则默认默认为空。如果您传入一个非空的默认值,则无法获得空值。获取 null 的唯一方法是显式传递它或完全省略 defaultValue 参数。 这似乎是条件类型的一个很好的候选者。但是,我似乎无法争论。这可能是因为根据 TypeScript 的某些语义,这是不可能的。

让我向您展示我正在努力实现的目标。以下不编译:

function resolve<T, TDefault extends T>(value: T | undefined | null, defaultValue?: TDefault): T | (typeof defaultValue extends undefined ? null : TDefault) {
    if (value !== undefined && value !== null) {
      return value;
    } else {
      if (defaultValue !== undefined) {
        return defaultValue;
      } else {
        return null;
      }
    }
  }

  let myVar: string | undefined = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)

  let e = resolve(myVar); // Should return string | null
  let f = resolve(myVar, 0); // Should not compile. Number is not assignable to string
  let g = resolve(myVar, ''); // Should return string, always.

具体看一下return类型:

T | (typeof defaultValue extends undefined ? null : TDefault)

我希望你能明白我在这里的目的。如果参数defaultValue未定义(省略)则return类型为T | null,否则return类型为T | TDefault(然而,TDefault extends T,因此 ACTUAL return 类型只是 T)。我知道我所拥有的是行不通的,甚至真的没有意义。我写它只是为了演示目的。 typeof defaultValue等同于写TDefault | undefined,所以我写的条件类型是分配的等等。 我尝试了各种不同的东西,但我似乎无法理解。我想知道这个函数的语义是否有可能被 TypeScript 捕获。

有什么想法吗?

我觉得你很努力。您的类型定义不应该有条件参数,而是指定所有选项。为什么?代码定义逻辑,类型定义只定义可能的输入和输出。

 (typeof defaultValue extends undefined ? null : TDefault)

似乎可以在 playground 中编译,但实际上您需要的所有输出是:

function resolve<T>(value: T | undefined | null, defaultValue: T | null = null): T | null {
    return value || defaultValue; //for demo purposes
}

因为T已经定义了T的子类的输入是允许的,你不需要指定TDefault。因为在执行这个函数之后你不会知道结果是 T 还是 T 的子类。你唯一真正能做的就是类型检查:

if(output instanceof SubClassOfT){
    (output as SubClassOfT).SomeSpecificStuf
}

我认为重载会更好地服务于您的用例。

function resolve(value: undefined| null) : null
function resolve<TDefault>(value: undefined| null, defaultValue: TDefault) : TDefault
function resolve<T, TDefault extends T>(value: T, defaultValue: TDefault) : T | TDefault
function resolve<T>(value: T) : T | null
function resolve(value, defaultValue=null) {
    if (value !== undefined && value !== null) {
        return value;
    } else {
        return defaultValue;
    }
}

let a = resolve(undefined); // null
let b = resolve(2); // number
let c = resolve(13, 0); // This one is an error since 0 does not extends 13, but maybe this should be the behaviour ? 
let d = resolve(null, ''); // ''

let myVar = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)

let e = resolve(myVar); // string | nul
let f = resolve(myVar, 0); // err
g = resolve(myVar, ''); //string