在打字稿中使用curry时反向传播类型推断

时间:2018-11-25 18:26:11

标签: typescript

我希望标题有意义。我想做的是有一个工厂函数,该函数接受一个带有参数的函数,该参数将在以后调用返回的函数时提供。本质上:

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} />)

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中遇到类型错误,而不是稍后在组件使用中出现类型错误。可能不可能,但我将对此开放一段时间,以了解是否没有人知道方法。

相关问题