TypeScript 暴露推断类型以用于类型注解

u3r8eeie  于 6个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(58)

搜索词

infer 推断类型注解

建议

当注解变量的类型时,允许变量访问将为右侧推断出的类型,以便作者可以从该推断值构建一个类型注解。这个值可以作为 inferred 关键字或类似的方式暴露出来。

用例/示例

// All properties in `deletable` are subject to being deleted, so we want
// the type to be a Partial of the would-have-been-inferred type.
// I don't think there's a great way to write this at the moment.
const deletable: Partial<inferred> = { a: true, b: false, c: "xyz" };

// The `mutable` property might be reassigned to, but all the other properties wont.
// So combined `Readonly` and `inferred`, with an exception for mutable.
type Legal = "initial" | "middle" | "final"
const partiallyMutable: Readonly<Omit<inferred, "mutable">> & { mutable: Legal } = { 
  x: "literally", 
  y: true,
  mutable: "initial",
  lotsOfOther: 47,
  literalPropsHere: "name"
}

// We want to verify assignability to the mapped type, to make sure that, as new 
// required keys are added, this object literal is updated. However, we also want the
// values at each key to be inferred narrowly as a literal type for use in the code that follows.
type RequiredKeys = "A" | "B" | "C"
const mustHaveAllKeys: inferred & { [K in RequiredKeys]: any } =  = {
  "A": true,
  "B": false,
  "C": "hi!"
}

// typeof mustHaveAllKeys.A should be true!

相关问题

检查清单

我的建议满足以下准则:

  • 这不会对现有的 TypeScript/JavaScript 代码造成破坏性改变
  • 这不会改变现有 JavaScript 代码的运行时行为
  • 这可以在不根据表达式的类型发出不同的 JS 的情况下实现
  • 这不是一个运行时特性(例如库功能、带有 JavaScript 输出的非 ECMAScript 语法等)
  • 这个特性会与 TypeScript's Design Goals 的其他部分保持一致。
dgenwo3n

dgenwo3n1#

重复 #32738 ,其他。另请参阅 #7481
存在循环性问题,使得上述描述的一些场景非常有问题;最好使用辅助函数。

9cbw7uwe

9cbw7uwe2#

存在循环性问题,使得上述描述的一些场景变得非常有问题。

你能详细解释一下吗?我想象了一个相当简单的过程,我认为它适用于上述所有示例:

  1. 当注解包含 inferred 时,计算RHS的类型,就好像没有注解一样。
  2. 用计算出的类型替换 inferred 关键字
    复制 #32738 等其他内容。另请参阅 #7481
    是的, #32738 看起来像是一个重复项......但由于没有提出提案就被关闭了,希望我们可以在这里继续讨论。我真的看不出与 #7481 的联系(但还没有阅读整个线程);我确实在 #7481 的线程中搜索了你在 #32738 的评论中提到的 as? 运算符,但找不到任何地方......
2ul0zpep

2ul0zpep3#

当注解类型是Map类型时,对于人类来说理解起来相当简单。对于其他几乎所有情况,都是非常不清楚的。例如,如果类型是一个可以提供上下文类型的条件类型怎么办?

type Magic<T> = T extends (n: number) => void ? (n: string) => void : (n: number) => void;

// What is the type of p, and the type of k?
let p: Magic<inferred> = k => { };

计算RHS的类型,就好像没有注解一样。
这可能不太有效。例如,你不会期望在 m 上有一个隐式的 any:

type HasFoo<T> = T & { foo?: any };
const f: (n: number) => any & HasFoo<inferred> = m => {};
1cosmwyk

1cosmwyk4#

这将无法很好地工作。例如,你不会期望隐式的 any 在 m 上:
老实说,我认为对于 m 的隐式 any 是可以接受的,以保留此功能的简单心理模型:如果你在 LHS 中使用 inferred ,那么意味着 TS 必须首先推断 RHS 的类型(似乎直观),因此你会失去 LHS 的上下文类型。所以这个例子可能会被重写为:

const f: HasFoo<inferred> = (m: number) => {};

有了这个规则,你的条件类型示例解析为 (n: string) => void (假设 k 上的隐式 any 没有被设置为错误)。
话虽如此,也许可以有一个更复杂的规则:

  1. unknown 替换为 inferred 以产生其类型。
  2. 使用该类型上下文类型化 RHS。
  3. 用现在确定的 RHS 类型的最终类型替换 inferred 以获得 LHS 的最终类型;现在检查可分配性等。
    我认为这会让你的 const f 示例按原样工作,而 p 将被推断为 (n: number) => void (这有点奇怪,但可能不是一个非常常见的情况)。
    或者,可以使用其他过程将带有 inferred 注解的 LHS 转换为没有 inferred 注解的注解。例如,可以将 inferred 替换为 unknown (如上) 除非它出现在联合类型中,如 const x: string | inferred = ...const x: Something<inferred | boolean> ;在这些情况下,可以从联合中删除 inferred。这将为上下文类型保留更多的信息,即 const x: string | inferred = ... 的 RHS 将用 string 而不是用 unknown 进行上下文类型化(如果 inferred 被替换为 unknown ,并且产生的 string | unknown 注解被减少)。
dgiusagp

dgiusagp5#

通常情况下,我不会费心去发帖,因为我已经找到了一个解决方法,但你似乎非常关心语言,并试图让它更具能力,更适合企业使用。所以我决定投资午餐时间来分享我的经验,希望它能帮到你。

总结:

