【问题标题】:Back propagate type inference when currying in typescript在打字稿中进行柯里化时反向传播类型推断
【发布时间】:2018-11-25 18:26:10
【问题描述】:

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

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&lt;B, any&gt;,因为这是必需的,因此第一个参数实际上是稍后提供的对象的键,同时它必须是与fn 道具相同。

这个例子是人为设计的,但本质上,redux connect 风格的 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} />)

【问题讨论】:

  • 您还可以添加一个预期的用法示例吗?如果fn 已经有类型,那么它很简单.. 但返回的函数f 只能用于特定的A...
  • 我添加了我正在研究的完整 HOC,希望它能解决问题。
  • 它可以帮助你添加你的 HOC,我会尝试看看,但不能保证我今天会得到它......也许其他人会在此之前回答:)
  • @TitianCernicova-Dragomir 别担心你已经帮了我很多忙了。
  • 如果您调用foo(x).bar(y),编译器无法使用y 的类型来推断foo() 所需的类型参数。如果您将中间事物分配给变量const foox = foo(x);,然后随后调用foox.bar(y)foox.bar(z),这一点会变得特别清楚。在调用foox.bar(y)foox.bar(z) 之前设置foox 的类型。如果不是,foox.bar(y)foox.bar(z) 中的哪一个决定foox 的类型?两者都有?

标签: typescript


【解决方案1】:

到目前为止,我设法部分地做我想做的事,我认为这可能是@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 中出现类型错误,而不是稍后在组件使用中出现类型错误。可能不可能,但我会让这个开放一段时间,看看是否没有人知道方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    • 2018-08-12
    • 1970-01-01
    • 2018-06-09
    • 2020-08-20
    相关资源
    最近更新 更多