我正在尝试在React中实现Typography
组件,并且需要使其足够灵活,因此它将:
-
variant
prop是强制性的 -
as
道具是可选的。如果
as
是一个字符串,它将是一个本地html标签,并显示该标签的属性b。如果
as
是一个react元素,它将显示react元素的属性c。如果没有定义
as
,它将默认为变体的映射元素
例如,如果as="a"
,我们期望href
属性存在。
示例代码如下,当测试类型Proxy
时,它只工作,但与react组件一起使用时,它不起作用:
import React from "react";
const TypographyDefaultMapping = {
h1: 'h1',
h2: 'h2',
h3: 'h3',
'body-medium': 'span',
label: 'span',
} as const;
export type TypographyVariants = keyof typeof TypographyDefaultMapping;
type Tags = keyof HTMLElementTagNameMap;
type AsType = undefined | Tags | React.FunctionComponent<any>;
type VariantsDefaultType<T extends TypographyVariants> = Omit<
React.ComponentPropsWithoutRef<typeof TypographyDefaultMapping[T]>,
'as'
> & {
variant: TypographyVariants;
};
export type NewConditionalTypography<
T extends AsType,
V extends TypographyVariants
> = T extends undefined
? VariantsDefaultType<V> // Undefined "as" property, fallback to variant's default
: T extends Tags
? {
variant: TypographyVariants;
as: Tags;
bloup: boolean;
} & React.ComponentPropsWithoutRef<T> // "as" property is a valid HTML tag
: T extends React.FunctionComponent<any> // "as" property is a React element
? {
variant: TypographyVariants;
as: React.FunctionComponent<any>;
bib: boolean;
} & React.ComponentProps<T>
: never;
type Proxy<T = unknown> = T extends {
as: infer As extends AsType;
variant: infer Var extends TypographyVariants;
}
? NewConditionalTypography<As, Var>
: T extends {
variant: infer Var extends TypographyVariants; // case of not defined "as"
}
? NewConditionalTypography<undefined, Var>
: never;
const NewTypography = (props: Proxy) => (
<div>Test: {JSON.stringify(props)}</div>
);
const Link = ({ linkClick }: { linkClick: () => void }) => (
<button onClick={linkClick}>Test</button>
);
// Expected to get HTMLHeadingElement props + variant prop
type TestProxy = Proxy<{ variant: 'h2' }>;
// ^?
// Expected to get HTMLSpanElement props + variant prop
type TestProxySpan = Proxy<{ variant: 'body-medium' }>;
// ^?
// Expected to get HTMLAnchor props + variant prop
type TestProxy2 = Proxy<{ variant: 'h2'; as: 'a' }>;
// ^?
// Expected to get Link element props + variant prop
type TestProxy3 = Proxy<{ variant: 'h2'; as: typeof Link }>;
// ^?
const TestingComponent = () => (
<div>
// Should allow (and auto-complete) "href"
<NewTypography variant="h2" />
// Should allow (and auto-complete) "linkClick"
<NewTypography
variant="body-medium"
as={Link}
// linkClick={() => console.log('Test')}
/>
// Should not allow "href"
<NewTypography variant="body-medium" as="span" href="www.example.com" />
</div>
);
或Typescript Playground链接。
有什么线索可以实现这一点,如果它是可行的吗?
[编辑]:
- 基于注释的简化游乐场
您需要将类型参数添加到函数中,以便可以推断它们。之后,对NewConditionalTypography
进行一些小调整,它应该可以工作了:
export type NewConditionalTypography<
T extends AsType,
V extends TypographyVariants
> = // Undefined "as" property, fallback to variant's default
T extends undefined? VariantsDefaultType<V>:
// "as" property is a React element
T extends (p: any) => JSX.Element ? { bib: boolean; } & React.ComponentProps<T>:
// "as" property is a valid HTML tag
T extends Tags ? { bloup: boolean; } & React.ComponentPropsWithoutRef<T> :
{}
const NewTypography = <TVar extends TypographyVariants, TAs extends AsType = undefined>(props: {
as?: TAs;
variant: TVar;
} & NewConditionalTypography<TAs, TVar> ) => (
<div>Test: {JSON.stringify(props)}</div>
);
操场上联系