我只是遇到了一些讨厌的嵌套数据,我想用Typescript接口来描述。
让我们首先看一下数据样本
const d = {
callbacks: {
x: { cb: (data: number) => {} },
y: { cb: (data: string) => {} }
},
foo: {
callbacks: {
z: { cb: (data: boolean) => {} }
},
bar: { /* .... */ }
},
baz: { /* .... */ }
}
注意:每个级别都有一个callback
(必需)键和一些随机键(例如foo
和bar
)。还请注意,函数的参数可以是任何东西!
我尝试为此数据结构创建接口的结果是:
interface DataItem<T> {
cb: (data: T) => void;
}
interface Data {
[key: string]: DataItem | Data;
}
现在我看到2个问题:
1)Data
界面没有说明所需的密钥callback
2)Data
接口使用DataItem
,它需要一个参数
任何关于从这里出发的指南都将受到赞赏
答案 0 :(得分:2)
我认为代表您的数据的最合理的具体类型是:
interface Callbacks {
[k: string]: { cb: (d: any) => void };
}
interface Data {
callbacks: Callbacks;
[k: string]: Callbacks | Data;
}
与您所做的主要区别在于:
Callbacks
并未在其子属性的cb
函数/方法中强烈键入参数。它使用any
允许任何内容。Data
具有类型为callbacks
的必需Callbacks
属性,而其他属性可以为Callbacks | Data
。我知道您可能希望其他属性仅是Data
,但是不幸的是,当您使用string index signature时,需要确保所有字符串属性都与之匹配,包括特定的{{1 }}属性。有很多方法可以表示更严格的约束,但是它们往往是通用类型,而不是具体类型。无论如何,此定义将接受您的数据:
"callbacks"
但正如我所说,它还会接受您可能要禁止的一些数据:
const d: Data = {
callbacks: {
x: { cb: (data: number) => {} },
y: { cb: (data: string) => {} }
},
foo: {
callbacks: {
z: { cb: (data: boolean) => {} }
},
bar: {
/* .... */
}
}
};
请参见,const oops: Data = {
callbacks: {},
foo: { throwbacks: { cb: (x: number) => {} } } // hmm
};
不是throwbacks
,但是它被接受是因为callbacks
的每个属性都可以采用Data
的值。对于您来说,这可能并不重要。我倾向于暂时保留它,因为禁止它意味着将Callbacks
设为您必须在各处指定的通用类型。
这里的另一个缺点是类型Data
有很多索引签名和一个Data
,当您尝试使用它时,它们会忘记对象文字的特定推断类型:< / p>
any
如果您想保留对象文字的知识,但要求它符合d.callbacks.x.cb(1); // okay
d.foo; // okay
d.callbacks.x.cb("1"); // oops, no error?
d.flop; // oops, no error?
d.foo.callbacks.z.cb(true); // oops, error?
,我在这里的建议是使用一个通用的辅助函数,该函数接受与Data
匹配的任何内容并返回它的输入而不扩大它:
Data
并像这样使用它:
const dataHelper = <D extends Data>(d: D) => d;
我们可以在这里停下来,但是如果您真的想超越具体的const d2 = dataHelper({
callbacks: {
x: { cb: (data: number) => {} },
y: { cb: (data: string) => {} }
},
foo: {
callbacks: {
z: { cb: (data: boolean) => {} }
},
bar: {
/* .... */
}
}
});
d2.callbacks.x.cb(1); // okay
d2.foo; // okay
d2.callbacks.x.cb("1"); // error as desired
d2.flop; // error as desired
d2.foo.callbacks.z.cb(true); // okay as desired
类型,并且您不介意复杂性,我们可以使辅助函数强制其参数的类型严格匹配“具有类型为Data
的{{1}}属性和所有其他属性均为类型callbacks
“:
Callbacks
Data
是mapped和conditional类型,表示仅type DataConstraint<T extends Data> = {
[K in keyof T]: K extends "callbacks"
? Callbacks
: T[K] extends Data ? DataConstraint<T[K]> : Data
};
const dataHelper2 = <D extends Data & DataConstraint<D>>(d: D) => d;
属性应为DataConstraint
类型的约束。让我们看看它是如何工作的:
"callbacks"
嘿,它给出了一个我错过的错误... Callbacks
下的const d3 = dataHelper2({
callbacks: {
x: { cb: (data: number) => {} },
y: { cb: (data: string) => {} }
},
foo: {
callbacks: {
z: { cb: (data: boolean) => {} }
},
bar: { // error! missing callbacks ?
/* ... */
}
}
});
属性缺少其必需的bar
。而且我们也禁止以前的foo
值错误:
callbacks
好的,希望能有所帮助;祝你好运!