【问题标题】:Typescript complex Generics with Components带有组件的 Typescript 复杂泛型
【发布时间】:2022-02-03 20:25:41
【问题描述】:

我用这个基本概念的更复杂的版本问这个问题
联系方式:Can Generic JSX.Elements work in Typescript

我把它缩小到核心元素:

这是Object A,它从TypeA获取参数

type TypeA = {
  label: string
  value: number
}
const ObjA = ({ label, value }:TypeA) => {
  return <div>
    <div>Label: {label}</div>
    <div>Value: {value}</div>
  </div>
}

这是Object B,它从TypeB获取参数

type TypeB = {
  label: string
  value: string
  bool: boolean
}
const ObjB = ({ label, value, bool }:TypeB) => {
  return <div>
    <div>Label: {label}</div>
    {bool && <div>Value: {value}</div>}
  </div>
}

现在我在一个数组中收集这个 ComponentGroup 并从这个数组中创建一个类型:

const ComponentCollection = [
  ObjA,
  ObjB
] as const
type Components = typeof ComponentCollection[number]

然后我创建一个通用组件:

interface GenericProps<T extends Components> {
  Component: T
  title: string
}
const Generic = <T extends Components,>({ Component, title, ...props }:GenericProps<T>) => {
  return (
    <div>
      <label>{title}</label>
      <Component {...props}/>
  </div>
  )
}

最后我可以调用通用组件如下:

<Generic Component={ObjA} title={'Usage A'}           label={'Object A'} value={'String A'}/>
<Generic Component={ObjB} title={'Usage B no Bool'}   label={'Object B'} value={0}/>
<Generic Component={ObjB} title={'Usage B with Bool'} label={'Object B'} value={0} bool/>

虽然它在 JavaScript 中运行得非常好,但我在打字时搞砸了。

我设置了一个 TS-Playground 和一个 Codepen:
TS-游乐场:https://tsplay.dev/WvVarW
代码笔:https://codepen.io/Cascade8/pen/eYezGVV

目标:

  1. 将此代码转换为正确的 TypeScript 代码
  2. 编译时没有任何 TS 错误或/@ts-ignore
  3. 使 IntelliSense 工作,因此如果您键入 &lt;Generic Component={ObjA} ...,它将显示此对象的可用类型属性。在这种情况下:label={string: } value={string: }

我不想要的:

  1. 使用类或旧的函数语法,因为我们的 EsLint 要求我们尽可能使用箭头函数。
  2. 将对象作为子对象传递。
    我知道这行得通,但它不是首选的解决方案,因为主项目有很多这样的组会像这样渲染。
    以及为什么在 JavaScript 中运行非常简单的 TypeScript 不能运行。

