【问题标题】:How do I define a type for an object which may or may not have a property?如何为可能具有或不具有属性的对象定义类型?
【发布时间】:2021-06-21 06:49:51
【问题描述】:

我最近遇到了一个运行时 TypeError,我认为应该可以在使用 TypeScript 的编译期间捕获一些东西。代码如下:

type MyObject = Record<string, Record<string, string>>;

function getParameters(): MyObject
{
    return {
        test: {
            test: "val",
        }
    };
}

const params = getParameters();

const v = params.Section.Val; // TypeError: params.Section is undefined

由于MyObject 是一个对象,其中键为strings,TypeScript 似乎认为任何string 都可以用作键,并将产生Record&lt;string, string&gt; 值。这通常是不正确的,因为对象上通常有有限数量的键,除非您在对象上定义一个 getter,它可以为任何其他作为键给出的字符串返回一个字符串。

所以我尝试使用type MyObject = Partial&lt;Record&lt;string, Record&lt;string, string&gt;&gt;&gt;。使用这种类型会捕获上述错误,但是当您尝试迭代对象时会引入问题:

type MyObject = Partial<Record<string, Record<string, string>>>;

function getParameters(): MyObject
{
    return {
        test: {
            test: "val",
        }
    };
}

const params = getParameters();

Object.values(params).filter(v => v.Value); // v: Object is possibly undefined

上面的错误永远不会在运行时发生,因为如果属性存在于params,我知道它将是Record&lt;string, string&gt;。但我不知道如何使用类型系统来表达。

所以我的问题是:如何为对象定义一个类型

  • 只有字符串键,并且
  • 没有所有字符串定义了属性,并且
  • 如果定义了一个属性,我知道对应值的类型是Record&lt;string, string&gt;?

编辑:我知道可以指定类型中的所有键(如Record&lt;"keya"|"keyb", string&gt;),但这不是我想要的,因为我事先不知道所有键。我想要的是一种我们可以称之为PartialRecord的类型,如果params: PartialRecord&lt;string, T&gt;,那么params.SOMEKEY的类型为T | undefined(因为密钥SOMEKEY可能不存在,这意味着访问它将返回undefined)但是Object.values(params) 应该有T[] 类型,因为我们知道Object.values 只会返回存在的值,并且它们都是T 类型。

【问题讨论】:

标签: typescript


【解决方案1】:

您的第一种类型 MyObject 很好,但是当您尝试访问其上的属性时,您无法事先知道它是否已定义。在这种情况下,您可以使用可选链。

const v = params?.Section?.Val;

如果params 上不存在Sectionv 的值将是未定义的,Val 也是如此。因此,请确保在使用之前检查 v 是否未定义。

如果您还想更严格地定义应该存在的属性,您可以键入一个对象。

type MyObject = {
    test: Record<string, string>;
    [key: string]: Record<string, string> | undefined;
};

在这种情况下,test 属性是 Record&lt;string, string&gt; 类型的必需属性,以及可选的 Record&lt;string, string&gt; 或未定义的任何其他属性。

编辑:重新评估您的问题和您的编辑,您正在寻找的内容似乎可以通过tsconfig.json 中的noUncheckedIndexedAccess 标志启用。你可以在这里阅读它https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess

【讨论】:

  • 问题是,如果我忘记使用可选链接,TS 无论如何都会愉快地编译代码,即使可能存在运行时类型错误。如果我忘记了可选链接,我希望打字稿阻止我。
  • 在这种情况下,考虑像这样的类型type MyObject = Record&lt;string, Record&lt;string, string&gt; | undefined&gt;;
  • 这就是我在问题的第二个示例中所做的,但它不满足我要表达的第三个条件(“如果定义了属性,我知道对应值的类型是记录").
  • @LarsNyström 我已经更新了答案,它有点乱,但目前我不想清理它。我想这可能是你想要的。
  • @LarsNyström 看看这个打字稿标志typescriptlang.org/tsconfig#noUncheckedIndexedAccess
【解决方案2】:

您需要以如下方式列出您的对象可能拥有的所有键:

type MyObject = Record<"test" | "Section" | "foo", Record<string, string>>;

function getParameters(): Pick<MyObject, "test">
{
    return {
        test: {
            test: "val",
        }
    };
}

const params = getParameters();

const v = params.Section.Val; // Property 'Section' does not exist on type 'Pick<MyObject, "test">'

https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys

您还可以在单​​独的变量中列出所有键:

type keys = "test" | "Section" | "foo";
type MyObject = Record<keys, Record<string, string>>;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-26
    • 2019-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多