如何为可能有也可能没有属性的对象定义类型?

时间:2021-06-21 06:49:51

标签: typescript

我最近收到了一个运行时 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 是一个键为 string 的对象,因此 TypeScript 似乎认为任何 string 都可以用作键并产生 Record<string, string> 值.这通常是不正确的,因为通常对象上的键数是有限的,除非您在对象上定义一个 getter,它可以为作为键给出的任何其他字符串返回一个字符串。

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

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<string, string>。但我不知道如何使用类型系统来表达。

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

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

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

2 个答案:

答案 0 :(得分:0)

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


@if ($isCsr)
    <x-table.table :headers="[
        ['name' => 'Name', 'align' => 'left'],
        ['name' => 'Owner', 'align' => 'left'],
        ['name' => 'Company', 'align' => 'left'],
        'Status',
        'Created',
        'Requests',
        'Progress']"
    >
@else
    <x-table.table :headers="[
        ['name' => 'Name', 'align' => 'left'],
        ['name' => 'Owner', 'align' => 'left'],
        'Status',
        'Created',
        'Requests',
        'Progress']"
    >
@endif

@foreach($projects as $project)
    <x-table.tr-a link="{{ route('project.show',$project->id) }}">
        <x-table.td align="left">{{$project->name}}</x-table.td>
        <x-table.td align="left">{{$project->user->name}}</x-table.td>
        <x-table.td align="left">{{$project->company->name}}</x-table.td>
        <x-table.td>
            {{$project->status->name}}
        </x-table.td>
        <x-table.td>{{$project->created_at->format('M d, Y')}}</x-table.td>
        <x-table.td>{{$project->requests->count()}}</x-table.td>
        <x-table.td>
            @if($project->requests->count() > 0)
            {{number_format(($project->requestsClosed->count() / $project->requests->count()) * 100)}} %
            @else
            Not Started
            @endif
        </x-table.td>
    </x-table.tr>
@endforeach
</x-table.table>

如果 const v = params?.Section?.Val; 上不存在 Sectionparams 的值将是未定义的,v 也是如此。因此,请确保在使用前检查 Val 是否未定义。

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

v

在这种情况下,属性 type MyObject = { test: Record<string, string>; [key: string]: Record<string, string> | undefined; }; test 类型的必需属性,以及可选的任何其他 Record<string, string> 或未定义属性。

EDIT:重新评估您的问题和您的编辑,似乎可以使用 Record<string, string> 中的 noUncheckedIndexedAccess 标志启用您正在寻找的内容。您可以在此处阅读https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess

答案 1 :(得分:0)

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

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>>;