【问题标题】:Strange Typescript error with union types联合类型的奇怪打字稿错误
【发布时间】:2022-01-20 13:10:26
【问题描述】:

我有一个Optional 类型:

export type Optional<T> =
  | {
  hasValue: false;
}
  | {
  hasValue: true;
  value: T;
};

还有一个人为的泛型类型Value

type Value<PAYLOAD> = {
  value: PAYLOAD;
};

正如预期的那样,这个函数没有错误:

function workingAsExpected1(msg: Optional<string>) {
  const v: Value<Optional<string>> = {
    value: msg,
  }
}

现在,我定义了一个 SpecialValue 泛型类型,它基本上等同于 Value 类型,但对于导致我遇到的问题是必要的:

type SpecialValue<PAYLOAD> = PAYLOAD extends undefined
  ? never
  : Value<PAYLOAD>;

现在,当msg 在函数范围内定义时,一切都按预期工作:

function workingAsExpected2() {
  const msg: Optional<string> = {
    hasValue: true,
    value: '',
  };
  const v: SpecialValue<Optional<string>> = {
    value: msg,
  };
}

但是当我将msg 定义为函数的参数时——具有完全相同的类型——我在v 下得到一个错误:

function notWorking(msg: Optional<string>) {
  const v: SpecialValue<Optional<string>> = {
    value: msg,
  };
};

“'{ hasValue: false; }' 类型中缺少属性'value',但在'{ hasValue: true; value: string; }'类型中是必需的”

我在这里缺少什么?为什么SpecialValue 会导致错误,而Value 不会,当它们评估为完全相同的类型时?为什么当我在范围内定义变量时它会起作用,但当我将它定义为参数时它却不起作用,即使它们是完全相同的类型?

Playground link

【问题讨论】:

  • workingAsExpected 内部,编译器使用control flow analysis on assignmentmsg 的类型缩小到const msg: { hasValue: true; value: string;},但在notWorking() 的主体内部没有这样的赋值,所以它必须处理@ 987654347@ 作为完整的联合类型。你可以用任何你想要的Optional&lt;string&gt; 调用notWorking(msg),即使hasValuefalse
  • 我很乐意写一个答案,但也许您可以先edit 您的问题将代码简化为minimal reproducible example,如this?似乎SpecialValue&lt;T&gt;Value&lt;T&gt; 并不是真正直接相关的。告诉我。
  • @jcalz 但是为什么workingAsExpected1 有效?这和notWorking 之间的唯一区别是ValueSpecialValue
  • SpecialValue&lt;T&gt;Value&lt;T&gt; 是不同的类型;前者distributes over unions in T 而后者没有。 {value: {hasValue: false} | {hasValue: true, value: string}}{value: {hasValue: false}} | {value: {hasValue: true, value: string}} 不被视为同一类型。如果您问的是 那个 差异,那么 workingAsExpected2 中发生的控制流分析可能不相关,您应该简化为 this
  • 有几次你说几种类型是“相同的”和“等价的”,但实际上它们不是。为了将问题保持在合理的范围内,也许您可​​以将自己限制在仅询问这些看似等效的类型中的一种……无论您主要关心的是哪一种。您总是可以打开多个问题。否则答案将由多个互不相关的子答案组成。

标签: typescript


【解决方案1】:

除非您的程序出于某种原因在 nullundefined 之间进行语义区分,否则您不需要将值包装在对象中并使用额外属性来区分是否存在潜在值的仪式。

相反,您可以使用内置类型实用程序NonNullable&lt;Type&gt; 来缩小值范围,并使用type predicate 更容易在代码中进行区分。考虑这个例子:

TS Playground

type Maybe<T> = T | null | undefined;

// You can think of this type predicate as a functional form of "hasValue"
function exists <T>(value: T): value is NonNullable<T> {
  // return value !== undefined && value !== null;
  // Can be shortened to:
  return value != null;
}

function doSomethingWithValue (value: Maybe<string>) {
  if (exists(value)) {
    value // string
  }
  else {
    value // null | undefined
  }
}

// This also has the advantage of being able to use the "nullish" family of
// of operators in your code to discriminate a potential value:
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

function greet (name?: Maybe<string>) {
  name ??= 'World';
  name; // string
  console.log(`Hello ${name}`);
}

greet(); // "Hello World"
greet(undefined); // "Hello World"
greet(null); // "Hello World"
greet(''); // "Hello "
greet('Bill'); // "Hello Bill"

【讨论】:

  • 我应该在我的问题中更清楚地说明这一点,但 Optional 类型是一种人为的类型,类似于我的应用程序中更有意义的类型。它必须是对象的联合类型。在与 jcalz 讨论之后,我意识到我真正要寻找的是如何定义 SpecialValue,以便它直接映射到 Value,如果其泛型类型参数未定义。我想我会为此提出另一个更直接的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
  • 2023-01-28
  • 1970-01-01
  • 2018-12-06
  • 1970-01-01
  • 2017-05-18
相关资源
最近更新 更多