使用 属性 和通用类型获取 "keyof" 中的项目类型
Get the type of an item in "keyof" using a property and generic types
我想定义一个通用类型的接口,它必须接受一个对象,其键作为“根字段名称”,值作为对象数组,该对象数组定义一些子字段,键作为名称子字段和类型作为字段值的类型。
像这样:
interface Inputs{
emails: { email: string, active: boolean, i: number }[]
}
const obj:Inputs = {emails: [ {email: "...", active: true, i: 100} ]}
将此作为通用类型接收的接口有一个“名称”属性,它将接收子字段的(keyof)名称(例如 active )和一个具有参数的函数接收名称 属性.
中定义的子字段的类型
像这样
[
{
name: "active",
component: ({ value, values }) => {
console.log(value, values);
return <>Component</>;
}
}
]
在此示例中,值必须将“boolean”作为唯一可接受的类型,因为对象中的活动键具有布尔类型。
我想做的几乎都做到了。唯一的问题是,函数的参数不是接收子字段的确切类型,而是获取对象所有类型的联合。
所以在前面的例子中,由于“email”是字符串类型,所以value应该是string类型,而不是接收类型是string |编号 |布尔值(对象中的所有可用类型)。
不知道自己有没有讲清楚,不过我准备了一个sandbox来做的更好
https://codesandbox.io/s/boring-fast-pmmhxx?file=/src/App.tsx
interface Options<
T extends { [key: string]: unknown }[],
Key extends keyof T[number]
> {
values: T;
value: Key;
}
interface InputDef<
T extends { [key: string]: any }[],
Key extends keyof T[number]
> {
name: Key;
component: (props: Options<T, T[number][Key]>) => React.ReactNode;
}
interface Props<T extends { [key: string]: [] }, Key extends keyof T> {
name: Key;
inputs: InputDef<T[Key], keyof T[Key][number]>[];
callback: (values: T) => void;
}
interface Inputs {
firstName: string;
lastName: string;
emails: { email: string; active: boolean; other: number }[];
}
const GenComponent = <T extends { [key: string]: any }, Key extends keyof T>({
name,
inputs
}: Props<T, Key>) => {
console.log(inputs);
return (
<div>
{name} {JSON.stringify(inputs)}
</div>
);
};
interface MainComponentProps {
callback: TestCallback<Inputs>;
}
const MainComponent: React.FC<MainComponentProps> = ({ callback }) => {
return (
<>
<GenComponent
callback={callback}
name="emails"
inputs={[
{
name: "active",
component: ({ value, values }) => {
console.log(value, values);
return <>Component</>;
}
}
]}
/>
</>
);
};
type TestCallback<Data> = (values: Data) => void;
function test<Data>(values: Data): void {
console.log(values);
}
export default function App() {
return (
<div className="App">
<MainComponent callback={test} />
</div>
);
}
在第 57 行,由于对象中的名称是“active”,因此值的类型应该是“boolean”而不是“string | number | boolean”。
我怎样才能做到这一点?
谢谢!
我将简化您的示例以说明问题出在哪里以及如何解决。首先,你有一个泛型 KeyValFunc<T, K>
类型,它接受一个对象类型 T
和一个如果它的键类型 K
,并保存该键和一个接受类型匹配的值的函数该键的对象类型 属性:
interface KeyValFunc<T, K extends keyof T> {
key: K,
valFunc: (val: T[K]) => void
}
所以对于这个界面
interface Foo {
x: number,
y: string,
z: boolean
}
你可以写一个 KeyValFunc<Foo, "x">
类型的值,它的 key
是 "x"
类型,它的 valFunc
是 (val: number) => void
类型:
const obj: KeyValFunc<Foo, "x"> = { key: "x", valFunc: val => val.toFixed() }; // okay
一切都很好,但是现在您想要一个 KeyValFunc<T, K>
的数组,用于一些给定的 T
但您不关心具体的 K
是什么,并且在事实上 K
每个数组元素都可以不同。也就是说,您想要一个 异构数组 类型。您的想法是将其写为 KeyValFunc<T, keyof T>[]
:
type KeyValFuncArray<T> = KeyValFunc<T, keyof T>[];
但是,不幸的是,这不起作用:
const arr: KeyValFuncArray<Foo> = [
{ key: "x", valFunc: val => val.toFixed() } // error!
// ---------------------------> ~~~~~~~
// Property 'toFixed' does not exist on type 'string | number | boolean'.
]
为什么编译器没有意识到 x
键与 number
值一起出现?为什么 val
键入为 string | number | boolean
?
问题是 KeyValFunc<T, keyof T>
不是您想要的元素类型。让我们检查一下 Foo
:
type Test = KeyValFunc<Foo, keyof Foo>;
/* type Test = KeyValFunc<Foo, keyof Foo> */;
哦,这不是很有用。让我们定义一个身份 mapped type 以便我们可以在 KeyValFunc
上使用它来查看每个 属性.
type Id<T> = { [K in keyof T]: T[K] };
type Test = Id<KeyValFunc<Foo, keyof Foo>>;
/* type Test = {
key: keyof Foo;
valFunc: (val: string | number | boolean) => void;
} */
这就是问题所在。通过对K
使用keyof T
,每个数组元素可以有Foo
(keyof Foo
)的任意属性键,而valFunc
的参数可以是 Foo
(Foo[keyof Foo]
) 的任何 属性 值。这不是我们想要的。
而不是像K
那样在KeyValFunc<T, K>
中插入keyof T
,我们真正想做的是分发KeyValFunc<T, K>
在 K
中跨越 unions。也就是说,对于 keyof T
联合中的每个 K
,我们要评估 KeyValFunc<T, K>
,然后将它们组合在一个新的联合中。
这是一种写法:
type SomeKeyValueFunc<T> = { [K in keyof T]-?: KeyValFunc<T, K> }[keyof T]
这是一个映射类型,它的所有键立即 indexed into,以生成我们想要的联合 属性。让我们验证它是否按照我们想要的 Foo
:
type Test = SomeKeyValueFunc<Foo>;
//type Test = KeyValFunc<Foo, "x"> | KeyValFunc<Foo, "y"> | KeyValFunc<Foo, "z">
是的,这样更好。现在 Test
本身是三种类型的联合,每种类型都是 KeyValFunc
对应 Foo
的特定键。所以对于 KeyValFuncArray<T>
,我们想使用 SomeKeyValueFunc<T>
而不是 KeyValueFunc<T, keyof T>
:
type KeyValFuncArray<T> = SomeKeyValueFunc<T>[];
然后事情突然按预期运行:
const arr: KeyValFuncArray<Foo> = [
{ key: "x", valFunc: val => val.toFixed() } // okay
]
我想定义一个通用类型的接口,它必须接受一个对象,其键作为“根字段名称”,值作为对象数组,该对象数组定义一些子字段,键作为名称子字段和类型作为字段值的类型。 像这样:
interface Inputs{
emails: { email: string, active: boolean, i: number }[]
}
const obj:Inputs = {emails: [ {email: "...", active: true, i: 100} ]}
将此作为通用类型接收的接口有一个“名称”属性,它将接收子字段的(keyof)名称(例如 active )和一个具有参数的函数接收名称 属性.
中定义的子字段的类型像这样
[
{
name: "active",
component: ({ value, values }) => {
console.log(value, values);
return <>Component</>;
}
}
]
在此示例中,值必须将“boolean”作为唯一可接受的类型,因为对象中的活动键具有布尔类型。
我想做的几乎都做到了。唯一的问题是,函数的参数不是接收子字段的确切类型,而是获取对象所有类型的联合。
所以在前面的例子中,由于“email”是字符串类型,所以value应该是string类型,而不是接收类型是string |编号 |布尔值(对象中的所有可用类型)。
不知道自己有没有讲清楚,不过我准备了一个sandbox来做的更好
https://codesandbox.io/s/boring-fast-pmmhxx?file=/src/App.tsx
interface Options<
T extends { [key: string]: unknown }[],
Key extends keyof T[number]
> {
values: T;
value: Key;
}
interface InputDef<
T extends { [key: string]: any }[],
Key extends keyof T[number]
> {
name: Key;
component: (props: Options<T, T[number][Key]>) => React.ReactNode;
}
interface Props<T extends { [key: string]: [] }, Key extends keyof T> {
name: Key;
inputs: InputDef<T[Key], keyof T[Key][number]>[];
callback: (values: T) => void;
}
interface Inputs {
firstName: string;
lastName: string;
emails: { email: string; active: boolean; other: number }[];
}
const GenComponent = <T extends { [key: string]: any }, Key extends keyof T>({
name,
inputs
}: Props<T, Key>) => {
console.log(inputs);
return (
<div>
{name} {JSON.stringify(inputs)}
</div>
);
};
interface MainComponentProps {
callback: TestCallback<Inputs>;
}
const MainComponent: React.FC<MainComponentProps> = ({ callback }) => {
return (
<>
<GenComponent
callback={callback}
name="emails"
inputs={[
{
name: "active",
component: ({ value, values }) => {
console.log(value, values);
return <>Component</>;
}
}
]}
/>
</>
);
};
type TestCallback<Data> = (values: Data) => void;
function test<Data>(values: Data): void {
console.log(values);
}
export default function App() {
return (
<div className="App">
<MainComponent callback={test} />
</div>
);
}
在第 57 行,由于对象中的名称是“active”,因此值的类型应该是“boolean”而不是“string | number | boolean”。 我怎样才能做到这一点?
谢谢!
我将简化您的示例以说明问题出在哪里以及如何解决。首先,你有一个泛型 KeyValFunc<T, K>
类型,它接受一个对象类型 T
和一个如果它的键类型 K
,并保存该键和一个接受类型匹配的值的函数该键的对象类型 属性:
interface KeyValFunc<T, K extends keyof T> {
key: K,
valFunc: (val: T[K]) => void
}
所以对于这个界面
interface Foo {
x: number,
y: string,
z: boolean
}
你可以写一个 KeyValFunc<Foo, "x">
类型的值,它的 key
是 "x"
类型,它的 valFunc
是 (val: number) => void
类型:
const obj: KeyValFunc<Foo, "x"> = { key: "x", valFunc: val => val.toFixed() }; // okay
一切都很好,但是现在您想要一个 KeyValFunc<T, K>
的数组,用于一些给定的 T
但您不关心具体的 K
是什么,并且在事实上 K
每个数组元素都可以不同。也就是说,您想要一个 异构数组 类型。您的想法是将其写为 KeyValFunc<T, keyof T>[]
:
type KeyValFuncArray<T> = KeyValFunc<T, keyof T>[];
但是,不幸的是,这不起作用:
const arr: KeyValFuncArray<Foo> = [
{ key: "x", valFunc: val => val.toFixed() } // error!
// ---------------------------> ~~~~~~~
// Property 'toFixed' does not exist on type 'string | number | boolean'.
]
为什么编译器没有意识到 x
键与 number
值一起出现?为什么 val
键入为 string | number | boolean
?
问题是 KeyValFunc<T, keyof T>
不是您想要的元素类型。让我们检查一下 Foo
:
type Test = KeyValFunc<Foo, keyof Foo>;
/* type Test = KeyValFunc<Foo, keyof Foo> */;
哦,这不是很有用。让我们定义一个身份 mapped type 以便我们可以在 KeyValFunc
上使用它来查看每个 属性.
type Id<T> = { [K in keyof T]: T[K] };
type Test = Id<KeyValFunc<Foo, keyof Foo>>;
/* type Test = {
key: keyof Foo;
valFunc: (val: string | number | boolean) => void;
} */
这就是问题所在。通过对K
使用keyof T
,每个数组元素可以有Foo
(keyof Foo
)的任意属性键,而valFunc
的参数可以是 Foo
(Foo[keyof Foo]
) 的任何 属性 值。这不是我们想要的。
而不是像K
那样在KeyValFunc<T, K>
中插入keyof T
,我们真正想做的是分发KeyValFunc<T, K>
在 K
中跨越 unions。也就是说,对于 keyof T
联合中的每个 K
,我们要评估 KeyValFunc<T, K>
,然后将它们组合在一个新的联合中。
这是一种写法:
type SomeKeyValueFunc<T> = { [K in keyof T]-?: KeyValFunc<T, K> }[keyof T]
这是一个映射类型,它的所有键立即 indexed into,以生成我们想要的联合 属性。让我们验证它是否按照我们想要的 Foo
:
type Test = SomeKeyValueFunc<Foo>;
//type Test = KeyValFunc<Foo, "x"> | KeyValFunc<Foo, "y"> | KeyValFunc<Foo, "z">
是的,这样更好。现在 Test
本身是三种类型的联合,每种类型都是 KeyValFunc
对应 Foo
的特定键。所以对于 KeyValFuncArray<T>
,我们想使用 SomeKeyValueFunc<T>
而不是 KeyValueFunc<T, keyof T>
:
type KeyValFuncArray<T> = SomeKeyValueFunc<T>[];
然后事情突然按预期运行:
const arr: KeyValFuncArray<Foo> = [
{ key: "x", valFunc: val => val.toFixed() } // okay
]