reactjs React元件的Typescript泛型型别

mklgxw1f  于 2022-12-12  发布在  React
关注(0)|答案(2)|浏览(165)

我正在尝试弄清楚如何为Data条目编写正确的类型,以便它接受任何带有属性的组件
示例:

const A = (props: { a: number }) => <>{props.a}</>
const B = (props: { b: string }) => <>{props.b}</>

type Data = {
  [id: string]: <P>(props: P) => JSX.Element | null
}

const data: Data = {
  aa: (props: Parameters<typeof A>[0]) => A(props),
  bb: (props: Parameters<typeof B>[0]) => B(props)
}

const Usage = () => (
  <>
    A: {data.aa({ a: 12 })}
    B: {data.bb({ b: 'hi' })}
  </>
)

即使我已经为每个组件指定了属性类型,我仍然收到以下错误:

TS2322: Type '(props: Parameters<typeof A>[0]) => JSX.Element' is not assignable to type '<P>(props: P) => Element | null'.
  Types of parameters 'props' and 'props' are incompatible.
    Type 'P' is not assignable to type '{ a: number; }'

我应该在Data类型中写什么,这样它就可以接受任何组件?

insrf1ej

insrf1ej1#

如果您已经安装了@types/react@types/react-dom,则大多数React类型都已定义。
功能组件的类型为FunctionComponentFC(简称)。它也是通用的,因此您可以指定属性类型。

import type { FC } from 'react';

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

type Data = {
  [id: string]: FC<any>;
};

const data: Data = {
  aa: A,
  bb: B,
};

const Usage = () => (
  <>
    A: {data.aa({ a: 12 })}
    B: {data.bb({ b: 'hi' })}
  </>
);

演示:https://stackblitz.com/edit/react-ts-y9ejme?file=App.tsx
但是注意,如果输入Data对象,你会丢失props类型,因为你允许任何props类型。Typescript不会看到b应该是一个数字,而不是字符串。最好排除该类型,让typescript从对象文字中推断类型。

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

const data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ b: 'hi' })} 
    </>
  );
}

演示:https://stackblitz.com/edit/react-ts-j8uagi?file=App.tsx
否则,必须显式键入属性:

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

type Data = {
  aa: typeof A;
  bb: typeof B;
};

const data: Data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ b: 'hi' })}
    </>
  );
}

演示:https://stackblitz.com/edit/react-ts-ykbq8q?file=App.tsx
或者,确保您正在访问的属性对所有组件都是通用的:

const A: FC<{ a: number }> = ({ a }) => <>I'm an A {a}</>;
const B: FC<{ a: number, b: number }> = ({ a, b }) => <>I'm a B {a}</>;

type Data = {
  [id: string]: FC<{ a: number }>;
};

const data: Data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ a: 'hi' })}
      {/* You can pass b here, but it won't be typechecked */}
    </>
  );
}

演示:https://stackblitz.com/edit/react-ts-atcwwh?file=App.tsx
还有其他选择,但这实际上取决于您的使用情形。

jmo0nnb3

jmo0nnb32#

我喜欢Chris汉密尔顿提到的使用react中的函数组件类。我读到这个问题更多的是关于如何处理带约束的typescript泛型类型。我能看到的最好的方法是创建一个AProps和BProps的联合类型,用于在数据类型定义中定义类型约束,并使用typeguard强制每个数据函数中使用的内容。然而,如果您想让这种方法适用于任何类型的组件,那么它将变得很笨拙,因为您必须为所有可能的组件定义联合类型。

type AProps = { a: number };
type BProps = { b: string };

const A = (props: AProps) => <>{props.a}</>
const B = (props: BProps) => <>{props.b}</>

type GenericParams = AProps | BProps;

type Data = {
    [id: string]: <P extends GenericParams>(props: P) => JSX.Element | null
}

const data: Data = {
    aa: (props: GenericParams) => isAProps(props) ? A(props) : null,
    bb: (props: GenericParams) => isBProps(props) ? B(props) : null
}

const Usage = () => (
    <>
        A: {data.aa({ a: 12 })}
        B: {data.bb({ b: 'hi' })}
    </>
)

function isAProps(props: GenericParams): props is AProps
{
    if ("a" in props && typeof props.a === "number")
        return true;

    return false;
}

function isBProps(props: GenericParams): props is BProps
{
    if ("b" in props && typeof props.b === "string")
        return true;

    return false;
}

相关问题