【问题标题】:Can I force TypeScript to resolve the type of Pick<T, K> for display?我可以强制 TypeScript 解析 Pick<T, K> 的显示类型吗?
【发布时间】:2021-12-26 18:17:51
【问题描述】:

我有一些使用pg-to-ts 生成的模式生成类型化 SQL 查询的 TypeScript 代码:

const getUserByEmail = selectBy('user', ['email']);
// const getUserByEmail: (db: Queryable, select: Pick<User, "email">) => Promise<User[]>

这个函数工作正常并且类型正确。但我不喜欢它的显示方式。而不是在参数列表中显示select: Pick&lt;User, "email"&gt;,我希望它显示select: {email: string},我发现它更清晰。

奇怪的是,在类型别名中写出 Pick&lt;User, "email"&gt; 会在您将鼠标悬停在它上面时产生所需的显示:

type ByEmail = Pick<User, 'email'>;
// type ByEmail = { email: string; }

那么如何在我的selectBy 函数中匹配该显示?这是实现的草图(full source 在 TypeScript 操场上):

export function selectBy<T extends TableNames, K extends keyof TableType<T>>(
  tableName: T,
  columns: K[],
) {
  type OutType = TableType<T>;
  type SelectType = Pick<OutType, K>;

  let query = `SELECT * FROM ${tableName} ...`;

  return async (db: Queryable, select: SelectType): Promise<OutType[]> =>
    (
      await db.query(
        query,
        columns.map(c => select[c]),
      )
    ).rows;
}

我知道一些控制类型显示的技巧,例如 {} &amp;Exclude&lt;K, never&gt;,但我无法让它们在这个 sn-p 上工作。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以使用简单的 NOP 类型,该类型仅将类型的每个键映射到相同的值,以强制智能感知功能显示所有属性。

    为了在功能上正确,我们需要单独保留函数和构造函数类型,我认为这个检查比Exclude&lt;T,never&gt; 更有助于显示,因为打字稿必须解析类型足以确定它不是函数并且然后到那时它知道所有的成员,所以它可以展示给他们。我不能 100% 确定这是怎么回事,但我认为这可能是其中的一部分。

    playground

    // maps any type to a trivial equivelent to the same type (handles functions and constructors correctly, there may be other types that are broken by homeomorphic mapped types)
    type NOOP<T> = T extends (...args:any[])=>any ? T : T extends (abstract new (...args:any[])=>any) ? T : {[K in keyof T]: T[K]}
    
    interface TEST{
      foo: 1
      bar: 2
      baz: 3
    }
    
    declare function f(test: Pick<TEST, 'foo'>): void
    //                 ^? test: Pick<TEST,'foo'>
    
    declare function g(test: NOOP<Pick<TEST,'foo'>>): void
    //                  ^? test: {foo:1; }
    

    请注意,这几乎等同于仅检查 T extends Function 是否存在,但从技术上讲,Function 有一些方法也会被检查,因此如果该类定义了 callapplybind 的静态属性等,它会打破。 (playground)

    class W{
      static call = "a"
    }
    type AnyFunc = ((...args:any[])=>any) | (abstract new (...args:any[])=>any);
    type NOP<T> =     T extends AnyFunc  ? T : {[K in keyof T]: T[K]}
    type NOP_bad<T> = T extends Function ? T : {[K in keyof T]:T[K]}
    
    const correctly_marked_as_error: NOP<typeof W> = {call:"a", prototype: new W()};
    
    const allowed_but_probably_shouldnt: NOP_bad<typeof W> = {call:"a", prototype: new W()};
    

    这是打字稿中的一种灰色区域,其中调用签名的类型就像它具有预期的函数方法一样,但使用代理和类,您可以定义打破其中一些假设的非标准行为。

    【讨论】:

    • 不错的解决方案!
    • 这和T extends Function ? T : {[K in keyof T]: T[K]}有区别吗?
    • @danvk 从技术上讲不,我编辑了答案以显示一个反例,但我不确定边缘情况是否值得诚实考虑。
    • 更正:是的,它是不同的,但这只是因为Function带有一些无法匹配目标的方法。
    猜你喜欢
    • 2019-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-02
    • 2022-01-03
    • 2018-05-30
    相关资源
    最近更新 更多