【问题讨论】:

    标签: javascript reactjs typescript typescript-generics react-tsx


    【解决方案1】:

    按照您构建类型的方式,您可以使用 GenericProps 中的泛型定义来完成它

    (请注意,我已将道具捆绑到一个新的props 道具中,因为如果您有命名冲突,这应该可以避免名称冲突)

    import React from 'React'
    
    type TypeA = {
      label: string
      value: number
    }
    const ObjA = ({ label, value }:TypeA) => {
      return <div>
        <label>{label}</label>
        <label>{value}</label>
      </div>
    }
    type TypeB = {
      label: string
      value: string
      bool: boolean
    }
    const ObjB = ({ label, value, bool }:TypeB) => {
      return <div>
        <label>{label}</label>
        {bool && <label>{value}</label>}
      </div>
    }
    
    type Components = typeof ObjA | typeof ObjB;
    
    interface GenericProps<T extends (...args: any) => any> {
      Component: T
      title: string
      props: Parameters<T>[0]
    }
    
    const Generic = <T extends Components,>({ Component, title, props }:GenericProps<T>) => {
      return (
        <div>
          <label>{title}</label>
          <Component {...props as any}/>
        </div>
      )
    }
    const Usage = () => {
      return <Generic Component={ObjA} title={'Usage'} props={{label: 'ObjectA'}}/>
    }
    export default Generic
    

    【讨论】:

    • 谢谢,这是最接近实现它的方法。我在 TS Playground 中重新创建了它:tsplay.dev/WyblKw 遗憾的是组件部分内部仍然存在错误
    • 是的,你是对的。这是因为在Generic 内部,道具的类型未指定为与Components 中的条目匹配,这是在GenericProps 内部完成的。您可以在使用它们的行中声明类型。你可以在那里投到as any。它不会影响Generic 中的类型
    • @Cascade_Ho,调用 Component 作为普通函数而不是通过 JSX,即 Component(props) 而不是 &lt;Component {...props}/&gt;。我相信,这将避免导致您的问题的 LibraryManagedAttributes。
    【解决方案2】:

    可以不使用任何类型的断言。 考虑这个例子:

    import React, { FC, } from 'react'
    
    type Type = {
      label: string;
      value: string | number
    }
    
    type TypeA = {
      label: string
      value: number
    }
    
    type FixSubtyping<T> = Omit<T, 'title'>
    
    const ObjA = ({ label, value }: FixSubtyping<TypeA>) => {
      return <div>
        <label>{label}</label>
        <label>{value}</label>
      </div>
    }
    
    type TypeB = {
      label: string
      value: string
      bool: boolean
    }
    
    const ObjB = ({ label, value, bool }: FixSubtyping<TypeB>) => {
      return <div>
        <label>{label}</label>
        {bool && <label>{value}</label>}
      </div>
    }
    
    
    type Props<T> = T & {
      title: string
    }
    
    
    const withTitle = <T extends Type>(Component: FC<FixSubtyping<Props<T>>>) =>
      ({ title, ...props }: Props<T>) => (
        <div>
          <label>{title}</label>
          <Component {...props} />
        </div>
      )
    
    const GenericA = withTitle(ObjA)
    const GenericB = withTitle(ObjB)
    
    const jsxA = <GenericA title={'Usage'} label={'A'} value={42} /> // // ok
    
    const jsxB = <GenericB title={'Usage'} label={'A'} value={'str'} bool={true} /> // // ok
    

    Playground

    出现此错误是因为props 被推断为Omit&lt;T,'title'&gt;,因此我们应该向TS 保证组件propsOmit&lt;T,'title'&gt; 兼容

    但是有一个缺点,你需要在其他组件中更新 props 类型。如果这不是一个选项,我认为最好的方法是重载你的 Generic 函数:

    import React, { FC, } from 'react'
    
    type TypeA = {
      label: string
      value: number
    }
    
    type FixSubtyping<T> = Omit<T, 'title'>
    
    const ObjA = ({ label, value }: TypeA) => {
      return <div>
        <label>{label}</label>
        <label>{value}</label>
      </div>
    }
    
    type TypeB = {
      label: string
      value: string
      bool: boolean
    }
    
    const ObjB = ({ label, value, bool }: TypeB) => {
      return <div>
        <label>{label}</label>
        {bool && <label>{value}</label>}
      </div>
    }
    
    
    type Props<T> = T & {
      Component: FC<T>,
      title: string
    }
    
    
    function Generic<T,>(props: Props<T>): JSX.Element
    function Generic({ Component, title, ...props }: Props<unknown>) {
      return (
        <div>
          <label>{title}</label>
          <Component {...props} />
        </div>
      )
    }
    
    const jsxA = <Generic Component={ObjA} title={'Usage'} label={'A'} value={42} /> // // ok
    
    const jsxB = <Generic Component={ObjB} title={'Usage'} label={'A'} value={'str'} bool={true} /> // // ok
    
    

    Playground

    【讨论】:

    • 为您的解决方案提供帮助。这可以用箭头功能做到这一点吗?我不知何故无法让它工作
    • @Cascade_Ho 很遗憾没有,它应该推断出T 泛型参数
    • 好的。好吧,EsLint 忽略 > @ts-ignore,我猜 :D
    • 我只是试图将其调整为我的主要问题,但现在我对组件没有类型限制。是否可以在底部解决方案中限制它?
    • @Cascade_Ho 你能给我举个例子吗,我的意思是你在哪里遇到类型限制的问题?
    猜你喜欢
    • 2020-03-04
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    相关资源
    最近更新 更多