【问题标题】:Typescript React: Conditionally optional props based on the type of another propTypescript React:基于另一个道具类型的有条件可选道具
【发布时间】:2018-10-12 13:15:40
【问题描述】:

我正在尝试键入我正在处理的提取器组件 API。这个想法很简单,你给它一个fetcher(promise 返回函数)和一个params 数组(表示位置参数)作为道具,它会将结果提供给渲染道具。

type FunctionType<A extends any[] = any[], R = any> = (...args: A) => R

type Arguments<T> = T extends FunctionType<infer R, any> ? R : never

type PromiseReturnType<T> = T extends (...args: any[]) => Promise<infer R>
  ? R
  : never

type ChildrenProps<F> = {
  data: PromiseReturnType<F>
}

type Props<F> = {
  fetcher: F
  params: Arguments<F>
  children: (props: ChildrenProps<F>) => React.ReactNode
}

class Fetch<F> extends React.Component<Props<F>> {
  render() {
    return null
  }
}

const usage = (
  <div>
    <Fetch fetcher={getUser} params={[{ id: 5 }]}>
      {({ data: user }) => (
        <div>
          {user.name} {user.email}
        </div>
      )}
    </Fetch>

    <Fetch fetcher={getCurrentUser} params={[]}> // <-- Redundant since getCurrentUser doesn't have any params
      {({ data: user }) => (
        <div>
          {user.name} {user.email}
        </div>
      )}
    </Fetch>
  </div>
)

我想知道是否有办法说如果 fetcher 函数不接受参数,则 params 属性不应该存在甚至是可选的?

我尝试修改props如下,如果fetcher有> 0个参数,只添加params字段

type Props<F> = {
  fetcher: F
  children: (props: ChildrenProps<F>) => React.ReactNode
} & (Arguments<F> extends [] // no arguments
  ? {} // no params
  : { // same as before
      params: Arguments<F>
    })

这确实有效,但是现在在渲染道具中,data 字段不再正确输入。

<Fetch fetcher={getUser} params={[{ id: 5 }]}>
  {({ data: user }) => ( // data: any, yet user: never...?
    <div>
      {user.name} {user.email} // name / email does not exist on type never
    </div>
  )}
</Fetch>

我不知道为什么会这样。


更新:

看起来修改后的Props实际上是基于以下示例工作的

type PropsWithGetUser = Props<typeof getUser>
type PropsWithGetCurrentUser = Props<typeof getCurrentUser>

const test1: PropsWithGetCurrentUser["children"] = input => input.data.email
const test2: PropsWithGetUser["children"] = input => input.data.email

可以正常工作。

PropsWithGetUserPropsWithGetCurrentUser 的结果类型也被推断为以下似乎正确的类型。

type PropsWithGetCurrentUser = {
  fetcher: () => Promise<User>
  children: (props: ChildrenProps<() => Promise<User>>) => React.ReactNode
}

type PropsWithGetUser = {
  fetcher: (
    {
      id,
    }: {
      id: number
    },
  ) => Promise<User>
  children: (
    props: ChildrenProps<
      (
        {
          id,
        }: {
          id: number
        },
      ) => Promise<User>
    >,
  ) => React.ReactNode
} & {
  params: [
    {
      id: number
    }
  ]
}

这个问题可能与我在 React 中使用它的方式有关吗?

【问题讨论】:

  • 是的,您的更新有效,因为类型已经解析,问题是当您要求编译器同时推断时..
  • 其他人可能会证明我错了(我希望他们这样做),但我认为你不能比明确指定函数类型做得更好,即:&lt;Fetch&lt;typeof getUser&gt; fetcher={getUser} params={[{ id: 5 }]}&gt;
  • 谢谢,Meligy 的回答似乎通过使用另一个类型参数来解决这个问题。你知道我的方法行不通的根本原因吗?还是它只是打字稿实现方式的一部分?
  • 不,这似乎很好,嫉妒我自己没想到:-P

标签: reactjs typescript


【解决方案1】:

这在 TypeScript 3.0 中是可能的。

为了回答这个问题,我使用了ArgumentTypes&lt;F&gt; from
this answer to: How to get argument types from function in Typescript

方法如下:
Demo on TypeScript playground

type ArgumentTypes<F extends Function> =
    F extends (...args: infer A) => any ? A : never;

type Props<F extends Function,
    Params = ArgumentTypes<F>> =
    Params extends { length: 0 } ?
    {
        fetcher: F;
    } : {
        fetcher: F;
        params: Params
    };

function runTest<F extends Function>(props: Props<F>) { }

function test0() { }
function test1(one: number) { }

runTest({
    fetcher: test0
    // , params: [] // Error
});
runTest({
    fetcher: test1
    // Comment it, 
    //  or replace it with invalid args
    //  and see error
    , params: [1]
});

【讨论】:

  • 这很好用。我猜,使用额外的类型参数Params = ArgumentTypes&lt;F&gt; 似乎可以通过提前解析参数类型来解决问题?没想到我会这样做。谢谢。
  • 是的。解析参数类型本身相对容易,但问题是当您将相同的类型用于另一件事时,在本例中为params,它使用这种类型的任何设置来代替,因为默认值因为 args 是 any[],很多东西都适合它。真正的魔力在于条件检查Params extends { length: 0 }
  • 其实我发现 render prop 中data 的类型仍然没有正确解析。我认为它正在工作,因为我正在按照 Titian Cernicova-Dragomir 的建议写 &lt;Fetch&lt;typeof getUser&gt; fetcher={getUser} params={[{ id: 5 }]}&gt;。当 fetchers 没有参数时,参数是可选的部分实际上已经在我的示例中工作了。
  • 这很酷,但现在的问题是如何将它扩展到3个或更多的条件类型? :)
猜你喜欢
  • 2021-06-29
  • 1970-01-01
  • 1970-01-01
  • 2019-07-27
  • 1970-01-01
  • 2019-09-21
  • 2017-11-28
  • 2022-06-11
  • 2022-01-26
相关资源
最近更新 更多