[英]Generics and Type Inference in TypeScript
(跟进这个问题)
我有一个 function 接受配置 object 与一些 state 定义(这是一个 Z9ED39E2EA561586B76E8 机器)
例子:
const machine = createStateMachine({
initial: 'inactive',
states: stateNode({
inactive: {
on: { ACTIVATE: 'active' },
}),
active: stateNode({
on: { DEACTIVATE: 'inactive' },
effect(send) {
send('DEACTIVATE') // Only accepts "DEACTIVATE".
},
}),
},
});
用户可以通过调用send
来切换当前的 state,有两个地方可用:
effect
function 返回createStateMachine
function 返回我想从配置中尽可能多地推断, stateNode
是一个中介 function 有助于类型推断:
function stateNode<T extends Record<keyof T, PropertyKey>>({on, effect}: {
on: T, effect?: (send: (action: keyof T) => void) => void}) {
return { on, ...effect && { effect } }
}
到目前为止一切正常,但我想让“on”属性成为可选的——有什么想法吗?
从概念上讲,您需要做的就是on
当前需要的任何地方都设置为可选,并在您以编程方式使用on
类型的任何地方使用NonNullable<T>
实用程序类型,这样就可以了:
type States<T extends Record<keyof T, { on?: any }>> = { [K in keyof T]: {
on?: Record<keyof (NonNullable<T[K]['on']>), keyof T>,
effect?(send: (action: keyof NonNullable<T[K]['on']>) => void): void;
} }
function createStateMachine<
T extends States<T>>(states: {
initial: keyof T,
states: T
}) {
return function send(arg: KeysOfTransition<NonNullable<T[keyof T]['on']>>) {
};
}
function stateNode<T extends Record<keyof T, PropertyKey>>({ on, effect }: {
on?: T, effect?: (send: (action: keyof T) => void) => void
}) {
return { ...on && { on }, ...effect && { effect } }
}
不幸的是,这并不完全奏效:
const a = createStateMachine({
initial: 'inactive',
states: {
inactive: stateNode({
on: {
ACTIVATE: 'active'
}
}),
frozen: stateNode({ effect() { console.log("Entered Frozen") } }), // error!
// ~~~~~~
// Type 'Record<string | number | symbol, string | number | symbol>' is not assignable to
// type 'Record<string | number | symbol, "inactive" | "active" | "frozen">'.
active: stateNode({
on: {
DEACTIVATE: 'inactive',
},
effect: send => {
send('DEACTIVATE')
},
})
},
});
// const a: (arg: string | number | symbol) => void
当您使用具有on
属性的东西调用stateNode
时,编译器会正确推断T
,但是当您忽略它时,编译器会推断Record<PropertyKey, PropertyKey>
,而这根本不是您想要的。 即使我可以编译, PropertyKey
也会让编译器忘记你的所有状态的名称,你最终会得到一些无用的东西,比如从createStateMachine()
出来的(arg: PropertyKey) => void
。 您希望T
undefined
或{}
或Record<never, any>
或其他内容。 好吧,我尝试使用stateNode
的单个调用签名,但上下文类型不断导致T
被推断为这种不幸的宽类型。
相反,我建议将stateNode()
重载的 function以便我们可以更好地控制省略on
属性时发生的情况:
function stateNode<T extends Record<keyof T, PropertyKey>>(param: {
on: T;
effect?: ((send: (action: keyof T) => void) => void) | undefined;
}): typeof param;
function stateNode(param: { effect?: () => void, on?: undefined }): typeof param;
function stateNode<T extends Record<keyof T, PropertyKey>>({ on, effect }: {
on?: T, effect?: (send: (action: keyof T) => void) => void
}) {
return { ...on && { on }, ...effect && { effect } }
}
如果您在调用stateNode()
时指定on
,则使用第一个调用签名并且行为与之前相同。 如果您忽略on
则使用第二个呼叫签名。 这里根本没有T
类型可以推断; 相反,编译器将effect
视为可选的无参数 function (因为,正如您在评论中所说,这就是您想要的)。 在这两种情况下,output 与输入的类型相同。 让我们看看这是否可以改善:
const a = createStateMachine({
initial: 'inactive',
states: {
inactive: stateNode({
on: {
ACTIVATE: 'active'
}
}),
frozen: stateNode({ effect() { console.log("Entered Frozen") } }),
active: stateNode({
on: {
DEACTIVATE: 'inactive',
},
effect: send => {
send('DEACTIVATE')
},
})
},
});
// const a: (arg: "ACTIVATE" | "DEACTIVATE") => void
看起来不错!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.