在类型注解中暴露推断类型是完全必要的,而Typescript缺乏这个功能是一个悲剧。根据我的经验,绝大多数时候,团队成员在使用他们实际上想要引用的推断类型的索引类型时,但由于Typescript的限制无法实现。我几乎要在我们的项目中禁止使用索引类型了。
原因:

const myConstantsObject = {
  'KEY' : {a: 1},
  'VALID-KEY-2' : {a: 2}
}

按预期工作,编译通过,提供vscode IDE补全功能,并抛出类型错误(例如 myConstantsObject['VALID-KEY'] 不存在)。
但是,一旦有人尝试创建一个类型以确保没有人添加错误的类型条目:

const myConstantsObject:{ [key: string]: {a: number} } = {
  'KEY' : {a: 1},
  'VALID-KEY-2' : {a: 2}
}

那么突然之间 myConstantsObject['VALID-KEY'] 就完全没问题了,只在运行时检查,而 myConstantsObject.KEY ,保证存在的 KEY 不再编译,因为它可能不存在于 { [key: string]: {a: number} } 中。
如果我自己发帖而不了解这些情况,我会建议使用枚举或其他解决方法,这些方法在现实生活中可能无法正常工作,而且我相信对于推断引用来说没有真正的用例。因此,我不得不遗憾地分享一个更复杂的解释,更接近现实生活中我们遇到的问题,这将不可避免地使这篇回复变成一堵文字墙。

深入背景:

我为一个工作的Javascript解决方案编写了类型,发现Typescript目前由于缺乏推断类型引用而无法胜任。这里是一个简化版的原始问题,导致我遇到了这个问题:

const objectOfHOFs = {
   fillInMissing : (objectOfHOFs) =>  (fnArgs) =>  ({...pass(objectOfHOFs), ...fnArgs}),
   overwrite : (objectOfHOFs) =>  (fnArgs) =>  ({...pass(objectOfHOFs), ...hofArgs}),
   /** dozens more... **/
}

其中 pass 是一个函数,对象随后与一个 for ... of 循环一起遍历,传递对 objectOfHOFs 的引用作为每个函数的参数。

function <A extends { [key: string]: (...args: any) => any }>pass(objectOfHOFs: A) {
  const objectOfFunctions = {} as { [K in keyof A]: ReturnType<A[K]> }
  for (const [key, hof] of Object.entries(objectOfHOFs)) {
    act[key as keyof A] = hof(objectOfHOFs)
  }
 return objectOfFunctions
}

结果是一个具有相同键的普通(即非高阶)函数的对象。
一切都按预期进行...直到你打开隐式any标志,也就是说,因为函数参数是隐式的 any 。此外 objectOfHOFs 实际上并不是类型安全的。也就是说,你可以添加一个不符合规定的类型的条目。例如覆盖属性: objectOfHOFs. fillInMissing = null 或者添加错误类型的条目是完全可以的,如果没有在代码审查中捕获它,它将在生产环境中静默崩溃。
理想情况下,我们希望为高阶函数的对象(objectOfHOFs)添加类型,而不是手动为整个对象的高阶函数中的每个单个键添加类型。换句话说:

const objectOfHOFs: {[K in keyof inferred]: (hofArgs: inferred) => (fnArgs: argType) => ReturnType<A[K]> }  = {
   fillInMissing : (objectOfHOFs) =>  (fnArgs) =>  ({...fnArgs, ...pass(objectOfHOFs)}),
   overwrite : (objectOfHOFs) =>  (fnArgs) =>   ({...pass(objectOfHOFs), ...fnArgs}),
   /* dozens more... */
} // ✅  Concise and everything is typesafe

如果可以引用推断类型的话,这是可能且容易做到的。然而,现在,一个人必须这样做:

const objectOfHOFs = {
   fillInMissing : (objectOfHOFs: { fillInMissing: =>  (fnArgs: argType) => ManuallyWrittenReturnType  , overwrite:  (fnArgs: argType) => ManuallyWrittenReturnType2  /* dozens more... */  }) =>  (fnArgs: argType) =>   ({...fnArgs, ...pass(objectOfHOFs)}),
   overwrite : (objectOfHOFs: { fillInMissing: =>  (fnArgs: argType) => ManuallyWrittenReturnType  , overwrite:  (fnArgs: argType) => ManuallyWrittenReturnType2  /* dozens more... */  }) =>  (fnArgs: argType) =>   ({...pass(objectOfHOFs), ...fnArgs}),
   /** dozens more... **/
} // ❌  It “works”, but it is a lot of manual effort, very verbose, still type unsafe and entries for the wrong type can still be added.

Typescript通常不会允许你编写自引用的泛型类型。然而,我意识到我可以通过编写一个带有泛型参数类型的辅助函数来规避这个限制。现在,每当我需要依赖于推断类型的东西时,我现在被迫编写一个完全不必要的函数,具有复杂的泛型类型和让我们团队里的每个人都感到困惑的复杂性:

const objectOfHOFs = typeAsObjectOfHOFs({
   fillInMissing : (objectOfHOFs) =>  (fnArgs) =>  ({...fnArgs, ...pass(objectOfHOFs)}),
   overwrite : (objectOfHOFs) =>  (fnArgs) =>   ({...pass(objectOfHOFs), ...fnArgs}),
   /* dozens more... */
}) // 😖  It works and it is typesafe but it is a hack that confuses my coworkers.

我希望你同意以下几点:1)这是一个权宜之计;2)为了规避类型系统的限制而增加不必要的实现并不是理想的解决方案;3)应该对此采取行动。
如果你有任何问题或希望我提供一个最小的代码沙盒以及最小的复现(或hacky function workaround),请不要犹豫询问。

相关问题