如何在 TypeScript 中键入这个“as”JSX 属性?
Posted
技术标签:
【中文标题】如何在 TypeScript 中键入这个“as”JSX 属性?【英文标题】:How do I type this 'as' JSX attribute in TypeScript? 【发布时间】: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 中输入这个?
【问题讨论】:
【参考方案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!
【讨论】:
您可以使用ComponentPropsWithRef
或ComponentPropsWithoutRef
而不是定义自己的PropsOf
类型
这很有帮助,谢谢。虽然我在这里可能仍然需要帮助。如果我的 Baz 是这样的: const Baz: React.FC以上是关于如何在 TypeScript 中键入这个“as”JSX 属性?的主要内容,如果未能解决你的问题,请参考以下文章
Typescript:在这种情况下如何使用泛型类型键入函数?
在 TypeScript 中,我可以轻松地键入命名箭头函数,但是如何在基于函数关键字的函数中做同样的事情呢?
如何在 TypeScript 3+ 中正确键入通用元组剩余参数?