【问题标题】:How do I correctly type a function that checks whether a key exists in object, and returns the corresponding value?如何正确键入检查对象中是否存在键并返回相应值的函数?
【发布时间】:2021-07-11 00:22:39
【问题描述】:

我推迟了很长时间,以为我会找到答案,但我仍然没有,所以我给了 SO 一个机会

import { PDFViewer, MSViewer } from './viewerclasses'

//tried adding in just a union of the keys
type ViewerTypes = 'xls' | 'xlsx' | 'doc' | 'docx' | 'pdf';

type PreviewTypes = {
    pdf: typeof PDFViewer;
    doc: typeof PDFViewer;
    docx: typeof PDFViewer;
    xls: typeof MSViewer;
    xlsx: typeof MSViewer;
}


const previewTypes: PreviewTypes = {
    pdf: PDFViewer,
    doc: PDFViewer,
    docx: PDFViewer,
    xls: MSViewer,
    xlsx: MSViewer
};

//attempt #1
type ViewerMap<T> = T extends ViewerTypes ? PreviewTypes[T] : false;
//attempt #2
type ViewerMaybe<T> = T extends keyof PreviewTypes ? PreviewTypes[T] : false

export function getViewer<K extends ViewerTypes>(filename: K): ViewerMaybe<typeof filename> {

    const type = (filename.split('.').pop()?.toLowerCase() as ViewerTypes) || 'unknown';

    const viewer = Object.prototype.hasOwnProperty.call(previewTypes, type) === true
    ? previewTypes[type]
    : false;

    return viewer;
}

但是我在这里只是在黑暗中拍摄,试图给 getViewer() 不同的类型、映射类型、索引访问类型等等,但是 TS 仍然不知道我在做什么。

我正在尝试正确键入 getViewer(),以便 Typescript 知道我是否将 previewTypes 的键作为参数提交,我会返回一个构造函数,如果没有,我会返回 false。我已经刷了这么久这个问题,但我想了解类型系统足以解决它。我知道有一种方法可以创建一个索引访问类型,其内容类似于

type ViewerIndexMap<T> = {
[Prop in keyof T]: Prop in keyof T ? T[Prop] : false 
}

然后,

export function getViewer(filename): ViewerIndexMap<typeof filename>

类似的东西

我哪里错了?我错过了什么?我刚刚重新阅读了 TS 手册,虽然我觉得映射类型很接近,但它们并没有让我准确地到达我需要的位置。

谢谢!

【问题讨论】:

    标签: javascript typescript function indexing types


    【解决方案1】:

    尽管了解映射类型始终是一个好计划,但您实际上并不需要它们。只需创建一个在 previewTypes 中查找的非泛型函数,就可以推断出正确的结果类型。

    首先,您可以通过从previewTypes 派生来稍微简化类型:

    const previewTypes = {
        pdf: PDFViewer,
        doc: PDFViewer,
        docx: PDFViewer,
        xls: MSViewer,
        xlsx: MSViewer
    } 
    
    type PreviewTypes = typeof previewTypes
    // evaluates to { pdf: typeof PDFViewer, .., xlsx: typeof MSViewer }
    type ViewerTypes = keyof PreviewTypes
    // evaluates to "pdf" | "doc" | "docx" | "xls" | "xlsx"
    

    对于查找功能,我会使用类似的东西

    export function getViewer(filename: string) {
      const ext = filename.split('.').pop()?.toLowerCase()
    
      return ext !== undefined && previewTypes.hasOwnProperty(ext)
             ? previewTypes[ext as ViewerTypes]
             : false
    }
    

    因为 TypeScript 无法从条件中的 hasOwnProperty(ext) 推断出 extpreviewTypes 的键,所以在 previewTypes 索引处有一个转换 ext as ViewerTypes。尽可能避免使用as 是最好的,但如果代码显然是正确的,并且确保类型安全会造成很大的麻烦,这是可以接受的。

    getViewer 的推断返回类型为false | typeof PDFViewer | typeof MSViewer,但您也可以显式指定签名false | PreviewTypes[ViewerTypes](其中PreviewTypes[ViewerTypes]indexed access type)。

    Playground Link

    更新:事实证明,推断extpreviewTypes 的键毕竟并不难。用下面的hasOwnProperty函数(见this TypeScript issue

    function hasOwnProperty<T extends object>(o: T, v: PropertyKey): v is keyof T {
      return o.hasOwnProperty(v)
    }
    

    你可以将return语句重写为

      return ext !== undefined && hasOwnProperty(previewTypes, ext)
             ? previewTypes[ext] : false
    

    Playground Link

    可能值得注意的是,虽然更清晰,但代码仍然不是类型安全的。通过使用 is,你告诉 TypeScript 相信 hasOwnProperty 的结果正确地确定了参数是否是有效的键。例如,如果您使用不正确的 return !o.hasOwnProperty(v),则不会产生类型错误。

    【讨论】:

    • 哇,感谢您的出色回答。我相信这不仅回答了我的问题,而且加深了我的理解,并给了我一些额外的考虑。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-26
    • 1970-01-01
    • 1970-01-01
    • 2018-09-14
    • 2020-07-11
    • 2021-01-29
    相关资源
    最近更新 更多