【问题标题】:How do I type this 'as' JSX attribute in TypeScript?如何在 TypeScript 中键入这个“as”JSX 属性?
【发布时间】:2019-05-31 16:05:50
【问题描述】:

我正在描述一个 React 库,它通过名为 as 的属性获取组件或 HTML 标记名称。当给定as 属性时,它会根据该组件/标签名称创建一个元素,并传递任何其他给定属性。

这里有一些例子:

<Foo as="a" href="https://example.com" />
<Foo as={FancyButton} fancyButtonAttr="hello!" />

我知道Semantic UI does something similar with augmentations。我将如何在 TypeScript 中输入这个?

【问题讨论】:

    标签: reactjs typescript jsx


    【解决方案1】:

    我将举例说明此处给出的最基本要求。你可以尝试泛化到一些更复杂的东西。

    首先,这是我们的魔法组件!

    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>
    }
    

    让我们回到我们提到的那两种类型。 AnyTag 是 JSX 标签可以是的任何东西。

    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
    ;
    

    现在让我们定义几个采用相同 props 的组件 - 一个函数和一个类。

    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!
    

    【讨论】:

    • 您可以使用ComponentPropsWithRefComponentPropsWithoutRef 而不是定义自己的PropsOf 类型
    • 这很有帮助,谢谢。虽然我在这里可能仍然需要帮助。如果我的 Baz 是这样的: const Baz: React.FC = (props) => ( ... ) AnyTag 不会接受它,我不知道如何修改它。谢谢!
    • 你怎样才能使那个组件类型的泛型?我的代码中有很多这样的组件,描述每个组件都会产生太多的样板代码
    猜你喜欢
    • 1970-01-01
    • 2018-07-05
    • 2018-07-26
    • 2021-02-28
    • 2020-12-05
    • 2020-09-10
    • 2017-02-26
    • 2023-04-04
    相关资源
    最近更新 更多