我最近收到了一个运行时 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}}。
答案 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;
上不存在 Section
,params
的值将是未定义的,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>>;