我正在描述一个React库,该库通过一个名为的属性采用一个组件或HTML标签名称as。给定as属性后,它将根据该组件/标签名称创建一个元素,并传递所有其他给定属性。
as
这里有些例子:
<Foo as="a" href="https://example.com" /> <Foo as={FancyButton} fancyButtonAttr="hello!" />
我知道语义UI在扩充方面做了类似的事情。我将如何在TypeScript中键入此内容?
我将在此处给出最基本要求的示例。您可以尝试将其概括为功能更复杂的产品。
首先,这是我们的魔力!
import * as React from "react"; function Foo<Tag extends AnyTag>(props: { as: Tag } & PropsOf<Tag>): JSX.Element;
注意两件事:
AnyTag
PropsOf
那是我们的公开签名。我们也许可以使用该签名以类型安全的方式实现此功能,但是我们可以在实现签名中“作弊”一些。作为实施者,这取决于您。
function Foo(props: any) { return <div>Implementation goes here!</div> }
让我们回到我们提到的两种类型。AnyTagJSX标签可以是任何东西。
type AnyTag = string | React.FunctionComponent<never> | (new (props: never) => React.Component);
PropsOf 尝试获取给定HTML标签名称或组件的预期属性。
type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag] : Tag extends React.ComponentType<infer Props> ? Props & JSX.IntrinsicAttributes : never ;
现在让我们定义几个具有相同属性的组件-一个函数和一个类。
interface SomeProps { x: boolean; y: boolean; z: boolean; } function Bar(props: SomeProps) { return <div>{props.x} {props.y} {props.z}</div>; } class Baz extends React.Component<SomeProps> { render() { const { x, y, z } = this.props; return <div>{x} {y} {z}</div>; } }
现在这里有一些用法!
let a1 = <Foo as="a" href="https://kthxb.ai" />; // good! let a2 = <Foo as="div" href="https://kthxb.ai" />; // error! let a3 = <Foo as="a" href={100} />; // error! let b1 = <Foo as={Bar} x y z />; // good! let b2 = <Foo as={Bar} x y z asdsadsada />; // error! let b3 = <Foo as={Bar} x={1} y={2} z={3} asdsadsada />; // error! let c1 = <Foo as={Baz} x y z />; // good! let c2 = <Foo as={Baz} x y z asdsadsada />; // error! let c3 = <Foo as={Baz} x={1} y={2} z={3} asdsadsada />; // error!
共
import * as React from "react"; // Here's our magic component! // Note two things: // - A type called AnyTag // - A utility type called PropsOf function Foo<Tag extends AnyTag>(props: { as: Tag } & PropsOf<Tag>): JSX.Element; // That was our public signature. We might be able to implement this in a type-safe way using that signature, // but we can "cheat" a little here in the implementation signature. This is up to you as the implementer. function Foo(props: any) { return <div>Implementation goes here!</div> } // AnyTag is anything that a JSX tag can be. type AnyTag = string | React.FunctionComponent<never> | (new (props: never) => React.Component); // PropsOf tries to get the expected properties for a given HTML tag name or component. type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag] : Tag extends React.ComponentType<infer Props> ? Props & JSX.IntrinsicAttributes : never ; // Let's now define a few components taking the same props - one function and one class. interface SomeProps { x: boolean; y: boolean; z: boolean; } function Bar(props: SomeProps) { return <div>{props.x} {props.y} {props.z}</div>; } class Baz extends React.Component<SomeProps> { render() { const { x, y, z } = this.props; return <div>{x} {y} {z}</div>; } } // Now here's some usage! let a1 = <Foo as="a" href="https://kthxb.ai" />; // good! let a2 = <Foo as="div" href="https://kthxb.ai" />; // error! let a3 = <Foo as="a" href={100} />; // error! let b1 = <Foo as={Bar} x y z />; // good! let b2 = <Foo as={Bar} x y z asdsadsada />; // error! let b3 = <Foo as={Bar} x={1} y={2} z={3} asdsadsada />; // error! let c1 = <Foo as={Baz} x y z />; // good! let c2 = <Foo as={Baz} x y z asdsadsada />; // error! let c3 = <Foo as={Baz} x={1} y={2} z={3} asdsadsada />; // error!