如何键入一个接受另一个组件作为道具的 React 组件,以及该组件的所有道具?

How to type a React component that accepts another component as a prop, and all of that component's props?

我有一个 React 组件 <Wrapper>,我可以按如下方式使用它:

// Assuming Link is a component that takes a `to` prop: <Link to="/somewhere">label</Link>
<Wrapped as={Link} to="/somewhere">label</Wrapped>

如果没有传递 as 属性,它将假设一个 <a>。但是如果一个组件被传递给 as,所有对该组件有效的 props 现在也应该是 Wrapped.

的有效 props

有没有办法在 TypeScript 中输入这个?我目前正在沿着这些思路思考:

type Props<El extends JSX.Element = React.ReactHTMLElement<HTMLAnchorElement>> = { as: El } & React.ComponentProps<El>;
const Wrapped: React.FC<Props> = (props) => /* ... */;

但是,我不确定JSX.Element and/or React.ComponentProps 是否是这里的相关类型,并且这不会编译,因为 El 无法传递给ComponentProps。正确的类型是什么,这样的事情是否可能?

我认为这可能是这样的:

import React from 'React';

type WrappedProps = {
  as: React.ReactNode,
  to: string;
}

参考:https://github.com/typescript-cheatsheets/react-typescript-cheatsheet

这可能适合你:

type Props = {
  as?: React.FC<{ to: string, children: any }>,
  to: string,
  children: any
}

const Wrapped: React.FC<Props> = ({ as: El, to, children }) => (
  El
    ? <El to={to}>{children}</El>
    : <a href={to}>{children}</a>
)

然后你可以使用 as prop:

<Wrapped as={Link} to="/somewhere">label</Wrapped>

...或不默认为 <a> 元素:

<Wrapped to="/somewhere">label</Wrapped>




根据您在下面的评论,如果您希望 Wrapped 接受传递给 as 道具的组件的道具,那么我不确定如何执行此操作或是否可行。

不过,您可以尝试这样的操作:

<Wrapped as={<Link to="somewhere">{label}</Link>} />

...所以以这种方式将 Link 传递给 as 道具意味着您不需要将道具传递给 Wrapped.

type Props = {
  as?: React.ReactNode
  children: any
}

const Wrapped: React.FC<Props> = ({ as, children }) => (
  <>{ as ? as : <a href="/somewhere">{children}</a>}</>
)

您需要的类型是 ComponentTypeElementType

import React, { ComponentType, ElementType, ReactNode } from 'react';

type WrappedProps <P = {}> = { 
  as?: ComponentType<P> | ElementType
} & P

function Wrapped<P = {}>({ as: Component = 'a', ...props }: WrappedProps<P>) {
  return (
    <Component {...props} />
  );
}

有了它,您可以做到:

interface LinkProps {
  to: string,
  children: ReactNode,
}
function Link({ to, children }: LinkProps) {
  return (
    <a href={to}>{children}</a>
  );
}

function App() {
  return (
    <div>
      <Wrapped<LinkProps> as={Link} to="/foo">Something</Wrapped>
      <Wrapped as="div" style={{ margin: 10 }} />
      <Wrapped />
    </div>
  );
}