从打字稿中引用自身的父对象推断窄类型
Infer narrow type from parent object referencing itself in typescript
我想像这样约束对象内部的回调函数的参数类型:
makeObj({
items: [
{
value: "foo",
func(items) {}
},
{
value: "bar",
func(items) {}
}
]
});
我想将 func
的参数 items
约束为 "foo" | "bar"
。 value
的值可以是任何字符串。
我已经有了这个,但显然不起作用:
interface MyObject<T extends string, Values extends readonly MyObject<T, Values>[]> {
value: T;
func: (items: Values) => void;
}
interface Items<T extends string, Data extends readonly MyObject<T, Data>[]> {
items: readonly [...Data];
}
function makeObj<T extends string, Data extends readonly MyObject<T, Data>[]>(opt: Items<T, Data>) {
return opt;
}
makeObj({
items: [
{
value: "foo",
func(items) {}
/* Error on "func":
Type '(items: MyObject<string, unknown>) => void' is not assignable to type '(items: unknown) => void'.
Types of parameters 'items' and 'items' are incompatible.
Type 'unknown' is not assignable to type 'MyObject<string, unknown>'.
*/
},
{
value: "bar",
func(items) {} // same error on "func"
}
]
});
一些背景信息:
我正在编写一个程序,其中定义了一些“动作”。每个动作都是一个对象中的一个函数来描述这个函数(给它一个名字、描述、输入类型、输出类型、次级输入类型)。辅助输入依赖于用户提供的数据。此数据的每一项都有一个名称和定义的类型。它的值由最终用户提供。根据其他项目的价值,一个项目是否可以显示给用户。最后一部分由相关回调函数控制。
我已经使用窄类型推断设置了所有这些,以便在编写每个操作时不会犯任何错误,但是无论回调函数具有哪种类型,这种窄类型推断都会失效。要么是因为它不能再推断出窄类型,要么是因为我收到类型错误(例如,当我使用 object
作为参数的类型时(我最终想使用一个对象作为参数,而不仅仅是一个字符串)).
这种窄类型推断是这样工作的:
编辑:
我想要一个解决方案,其中我没有将类型嵌入到函数参数中,而是作为一个 interface/type 可以参考,例如:
interface Magic<MagicParam> {
items: MagicParam;
}
makeObj<MagicParam>(opt: Magic<MagicParam>) {
return opt;
}
Magic
可以是接口或类型,可以有任意数量的类型参数。 makeObj
也可以有任意数量的类型参数。
您的 makeObj()
函数可能 generic in the union of string literal types 对应于函数参数 items
属性 的 value
属性。如果我们调用泛型类型参数 K
,并且如果您传入 {items: [{value: "x"},{value: "y"},{value: "z"}]}
(暂时忽略 func
),那么 K
应该是 "x" | "y" | "z"
那么你可以用K
来表达对makeObj()
的论证。如果我们称那个类型为Opt<K>
,我们可以这样写:
interface Opt<K extends string> {
items: Array<{ value: K, func: (items: K) => void }>
}
这意味着,给定 value
属性的 K
,我们希望 Opt<K>
的 items
属性 是一个数组,其中的元素该数组是具有类型 K
的 value
属性 和输入类型为 K
.[=48 的 func
回调 属性 的对象=]
让我们通过评估 Opt<"foo" | "bar">
(并使用 conditional type inference and a mapped type 诱使编译器显示结构的详细信息)来确保这就是您想要的:
type Test = Opt<"foo" | "bar"> extends
infer O ? { [K in keyof O]: O[K] } : never
/* type Test = {
items: {
value: "foo" | "bar";
func: (items: "foo" | "bar") => void;
}[];
} */
看起来不错。
无论如何,如上所述,makeObj
在K
中将是通用的,并接受类型为Opt<K>
的参数:
function makeObj<K extends string>(opt: Opt<K>) {
return opt;
}
让我们看看它是否有效:
const obj = makeObj({
items: [
{
value: "foo",
func(items) {
// (parameter) items: "foo" | "bar"
}
},
{
value: "bar",
func(items) {
// (parameter) items: "foo" | "bar"
}
}
]
});
// const obj: Opt<"foo" | "bar">
看起来也不错。编译器推断 K
是 "foo" | "bar"
,然后 contextually types func
属性 的 items
回调参数也是 "foo" | "bar"
.并且出现的 obj
是所需的 Opt<"foo" | "bar">
类型。
我想像这样约束对象内部的回调函数的参数类型:
makeObj({
items: [
{
value: "foo",
func(items) {}
},
{
value: "bar",
func(items) {}
}
]
});
我想将 func
的参数 items
约束为 "foo" | "bar"
。 value
的值可以是任何字符串。
我已经有了这个,但显然不起作用:
interface MyObject<T extends string, Values extends readonly MyObject<T, Values>[]> {
value: T;
func: (items: Values) => void;
}
interface Items<T extends string, Data extends readonly MyObject<T, Data>[]> {
items: readonly [...Data];
}
function makeObj<T extends string, Data extends readonly MyObject<T, Data>[]>(opt: Items<T, Data>) {
return opt;
}
makeObj({
items: [
{
value: "foo",
func(items) {}
/* Error on "func":
Type '(items: MyObject<string, unknown>) => void' is not assignable to type '(items: unknown) => void'.
Types of parameters 'items' and 'items' are incompatible.
Type 'unknown' is not assignable to type 'MyObject<string, unknown>'.
*/
},
{
value: "bar",
func(items) {} // same error on "func"
}
]
});
一些背景信息:
我正在编写一个程序,其中定义了一些“动作”。每个动作都是一个对象中的一个函数来描述这个函数(给它一个名字、描述、输入类型、输出类型、次级输入类型)。辅助输入依赖于用户提供的数据。此数据的每一项都有一个名称和定义的类型。它的值由最终用户提供。根据其他项目的价值,一个项目是否可以显示给用户。最后一部分由相关回调函数控制。
我已经使用窄类型推断设置了所有这些,以便在编写每个操作时不会犯任何错误,但是无论回调函数具有哪种类型,这种窄类型推断都会失效。要么是因为它不能再推断出窄类型,要么是因为我收到类型错误(例如,当我使用 object
作为参数的类型时(我最终想使用一个对象作为参数,而不仅仅是一个字符串)).
这种窄类型推断是这样工作的:
编辑:
我想要一个解决方案,其中我没有将类型嵌入到函数参数中,而是作为一个 interface/type 可以参考,例如:
interface Magic<MagicParam> {
items: MagicParam;
}
makeObj<MagicParam>(opt: Magic<MagicParam>) {
return opt;
}
Magic
可以是接口或类型,可以有任意数量的类型参数。 makeObj
也可以有任意数量的类型参数。
您的 makeObj()
函数可能 generic in the union of string literal types 对应于函数参数 items
属性 的 value
属性。如果我们调用泛型类型参数 K
,并且如果您传入 {items: [{value: "x"},{value: "y"},{value: "z"}]}
(暂时忽略 func
),那么 K
应该是 "x" | "y" | "z"
那么你可以用K
来表达对makeObj()
的论证。如果我们称那个类型为Opt<K>
,我们可以这样写:
interface Opt<K extends string> {
items: Array<{ value: K, func: (items: K) => void }>
}
这意味着,给定 value
属性的 K
,我们希望 Opt<K>
的 items
属性 是一个数组,其中的元素该数组是具有类型 K
的 value
属性 和输入类型为 K
.[=48 的 func
回调 属性 的对象=]
让我们通过评估 Opt<"foo" | "bar">
(并使用 conditional type inference and a mapped type 诱使编译器显示结构的详细信息)来确保这就是您想要的:
type Test = Opt<"foo" | "bar"> extends
infer O ? { [K in keyof O]: O[K] } : never
/* type Test = {
items: {
value: "foo" | "bar";
func: (items: "foo" | "bar") => void;
}[];
} */
看起来不错。
无论如何,如上所述,makeObj
在K
中将是通用的,并接受类型为Opt<K>
的参数:
function makeObj<K extends string>(opt: Opt<K>) {
return opt;
}
让我们看看它是否有效:
const obj = makeObj({
items: [
{
value: "foo",
func(items) {
// (parameter) items: "foo" | "bar"
}
},
{
value: "bar",
func(items) {
// (parameter) items: "foo" | "bar"
}
}
]
});
// const obj: Opt<"foo" | "bar">
看起来也不错。编译器推断 K
是 "foo" | "bar"
,然后 contextually types func
属性 的 items
回调参数也是 "foo" | "bar"
.并且出现的 obj
是所需的 Opt<"foo" | "bar">
类型。