更新1 - >见下文
给出以下接口
interface Model {
bla: Child1;
bwap: string;
}
interface Child1 {
blu: Child2[];
}
interface Child2 {
whi: string;
}
我正在尝试编写一个基本上像这样使用的函数:
let mapper = path<Model>("bla")("blu");
let myPropertyPath = mapper.path; //["bla", "blu"]
函数调用x只接受在对象层次结构中在级别x找到的类型的键的参数。
我有一个工作版本,灵感来自RafaelSalguero的帖子here,看起来像这样:
interface IPathResult<TRoot, TProp> {
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
}
function path<TRoot>() {
return <TKey extends keyof TRoot>(key: TKey) => subpath<TRoot, TRoot, TKey>([], key);
}
function subpath<TRoot, T, TKey extends keyof T>(parent: string[], key: TKey): IPathResult<TRoot, T[TKey]> {
const newPath = [...parent, key];
const x = (<TSubKey extends keyof T[TKey]>(subkey: TSubKey) => subpath<TRoot, T[TKey], TSubKey>(newPath, subkey)) as IPathResult<TRoot, T[TKey]>;
x.path = newPath;
return x;
}
这里有一些更多的背景,以便了解我在哪里:我正在编写一个与(p)反应一起使用的绑定库。从Abraham Serafino的指南中汲取灵感,发现here:他使用普通的虚线来描述一条路径;这样做时我想要类型安全。
因此,IPathResult<TRoot, TProp>
实例可以读取为:这为我提供了一条从TRoot
开始的路径,该路径会导致TProp
类型的任何属性。
其用法的一个例子是(没有实现细节)
<input type="text" {...bind.text(s => s("bwap")) } />
其中s
参数为path<TState>()
和TState = Model
。
bind.text
函数将返回value
属性和onChange
处理程序,该处理程序将调用组件的setState
方法,设置通过我们映射到的路径找到的值新的价值。
这一切都很有效,直到你在混合中抛出数组属性。
假设我想为blu
接口上的Child1
属性中找到的所有元素呈现输入。我想做的是写下这样的东西:
let mapper = path<Model>("bla"); //IPathResult<Model, Child1>
for(let i = 0; i < 2; i++) { //just assume there are 2 elements in that array
let myPath = mapper("blu", i)("whi"); //IPathResult<Model, string>, note: we're writing "whi", which is a key of Child2, NOT of Array<Child2>
}
因此,映射器调用将有一个重载,它为数组TProp
提供额外的参数,指定要绑定到的数组中元素的索引,以及最重要的部分:下一个映射器调用将需要数组元素类型的键,而不是数组类型本身!
因此将其插入到实际绑定示例中,这可能会变为:
<input type="text" {...bind.text(s => s("bla")("blu", i)("whi")) } />
所以这就是我遇到的问题:即使忽视实际的实现,我如何在IPathResult<TRoot, TProp>
接口中定义该重载?以下是它的要点,但不起作用:
interface IPathResult<TRoot, TProp> {
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
<TSubKey extends keyof TProp>(key: TSubKey, index: number): IPathResult<TRoot, TProp[TSubKey][0]>;
path: string[];
}
Typescript不接受TProp[TSubKey][0]
部分,说type 0 cannot be used to index type TProp[TSubKey]
。我想这是有道理的,因为打字系统无法知道TProp[TSubKey]
可以以这种方式编入索引,它根本就没有被定义。
如果我写这样的界面:
interface IArrayPathResult<TRoot, TProp extends Array<TProp[0]>> {
<TSubKey extends keyof TProp[0]>(key: TSubKey): IPathResult<TRoot, TProp[0][TSubKey]>;
path: string[];
}
允许我实际输入:
const result: IArrayPathResult<Model, Child2[]> = null;
result("whi"); //woohoow!
当然,这本身就是一个毫无意义的例子,但只是表明可以获得返回的数组元素类型。但是TProp
现在被定义为始终是一个数组,显然情况并非如此。
我觉得我已经非常接近这一点了,但把它们放在一起却让我望而却步。 有什么想法吗?
重要提示:没有用于推断类型的实例参数,并且不应该是任何参数!
更新1:
使用带有条件类型的Titian solution,我可以使用我想要的语法。 但是,某些类型的信息似乎不见了:
const mapper = path<Model>();
const test: IPathResult<Model, string> = mapper("bla"); //doesn't error!
这不应该有效,因为mapper("bla")
调用会返回IPathResult<Model, Child1>
,而IPathResult<Model, string>
无法分配给IPathResult
!
如果您在import signal
import time
class GracefulKiller:
def __init__(self):
signal.signal(signal.SIGTERM, self.exit_gracefully)
self.kill_now = False
def exit_gracefully(self, signum, frame):
self.kill_now = True
def run_something(self):
print("starting")
time.sleep(5)
print("ending")
if __name__ == '__main__':
killer = GracefulKiller()
print(os.getpid())
while True:
killer.run_something()
if killer.kill_now:
break
print("End of the program. I was killed gracefully :)")
界面中删除了2个方法重载中的一个,则上面的代码正确错误,说&#34;键入&#39; Child1&#39;不能指定为&#39; string&#39;。&#34;
如此接近!还有更多想法吗?
答案 0 :(得分:1)
您可以使用条件类型来提取item元素,并确保仅使用数组键调用数组版本:
type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp? P : never}[keyof T];
type ElementTypeIfArray<T> = T extends Array<infer U> ? U : never
interface IPathResult<TRoot, TProp> {
<TSubKey extends KeysOfType<TProp, Array<any>>>(key: TSubKey, index: number): IPathResult<TRoot, ElementTypeIfArray<TProp[TSubKey]>>;
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
}
let mapper = path<Model>()("bla")("blu", 1)("whi"); // works
let mapper2 = path<Model>()("bla", 1) // error bla not an array key
<强>更新强>
不确定为什么ts认为IPathResult<Model, string>
与IPathResult<Model, Child1>
兼容,如果我删除任何重载,它确实会报告兼容性错误。也许这是一个编译器错误,如果我有机会,我可能会在以后调查它。最简单的方法是向IPathResult
添加一个直接使用TProp
的字段,以确保不兼容:
interface IPathResult<TRoot, TProp> {
<TSubKey extends KeysOfType<TProp, Array<any>>>(key: TSubKey, index: number): IPathResult<TRoot, ElementTypeIfArray<TProp[TSubKey]>>;
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
__: TProp;
}