I'll give an example of the most basic requirements given here. You can try to generalize to something that does something more sophisticated.
First, here's our magic component!
import * as React from "react";
function Foo<Tag extends AnyTag>(props: { as: Tag } & PropsOf<Tag>): JSX.Element;
Note two things:
- A type called
AnyTag
- A utility type called
PropsOf
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>
}
Let's go back to those two types we mentioned. 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!
Altogether
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!
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…