我希望标题有意义。我想做的是有一个工厂函数,该函数接受一个带有参数的函数,该参数将在以后调用返回的函数时提供。本质上:
const f = <B extends keyof any>(arg: B, fn: (props: A) => void) => <A extends Record<B, any>>(obj: A): Omit<A, B> => {
fn(obj)
delete obj[arg]
return obj
}
显然A
对第一个函数定义不可用,它必须在第二个函数定义中才能正确推断(请参阅我的上一个问题How to write omit function with proper types in typescript)。
我认为至少有一种方法可以将A
约束到两个A extends Record<B, any>
,这样第一个arg实际上是后来提供的对象的键,而同时它必须与fn
道具相同。
该示例是人为设计的,但从本质上讲,redux connect style HOC应该需要类似的东西。问题是我不太了解redux类型定义,以至于不知道如何使用它们并针对我的用例进行修改。
编辑: 我要创建的HOC示例:
export const withAction = <A extends keyof any, B extends object>(
actionName: A,
// The B here should actually be OuterProps
actionFunc: (props: B, ...args: any[]) => Promise<any>,
) => <InnerProps extends object, OuterProps extends Omit<InnerProps, A>>(
WrappedComponent: React.ComponentType<InnerProps>,
): React.ComponentType<OuterProps> => {
return (props: OuterProps) => {
// This is a react hook, but basically it just wraps the function with some
// state so the action here is an object with loading, error, response and run
// attributes. We just need to wrap it like this to be able to reuse hook
// as a HOC for class based components.
const action = useAction((...args) => {
// At this moment the props here does not match the function arguments and
// it triggers a TS error
return actionFunc(props, ...args)
})
// The action is injected here as the actionName
return <WrappedComponent {...props} {...{ [actionName]: action }} />
}
}
// Usage:
class Component extends React.Component<{ id: number, loadData: any }> {}
// Here I would like to check that 'loadData' is actually something that the
// component want to consume and that 'id' is a also a part of the props while
// 'somethingElse' should trigger an error which it does not at the moment.
const ComponentWithAction = withAction('loadData', ({ id, somethingElse }) =>
API.loadData(id),
)(Component)
// Here the ComponentWithAction should be React.ComponentType<{id: number}>
render(<ComponentWithAction id={1} />)
答案 0 :(得分:0)
到目前为止,我设法部分完成了我想做的事情,我认为这可能是唯一可能的事情,如@jcalz在评论中提到的那样。
在一般示例中:
// Adding '& C' to second generic params
const f = <B extends keyof any, C extends object>(arg: B, fn: (props: C) => void) => <A extends Record<B, any> & C>(obj: A): Omit<A, B> => {
fn(obj)
delete obj[arg]
return obj
}
const a = f('test', (props: { another: number }) => {})
// TS error, another: number is missing which is good
const b = a({ test: 1 })
const d = a({ test: 1, another: 1 })
// test does not exist which is good
const c = d.test
在HOC示例中:
export const withAction = <A extends keyof any, B extends object>(
actionName: A,
actionFunc: (props: B, ...args: any[]) => Promise<any>,
) => <
InnerProps extends object,
// The only change is here that OuterProps has '& B'
OuterProps extends Omit<InnerProps, A> & B
>(
WrappedComponent: React.ComponentType<InnerProps>,
): React.ComponentType<OuterProps> => {
return (props: OuterProps) => {
const action = useAction((...args) => {
return actionFunc(props, ...args)
})
return <WrappedComponent {...props} {...{ [actionName]: action }} />
}
}
// Usage:
class Component extends React.Component<{ id: number; loadData: any }> {}
const ComponentWithAction = withAction(
'loadData',
// somethingElse here does not trigger the error which would be nice but
// probably not possible
(props: { id: number; somethingElse: number }) => API.loadData(props.id),
)(Component)
// Here the ComponentWithAction is React.ComponentType<{id: number, somethingElse: number }>
// and so TS correctly errors that somethingElse is missing
render(<ComponentWithAction id={1} />)
// This is correct but strangely some Webstorm inspection thinks loadData is
// required and missing here. So far first time I see Webstorm trip with TS.
render(<ComponentWithAction id={1} somethingElse={3} />)
所以这似乎是足够安全的类型,并且对于我的用例是正确的,我唯一的挑剔是我想在actionFunction中遇到类型错误,而不是稍后在组件使用中出现类型错误。可能不可能,但我将对此开放一段时间,以了解是否没有人知道方法。